1 /* 2 * Copyright (c) 1998, 2019, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.media.sound; 27 28 import java.util.ArrayList; 29 import java.util.List; 30 31 import javax.sound.midi.ControllerEventListener; 32 import javax.sound.midi.MetaEventListener; 33 import javax.sound.midi.MetaMessage; 34 import javax.sound.midi.ShortMessage; 35 import javax.sound.sampled.LineEvent; 36 import javax.sound.sampled.LineListener; 37 38 /** 39 * EventDispatcher. Used by various classes in the Java Sound implementation 40 * to send events. 41 * 42 * @author David Rivas 43 * @author Kara Kytle 44 * @author Florian Bomers 45 */ 46 final class EventDispatcher implements Runnable { 47 48 /** 49 * time of inactivity until the auto closing clips 50 * are closed. 51 */ 52 private static final int AUTO_CLOSE_TIME = 5000; 53 54 /** 55 * List of events. 56 */ 57 private final ArrayList<EventInfo> eventQueue = new ArrayList<>(); 58 59 /** 60 * Thread object for this EventDispatcher instance. 61 */ 62 private Thread thread = null; 63 64 /* 65 * support for auto-closing Clips 66 */ 67 private final ArrayList<ClipInfo> autoClosingClips = new ArrayList<>(); 68 69 /* 70 * support for monitoring data lines 71 */ 72 private final ArrayList<LineMonitor> lineMonitors = new ArrayList<>(); 73 74 /** 75 * Approximate interval between calls to LineMonitor.checkLine 76 */ 77 static final int LINE_MONITOR_TIME = 400; 78 79 /** 80 * This start() method starts an event thread if one is not already active. 81 */ start()82 synchronized void start() { 83 84 if(thread == null) { 85 thread = JSSecurityManager.createThread(this, 86 "Java Sound Event Dispatcher", // name 87 true, // daemon 88 -1, // priority 89 true); // doStart 90 } 91 } 92 93 /** 94 * Invoked when there is at least one event in the queue. 95 * Implement this as a callback to process one event. 96 */ processEvent(EventInfo eventInfo)97 void processEvent(EventInfo eventInfo) { 98 int count = eventInfo.getListenerCount(); 99 100 // process an LineEvent 101 if (eventInfo.getEvent() instanceof LineEvent) { 102 LineEvent event = (LineEvent) eventInfo.getEvent(); 103 for (int i = 0; i < count; i++) { 104 try { 105 ((LineListener) eventInfo.getListener(i)).update(event); 106 } catch (Throwable t) { 107 if (Printer.err) t.printStackTrace(); 108 } 109 } 110 return; 111 } 112 113 // process a MetaMessage 114 if (eventInfo.getEvent() instanceof MetaMessage) { 115 MetaMessage event = (MetaMessage)eventInfo.getEvent(); 116 for (int i = 0; i < count; i++) { 117 try { 118 ((MetaEventListener) eventInfo.getListener(i)).meta(event); 119 } catch (Throwable t) { 120 if (Printer.err) t.printStackTrace(); 121 } 122 } 123 return; 124 } 125 126 // process a Controller or Mode Event 127 if (eventInfo.getEvent() instanceof ShortMessage) { 128 ShortMessage event = (ShortMessage)eventInfo.getEvent(); 129 int status = event.getStatus(); 130 131 // Controller and Mode events have status byte 0xBc, where 132 // c is the channel they are sent on. 133 if ((status & 0xF0) == 0xB0) { 134 for (int i = 0; i < count; i++) { 135 try { 136 ((ControllerEventListener) eventInfo.getListener(i)).controlChange(event); 137 } catch (Throwable t) { 138 if (Printer.err) t.printStackTrace(); 139 } 140 } 141 } 142 return; 143 } 144 145 Printer.err("Unknown event type: " + eventInfo.getEvent()); 146 } 147 148 /** 149 * Wait until there is something in the event queue to process. Then 150 * dispatch the event to the listeners.The entire method does not 151 * need to be synchronized since this includes taking the event out 152 * from the queue and processing the event. We only need to provide 153 * exclusive access over the code where an event is removed from the 154 *queue. 155 */ dispatchEvents()156 void dispatchEvents() { 157 158 EventInfo eventInfo = null; 159 160 synchronized (this) { 161 162 // Wait till there is an event in the event queue. 163 try { 164 165 if (eventQueue.size() == 0) { 166 if (autoClosingClips.size() > 0 || lineMonitors.size() > 0) { 167 int waitTime = AUTO_CLOSE_TIME; 168 if (lineMonitors.size() > 0) { 169 waitTime = LINE_MONITOR_TIME; 170 } 171 wait(waitTime); 172 } else { 173 wait(); 174 } 175 } 176 } catch (InterruptedException e) { 177 } 178 if (eventQueue.size() > 0) { 179 // Remove the event from the queue and dispatch it to the listeners. 180 eventInfo = eventQueue.remove(0); 181 } 182 183 } // end of synchronized 184 if (eventInfo != null) { 185 processEvent(eventInfo); 186 } else { 187 if (autoClosingClips.size() > 0) { 188 closeAutoClosingClips(); 189 } 190 if (lineMonitors.size() > 0) { 191 monitorLines(); 192 } 193 } 194 } 195 196 /** 197 * Queue the given event in the event queue. 198 */ postEvent(EventInfo eventInfo)199 private synchronized void postEvent(EventInfo eventInfo) { 200 eventQueue.add(eventInfo); 201 notifyAll(); 202 } 203 204 /** 205 * A loop to dispatch events. 206 */ 207 @Override run()208 public void run() { 209 210 while (true) { 211 try { 212 dispatchEvents(); 213 } catch (Throwable t) { 214 if (Printer.err) t.printStackTrace(); 215 } 216 } 217 } 218 219 /** 220 * Send audio and MIDI events. 221 */ sendAudioEvents(Object event, List<Object> listeners)222 void sendAudioEvents(Object event, List<Object> listeners) { 223 if ((listeners == null) 224 || (listeners.size() == 0)) { 225 // nothing to do 226 return; 227 } 228 229 start(); 230 231 EventInfo eventInfo = new EventInfo(event, listeners); 232 postEvent(eventInfo); 233 } 234 235 /* 236 * go through the list of registered auto-closing 237 * Clip instances and close them, if appropriate 238 * 239 * This method is called in regular intervals 240 */ closeAutoClosingClips()241 private void closeAutoClosingClips() { 242 synchronized(autoClosingClips) { 243 long currTime = System.currentTimeMillis(); 244 for (int i = autoClosingClips.size()-1; i >= 0 ; i--) { 245 ClipInfo info = autoClosingClips.get(i); 246 if (info.isExpired(currTime)) { 247 AutoClosingClip clip = info.getClip(); 248 // sanity check 249 if (!clip.isOpen() || !clip.isAutoClosing()) { 250 autoClosingClips.remove(i); 251 } 252 else if (!clip.isRunning() && !clip.isActive() && clip.isAutoClosing()) { 253 clip.close(); 254 } else { 255 } 256 } 257 } 258 } 259 } 260 getAutoClosingClipIndex(AutoClosingClip clip)261 private int getAutoClosingClipIndex(AutoClosingClip clip) { 262 synchronized(autoClosingClips) { 263 for (int i = autoClosingClips.size()-1; i >= 0; i--) { 264 if (clip.equals(autoClosingClips.get(i).getClip())) { 265 return i; 266 } 267 } 268 } 269 return -1; 270 } 271 272 /** 273 * called from auto-closing clips when one of their open() method is called. 274 */ autoClosingClipOpened(AutoClosingClip clip)275 void autoClosingClipOpened(AutoClosingClip clip) { 276 int index = 0; 277 synchronized(autoClosingClips) { 278 index = getAutoClosingClipIndex(clip); 279 if (index == -1) { 280 autoClosingClips.add(new ClipInfo(clip)); 281 } 282 } 283 if (index == -1) { 284 synchronized (this) { 285 // this is only for the case that the first clip is set to autoclosing, 286 // and it is already open, and nothing is done with it. 287 // EventDispatcher.process() method would block in wait() and 288 // never close this first clip, keeping the device open. 289 notifyAll(); 290 } 291 } 292 } 293 294 /** 295 * called from auto-closing clips when their closed() method is called. 296 */ autoClosingClipClosed(AutoClosingClip clip)297 void autoClosingClipClosed(AutoClosingClip clip) { 298 synchronized(autoClosingClips) { 299 int index = getAutoClosingClipIndex(clip); 300 if (index != -1) { 301 autoClosingClips.remove(index); 302 } 303 } 304 } 305 306 307 // ////////////////////////// Line Monitoring Support /////////////////// // 308 /* 309 * go through the list of registered line monitors 310 * and call their checkLine method 311 * 312 * This method is called in regular intervals 313 */ monitorLines()314 private void monitorLines() { 315 synchronized(lineMonitors) { 316 for (int i = 0; i < lineMonitors.size(); i++) { 317 lineMonitors.get(i).checkLine(); 318 } 319 } 320 } 321 322 /** 323 * Add this LineMonitor instance to the list of monitors. 324 */ addLineMonitor(LineMonitor lm)325 void addLineMonitor(LineMonitor lm) { 326 synchronized(lineMonitors) { 327 if (lineMonitors.indexOf(lm) >= 0) { 328 return; 329 } 330 lineMonitors.add(lm); 331 } 332 synchronized (this) { 333 // need to interrupt the infinite wait() 334 notifyAll(); 335 } 336 } 337 338 /** 339 * Remove this LineMonitor instance from the list of monitors. 340 */ removeLineMonitor(LineMonitor lm)341 void removeLineMonitor(LineMonitor lm) { 342 synchronized(lineMonitors) { 343 if (lineMonitors.indexOf(lm) < 0) { 344 return; 345 } 346 lineMonitors.remove(lm); 347 } 348 } 349 350 /** 351 * Container for an event and a set of listeners to deliver it to. 352 */ 353 private class EventInfo { 354 355 private final Object event; 356 private final Object[] listeners; 357 358 /** 359 * Create a new instance of this event Info class 360 * @param event the event to be dispatched 361 * @param listeners listener list; will be copied 362 */ EventInfo(Object event, List<Object> listeners)363 EventInfo(Object event, List<Object> listeners) { 364 this.event = event; 365 this.listeners = listeners.toArray(); 366 } 367 getEvent()368 Object getEvent() { 369 return event; 370 } 371 getListenerCount()372 int getListenerCount() { 373 return listeners.length; 374 } 375 getListener(int index)376 Object getListener(int index) { 377 return listeners[index]; 378 } 379 380 } // class EventInfo 381 382 383 /** 384 * Container for a clip with its expiration time. 385 */ 386 private class ClipInfo { 387 388 private final AutoClosingClip clip; 389 private final long expiration; 390 391 /** 392 * Create a new instance of this clip Info class. 393 */ ClipInfo(AutoClosingClip clip)394 ClipInfo(AutoClosingClip clip) { 395 this.clip = clip; 396 this.expiration = System.currentTimeMillis() + AUTO_CLOSE_TIME; 397 } 398 getClip()399 AutoClosingClip getClip() { 400 return clip; 401 } 402 isExpired(long currTime)403 boolean isExpired(long currTime) { 404 return currTime > expiration; 405 } 406 } // class ClipInfo 407 408 409 /** 410 * Interface that a class that wants to get regular 411 * line monitor events implements. 412 */ 413 interface LineMonitor { 414 /** 415 * Called by event dispatcher in regular intervals. 416 */ checkLine()417 void checkLine(); 418 } 419 420 } // class EventDispatcher 421