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