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