1 /* 2 * Copyright (c) 1998, 2013, 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 */ 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 */ 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 if (Printer.debug) Printer.debug("Sending "+event+" to "+count+" listeners"); 104 for (int i = 0; i < count; i++) { 105 try { 106 ((LineListener) eventInfo.getListener(i)).update(event); 107 } catch (Throwable t) { 108 if (Printer.err) t.printStackTrace(); 109 } 110 } 111 return; 112 } 113 114 // process a MetaMessage 115 if (eventInfo.getEvent() instanceof MetaMessage) { 116 MetaMessage event = (MetaMessage)eventInfo.getEvent(); 117 for (int i = 0; i < count; i++) { 118 try { 119 ((MetaEventListener) eventInfo.getListener(i)).meta(event); 120 } catch (Throwable t) { 121 if (Printer.err) t.printStackTrace(); 122 } 123 } 124 return; 125 } 126 127 // process a Controller or Mode Event 128 if (eventInfo.getEvent() instanceof ShortMessage) { 129 ShortMessage event = (ShortMessage)eventInfo.getEvent(); 130 int status = event.getStatus(); 131 132 // Controller and Mode events have status byte 0xBc, where 133 // c is the channel they are sent on. 134 if ((status & 0xF0) == 0xB0) { 135 for (int i = 0; i < count; i++) { 136 try { 137 ((ControllerEventListener) eventInfo.getListener(i)).controlChange(event); 138 } catch (Throwable t) { 139 if (Printer.err) t.printStackTrace(); 140 } 141 } 142 } 143 return; 144 } 145 146 Printer.err("Unknown event type: " + eventInfo.getEvent()); 147 } 148 149 /** 150 * Wait until there is something in the event queue to process. Then 151 * dispatch the event to the listeners.The entire method does not 152 * need to be synchronized since this includes taking the event out 153 * from the queue and processing the event. We only need to provide 154 * exclusive access over the code where an event is removed from the 155 *queue. 156 */ 157 void dispatchEvents() { 158 159 EventInfo eventInfo = null; 160 161 synchronized (this) { 162 163 // Wait till there is an event in the event queue. 164 try { 165 166 if (eventQueue.size() == 0) { 167 if (autoClosingClips.size() > 0 || lineMonitors.size() > 0) { 168 int waitTime = AUTO_CLOSE_TIME; 169 if (lineMonitors.size() > 0) { 170 waitTime = LINE_MONITOR_TIME; 171 } 172 wait(waitTime); 173 } else { 174 wait(); 175 } 176 } 177 } catch (InterruptedException e) { 178 } 179 if (eventQueue.size() > 0) { 180 // Remove the event from the queue and dispatch it to the listeners. 181 eventInfo = eventQueue.remove(0); 182 } 183 184 } // end of synchronized 185 if (eventInfo != null) { 186 processEvent(eventInfo); 187 } else { 188 if (autoClosingClips.size() > 0) { 189 closeAutoClosingClips(); 190 } 191 if (lineMonitors.size() > 0) { 192 monitorLines(); 193 } 194 } 195 } 196 197 /** 198 * Queue the given event in the event queue. 199 */ 200 private synchronized void postEvent(EventInfo eventInfo) { 201 eventQueue.add(eventInfo); 202 notifyAll(); 203 } 204 205 /** 206 * A loop to dispatch events. 207 */ 208 @Override 209 public void run() { 210 211 while (true) { 212 try { 213 dispatchEvents(); 214 } catch (Throwable t) { 215 if (Printer.err) t.printStackTrace(); 216 } 217 } 218 } 219 220 /** 221 * Send audio and MIDI events. 222 */ 223 void sendAudioEvents(Object event, List<Object> listeners) { 224 if ((listeners == null) 225 || (listeners.size() == 0)) { 226 // nothing to do 227 return; 228 } 229 230 start(); 231 232 EventInfo eventInfo = new EventInfo(event, listeners); 233 postEvent(eventInfo); 234 } 235 236 /* 237 * go through the list of registered auto-closing 238 * Clip instances and close them, if appropriate 239 * 240 * This method is called in regular intervals 241 */ 242 private void closeAutoClosingClips() { 243 synchronized(autoClosingClips) { 244 if (Printer.debug)Printer.debug("> EventDispatcher.closeAutoClosingClips ("+autoClosingClips.size()+" clips)"); 245 long currTime = System.currentTimeMillis(); 246 for (int i = autoClosingClips.size()-1; i >= 0 ; i--) { 247 ClipInfo info = autoClosingClips.get(i); 248 if (info.isExpired(currTime)) { 249 AutoClosingClip clip = info.getClip(); 250 // sanity check 251 if (!clip.isOpen() || !clip.isAutoClosing()) { 252 if (Printer.debug)Printer.debug("EventDispatcher: removing clip "+clip+" isOpen:"+clip.isOpen()); 253 autoClosingClips.remove(i); 254 } 255 else if (!clip.isRunning() && !clip.isActive() && clip.isAutoClosing()) { 256 if (Printer.debug)Printer.debug("EventDispatcher: closing clip "+clip); 257 clip.close(); 258 } else { 259 if (Printer.debug)Printer.debug("Doing nothing with clip "+clip+":"); 260 if (Printer.debug)Printer.debug(" open="+clip.isOpen()+", autoclosing="+clip.isAutoClosing()); 261 if (Printer.debug)Printer.debug(" isRunning="+clip.isRunning()+", isActive="+clip.isActive()); 262 } 263 } else { 264 if (Printer.debug)Printer.debug("EventDispatcher: clip "+info.getClip()+" not yet expired"); 265 } 266 } 267 } 268 if (Printer.debug)Printer.debug("< EventDispatcher.closeAutoClosingClips ("+autoClosingClips.size()+" clips)"); 269 } 270 271 private int getAutoClosingClipIndex(AutoClosingClip clip) { 272 synchronized(autoClosingClips) { 273 for (int i = autoClosingClips.size()-1; i >= 0; i--) { 274 if (clip.equals(autoClosingClips.get(i).getClip())) { 275 return i; 276 } 277 } 278 } 279 return -1; 280 } 281 282 /** 283 * called from auto-closing clips when one of their open() method is called. 284 */ 285 void autoClosingClipOpened(AutoClosingClip clip) { 286 if (Printer.debug)Printer.debug("> EventDispatcher.autoClosingClipOpened "); 287 int index = 0; 288 synchronized(autoClosingClips) { 289 index = getAutoClosingClipIndex(clip); 290 if (index == -1) { 291 if (Printer.debug)Printer.debug("EventDispatcher: adding auto-closing clip "+clip); 292 autoClosingClips.add(new ClipInfo(clip)); 293 } 294 } 295 if (index == -1) { 296 synchronized (this) { 297 // this is only for the case that the first clip is set to autoclosing, 298 // and it is already open, and nothing is done with it. 299 // EventDispatcher.process() method would block in wait() and 300 // never close this first clip, keeping the device open. 301 notifyAll(); 302 } 303 } 304 if (Printer.debug)Printer.debug("< EventDispatcher.autoClosingClipOpened finished("+autoClosingClips.size()+" clips)"); 305 } 306 307 /** 308 * called from auto-closing clips when their closed() method is called. 309 */ 310 void autoClosingClipClosed(AutoClosingClip clip) { 311 // nothing to do -- is removed from arraylist above 312 } 313 314 315 // ////////////////////////// Line Monitoring Support /////////////////// // 316 /* 317 * go through the list of registered line monitors 318 * and call their checkLine method 319 * 320 * This method is called in regular intervals 321 */ 322 private void monitorLines() { 323 synchronized(lineMonitors) { 324 if (Printer.debug)Printer.debug("> EventDispatcher.monitorLines ("+lineMonitors.size()+" monitors)"); 325 for (int i = 0; i < lineMonitors.size(); i++) { 326 lineMonitors.get(i).checkLine(); 327 } 328 } 329 if (Printer.debug)Printer.debug("< EventDispatcher.monitorLines("+lineMonitors.size()+" monitors)"); 330 } 331 332 /** 333 * Add this LineMonitor instance to the list of monitors. 334 */ 335 void addLineMonitor(LineMonitor lm) { 336 if (Printer.trace)Printer.trace("> EventDispatcher.addLineMonitor("+lm+")"); 337 synchronized(lineMonitors) { 338 if (lineMonitors.indexOf(lm) >= 0) { 339 if (Printer.trace)Printer.trace("< EventDispatcher.addLineMonitor finished -- this monitor already exists!"); 340 return; 341 } 342 if (Printer.debug)Printer.debug("EventDispatcher: adding line monitor "+lm); 343 lineMonitors.add(lm); 344 } 345 synchronized (this) { 346 // need to interrupt the infinite wait() 347 notifyAll(); 348 } 349 if (Printer.debug)Printer.debug("< EventDispatcher.addLineMonitor finished -- now ("+lineMonitors.size()+" monitors)"); 350 } 351 352 /** 353 * Remove this LineMonitor instance from the list of monitors. 354 */ 355 void removeLineMonitor(LineMonitor lm) { 356 if (Printer.trace)Printer.trace("> EventDispatcher.removeLineMonitor("+lm+")"); 357 synchronized(lineMonitors) { 358 if (lineMonitors.indexOf(lm) < 0) { 359 if (Printer.trace)Printer.trace("< EventDispatcher.removeLineMonitor finished -- this monitor does not exist!"); 360 return; 361 } 362 if (Printer.debug)Printer.debug("EventDispatcher: removing line monitor "+lm); 363 lineMonitors.remove(lm); 364 } 365 if (Printer.debug)Printer.debug("< EventDispatcher.removeLineMonitor finished -- now ("+lineMonitors.size()+" monitors)"); 366 } 367 368 /** 369 * Container for an event and a set of listeners to deliver it to. 370 */ 371 private class EventInfo { 372 373 private final Object event; 374 private final Object[] listeners; 375 376 /** 377 * Create a new instance of this event Info class 378 * @param event the event to be dispatched 379 * @param listeners listener list; will be copied 380 */ 381 EventInfo(Object event, List<Object> listeners) { 382 this.event = event; 383 this.listeners = listeners.toArray(); 384 } 385 386 Object getEvent() { 387 return event; 388 } 389 390 int getListenerCount() { 391 return listeners.length; 392 } 393 394 Object getListener(int index) { 395 return listeners[index]; 396 } 397 398 } // class EventInfo 399 400 401 /** 402 * Container for a clip with its expiration time. 403 */ 404 private class ClipInfo { 405 406 private final AutoClosingClip clip; 407 private final long expiration; 408 409 /** 410 * Create a new instance of this clip Info class. 411 */ 412 ClipInfo(AutoClosingClip clip) { 413 this.clip = clip; 414 this.expiration = System.currentTimeMillis() + AUTO_CLOSE_TIME; 415 } 416 417 AutoClosingClip getClip() { 418 return clip; 419 } 420 421 boolean isExpired(long currTime) { 422 return currTime > expiration; 423 } 424 } // class ClipInfo 425 426 427 /** 428 * Interface that a class that wants to get regular 429 * line monitor events implements. 430 */ 431 interface LineMonitor { 432 /** 433 * Called by event dispatcher in regular intervals. 434 */ 435 void checkLine(); 436 } 437 438 } // class EventDispatcher