--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/com/sun/media/sound/EventDispatcher.java Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,456 @@
+/*
+ * Copyright 1998-2007 Sun Microsystems, Inc. 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. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package com.sun.media.sound;
+
+import java.util.EventObject;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.sound.sampled.Clip;
+import javax.sound.sampled.Line;
+import javax.sound.sampled.LineEvent;
+import javax.sound.sampled.LineListener;
+
+import javax.sound.midi.MetaMessage;
+import javax.sound.midi.ShortMessage;
+import javax.sound.midi.MetaEventListener;
+import javax.sound.midi.ControllerEventListener;
+
+
+
+/**
+ * EventDispatcher. Used by various classes in the Java Sound implementation
+ * to send events.
+ *
+ * @author David Rivas
+ * @author Kara Kytle
+ * @author Florian Bomers
+ */
+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 ArrayList eventQueue = new ArrayList();
+
+
+ /**
+ * Thread object for this EventDispatcher instance
+ */
+ private Thread thread = null;
+
+
+ /*
+ * support for auto-closing Clips
+ */
+ private ArrayList<ClipInfo> autoClosingClips = new ArrayList<ClipInfo>();
+
+ /*
+ * support for monitoring data lines
+ */
+ private ArrayList<LineMonitor> lineMonitors = new ArrayList<LineMonitor>();
+
+ /**
+ * 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.
+ */
+ protected 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.
+ */
+ protected 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 = (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.
+ */
+ 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 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)");
+ }
+
+ // /////////////////////////////////// INNER CLASSES ////////////////////////////////////////// //
+
+ /**
+ * Container for an event and a set of listeners to deliver it to.
+ */
+ private class EventInfo {
+
+ private Object event;
+ private 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 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 AutoClosingClip clip;
+ private 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
+ */
+ public void checkLine();
+ }
+
+} // class EventDispatcher