| /* |
| * Copyright (c) 1998, 2013, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package com.sun.media.sound; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import javax.sound.midi.ControllerEventListener; |
| import javax.sound.midi.MetaEventListener; |
| import javax.sound.midi.MetaMessage; |
| import javax.sound.midi.ShortMessage; |
| import javax.sound.sampled.LineEvent; |
| import javax.sound.sampled.LineListener; |
| |
| /** |
| * EventDispatcher. Used by various classes in the Java Sound implementation |
| * to send events. |
| * |
| * @author David Rivas |
| * @author Kara Kytle |
| * @author Florian Bomers |
| */ |
| final class EventDispatcher implements Runnable { |
| |
| /** |
| * time of inactivity until the auto closing clips |
| * are closed. |
| */ |
| private static final int AUTO_CLOSE_TIME = 5000; |
| |
| /** |
| * List of events. |
| */ |
| private final ArrayList<EventInfo> eventQueue = new ArrayList<>(); |
| |
| /** |
| * Thread object for this EventDispatcher instance. |
| */ |
| private Thread thread = null; |
| |
| /* |
| * support for auto-closing Clips |
| */ |
| private final ArrayList<ClipInfo> autoClosingClips = new ArrayList<>(); |
| |
| /* |
| * support for monitoring data lines |
| */ |
| private final ArrayList<LineMonitor> lineMonitors = new ArrayList<>(); |
| |
| /** |
| * Approximate interval between calls to LineMonitor.checkLine |
| */ |
| static final int LINE_MONITOR_TIME = 400; |
| |
| /** |
| * This start() method starts an event thread if one is not already active. |
| */ |
| synchronized void start() { |
| |
| if(thread == null) { |
| thread = JSSecurityManager.createThread(this, |
| "Java Sound Event Dispatcher", // name |
| true, // daemon |
| -1, // priority |
| true); // doStart |
| } |
| } |
| |
| /** |
| * Invoked when there is at least one event in the queue. |
| * Implement this as a callback to process one event. |
| */ |
| void processEvent(EventInfo eventInfo) { |
| int count = eventInfo.getListenerCount(); |
| |
| // process an LineEvent |
| if (eventInfo.getEvent() instanceof LineEvent) { |
| LineEvent event = (LineEvent) eventInfo.getEvent(); |
| if (Printer.debug) Printer.debug("Sending "+event+" to "+count+" listeners"); |
| for (int i = 0; i < count; i++) { |
| try { |
| ((LineListener) eventInfo.getListener(i)).update(event); |
| } catch (Throwable t) { |
| if (Printer.err) t.printStackTrace(); |
| } |
| } |
| return; |
| } |
| |
| // process a MetaMessage |
| if (eventInfo.getEvent() instanceof MetaMessage) { |
| MetaMessage event = (MetaMessage)eventInfo.getEvent(); |
| for (int i = 0; i < count; i++) { |
| try { |
| ((MetaEventListener) eventInfo.getListener(i)).meta(event); |
| } catch (Throwable t) { |
| if (Printer.err) t.printStackTrace(); |
| } |
| } |
| return; |
| } |
| |
| // process a Controller or Mode Event |
| if (eventInfo.getEvent() instanceof ShortMessage) { |
| ShortMessage event = (ShortMessage)eventInfo.getEvent(); |
| int status = event.getStatus(); |
| |
| // Controller and Mode events have status byte 0xBc, where |
| // c is the channel they are sent on. |
| if ((status & 0xF0) == 0xB0) { |
| for (int i = 0; i < count; i++) { |
| try { |
| ((ControllerEventListener) eventInfo.getListener(i)).controlChange(event); |
| } catch (Throwable t) { |
| if (Printer.err) t.printStackTrace(); |
| } |
| } |
| } |
| return; |
| } |
| |
| Printer.err("Unknown event type: " + eventInfo.getEvent()); |
| } |
| |
| /** |
| * Wait until there is something in the event queue to process. Then |
| * dispatch the event to the listeners.The entire method does not |
| * need to be synchronized since this includes taking the event out |
| * from the queue and processing the event. We only need to provide |
| * exclusive access over the code where an event is removed from the |
| *queue. |
| */ |
| void dispatchEvents() { |
| |
| EventInfo eventInfo = null; |
| |
| synchronized (this) { |
| |
| // Wait till there is an event in the event queue. |
| try { |
| |
| if (eventQueue.size() == 0) { |
| if (autoClosingClips.size() > 0 || lineMonitors.size() > 0) { |
| int waitTime = AUTO_CLOSE_TIME; |
| if (lineMonitors.size() > 0) { |
| waitTime = LINE_MONITOR_TIME; |
| } |
| wait(waitTime); |
| } else { |
| wait(); |
| } |
| } |
| } catch (InterruptedException e) { |
| } |
| if (eventQueue.size() > 0) { |
| // Remove the event from the queue and dispatch it to the listeners. |
| eventInfo = eventQueue.remove(0); |
| } |
| |
| } // end of synchronized |
| if (eventInfo != null) { |
| processEvent(eventInfo); |
| } else { |
| if (autoClosingClips.size() > 0) { |
| closeAutoClosingClips(); |
| } |
| if (lineMonitors.size() > 0) { |
| monitorLines(); |
| } |
| } |
| } |
| |
| /** |
| * Queue the given event in the event queue. |
| */ |
| private synchronized void postEvent(EventInfo eventInfo) { |
| eventQueue.add(eventInfo); |
| notifyAll(); |
| } |
| |
| /** |
| * A loop to dispatch events. |
| */ |
| @Override |
| public void run() { |
| |
| while (true) { |
| try { |
| dispatchEvents(); |
| } catch (Throwable t) { |
| if (Printer.err) t.printStackTrace(); |
| } |
| } |
| } |
| |
| /** |
| * Send audio and MIDI events. |
| */ |
| void sendAudioEvents(Object event, List<Object> listeners) { |
| if ((listeners == null) |
| || (listeners.size() == 0)) { |
| // nothing to do |
| return; |
| } |
| |
| start(); |
| |
| EventInfo eventInfo = new EventInfo(event, listeners); |
| postEvent(eventInfo); |
| } |
| |
| /* |
| * go through the list of registered auto-closing |
| * Clip instances and close them, if appropriate |
| * |
| * This method is called in regular intervals |
| */ |
| private void closeAutoClosingClips() { |
| synchronized(autoClosingClips) { |
| if (Printer.debug)Printer.debug("> EventDispatcher.closeAutoClosingClips ("+autoClosingClips.size()+" clips)"); |
| long currTime = System.currentTimeMillis(); |
| for (int i = autoClosingClips.size()-1; i >= 0 ; i--) { |
| ClipInfo info = autoClosingClips.get(i); |
| if (info.isExpired(currTime)) { |
| AutoClosingClip clip = info.getClip(); |
| // sanity check |
| if (!clip.isOpen() || !clip.isAutoClosing()) { |
| if (Printer.debug)Printer.debug("EventDispatcher: removing clip "+clip+" isOpen:"+clip.isOpen()); |
| autoClosingClips.remove(i); |
| } |
| else if (!clip.isRunning() && !clip.isActive() && clip.isAutoClosing()) { |
| if (Printer.debug)Printer.debug("EventDispatcher: closing clip "+clip); |
| clip.close(); |
| } else { |
| if (Printer.debug)Printer.debug("Doing nothing with clip "+clip+":"); |
| if (Printer.debug)Printer.debug(" open="+clip.isOpen()+", autoclosing="+clip.isAutoClosing()); |
| if (Printer.debug)Printer.debug(" isRunning="+clip.isRunning()+", isActive="+clip.isActive()); |
| } |
| } else { |
| if (Printer.debug)Printer.debug("EventDispatcher: clip "+info.getClip()+" not yet expired"); |
| } |
| } |
| } |
| if (Printer.debug)Printer.debug("< EventDispatcher.closeAutoClosingClips ("+autoClosingClips.size()+" clips)"); |
| } |
| |
| private int getAutoClosingClipIndex(AutoClosingClip clip) { |
| synchronized(autoClosingClips) { |
| for (int i = autoClosingClips.size()-1; i >= 0; i--) { |
| if (clip.equals(autoClosingClips.get(i).getClip())) { |
| return i; |
| } |
| } |
| } |
| return -1; |
| } |
| |
| /** |
| * called from auto-closing clips when one of their open() method is called. |
| */ |
| void autoClosingClipOpened(AutoClosingClip clip) { |
| if (Printer.debug)Printer.debug("> EventDispatcher.autoClosingClipOpened "); |
| int index = 0; |
| synchronized(autoClosingClips) { |
| index = getAutoClosingClipIndex(clip); |
| if (index == -1) { |
| if (Printer.debug)Printer.debug("EventDispatcher: adding auto-closing clip "+clip); |
| autoClosingClips.add(new ClipInfo(clip)); |
| } |
| } |
| if (index == -1) { |
| synchronized (this) { |
| // this is only for the case that the first clip is set to autoclosing, |
| // and it is already open, and nothing is done with it. |
| // EventDispatcher.process() method would block in wait() and |
| // never close this first clip, keeping the device open. |
| notifyAll(); |
| } |
| } |
| if (Printer.debug)Printer.debug("< EventDispatcher.autoClosingClipOpened finished("+autoClosingClips.size()+" clips)"); |
| } |
| |
| /** |
| * called from auto-closing clips when their closed() method is called. |
| */ |
| void autoClosingClipClosed(AutoClosingClip clip) { |
| // nothing to do -- is removed from arraylist above |
| } |
| |
| |
| // ////////////////////////// Line Monitoring Support /////////////////// // |
| /* |
| * go through the list of registered line monitors |
| * and call their checkLine method |
| * |
| * This method is called in regular intervals |
| */ |
| private void monitorLines() { |
| synchronized(lineMonitors) { |
| if (Printer.debug)Printer.debug("> EventDispatcher.monitorLines ("+lineMonitors.size()+" monitors)"); |
| for (int i = 0; i < lineMonitors.size(); i++) { |
| lineMonitors.get(i).checkLine(); |
| } |
| } |
| if (Printer.debug)Printer.debug("< EventDispatcher.monitorLines("+lineMonitors.size()+" monitors)"); |
| } |
| |
| /** |
| * Add this LineMonitor instance to the list of monitors. |
| */ |
| void addLineMonitor(LineMonitor lm) { |
| if (Printer.trace)Printer.trace("> EventDispatcher.addLineMonitor("+lm+")"); |
| synchronized(lineMonitors) { |
| if (lineMonitors.indexOf(lm) >= 0) { |
| if (Printer.trace)Printer.trace("< EventDispatcher.addLineMonitor finished -- this monitor already exists!"); |
| return; |
| } |
| if (Printer.debug)Printer.debug("EventDispatcher: adding line monitor "+lm); |
| lineMonitors.add(lm); |
| } |
| synchronized (this) { |
| // need to interrupt the infinite wait() |
| notifyAll(); |
| } |
| if (Printer.debug)Printer.debug("< EventDispatcher.addLineMonitor finished -- now ("+lineMonitors.size()+" monitors)"); |
| } |
| |
| /** |
| * Remove this LineMonitor instance from the list of monitors. |
| */ |
| void removeLineMonitor(LineMonitor lm) { |
| if (Printer.trace)Printer.trace("> EventDispatcher.removeLineMonitor("+lm+")"); |
| synchronized(lineMonitors) { |
| if (lineMonitors.indexOf(lm) < 0) { |
| if (Printer.trace)Printer.trace("< EventDispatcher.removeLineMonitor finished -- this monitor does not exist!"); |
| return; |
| } |
| if (Printer.debug)Printer.debug("EventDispatcher: removing line monitor "+lm); |
| lineMonitors.remove(lm); |
| } |
| if (Printer.debug)Printer.debug("< EventDispatcher.removeLineMonitor finished -- now ("+lineMonitors.size()+" monitors)"); |
| } |
| |
| /** |
| * Container for an event and a set of listeners to deliver it to. |
| */ |
| private class EventInfo { |
| |
| private final Object event; |
| private final Object[] listeners; |
| |
| /** |
| * Create a new instance of this event Info class |
| * @param event the event to be dispatched |
| * @param listeners listener list; will be copied |
| */ |
| EventInfo(Object event, List<Object> listeners) { |
| this.event = event; |
| this.listeners = listeners.toArray(); |
| } |
| |
| Object getEvent() { |
| return event; |
| } |
| |
| int getListenerCount() { |
| return listeners.length; |
| } |
| |
| Object getListener(int index) { |
| return listeners[index]; |
| } |
| |
| } // class EventInfo |
| |
| |
| /** |
| * Container for a clip with its expiration time. |
| */ |
| private class ClipInfo { |
| |
| private final AutoClosingClip clip; |
| private final long expiration; |
| |
| /** |
| * Create a new instance of this clip Info class. |
| */ |
| ClipInfo(AutoClosingClip clip) { |
| this.clip = clip; |
| this.expiration = System.currentTimeMillis() + AUTO_CLOSE_TIME; |
| } |
| |
| AutoClosingClip getClip() { |
| return clip; |
| } |
| |
| boolean isExpired(long currTime) { |
| return currTime > expiration; |
| } |
| } // class ClipInfo |
| |
| |
| /** |
| * Interface that a class that wants to get regular |
| * line monitor events implements. |
| */ |
| interface LineMonitor { |
| /** |
| * Called by event dispatcher in regular intervals. |
| */ |
| void checkLine(); |
| } |
| |
| } // class EventDispatcher |