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