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