jdk/src/share/classes/javax/sound/midi/MidiSystem.java
changeset 2 90ce3da70b43
child 5506 202f599c92aa
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/javax/sound/midi/MidiSystem.java	Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,1463 @@
+/*
+ * Copyright 1999-2006 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 javax.sound.midi;
+
+import java.io.FileInputStream;
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.IOException;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import java.net.URL;
+
+import javax.sound.midi.spi.MidiFileWriter;
+import javax.sound.midi.spi.MidiFileReader;
+import javax.sound.midi.spi.SoundbankReader;
+import javax.sound.midi.spi.MidiDeviceProvider;
+
+import com.sun.media.sound.JDK13Services;
+import com.sun.media.sound.ReferenceCountingDevice;
+import com.sun.media.sound.AutoConnectSequencer;
+
+
+/**
+ * The <code>MidiSystem</code> class provides access to the installed MIDI
+ * system resources, including devices such as synthesizers, sequencers, and
+ * MIDI input and output ports.  A typical simple MIDI application might
+ * begin by invoking one or more <code>MidiSystem</code> methods to learn
+ * what devices are installed and to obtain the ones needed in that
+ * application.
+ * <p>
+ * The class also has methods for reading files, streams, and  URLs that
+ * contain standard MIDI file data or soundbanks.  You can query the
+ * <code>MidiSystem</code> for the format of a specified MIDI file.
+ * <p>
+ * You cannot instantiate a <code>MidiSystem</code>; all the methods are
+ * static.
+ *
+ * <p>Properties can be used to specify default MIDI devices.
+ * Both system properties and a properties file are considered.
+ * The properties file is &quot;lib/sound.properties&quot; in the JRE
+ * directory. If a property exists both as a system property and in the
+ * properties file, the system property takes precedence. If none is
+ * specified, a suitable default is chosen among the available devices.
+ * The syntax of the properties file is specified in
+ * {@link java.util.Properties#load(InputStream) Properties.load}. The
+ * following table lists the available property keys and which methods
+ * consider them:
+ *
+ * <table border=0>
+ *  <tr>
+ *   <th>Property Key</th>
+ *   <th>Interface</th>
+ *   <th>Affected Method</th>
+ *  </tr>
+ *  <tr>
+ *   <td><code>javax.sound.midi.Receiver</code></td>
+ *   <td>{@link Receiver}</td>
+ *   <td>{@link #getReceiver}</td>
+ *  </tr>
+ *  <tr>
+ *   <td><code>javax.sound.midi.Sequencer</code></td>
+ *   <td>{@link Sequencer}</td>
+ *   <td>{@link #getSequencer}</td>
+ *  </tr>
+ *  <tr>
+ *   <td><code>javax.sound.midi.Synthesizer</code></td>
+ *   <td>{@link Synthesizer}</td>
+ *   <td>{@link #getSynthesizer}</td>
+ *  </tr>
+ *  <tr>
+ *   <td><code>javax.sound.midi.Transmitter</code></td>
+ *   <td>{@link Transmitter}</td>
+ *   <td>{@link #getTransmitter}</td>
+ *  </tr>
+ * </table>
+ *
+ * The property value consists of the provider class name
+ * and the device name, separated by the hash mark (&quot;#&quot;).
+ * The provider class name is the fully-qualified
+ * name of a concrete {@link javax.sound.midi.spi.MidiDeviceProvider
+ * MIDI device provider} class. The device name is matched against
+ * the <code>String</code> returned by the <code>getName</code>
+ * method of <code>MidiDevice.Info</code>.
+ * Either the class name, or the device name may be omitted.
+ * If only the class name is specified, the trailing hash mark
+ * is optional.
+ *
+ * <p>If the provider class is specified, and it can be
+ * successully retrieved from the installed providers,
+ * the list of
+ * <code>MidiDevice.Info</code> objects is retrieved
+ * from the provider. Otherwise, or when these devices
+ * do not provide a subsequent match, the list is retrieved
+ * from {@link #getMidiDeviceInfo} to contain
+ * all available <code>MidiDevice.Info</code> objects.
+ *
+ * <p>If a device name is specified, the resulting list of
+ * <code>MidiDevice.Info</code> objects is searched:
+ * the first one with a matching name, and whose
+ * <code>MidiDevice</code> implements the
+ * respective interface, will be returned.
+ * If no matching <code>MidiDevice.Info</code> object
+ * is found, or the device name is not specified,
+ * the first suitable device from the resulting
+ * list will be returned. For Sequencer and Synthesizer,
+ * a device is suitable if it implements the respective
+ * interface; whereas for Receiver and Transmitter, a device is
+ * suitable if it
+ * implements neither Sequencer nor Synthesizer and provides
+ * at least one Receiver or Transmitter, respectively.
+ *
+ * For example, the property <code>javax.sound.midi.Receiver</code>
+ * with a value
+ * <code>&quot;com.sun.media.sound.MidiProvider#SunMIDI1&quot;</code>
+ * will have the following consequences when
+ * <code>getReceiver</code> is called:
+ * if the class <code>com.sun.media.sound.MidiProvider</code> exists
+ * in the list of installed MIDI device providers,
+ * the first <code>Receiver</code> device with name
+ * <code>&quot;SunMIDI1&quot;</code> will be returned. If it cannot
+ * be found, the first <code>Receiver</code> from that provider
+ * will be returned, regardless of name.
+ * If there is none, the first <code>Receiver</code> with name
+ * <code>&quot;SunMIDI1&quot;</code> in the list of all devices
+ * (as returned by <code>getMidiDeviceInfo</code>) will be returned,
+ * or, if not found, the first <code>Receiver</code> that can
+ * be found in the list of all devices is returned.
+ * If that fails, too, a <code>MidiUnavailableException</code>
+ * is thrown.
+ *
+ * @author Kara Kytle
+ * @author Florian Bomers
+ * @author Matthias Pfisterer
+ */
+public class MidiSystem {
+
+    /**
+     * Private no-args constructor for ensuring against instantiation.
+     */
+    private MidiSystem() {
+    }
+
+
+    /**
+     * Obtains an array of information objects representing
+     * the set of all MIDI devices available on the system.
+     * A returned information object can then be used to obtain the
+     * corresponding device object, by invoking
+     * {@link #getMidiDevice(MidiDevice.Info) getMidiDevice}.
+     *
+     * @return an array of <code>MidiDevice.Info</code> objects, one
+     * for each installed MIDI device.  If no such devices are installed,
+     * an array of length 0 is returned.
+     */
+    public static MidiDevice.Info[] getMidiDeviceInfo() {
+        List allInfos = new ArrayList();
+        List providers = getMidiDeviceProviders();
+
+        for(int i = 0; i < providers.size(); i++) {
+            MidiDeviceProvider provider = (MidiDeviceProvider) providers.get(i);
+            MidiDevice.Info[] tmpinfo = provider.getDeviceInfo();
+            for (int j = 0; j < tmpinfo.length; j++) {
+                allInfos.add( tmpinfo[j] );
+            }
+        }
+        MidiDevice.Info[] infosArray = (MidiDevice.Info[]) allInfos.toArray(new MidiDevice.Info[0]);
+        return infosArray;
+    }
+
+
+    /**
+     * Obtains the requested MIDI device.
+     *
+     * @param info a device information object representing the desired device.
+     * @return the requested device
+     * @throws MidiUnavailableException if the requested device is not available
+     * due to resource restrictions
+     * @throws IllegalArgumentException if the info object does not represent
+     * a MIDI device installed on the system
+     * @see #getMidiDeviceInfo
+     */
+    public static MidiDevice getMidiDevice(MidiDevice.Info info) throws MidiUnavailableException {
+        List providers = getMidiDeviceProviders();
+
+        for(int i = 0; i < providers.size(); i++) {
+            MidiDeviceProvider provider = (MidiDeviceProvider) providers.get(i);
+            if (provider.isDeviceSupported(info)) {
+                MidiDevice device = provider.getDevice(info);
+                return device;
+            }
+        }
+        throw new IllegalArgumentException("Requested device not installed: " + info);
+    }
+
+
+    /**
+     * Obtains a MIDI receiver from an external MIDI port
+     * or other default device.
+     *
+     * <p>If the system property
+     * <code>javax.sound.midi.Receiver</code>
+     * is defined or it is defined in the file &quot;sound.properties&quot;,
+     * it is used to identify the device that provides the default receiver.
+     * For details, refer to the {@link MidiSystem class description}.
+     *
+     * If a suitable MIDI port is not available, the Receiver is
+     * retrieved from an installed synthesizer.
+     *
+     * <p>If this method returns successfully, the {@link
+     * javax.sound.midi.MidiDevice MidiDevice} the
+     * <code>Receiver</code> belongs to is opened implicitly, if it is
+     * not already open. It is possible to close an implicitly opened
+     * device by calling {@link javax.sound.midi.Receiver#close close}
+     * on the returned <code>Receiver</code>. All open <code>Receiver</code>
+     * instances have to be closed in order to release system resources
+     * hold by the <code>MidiDevice</code>. For a
+     * detailed description of open/close behaviour see the class
+     * description of {@link javax.sound.midi.MidiDevice MidiDevice}.
+     *
+     *
+     * @return the default MIDI receiver
+     * @throws MidiUnavailableException if the default receiver is not
+     *         available due to resource restrictions,
+     *         or no device providing receivers is installed in the system
+     */
+    public static Receiver getReceiver() throws MidiUnavailableException {
+        // may throw MidiUnavailableException
+        MidiDevice device = getDefaultDeviceWrapper(Receiver.class);
+        Receiver receiver;
+        if (device instanceof ReferenceCountingDevice) {
+            receiver = ((ReferenceCountingDevice) device).getReceiverReferenceCounting();
+        } else {
+            receiver = device.getReceiver();
+        }
+        return receiver;
+    }
+
+
+    /**
+     * Obtains a MIDI transmitter from an external MIDI port
+     * or other default source.
+     *
+     * <p>If the system property
+     * <code>javax.sound.midi.Transmitter</code>
+     * is defined or it is defined in the file &quot;sound.properties&quot;,
+     * it is used to identify the device that provides the default transmitter.
+     * For details, refer to the {@link MidiSystem class description}.
+     *
+     * If this method returns successfully, the {@link
+     * javax.sound.midi.MidiDevice MidiDevice} the
+     * <code>Transmitter</code> belongs to is opened implicitly, if it
+     * is not already open. It is possible to close an implicitly
+     * opened device by calling {@link
+     * javax.sound.midi.Transmitter#close close} on the returned
+     * <code>Transmitter</code>. All open <code>Transmitter</code>
+     * instances have to be closed in order to release system resources
+     * hold by the <code>MidiDevice</code>. For a detailed description
+     * of open/close behaviour see the class description of {@link
+     * javax.sound.midi.MidiDevice MidiDevice}.
+     *
+     * @return the default MIDI transmitter
+     * @throws MidiUnavailableException if the default transmitter is not
+     *         available due to resource restrictions,
+     *         or no device providing transmitters is installed in the system
+     */
+    public static Transmitter getTransmitter() throws MidiUnavailableException {
+        // may throw MidiUnavailableException
+        MidiDevice device = getDefaultDeviceWrapper(Transmitter.class);
+        Transmitter transmitter;
+        if (device instanceof ReferenceCountingDevice) {
+            transmitter = ((ReferenceCountingDevice) device).getTransmitterReferenceCounting();
+        } else {
+            transmitter = device.getTransmitter();
+        }
+        return transmitter;
+    }
+
+
+    /**
+     * Obtains the default synthesizer.
+     *
+     * <p>If the system property
+     * <code>javax.sound.midi.Synthesizer</code>
+     * is defined or it is defined in the file &quot;sound.properties&quot;,
+     * it is used to identify the default synthesizer.
+     * For details, refer to the {@link MidiSystem class description}.
+     *
+     * @return the default synthesizer
+     * @throws MidiUnavailableException if the synthesizer is not
+     *         available due to resource restrictions,
+     *         or no synthesizer is installed in the system
+     */
+    public static Synthesizer getSynthesizer() throws MidiUnavailableException {
+        // may throw MidiUnavailableException
+        return (Synthesizer) getDefaultDeviceWrapper(Synthesizer.class);
+    }
+
+
+    /**
+     * Obtains the default <code>Sequencer</code>, connected to
+     * a default device.
+     * The returned <code>Sequencer</code> instance is
+     * connected to the default <code>Synthesizer</code>,
+     * as returned by {@link #getSynthesizer}.
+     * If there is no <code>Synthesizer</code>
+     * available, or the default <code>Synthesizer</code>
+     * cannot be opened, the <code>sequencer</code> is connected
+     * to the default <code>Receiver</code>, as returned
+     * by {@link #getReceiver}.
+     * The connection is made by retrieving a <code>Transmitter</code>
+     * instance from the <code>Sequencer</code> and setting its
+     * <code>Receiver</code>.
+     * Closing and re-opening the sequencer will restore the
+     * connection to the default device.
+     *
+     * <p>This method is equivalent to calling
+     * <code>getSequencer(true)</code>.
+     *
+     * <p>If the system property
+     * <code>javax.sound.midi.Sequencer</code>
+     * is defined or it is defined in the file &quot;sound.properties&quot;,
+     * it is used to identify the default sequencer.
+     * For details, refer to the {@link MidiSystem class description}.
+     *
+     * @return the default sequencer, connected to a default Receiver
+     * @throws MidiUnavailableException if the sequencer is not
+     *         available due to resource restrictions,
+     *         or there is no <code>Receiver</code> available by any
+     *         installed <code>MidiDevice</code>,
+     *         or no sequencer is installed in the system.
+     * @see #getSequencer(boolean)
+     * @see #getSynthesizer
+     * @see #getReceiver
+     */
+    public static Sequencer getSequencer() throws MidiUnavailableException {
+        return getSequencer(true);
+    }
+
+
+
+    /**
+     * Obtains the default <code>Sequencer</code>, optionally
+     * connected to a default device.
+     *
+     * <p>If <code>connected</code> is true, the returned
+     * <code>Sequencer</code> instance is
+     * connected to the default <code>Synthesizer</code>,
+     * as returned by {@link #getSynthesizer}.
+     * If there is no <code>Synthesizer</code>
+     * available, or the default <code>Synthesizer</code>
+     * cannot be opened, the <code>sequencer</code> is connected
+     * to the default <code>Receiver</code>, as returned
+     * by {@link #getReceiver}.
+     * The connection is made by retrieving a <code>Transmitter</code>
+     * instance from the <code>Sequencer</code> and setting its
+     * <code>Receiver</code>.
+     * Closing and re-opening the sequencer will restore the
+     * connection to the default device.
+     *
+     * <p>If <code>connected</code> is false, the returned
+     * <code>Sequencer</code> instance is not connected, it
+     * has no open <code>Transmitters</code>. In order to
+     * play the sequencer on a MIDI device, or a <code>Synthesizer</code>,
+     * it is necessary to get a <code>Transmitter</code> and set its
+     * <code>Receiver</code>.
+     *
+     * <p>If the system property
+     * <code>javax.sound.midi.Sequencer</code>
+     * is defined or it is defined in the file "sound.properties",
+     * it is used to identify the default sequencer.
+     * For details, refer to the {@link MidiSystem class description}.
+     *
+     * @return the default sequencer
+     * @throws MidiUnavailableException if the sequencer is not
+     *         available due to resource restrictions,
+     *         or no sequencer is installed in the system,
+     *         or if <code>connected</code> is true, and there is
+     *         no <code>Receiver</code> available by any installed
+     *         <code>MidiDevice</code>
+     * @see #getSynthesizer
+     * @see #getReceiver
+     * @since 1.5
+     */
+    public static Sequencer getSequencer(boolean connected)
+        throws MidiUnavailableException {
+        Sequencer seq = (Sequencer) getDefaultDeviceWrapper(Sequencer.class);
+
+        if (connected) {
+            // IMPORTANT: this code needs to be synch'ed with
+            //            all AutoConnectSequencer instances,
+            //            (e.g. RealTimeSequencer) because the
+            //            same algorithm for synth retrieval
+            //            needs to be used!
+
+            Receiver rec = null;
+            MidiUnavailableException mue = null;
+
+            // first try to connect to the default synthesizer
+            try {
+                Synthesizer synth = getSynthesizer();
+                if (synth instanceof ReferenceCountingDevice) {
+                    rec = ((ReferenceCountingDevice) synth).getReceiverReferenceCounting();
+                    // only use MixerSynth if it could successfully load a soundbank
+                    if (synth.getClass().toString().contains("com.sun.media.sound.MixerSynth")
+                        && (synth.getDefaultSoundbank() == null)) {
+                        // don't use this receiver if no soundbank available
+                        rec = null;
+                        synth.close();
+                    }
+                } else {
+                    synth.open();
+                    try {
+                        rec = synth.getReceiver();
+                    } finally {
+                        // make sure that the synth is properly closed
+                        if (rec == null) {
+                            synth.close();
+                        }
+                    }
+                }
+            } catch (MidiUnavailableException e) {
+                // something went wrong with synth
+                if (e instanceof MidiUnavailableException) {
+                    mue = (MidiUnavailableException) e;
+                }
+            }
+            if (rec == null) {
+                // then try to connect to the default Receiver
+                try {
+                    rec = MidiSystem.getReceiver();
+                } catch (Exception e) {
+                    // something went wrong. Nothing to do then!
+                    if (e instanceof MidiUnavailableException) {
+                        mue = (MidiUnavailableException) e;
+                    }
+                }
+            }
+            if (rec != null) {
+                seq.getTransmitter().setReceiver(rec);
+                if (seq instanceof AutoConnectSequencer) {
+                    ((AutoConnectSequencer) seq).setAutoConnect(rec);
+                }
+            } else {
+                if (mue != null) {
+                    throw mue;
+                }
+                throw new MidiUnavailableException("no receiver available");
+            }
+        }
+        return seq;
+    }
+
+
+
+
+    /**
+     * Constructs a MIDI sound bank by reading it from the specified stream.
+     * The stream must point to
+     * a valid MIDI soundbank file.  In general, MIDI soundbank providers may
+     * need to read some data from the stream before determining whether they
+     * support it.  These parsers must
+     * be able to mark the stream, read enough data to determine whether they
+     * support the stream, and, if not, reset the stream's read pointer to
+     * its original position.  If the input stream does not support this,
+     * this method may fail with an IOException.
+     * @param stream the source of the sound bank data.
+     * @return the sound bank
+     * @throws InvalidMidiDataException if the stream does not point to
+     * valid MIDI soundbank data recognized by the system
+     * @throws IOException if an I/O error occurred when loading the soundbank
+     * @see InputStream#markSupported
+     * @see InputStream#mark
+     */
+    public static Soundbank getSoundbank(InputStream stream)
+        throws InvalidMidiDataException, IOException {
+
+        SoundbankReader sp = null;
+        Soundbank s = null;
+
+        List providers = getSoundbankReaders();
+
+        for(int i = 0; i < providers.size(); i++) {
+            sp = (SoundbankReader)providers.get(i);
+            s = sp.getSoundbank(stream);
+
+            if( s!= null) {
+                return s;
+            }
+        }
+        throw new InvalidMidiDataException("cannot get soundbank from stream");
+
+    }
+
+
+    /**
+     * Constructs a <code>Soundbank</code> by reading it from the specified URL.
+     * The URL must point to a valid MIDI soundbank file.
+     *
+     * @param url the source of the sound bank data
+     * @return the sound bank
+     * @throws InvalidMidiDataException if the URL does not point to valid MIDI
+     * soundbank data recognized by the system
+     * @throws IOException if an I/O error occurred when loading the soundbank
+     */
+    public static Soundbank getSoundbank(URL url)
+        throws InvalidMidiDataException, IOException {
+
+        SoundbankReader sp = null;
+        Soundbank s = null;
+
+        List providers = getSoundbankReaders();
+
+        for(int i = 0; i < providers.size(); i++) {
+            sp = (SoundbankReader)providers.get(i);
+            s = sp.getSoundbank(url);
+
+            if( s!= null) {
+                return s;
+            }
+        }
+        throw new InvalidMidiDataException("cannot get soundbank from stream");
+
+    }
+
+
+    /**
+     * Constructs a <code>Soundbank</code> by reading it from the specified
+     * <code>File</code>.
+     * The <code>File</code> must point to a valid MIDI soundbank file.
+     *
+     * @param file the source of the sound bank data
+     * @return the sound bank
+     * @throws InvalidMidiDataException if the <code>File</code> does not
+     * point to valid MIDI soundbank data recognized by the system
+     * @throws IOException if an I/O error occurred when loading the soundbank
+     */
+    public static Soundbank getSoundbank(File file)
+        throws InvalidMidiDataException, IOException {
+
+        SoundbankReader sp = null;
+        Soundbank s = null;
+
+        List providers = getSoundbankReaders();
+
+        for(int i = 0; i < providers.size(); i++) {
+            sp = (SoundbankReader)providers.get(i);
+            s = sp.getSoundbank(file);
+
+            if( s!= null) {
+                return s;
+            }
+        }
+        throw new InvalidMidiDataException("cannot get soundbank from stream");
+    }
+
+
+
+    /**
+     * Obtains the MIDI file format of the data in the specified input stream.
+     * The stream must point to valid MIDI file data for a file type recognized
+     * by the system.
+     * <p>
+     * This method and/or the code it invokes may need to read some data from
+     * the stream to determine whether its data format is supported.  The
+     * implementation may therefore
+     * need to mark the stream, read enough data to determine whether it is in
+     * a supported format, and reset the stream's read pointer to its original
+     * position.  If the input stream does not permit this set of operations,
+     * this method may fail with an <code>IOException</code>.
+     * <p>
+     * This operation can only succeed for files of a type which can be parsed
+     * by an installed file reader.  It may fail with an InvalidMidiDataException
+     * even for valid files if no compatible file reader is installed.  It
+     * will also fail with an InvalidMidiDataException if a compatible file reader
+     * is installed, but encounters errors while determining the file format.
+     *
+     * @param stream the input stream from which file format information
+     * should be extracted
+     * @return an <code>MidiFileFormat</code> object describing the MIDI file
+     * format
+     * @throws InvalidMidiDataException if the stream does not point to valid
+     * MIDI file data recognized by the system
+     * @throws IOException if an I/O exception occurs while accessing the
+     * stream
+     * @see #getMidiFileFormat(URL)
+     * @see #getMidiFileFormat(File)
+     * @see InputStream#markSupported
+     * @see InputStream#mark
+     */
+    public static MidiFileFormat getMidiFileFormat(InputStream stream)
+        throws InvalidMidiDataException, IOException {
+
+        List providers = getMidiFileReaders();
+        MidiFileFormat format = null;
+
+        for(int i = 0; i < providers.size(); i++) {
+            MidiFileReader reader = (MidiFileReader) providers.get(i);
+            try {
+                format = reader.getMidiFileFormat( stream ); // throws IOException
+                break;
+            } catch (InvalidMidiDataException e) {
+                continue;
+            }
+        }
+
+        if( format==null ) {
+            throw new InvalidMidiDataException("input stream is not a supported file type");
+        } else {
+            return format;
+        }
+    }
+
+
+    /**
+     * Obtains the MIDI file format of the data in the specified URL.  The URL
+     * must point to valid MIDI file data for a file type recognized
+     * by the system.
+     * <p>
+     * This operation can only succeed for files of a type which can be parsed
+     * by an installed file reader.  It may fail with an InvalidMidiDataException
+     * even for valid files if no compatible file reader is installed.  It
+     * will also fail with an InvalidMidiDataException if a compatible file reader
+     * is installed, but encounters errors while determining the file format.
+     *
+     * @param url the URL from which file format information should be
+     * extracted
+     * @return a <code>MidiFileFormat</code> object describing the MIDI file
+     * format
+     * @throws InvalidMidiDataException if the URL does not point to valid MIDI
+     * file data recognized by the system
+     * @throws IOException if an I/O exception occurs while accessing the URL
+     *
+     * @see #getMidiFileFormat(InputStream)
+     * @see #getMidiFileFormat(File)
+     */
+    public static MidiFileFormat getMidiFileFormat(URL url)
+        throws InvalidMidiDataException, IOException {
+
+        List providers = getMidiFileReaders();
+        MidiFileFormat format = null;
+
+        for(int i = 0; i < providers.size(); i++) {
+            MidiFileReader reader = (MidiFileReader) providers.get(i);
+            try {
+                format = reader.getMidiFileFormat( url ); // throws IOException
+                break;
+            } catch (InvalidMidiDataException e) {
+                continue;
+            }
+        }
+
+        if( format==null ) {
+            throw new InvalidMidiDataException("url is not a supported file type");
+        } else {
+            return format;
+        }
+    }
+
+
+    /**
+     * Obtains the MIDI file format of the specified <code>File</code>.  The
+     * <code>File</code> must point to valid MIDI file data for a file type
+     * recognized by the system.
+     * <p>
+     * This operation can only succeed for files of a type which can be parsed
+     * by an installed file reader.  It may fail with an InvalidMidiDataException
+     * even for valid files if no compatible file reader is installed.  It
+     * will also fail with an InvalidMidiDataException if a compatible file reader
+     * is installed, but encounters errors while determining the file format.
+     *
+     * @param file the <code>File</code> from which file format information
+     * should be extracted
+     * @return a <code>MidiFileFormat</code> object describing the MIDI file
+     * format
+     * @throws InvalidMidiDataException if the <code>File</code> does not point
+     *  to valid MIDI file data recognized by the system
+     * @throws IOException if an I/O exception occurs while accessing the file
+     *
+     * @see #getMidiFileFormat(InputStream)
+     * @see #getMidiFileFormat(URL)
+     */
+    public static MidiFileFormat getMidiFileFormat(File file)
+        throws InvalidMidiDataException, IOException {
+
+        List providers = getMidiFileReaders();
+        MidiFileFormat format = null;
+
+        for(int i = 0; i < providers.size(); i++) {
+            MidiFileReader reader = (MidiFileReader) providers.get(i);
+            try {
+                format = reader.getMidiFileFormat( file ); // throws IOException
+                break;
+            } catch (InvalidMidiDataException e) {
+                continue;
+            }
+        }
+
+        if( format==null ) {
+            throw new InvalidMidiDataException("file is not a supported file type");
+        } else {
+            return format;
+        }
+    }
+
+
+    /**
+     * Obtains a MIDI sequence from the specified input stream.  The stream must
+     * point to valid MIDI file data for a file type recognized
+     * by the system.
+     * <p>
+     * This method and/or the code it invokes may need to read some data
+     * from the stream to determine whether
+     * its data format is supported.  The implementation may therefore
+     * need to mark the stream, read enough data to determine whether it is in
+     * a supported format, and reset the stream's read pointer to its original
+     * position.  If the input stream does not permit this set of operations,
+     * this method may fail with an <code>IOException</code>.
+     * <p>
+     * This operation can only succeed for files of a type which can be parsed
+     * by an installed file reader.  It may fail with an InvalidMidiDataException
+     * even for valid files if no compatible file reader is installed.  It
+     * will also fail with an InvalidMidiDataException if a compatible file reader
+     * is installed, but encounters errors while constructing the <code>Sequence</code>
+     * object from the file data.
+     *
+     * @param stream the input stream from which the <code>Sequence</code>
+     * should be constructed
+     * @return a <code>Sequence</code> object based on the MIDI file data
+     * contained in the input stream
+     * @throws InvalidMidiDataException if the stream does not point to
+     * valid MIDI file data recognized by the system
+     * @throws IOException if an I/O exception occurs while accessing the
+     * stream
+     * @see InputStream#markSupported
+     * @see InputStream#mark
+     */
+    public static Sequence getSequence(InputStream stream)
+        throws InvalidMidiDataException, IOException {
+
+        List providers = getMidiFileReaders();
+        Sequence sequence = null;
+
+        for(int i = 0; i < providers.size(); i++) {
+            MidiFileReader reader = (MidiFileReader) providers.get(i);
+            try {
+                sequence = reader.getSequence( stream ); // throws IOException
+                break;
+            } catch (InvalidMidiDataException e) {
+                continue;
+            }
+        }
+
+        if( sequence==null ) {
+            throw new InvalidMidiDataException("could not get sequence from input stream");
+        } else {
+            return sequence;
+        }
+    }
+
+
+    /**
+     * Obtains a MIDI sequence from the specified URL.  The URL must
+     * point to valid MIDI file data for a file type recognized
+     * by the system.
+     * <p>
+     * This operation can only succeed for files of a type which can be parsed
+     * by an installed file reader.  It may fail with an InvalidMidiDataException
+     * even for valid files if no compatible file reader is installed.  It
+     * will also fail with an InvalidMidiDataException if a compatible file reader
+     * is installed, but encounters errors while constructing the <code>Sequence</code>
+     * object from the file data.
+     *
+     * @param url the URL from which the <code>Sequence</code> should be
+     * constructed
+     * @return a <code>Sequence</code> object based on the MIDI file data
+     * pointed to by the URL
+     * @throws InvalidMidiDataException if the URL does not point to valid MIDI
+     * file data recognized by the system
+     * @throws IOException if an I/O exception occurs while accessing the URL
+     */
+    public static Sequence getSequence(URL url)
+        throws InvalidMidiDataException, IOException {
+
+        List providers = getMidiFileReaders();
+        Sequence sequence = null;
+
+        for(int i = 0; i < providers.size(); i++) {
+            MidiFileReader reader = (MidiFileReader) providers.get(i);
+            try {
+                sequence = reader.getSequence( url ); // throws IOException
+                break;
+            } catch (InvalidMidiDataException e) {
+                continue;
+            }
+        }
+
+        if( sequence==null ) {
+            throw new InvalidMidiDataException("could not get sequence from URL");
+        } else {
+            return sequence;
+        }
+    }
+
+
+    /**
+     * Obtains a MIDI sequence from the specified <code>File</code>.
+     * The <code>File</code> must point to valid MIDI file data
+     * for a file type recognized by the system.
+     * <p>
+     * This operation can only succeed for files of a type which can be parsed
+     * by an installed file reader.  It may fail with an InvalidMidiDataException
+     * even for valid files if no compatible file reader is installed.  It
+     * will also fail with an InvalidMidiDataException if a compatible file reader
+     * is installed, but encounters errors while constructing the <code>Sequence</code>
+     * object from the file data.
+     *
+     * @param file the <code>File</code> from which the <code>Sequence</code>
+     * should be constructed
+     * @return a <code>Sequence</code> object based on the MIDI file data
+     * pointed to by the File
+     * @throws InvalidMidiDataException if the File does not point to valid MIDI
+     * file data recognized by the system
+     * @throws IOException if an I/O exception occurs
+     */
+    public static Sequence getSequence(File file)
+        throws InvalidMidiDataException, IOException {
+
+        List providers = getMidiFileReaders();
+        Sequence sequence = null;
+
+        for(int i = 0; i < providers.size(); i++) {
+            MidiFileReader reader = (MidiFileReader) providers.get(i);
+            try {
+                sequence = reader.getSequence( file ); // throws IOException
+                break;
+            } catch (InvalidMidiDataException e) {
+                continue;
+            }
+        }
+
+        if( sequence==null ) {
+            throw new InvalidMidiDataException("could not get sequence from file");
+        } else {
+            return sequence;
+        }
+    }
+
+
+    /**
+     * Obtains the set of MIDI file types for which file writing support is
+     * provided by the system.
+     * @return array of unique file types.  If no file types are supported,
+     * an array of length 0 is returned.
+     */
+    public static int[] getMidiFileTypes() {
+
+        List providers = getMidiFileWriters();
+        Set allTypes = new HashSet();
+
+        // gather from all the providers
+
+        for (int i = 0; i < providers.size(); i++ ) {
+            MidiFileWriter writer = (MidiFileWriter) providers.get(i);
+            int[] types = writer.getMidiFileTypes();
+            for (int j = 0; j < types.length; j++ ) {
+                allTypes.add(new Integer(types[j]));
+            }
+        }
+        int resultTypes[] = new int[allTypes.size()];
+        int index = 0;
+        Iterator iterator = allTypes.iterator();
+        while (iterator.hasNext()) {
+            Integer integer = (Integer) iterator.next();
+            resultTypes[index++] = integer.intValue();
+        }
+        return resultTypes;
+    }
+
+
+    /**
+     * Indicates whether file writing support for the specified MIDI file type
+     * is provided by the system.
+     * @param fileType the file type for which write capabilities are queried
+     * @return <code>true</code> if the file type is supported,
+     * otherwise <code>false</code>
+     */
+    public static boolean isFileTypeSupported(int fileType) {
+
+        List providers = getMidiFileWriters();
+
+        for (int i = 0; i < providers.size(); i++ ) {
+            MidiFileWriter writer = (MidiFileWriter) providers.get(i);
+            if( writer.isFileTypeSupported(fileType)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+
+    /**
+     * Obtains the set of MIDI file types that the system can write from the
+     * sequence specified.
+     * @param sequence the sequence for which MIDI file type support
+     * is queried
+     * @return the set of unique supported file types.  If no file types are supported,
+     * returns an array of length 0.
+     */
+    public static int[] getMidiFileTypes(Sequence sequence) {
+
+        List providers = getMidiFileWriters();
+        Set allTypes = new HashSet();
+
+        // gather from all the providers
+
+        for (int i = 0; i < providers.size(); i++ ) {
+            MidiFileWriter writer = (MidiFileWriter) providers.get(i);
+            int[] types = writer.getMidiFileTypes(sequence);
+            for (int j = 0; j < types.length; j++ ) {
+                allTypes.add(new Integer(types[j]));
+            }
+        }
+        int resultTypes[] = new int[allTypes.size()];
+        int index = 0;
+        Iterator iterator = allTypes.iterator();
+        while (iterator.hasNext()) {
+            Integer integer = (Integer) iterator.next();
+            resultTypes[index++] = integer.intValue();
+        }
+        return resultTypes;
+    }
+
+
+    /**
+     * Indicates whether a MIDI file of the file type specified can be written
+     * from the sequence indicated.
+     * @param fileType the file type for which write capabilities
+     * are queried
+     * @param sequence the sequence for which file writing support is queried
+     * @return <code>true</code> if the file type is supported for this
+     * sequence, otherwise <code>false</code>
+     */
+    public static boolean isFileTypeSupported(int fileType, Sequence sequence) {
+
+        List providers = getMidiFileWriters();
+
+        for (int i = 0; i < providers.size(); i++ ) {
+            MidiFileWriter writer = (MidiFileWriter) providers.get(i);
+            if( writer.isFileTypeSupported(fileType,sequence)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+
+    /**
+     * Writes a stream of bytes representing a file of the MIDI file type
+     * indicated to the output stream provided.
+     * @param in sequence containing MIDI data to be written to the file
+     * @param fileType the file type of the file to be written to the output stream
+     * @param out stream to which the file data should be written
+     * @return the number of bytes written to the output stream
+     * @throws IOException if an I/O exception occurs
+     * @throws IllegalArgumentException if the file format is not supported by
+     * the system
+     * @see #isFileTypeSupported(int, Sequence)
+     * @see     #getMidiFileTypes(Sequence)
+     */
+    public static int write(Sequence in, int fileType, OutputStream out) throws IOException {
+
+        List providers = getMidiFileWriters();
+        //$$fb 2002-04-17: Fix for 4635287: Standard MidiFileWriter cannot write empty Sequences
+        int bytesWritten = -2;
+
+        for (int i = 0; i < providers.size(); i++ ) {
+            MidiFileWriter writer = (MidiFileWriter) providers.get(i);
+            if( writer.isFileTypeSupported( fileType, in ) ) {
+
+                bytesWritten = writer.write(in, fileType, out);
+                break;
+            }
+        }
+        if (bytesWritten == -2) {
+            throw new IllegalArgumentException("MIDI file type is not supported");
+        }
+        return bytesWritten;
+    }
+
+
+    /**
+     * Writes a stream of bytes representing a file of the MIDI file type
+     * indicated to the external file provided.
+     * @param in sequence containing MIDI data to be written to the file
+     * @param type the file type of the file to be written to the output stream
+     * @param out external file to which the file data should be written
+     * @return the number of bytes written to the file
+     * @throws IOException if an I/O exception occurs
+     * @throws IllegalArgumentException if the file type is not supported by
+     * the system
+     * @see #isFileTypeSupported(int, Sequence)
+     * @see     #getMidiFileTypes(Sequence)
+     */
+    public static int write(Sequence in, int type, File out) throws IOException {
+
+        List providers = getMidiFileWriters();
+        //$$fb 2002-04-17: Fix for 4635287: Standard MidiFileWriter cannot write empty Sequences
+        int bytesWritten = -2;
+
+        for (int i = 0; i < providers.size(); i++ ) {
+            MidiFileWriter writer = (MidiFileWriter) providers.get(i);
+            if( writer.isFileTypeSupported( type, in ) ) {
+
+                bytesWritten = writer.write(in, type, out);
+                break;
+            }
+        }
+        if (bytesWritten == -2) {
+            throw new IllegalArgumentException("MIDI file type is not supported");
+        }
+        return bytesWritten;
+    }
+
+
+
+    // HELPER METHODS
+
+    private static List getMidiDeviceProviders() {
+        return getProviders(MidiDeviceProvider.class);
+    }
+
+
+    private static List getSoundbankReaders() {
+        return getProviders(SoundbankReader.class);
+    }
+
+
+    private static List getMidiFileWriters() {
+        return getProviders(MidiFileWriter.class);
+    }
+
+
+    private static List getMidiFileReaders() {
+        return getProviders(MidiFileReader.class);
+    }
+
+
+    /** Attempts to locate and return a default MidiDevice of the specified
+     * type.
+     *
+     * This method wraps {@link #getDefaultDevice}. It catches the
+     * <code>IllegalArgumentException</code> thrown by
+     * <code>getDefaultDevice</code> and instead throws a
+     * <code>MidiUnavailableException</code>, with the catched
+     * exception chained.
+     *
+     * @param deviceClass The requested device type, one of Synthesizer.class,
+     * Sequencer.class, Receiver.class or Transmitter.class.
+     * @throws  MidiUnavalableException on failure.
+     */
+    private static MidiDevice getDefaultDeviceWrapper(Class deviceClass)
+        throws MidiUnavailableException{
+        try {
+            return getDefaultDevice(deviceClass);
+        } catch (IllegalArgumentException iae) {
+            MidiUnavailableException mae = new MidiUnavailableException();
+            mae.initCause(iae);
+            throw mae;
+        }
+    }
+
+
+    /** Attempts to locate and return a default MidiDevice of the specified
+     * type.
+     *
+     * @param deviceClass The requested device type, one of Synthesizer.class,
+     * Sequencer.class, Receiver.class or Transmitter.class.
+     * @throws  IllegalArgumentException on failure.
+     */
+    private static MidiDevice getDefaultDevice(Class deviceClass) {
+        List providers = getMidiDeviceProviders();
+        String providerClassName = JDK13Services.getDefaultProviderClassName(deviceClass);
+        String instanceName = JDK13Services.getDefaultInstanceName(deviceClass);
+        MidiDevice device;
+
+        if (providerClassName != null) {
+            MidiDeviceProvider defaultProvider = getNamedProvider(providerClassName, providers);
+            if (defaultProvider != null) {
+                if (instanceName != null) {
+                    device = getNamedDevice(instanceName, defaultProvider, deviceClass);
+                    if (device != null) {
+                        return device;
+                    }
+                }
+                device = getFirstDevice(defaultProvider, deviceClass);
+                if (device != null) {
+                    return device;
+                }
+            }
+        }
+
+        /* Provider class not specified or cannot be found, or
+           provider class specified, and no appropriate device available or
+           provider class and instance specified and instance cannot be found or is not appropriate */
+        if (instanceName != null) {
+            device = getNamedDevice(instanceName, providers, deviceClass);
+            if (device != null) {
+                return device;
+            }
+        }
+
+        /* No default are specified, or if something is specified, everything
+           failed. */
+        device = getFirstDevice(providers, deviceClass);
+        if (device != null) {
+            return device;
+        }
+        throw new IllegalArgumentException("Requested device not installed");
+    }
+
+
+
+    /** Return a MidiDeviceProcider of a given class from the list of
+        MidiDeviceProviders.
+
+        @param providerClassName The class name of the provider to be returned.
+        @param provider The list of MidiDeviceProviders that is searched.
+        @return A MidiDeviceProvider of the requested class, or null if none
+        is found.
+    */
+    private static MidiDeviceProvider getNamedProvider(String providerClassName, List providers) {
+        for(int i = 0; i < providers.size(); i++) {
+            MidiDeviceProvider provider = (MidiDeviceProvider) providers.get(i);
+            if (provider.getClass().getName().equals(providerClassName)) {
+                return provider;
+            }
+        }
+        return null;
+    }
+
+
+    /** Return a MidiDevice with a given name from a given MidiDeviceProvider.
+        @param deviceName The name of the MidiDevice to be returned.
+        @param provider The MidiDeviceProvider to check for MidiDevices.
+        @param deviceClass The requested device type, one of Synthesizer.class,
+        Sequencer.class, Receiver.class or Transmitter.class.
+
+        @return A MidiDevice matching the requirements, or null if none is found.
+    */
+    private static MidiDevice getNamedDevice(String deviceName,
+                                             MidiDeviceProvider provider,
+                                             Class deviceClass) {
+        MidiDevice device;
+        // try to get MIDI port
+        device = getNamedDevice(deviceName, provider, deviceClass,
+                                 false, false);
+        if (device != null) {
+            return device;
+        }
+
+        if (deviceClass == Receiver.class) {
+            // try to get Synthesizer
+            device = getNamedDevice(deviceName, provider, deviceClass,
+                                     true, false);
+            if (device != null) {
+                return device;
+            }
+        }
+
+        return null;
+    }
+
+
+    /** Return a MidiDevice with a given name from a given MidiDeviceProvider.
+      @param deviceName The name of the MidiDevice to be returned.
+      @param provider The MidiDeviceProvider to check for MidiDevices.
+      @param deviceClass The requested device type, one of Synthesizer.class,
+      Sequencer.class, Receiver.class or Transmitter.class.
+
+      @return A MidiDevice matching the requirements, or null if none is found.
+     */
+    private static MidiDevice getNamedDevice(String deviceName,
+                                             MidiDeviceProvider provider,
+                                             Class deviceClass,
+                                             boolean allowSynthesizer,
+                                             boolean allowSequencer) {
+        MidiDevice.Info[] infos = provider.getDeviceInfo();
+        for (int i = 0; i < infos.length; i++) {
+            if (infos[i].getName().equals(deviceName)) {
+                MidiDevice device = provider.getDevice(infos[i]);
+                if (isAppropriateDevice(device, deviceClass,
+                                        allowSynthesizer, allowSequencer)) {
+                    return device;
+                }
+            }
+        }
+        return null;
+    }
+
+
+    /** Return a MidiDevice with a given name from a list of
+        MidiDeviceProviders.
+        @param deviceName The name of the MidiDevice to be returned.
+        @param providers The List of MidiDeviceProviders to check for
+        MidiDevices.
+        @param deviceClass The requested device type, one of Synthesizer.class,
+        Sequencer.class, Receiver.class or Transmitter.class.
+        @return A Mixer matching the requirements, or null if none is found.
+    */
+    private static MidiDevice getNamedDevice(String deviceName,
+                                             List providers,
+                                             Class deviceClass) {
+        MidiDevice device;
+        // try to get MIDI port
+        device = getNamedDevice(deviceName, providers, deviceClass,
+                                 false, false);
+        if (device != null) {
+            return device;
+        }
+
+        if (deviceClass == Receiver.class) {
+            // try to get Synthesizer
+            device = getNamedDevice(deviceName, providers, deviceClass,
+                                     true, false);
+            if (device != null) {
+                return device;
+            }
+        }
+
+        return null;
+    }
+
+
+    /** Return a MidiDevice with a given name from a list of
+        MidiDeviceProviders.
+        @param deviceName The name of the MidiDevice to be returned.
+        @param providers The List of MidiDeviceProviders to check for
+        MidiDevices.
+        @param deviceClass The requested device type, one of Synthesizer.class,
+        Sequencer.class, Receiver.class or Transmitter.class.
+        @return A Mixer matching the requirements, or null if none is found.
+     */
+    private static MidiDevice getNamedDevice(String deviceName,
+                                             List providers,
+                                             Class deviceClass,
+                                             boolean allowSynthesizer,
+                                             boolean allowSequencer) {
+        for(int i = 0; i < providers.size(); i++) {
+            MidiDeviceProvider provider = (MidiDeviceProvider) providers.get(i);
+            MidiDevice device = getNamedDevice(deviceName, provider,
+                                               deviceClass,
+                                               allowSynthesizer,
+                                               allowSequencer);
+            if (device != null) {
+                return device;
+            }
+        }
+        return null;
+    }
+
+
+    /** From a given MidiDeviceProvider, return the first appropriate device.
+        @param provider The MidiDeviceProvider to check for MidiDevices.
+        @param deviceClass The requested device type, one of Synthesizer.class,
+        Sequencer.class, Receiver.class or Transmitter.class.
+        @return A MidiDevice is considered appropriate, or null if no
+        appropriate device is found.
+    */
+    private static MidiDevice getFirstDevice(MidiDeviceProvider provider,
+                                             Class deviceClass) {
+        MidiDevice device;
+        // try to get MIDI port
+        device = getFirstDevice(provider, deviceClass,
+                                false, false);
+        if (device != null) {
+            return device;
+        }
+
+        if (deviceClass == Receiver.class) {
+            // try to get Synthesizer
+            device = getFirstDevice(provider, deviceClass,
+                                    true, false);
+            if (device != null) {
+                return device;
+            }
+        }
+
+        return null;
+    }
+
+
+    /** From a given MidiDeviceProvider, return the first appropriate device.
+        @param provider The MidiDeviceProvider to check for MidiDevices.
+        @param deviceClass The requested device type, one of Synthesizer.class,
+        Sequencer.class, Receiver.class or Transmitter.class.
+        @return A MidiDevice is considered appropriate, or null if no
+        appropriate device is found.
+     */
+    private static MidiDevice getFirstDevice(MidiDeviceProvider provider,
+                                             Class deviceClass,
+                                             boolean allowSynthesizer,
+                                             boolean allowSequencer) {
+        MidiDevice.Info[] infos = provider.getDeviceInfo();
+        for (int j = 0; j < infos.length; j++) {
+            MidiDevice device = provider.getDevice(infos[j]);
+            if (isAppropriateDevice(device, deviceClass,
+                                    allowSynthesizer, allowSequencer)) {
+                return device;
+            }
+        }
+        return null;
+    }
+
+
+    /** From a List of MidiDeviceProviders, return the first appropriate
+        MidiDevice.
+        @param providers The List of MidiDeviceProviders to search.
+        @param deviceClass The requested device type, one of Synthesizer.class,
+        Sequencer.class, Receiver.class or Transmitter.class.
+        @return A MidiDevice that is considered appropriate, or null
+        if none is found.
+    */
+    private static MidiDevice getFirstDevice(List providers,
+                                             Class deviceClass) {
+        MidiDevice device;
+        // try to get MIDI port
+        device = getFirstDevice(providers, deviceClass,
+                                false, false);
+        if (device != null) {
+            return device;
+        }
+
+        if (deviceClass == Receiver.class) {
+            // try to get Synthesizer
+            device = getFirstDevice(providers, deviceClass,
+                                    true, false);
+            if (device != null) {
+                return device;
+            }
+        }
+
+        return null;
+    }
+
+
+    /** From a List of MidiDeviceProviders, return the first appropriate
+        MidiDevice.
+        @param providers The List of MidiDeviceProviders to search.
+        @param deviceClass The requested device type, one of Synthesizer.class,
+        Sequencer.class, Receiver.class or Transmitter.class.
+        @return A MidiDevice that is considered appropriate, or null
+        if none is found.
+     */
+    private static MidiDevice getFirstDevice(List providers,
+                                             Class deviceClass,
+                                             boolean allowSynthesizer,
+                                             boolean allowSequencer) {
+        for(int i = 0; i < providers.size(); i++) {
+            MidiDeviceProvider provider = (MidiDeviceProvider) providers.get(i);
+            MidiDevice device = getFirstDevice(provider, deviceClass,
+                                               allowSynthesizer,
+                                               allowSequencer);
+            if (device != null) {
+                return device;
+            }
+        }
+        return null;
+    }
+
+
+    /** Checks if a MidiDevice is appropriate.
+        If deviceClass is Synthesizer or Sequencer, a device implementing
+        the respective interface is considered appropriate. If deviceClass
+        is Receiver or Transmitter, a device is considered appropriate if
+        it implements neither Synthesizer nor Transmitter, and if it can
+        provide at least one Receiver or Transmitter, respectively.
+
+        @param device the MidiDevice to test
+        @param allowSynthesizer if true, Synthesizers are considered
+        appropriate. Otherwise only pure MidiDevices are considered
+        appropriate (unless allowSequencer is true). This flag only has an
+        effect for deviceClass Receiver and Transmitter. For other device
+        classes (Sequencer and Synthesizer), this flag has no effect.
+        @param allowSequencer if true, Sequencers are considered
+        appropriate. Otherwise only pure MidiDevices are considered
+        appropriate (unless allowSynthesizer is true). This flag only has an
+        effect for deviceClass Receiver and Transmitter. For other device
+        classes (Sequencer and Synthesizer), this flag has no effect.
+        @return true if the device is considered appropriate according to the
+        rules given above, false otherwise.
+    */
+    private static boolean isAppropriateDevice(MidiDevice device,
+                                               Class deviceClass,
+                                               boolean allowSynthesizer,
+                                               boolean allowSequencer) {
+        if (deviceClass.isInstance(device)) {
+            // This clause is for deviceClass being either Synthesizer
+            // or Sequencer.
+            return true;
+        } else {
+            // Now the case that deviceClass is Transmitter or
+            // Receiver. If neither allowSynthesizer nor allowSequencer is
+            // true, we require device instances to be
+            // neither Synthesizer nor Sequencer, since we only want
+            // devices representing MIDI ports.
+            // Otherwise, the respective type is accepted, too
+            if ( (! (device instanceof Sequencer) &&
+                  ! (device instanceof Synthesizer) ) ||
+                 ((device instanceof Sequencer) && allowSequencer) ||
+                 ((device instanceof Synthesizer) && allowSynthesizer)) {
+                // And of cource, the device has to be able to provide
+                // Receivers or Transmitters.
+                if ((deviceClass == Receiver.class &&
+                     device.getMaxReceivers() != 0) ||
+                    (deviceClass == Transmitter.class &&
+                     device.getMaxTransmitters() != 0)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+
+    /**
+     * Obtains the set of services currently installed on the system
+     * using sun.misc.Service, the SPI mechanism in 1.3.
+     * @return a List of instances of providers for the requested service.
+     * If no providers are available, a List of length 0 will be returned.
+     */
+    private static List getProviders(Class providerClass) {
+        return JDK13Services.getProviders(providerClass);
+    }
+}