1 /*
   2  * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javax.sound.midi;
  27 
  28 import java.io.FileInputStream;
  29 import java.io.File;
  30 import java.io.InputStream;
  31 import java.io.OutputStream;
  32 import java.io.IOException;
  33 
  34 import java.util.ArrayList;
  35 import java.util.HashSet;
  36 import java.util.Iterator;
  37 import java.util.List;
  38 import java.util.Set;
  39 
  40 import java.net.URL;
  41 
  42 import javax.sound.midi.spi.MidiFileWriter;
  43 import javax.sound.midi.spi.MidiFileReader;
  44 import javax.sound.midi.spi.SoundbankReader;
  45 import javax.sound.midi.spi.MidiDeviceProvider;
  46 
  47 import com.sun.media.sound.JDK13Services;
  48 import com.sun.media.sound.ReferenceCountingDevice;
  49 import com.sun.media.sound.AutoConnectSequencer;
  50 import com.sun.media.sound.MidiDeviceReceiverEnvelope;
  51 import com.sun.media.sound.MidiDeviceTransmitterEnvelope;
  52 
  53 
  54 /**
  55  * The <code>MidiSystem</code> class provides access to the installed MIDI
  56  * system resources, including devices such as synthesizers, sequencers, and
  57  * MIDI input and output ports.  A typical simple MIDI application might
  58  * begin by invoking one or more <code>MidiSystem</code> methods to learn
  59  * what devices are installed and to obtain the ones needed in that
  60  * application.
  61  * <p>
  62  * The class also has methods for reading files, streams, and  URLs that
  63  * contain standard MIDI file data or soundbanks.  You can query the
  64  * <code>MidiSystem</code> for the format of a specified MIDI file.
  65  * <p>
  66  * You cannot instantiate a <code>MidiSystem</code>; all the methods are
  67  * static.
  68  *
  69  * <p>Properties can be used to specify default MIDI devices.
  70  * Both system properties and a properties file are considered.
  71  * The properties file is &quot;lib/sound.properties&quot; in the JRE
  72  * directory. If a property exists both as a system property and in the
  73  * properties file, the system property takes precedence. If none is
  74  * specified, a suitable default is chosen among the available devices.
  75  * The syntax of the properties file is specified in
  76  * {@link java.util.Properties#load(InputStream) Properties.load}. The
  77  * following table lists the available property keys and which methods
  78  * consider them:
  79  *
  80  * <table border=0>
  81  *  <tr>
  82  *   <th>Property Key</th>
  83  *   <th>Interface</th>
  84  *   <th>Affected Method</th>
  85  *  </tr>
  86  *  <tr>
  87  *   <td><code>javax.sound.midi.Receiver</code></td>
  88  *   <td>{@link Receiver}</td>
  89  *   <td>{@link #getReceiver}</td>
  90  *  </tr>
  91  *  <tr>
  92  *   <td><code>javax.sound.midi.Sequencer</code></td>
  93  *   <td>{@link Sequencer}</td>
  94  *   <td>{@link #getSequencer}</td>
  95  *  </tr>
  96  *  <tr>
  97  *   <td><code>javax.sound.midi.Synthesizer</code></td>
  98  *   <td>{@link Synthesizer}</td>
  99  *   <td>{@link #getSynthesizer}</td>
 100  *  </tr>
 101  *  <tr>
 102  *   <td><code>javax.sound.midi.Transmitter</code></td>
 103  *   <td>{@link Transmitter}</td>
 104  *   <td>{@link #getTransmitter}</td>
 105  *  </tr>
 106  * </table>
 107  *
 108  * The property value consists of the provider class name
 109  * and the device name, separated by the hash mark (&quot;#&quot;).
 110  * The provider class name is the fully-qualified
 111  * name of a concrete {@link javax.sound.midi.spi.MidiDeviceProvider
 112  * MIDI device provider} class. The device name is matched against
 113  * the <code>String</code> returned by the <code>getName</code>
 114  * method of <code>MidiDevice.Info</code>.
 115  * Either the class name, or the device name may be omitted.
 116  * If only the class name is specified, the trailing hash mark
 117  * is optional.
 118  *
 119  * <p>If the provider class is specified, and it can be
 120  * successully retrieved from the installed providers,
 121  * the list of
 122  * <code>MidiDevice.Info</code> objects is retrieved
 123  * from the provider. Otherwise, or when these devices
 124  * do not provide a subsequent match, the list is retrieved
 125  * from {@link #getMidiDeviceInfo} to contain
 126  * all available <code>MidiDevice.Info</code> objects.
 127  *
 128  * <p>If a device name is specified, the resulting list of
 129  * <code>MidiDevice.Info</code> objects is searched:
 130  * the first one with a matching name, and whose
 131  * <code>MidiDevice</code> implements the
 132  * respective interface, will be returned.
 133  * If no matching <code>MidiDevice.Info</code> object
 134  * is found, or the device name is not specified,
 135  * the first suitable device from the resulting
 136  * list will be returned. For Sequencer and Synthesizer,
 137  * a device is suitable if it implements the respective
 138  * interface; whereas for Receiver and Transmitter, a device is
 139  * suitable if it
 140  * implements neither Sequencer nor Synthesizer and provides
 141  * at least one Receiver or Transmitter, respectively.
 142  *
 143  * For example, the property <code>javax.sound.midi.Receiver</code>
 144  * with a value
 145  * <code>&quot;com.sun.media.sound.MidiProvider#SunMIDI1&quot;</code>
 146  * will have the following consequences when
 147  * <code>getReceiver</code> is called:
 148  * if the class <code>com.sun.media.sound.MidiProvider</code> exists
 149  * in the list of installed MIDI device providers,
 150  * the first <code>Receiver</code> device with name
 151  * <code>&quot;SunMIDI1&quot;</code> will be returned. If it cannot
 152  * be found, the first <code>Receiver</code> from that provider
 153  * will be returned, regardless of name.
 154  * If there is none, the first <code>Receiver</code> with name
 155  * <code>&quot;SunMIDI1&quot;</code> in the list of all devices
 156  * (as returned by <code>getMidiDeviceInfo</code>) will be returned,
 157  * or, if not found, the first <code>Receiver</code> that can
 158  * be found in the list of all devices is returned.
 159  * If that fails, too, a <code>MidiUnavailableException</code>
 160  * is thrown.
 161  *
 162  * @author Kara Kytle
 163  * @author Florian Bomers
 164  * @author Matthias Pfisterer
 165  */
 166 public class MidiSystem {
 167 
 168     /**
 169      * Private no-args constructor for ensuring against instantiation.
 170      */
 171     private MidiSystem() {
 172     }
 173 
 174 
 175     /**
 176      * Obtains an array of information objects representing
 177      * the set of all MIDI devices available on the system.
 178      * A returned information object can then be used to obtain the
 179      * corresponding device object, by invoking
 180      * {@link #getMidiDevice(MidiDevice.Info) getMidiDevice}.
 181      *
 182      * @return an array of <code>MidiDevice.Info</code> objects, one
 183      * for each installed MIDI device.  If no such devices are installed,
 184      * an array of length 0 is returned.
 185      */
 186     public static MidiDevice.Info[] getMidiDeviceInfo() {
 187         List allInfos = new ArrayList();
 188         List providers = getMidiDeviceProviders();
 189 
 190         for(int i = 0; i < providers.size(); i++) {
 191             MidiDeviceProvider provider = (MidiDeviceProvider) providers.get(i);
 192             MidiDevice.Info[] tmpinfo = provider.getDeviceInfo();
 193             for (int j = 0; j < tmpinfo.length; j++) {
 194                 allInfos.add( tmpinfo[j] );
 195             }
 196         }
 197         MidiDevice.Info[] infosArray = (MidiDevice.Info[]) allInfos.toArray(new MidiDevice.Info[0]);
 198         return infosArray;
 199     }
 200 
 201 
 202     /**
 203      * Obtains the requested MIDI device.
 204      *
 205      * @param info a device information object representing the desired device.
 206      * @return the requested device
 207      * @throws MidiUnavailableException if the requested device is not available
 208      * due to resource restrictions
 209      * @throws IllegalArgumentException if the info object does not represent
 210      * a MIDI device installed on the system
 211      * @see #getMidiDeviceInfo
 212      */
 213     public static MidiDevice getMidiDevice(MidiDevice.Info info) throws MidiUnavailableException {
 214         List providers = getMidiDeviceProviders();
 215 
 216         for(int i = 0; i < providers.size(); i++) {
 217             MidiDeviceProvider provider = (MidiDeviceProvider) providers.get(i);
 218             if (provider.isDeviceSupported(info)) {
 219                 MidiDevice device = provider.getDevice(info);
 220                 return device;
 221             }
 222         }
 223         throw new IllegalArgumentException("Requested device not installed: " + info);
 224     }
 225 
 226 
 227     /**
 228      * Obtains a MIDI receiver from an external MIDI port
 229      * or other default device.
 230      * The returned receiver always implements
 231      * the {@code MidiDeviceReceiver} interface.
 232      *
 233      * <p>If the system property
 234      * <code>javax.sound.midi.Receiver</code>
 235      * is defined or it is defined in the file &quot;sound.properties&quot;,
 236      * it is used to identify the device that provides the default receiver.
 237      * For details, refer to the {@link MidiSystem class description}.
 238      *
 239      * If a suitable MIDI port is not available, the Receiver is
 240      * retrieved from an installed synthesizer.
 241      *
 242      * <p>If this method returns successfully, the {@link
 243      * javax.sound.midi.MidiDevice MidiDevice} the
 244      * <code>Receiver</code> belongs to is opened implicitly, if it is
 245      * not already open. It is possible to close an implicitly opened
 246      * device by calling {@link javax.sound.midi.Receiver#close close}
 247      * on the returned <code>Receiver</code>. All open <code>Receiver</code>
 248      * instances have to be closed in order to release system resources
 249      * hold by the <code>MidiDevice</code>. For a
 250      * detailed description of open/close behaviour see the class
 251      * description of {@link javax.sound.midi.MidiDevice MidiDevice}.
 252      *
 253      *
 254      * @return the default MIDI receiver
 255      * @throws MidiUnavailableException if the default receiver is not
 256      *         available due to resource restrictions,
 257      *         or no device providing receivers is installed in the system
 258      */
 259     public static Receiver getReceiver() throws MidiUnavailableException {
 260         // may throw MidiUnavailableException
 261         MidiDevice device = getDefaultDeviceWrapper(Receiver.class);
 262         Receiver receiver;
 263         if (device instanceof ReferenceCountingDevice) {
 264             receiver = ((ReferenceCountingDevice) device).getReceiverReferenceCounting();
 265         } else {
 266             receiver = device.getReceiver();
 267         }
 268         if (!(receiver instanceof MidiDeviceReceiver)) {
 269             receiver = new MidiDeviceReceiverEnvelope(device, receiver);
 270         }
 271         return receiver;
 272     }
 273 
 274 
 275     /**
 276      * Obtains a MIDI transmitter from an external MIDI port
 277      * or other default source.
 278      * The returned transmitter always implements
 279      * the {@code MidiDeviceTransmitter} interface.
 280      *
 281      * <p>If the system property
 282      * <code>javax.sound.midi.Transmitter</code>
 283      * is defined or it is defined in the file &quot;sound.properties&quot;,
 284      * it is used to identify the device that provides the default transmitter.
 285      * For details, refer to the {@link MidiSystem class description}.
 286      *
 287      * If this method returns successfully, the {@link
 288      * javax.sound.midi.MidiDevice MidiDevice} the
 289      * <code>Transmitter</code> belongs to is opened implicitly, if it
 290      * is not already open. It is possible to close an implicitly
 291      * opened device by calling {@link
 292      * javax.sound.midi.Transmitter#close close} on the returned
 293      * <code>Transmitter</code>. All open <code>Transmitter</code>
 294      * instances have to be closed in order to release system resources
 295      * hold by the <code>MidiDevice</code>. For a detailed description
 296      * of open/close behaviour see the class description of {@link
 297      * javax.sound.midi.MidiDevice MidiDevice}.
 298      *
 299      * @return the default MIDI transmitter
 300      * @throws MidiUnavailableException if the default transmitter is not
 301      *         available due to resource restrictions,
 302      *         or no device providing transmitters is installed in the system
 303      */
 304     public static Transmitter getTransmitter() throws MidiUnavailableException {
 305         // may throw MidiUnavailableException
 306         MidiDevice device = getDefaultDeviceWrapper(Transmitter.class);
 307         Transmitter transmitter;
 308         if (device instanceof ReferenceCountingDevice) {
 309             transmitter = ((ReferenceCountingDevice) device).getTransmitterReferenceCounting();
 310         } else {
 311             transmitter = device.getTransmitter();
 312         }
 313         if (!(transmitter instanceof MidiDeviceTransmitter)) {
 314             transmitter = new MidiDeviceTransmitterEnvelope(device, transmitter);
 315         }
 316         return transmitter;
 317     }
 318 
 319 
 320     /**
 321      * Obtains the default synthesizer.
 322      *
 323      * <p>If the system property
 324      * <code>javax.sound.midi.Synthesizer</code>
 325      * is defined or it is defined in the file &quot;sound.properties&quot;,
 326      * it is used to identify the default synthesizer.
 327      * For details, refer to the {@link MidiSystem class description}.
 328      *
 329      * @return the default synthesizer
 330      * @throws MidiUnavailableException if the synthesizer is not
 331      *         available due to resource restrictions,
 332      *         or no synthesizer is installed in the system
 333      */
 334     public static Synthesizer getSynthesizer() throws MidiUnavailableException {
 335         // may throw MidiUnavailableException
 336         return (Synthesizer) getDefaultDeviceWrapper(Synthesizer.class);
 337     }
 338 
 339 
 340     /**
 341      * Obtains the default <code>Sequencer</code>, connected to
 342      * a default device.
 343      * The returned <code>Sequencer</code> instance is
 344      * connected to the default <code>Synthesizer</code>,
 345      * as returned by {@link #getSynthesizer}.
 346      * If there is no <code>Synthesizer</code>
 347      * available, or the default <code>Synthesizer</code>
 348      * cannot be opened, the <code>sequencer</code> is connected
 349      * to the default <code>Receiver</code>, as returned
 350      * by {@link #getReceiver}.
 351      * The connection is made by retrieving a <code>Transmitter</code>
 352      * instance from the <code>Sequencer</code> and setting its
 353      * <code>Receiver</code>.
 354      * Closing and re-opening the sequencer will restore the
 355      * connection to the default device.
 356      *
 357      * <p>This method is equivalent to calling
 358      * <code>getSequencer(true)</code>.
 359      *
 360      * <p>If the system property
 361      * <code>javax.sound.midi.Sequencer</code>
 362      * is defined or it is defined in the file &quot;sound.properties&quot;,
 363      * it is used to identify the default sequencer.
 364      * For details, refer to the {@link MidiSystem class description}.
 365      *
 366      * @return the default sequencer, connected to a default Receiver
 367      * @throws MidiUnavailableException if the sequencer is not
 368      *         available due to resource restrictions,
 369      *         or there is no <code>Receiver</code> available by any
 370      *         installed <code>MidiDevice</code>,
 371      *         or no sequencer is installed in the system.
 372      * @see #getSequencer(boolean)
 373      * @see #getSynthesizer
 374      * @see #getReceiver
 375      */
 376     public static Sequencer getSequencer() throws MidiUnavailableException {
 377         return getSequencer(true);
 378     }
 379 
 380 
 381 
 382     /**
 383      * Obtains the default <code>Sequencer</code>, optionally
 384      * connected to a default device.
 385      *
 386      * <p>If <code>connected</code> is true, the returned
 387      * <code>Sequencer</code> instance is
 388      * connected to the default <code>Synthesizer</code>,
 389      * as returned by {@link #getSynthesizer}.
 390      * If there is no <code>Synthesizer</code>
 391      * available, or the default <code>Synthesizer</code>
 392      * cannot be opened, the <code>sequencer</code> is connected
 393      * to the default <code>Receiver</code>, as returned
 394      * by {@link #getReceiver}.
 395      * The connection is made by retrieving a <code>Transmitter</code>
 396      * instance from the <code>Sequencer</code> and setting its
 397      * <code>Receiver</code>.
 398      * Closing and re-opening the sequencer will restore the
 399      * connection to the default device.
 400      *
 401      * <p>If <code>connected</code> is false, the returned
 402      * <code>Sequencer</code> instance is not connected, it
 403      * has no open <code>Transmitters</code>. In order to
 404      * play the sequencer on a MIDI device, or a <code>Synthesizer</code>,
 405      * it is necessary to get a <code>Transmitter</code> and set its
 406      * <code>Receiver</code>.
 407      *
 408      * <p>If the system property
 409      * <code>javax.sound.midi.Sequencer</code>
 410      * is defined or it is defined in the file "sound.properties",
 411      * it is used to identify the default sequencer.
 412      * For details, refer to the {@link MidiSystem class description}.
 413      *
 414      * @return the default sequencer
 415      * @throws MidiUnavailableException if the sequencer is not
 416      *         available due to resource restrictions,
 417      *         or no sequencer is installed in the system,
 418      *         or if <code>connected</code> is true, and there is
 419      *         no <code>Receiver</code> available by any installed
 420      *         <code>MidiDevice</code>
 421      * @see #getSynthesizer
 422      * @see #getReceiver
 423      * @since 1.5
 424      */
 425     public static Sequencer getSequencer(boolean connected)
 426         throws MidiUnavailableException {
 427         Sequencer seq = (Sequencer) getDefaultDeviceWrapper(Sequencer.class);
 428 
 429         if (connected) {
 430             // IMPORTANT: this code needs to be synch'ed with
 431             //            all AutoConnectSequencer instances,
 432             //            (e.g. RealTimeSequencer) because the
 433             //            same algorithm for synth retrieval
 434             //            needs to be used!
 435 
 436             Receiver rec = null;
 437             MidiUnavailableException mue = null;
 438 
 439             // first try to connect to the default synthesizer
 440             try {
 441                 Synthesizer synth = getSynthesizer();
 442                 if (synth instanceof ReferenceCountingDevice) {
 443                     rec = ((ReferenceCountingDevice) synth).getReceiverReferenceCounting();
 444                 } else {
 445                     synth.open();
 446                     try {
 447                         rec = synth.getReceiver();
 448                     } finally {
 449                         // make sure that the synth is properly closed
 450                         if (rec == null) {
 451                             synth.close();
 452                         }
 453                     }
 454                 }
 455             } catch (MidiUnavailableException e) {
 456                 // something went wrong with synth
 457                 if (e instanceof MidiUnavailableException) {
 458                     mue = (MidiUnavailableException) e;
 459                 }
 460             }
 461             if (rec == null) {
 462                 // then try to connect to the default Receiver
 463                 try {
 464                     rec = MidiSystem.getReceiver();
 465                 } catch (Exception e) {
 466                     // something went wrong. Nothing to do then!
 467                     if (e instanceof MidiUnavailableException) {
 468                         mue = (MidiUnavailableException) e;
 469                     }
 470                 }
 471             }
 472             if (rec != null) {
 473                 seq.getTransmitter().setReceiver(rec);
 474                 if (seq instanceof AutoConnectSequencer) {
 475                     ((AutoConnectSequencer) seq).setAutoConnect(rec);
 476                 }
 477             } else {
 478                 if (mue != null) {
 479                     throw mue;
 480                 }
 481                 throw new MidiUnavailableException("no receiver available");
 482             }
 483         }
 484         return seq;
 485     }
 486 
 487 
 488 
 489 
 490     /**
 491      * Constructs a MIDI sound bank by reading it from the specified stream.
 492      * The stream must point to
 493      * a valid MIDI soundbank file.  In general, MIDI soundbank providers may
 494      * need to read some data from the stream before determining whether they
 495      * support it.  These parsers must
 496      * be able to mark the stream, read enough data to determine whether they
 497      * support the stream, and, if not, reset the stream's read pointer to
 498      * its original position.  If the input stream does not support this,
 499      * this method may fail with an IOException.
 500      * @param stream the source of the sound bank data.
 501      * @return the sound bank
 502      * @throws InvalidMidiDataException if the stream does not point to
 503      * valid MIDI soundbank data recognized by the system
 504      * @throws IOException if an I/O error occurred when loading the soundbank
 505      * @see InputStream#markSupported
 506      * @see InputStream#mark
 507      */
 508     public static Soundbank getSoundbank(InputStream stream)
 509         throws InvalidMidiDataException, IOException {
 510 
 511         SoundbankReader sp = null;
 512         Soundbank s = null;
 513 
 514         List providers = getSoundbankReaders();
 515 
 516         for(int i = 0; i < providers.size(); i++) {
 517             sp = (SoundbankReader)providers.get(i);
 518             s = sp.getSoundbank(stream);
 519 
 520             if( s!= null) {
 521                 return s;
 522             }
 523         }
 524         throw new InvalidMidiDataException("cannot get soundbank from stream");
 525 
 526     }
 527 
 528 
 529     /**
 530      * Constructs a <code>Soundbank</code> by reading it from the specified URL.
 531      * The URL must point to a valid MIDI soundbank file.
 532      *
 533      * @param url the source of the sound bank data
 534      * @return the sound bank
 535      * @throws InvalidMidiDataException if the URL does not point to valid MIDI
 536      * soundbank data recognized by the system
 537      * @throws IOException if an I/O error occurred when loading the soundbank
 538      */
 539     public static Soundbank getSoundbank(URL url)
 540         throws InvalidMidiDataException, IOException {
 541 
 542         SoundbankReader sp = null;
 543         Soundbank s = null;
 544 
 545         List providers = getSoundbankReaders();
 546 
 547         for(int i = 0; i < providers.size(); i++) {
 548             sp = (SoundbankReader)providers.get(i);
 549             s = sp.getSoundbank(url);
 550 
 551             if( s!= null) {
 552                 return s;
 553             }
 554         }
 555         throw new InvalidMidiDataException("cannot get soundbank from stream");
 556 
 557     }
 558 
 559 
 560     /**
 561      * Constructs a <code>Soundbank</code> by reading it from the specified
 562      * <code>File</code>.
 563      * The <code>File</code> must point to a valid MIDI soundbank file.
 564      *
 565      * @param file the source of the sound bank data
 566      * @return the sound bank
 567      * @throws InvalidMidiDataException if the <code>File</code> does not
 568      * point to valid MIDI soundbank data recognized by the system
 569      * @throws IOException if an I/O error occurred when loading the soundbank
 570      */
 571     public static Soundbank getSoundbank(File file)
 572         throws InvalidMidiDataException, IOException {
 573 
 574         SoundbankReader sp = null;
 575         Soundbank s = null;
 576 
 577         List providers = getSoundbankReaders();
 578 
 579         for(int i = 0; i < providers.size(); i++) {
 580             sp = (SoundbankReader)providers.get(i);
 581             s = sp.getSoundbank(file);
 582 
 583             if( s!= null) {
 584                 return s;
 585             }
 586         }
 587         throw new InvalidMidiDataException("cannot get soundbank from stream");
 588     }
 589 
 590 
 591 
 592     /**
 593      * Obtains the MIDI file format of the data in the specified input stream.
 594      * The stream must point to valid MIDI file data for a file type recognized
 595      * by the system.
 596      * <p>
 597      * This method and/or the code it invokes may need to read some data from
 598      * the stream to determine whether its data format is supported.  The
 599      * implementation may therefore
 600      * need to mark the stream, read enough data to determine whether it is in
 601      * a supported format, and reset the stream's read pointer to its original
 602      * position.  If the input stream does not permit this set of operations,
 603      * this method may fail with an <code>IOException</code>.
 604      * <p>
 605      * This operation can only succeed for files of a type which can be parsed
 606      * by an installed file reader.  It may fail with an InvalidMidiDataException
 607      * even for valid files if no compatible file reader is installed.  It
 608      * will also fail with an InvalidMidiDataException if a compatible file reader
 609      * is installed, but encounters errors while determining the file format.
 610      *
 611      * @param stream the input stream from which file format information
 612      * should be extracted
 613      * @return an <code>MidiFileFormat</code> object describing the MIDI file
 614      * format
 615      * @throws InvalidMidiDataException if the stream does not point to valid
 616      * MIDI file data recognized by the system
 617      * @throws IOException if an I/O exception occurs while accessing the
 618      * stream
 619      * @see #getMidiFileFormat(URL)
 620      * @see #getMidiFileFormat(File)
 621      * @see InputStream#markSupported
 622      * @see InputStream#mark
 623      */
 624     public static MidiFileFormat getMidiFileFormat(InputStream stream)
 625         throws InvalidMidiDataException, IOException {
 626 
 627         List providers = getMidiFileReaders();
 628         MidiFileFormat format = null;
 629 
 630         for(int i = 0; i < providers.size(); i++) {
 631             MidiFileReader reader = (MidiFileReader) providers.get(i);
 632             try {
 633                 format = reader.getMidiFileFormat( stream ); // throws IOException
 634                 break;
 635             } catch (InvalidMidiDataException e) {
 636                 continue;
 637             }
 638         }
 639 
 640         if( format==null ) {
 641             throw new InvalidMidiDataException("input stream is not a supported file type");
 642         } else {
 643             return format;
 644         }
 645     }
 646 
 647 
 648     /**
 649      * Obtains the MIDI file format of the data in the specified URL.  The URL
 650      * must point to valid MIDI file data for a file type recognized
 651      * by the system.
 652      * <p>
 653      * This operation can only succeed for files of a type which can be parsed
 654      * by an installed file reader.  It may fail with an InvalidMidiDataException
 655      * even for valid files if no compatible file reader is installed.  It
 656      * will also fail with an InvalidMidiDataException if a compatible file reader
 657      * is installed, but encounters errors while determining the file format.
 658      *
 659      * @param url the URL from which file format information should be
 660      * extracted
 661      * @return a <code>MidiFileFormat</code> object describing the MIDI file
 662      * format
 663      * @throws InvalidMidiDataException if the URL does not point to valid MIDI
 664      * file data recognized by the system
 665      * @throws IOException if an I/O exception occurs while accessing the URL
 666      *
 667      * @see #getMidiFileFormat(InputStream)
 668      * @see #getMidiFileFormat(File)
 669      */
 670     public static MidiFileFormat getMidiFileFormat(URL url)
 671         throws InvalidMidiDataException, IOException {
 672 
 673         List providers = getMidiFileReaders();
 674         MidiFileFormat format = null;
 675 
 676         for(int i = 0; i < providers.size(); i++) {
 677             MidiFileReader reader = (MidiFileReader) providers.get(i);
 678             try {
 679                 format = reader.getMidiFileFormat( url ); // throws IOException
 680                 break;
 681             } catch (InvalidMidiDataException e) {
 682                 continue;
 683             }
 684         }
 685 
 686         if( format==null ) {
 687             throw new InvalidMidiDataException("url is not a supported file type");
 688         } else {
 689             return format;
 690         }
 691     }
 692 
 693 
 694     /**
 695      * Obtains the MIDI file format of the specified <code>File</code>.  The
 696      * <code>File</code> must point to valid MIDI file data for a file type
 697      * recognized by the system.
 698      * <p>
 699      * This operation can only succeed for files of a type which can be parsed
 700      * by an installed file reader.  It may fail with an InvalidMidiDataException
 701      * even for valid files if no compatible file reader is installed.  It
 702      * will also fail with an InvalidMidiDataException if a compatible file reader
 703      * is installed, but encounters errors while determining the file format.
 704      *
 705      * @param file the <code>File</code> from which file format information
 706      * should be extracted
 707      * @return a <code>MidiFileFormat</code> object describing the MIDI file
 708      * format
 709      * @throws InvalidMidiDataException if the <code>File</code> does not point
 710      *  to valid MIDI file data recognized by the system
 711      * @throws IOException if an I/O exception occurs while accessing the file
 712      *
 713      * @see #getMidiFileFormat(InputStream)
 714      * @see #getMidiFileFormat(URL)
 715      */
 716     public static MidiFileFormat getMidiFileFormat(File file)
 717         throws InvalidMidiDataException, IOException {
 718 
 719         List providers = getMidiFileReaders();
 720         MidiFileFormat format = null;
 721 
 722         for(int i = 0; i < providers.size(); i++) {
 723             MidiFileReader reader = (MidiFileReader) providers.get(i);
 724             try {
 725                 format = reader.getMidiFileFormat( file ); // throws IOException
 726                 break;
 727             } catch (InvalidMidiDataException e) {
 728                 continue;
 729             }
 730         }
 731 
 732         if( format==null ) {
 733             throw new InvalidMidiDataException("file is not a supported file type");
 734         } else {
 735             return format;
 736         }
 737     }
 738 
 739 
 740     /**
 741      * Obtains a MIDI sequence from the specified input stream.  The stream must
 742      * point to valid MIDI file data for a file type recognized
 743      * by the system.
 744      * <p>
 745      * This method and/or the code it invokes may need to read some data
 746      * from the stream to determine whether
 747      * its data format is supported.  The implementation may therefore
 748      * need to mark the stream, read enough data to determine whether it is in
 749      * a supported format, and reset the stream's read pointer to its original
 750      * position.  If the input stream does not permit this set of operations,
 751      * this method may fail with an <code>IOException</code>.
 752      * <p>
 753      * This operation can only succeed for files of a type which can be parsed
 754      * by an installed file reader.  It may fail with an InvalidMidiDataException
 755      * even for valid files if no compatible file reader is installed.  It
 756      * will also fail with an InvalidMidiDataException if a compatible file reader
 757      * is installed, but encounters errors while constructing the <code>Sequence</code>
 758      * object from the file data.
 759      *
 760      * @param stream the input stream from which the <code>Sequence</code>
 761      * should be constructed
 762      * @return a <code>Sequence</code> object based on the MIDI file data
 763      * contained in the input stream
 764      * @throws InvalidMidiDataException if the stream does not point to
 765      * valid MIDI file data recognized by the system
 766      * @throws IOException if an I/O exception occurs while accessing the
 767      * stream
 768      * @see InputStream#markSupported
 769      * @see InputStream#mark
 770      */
 771     public static Sequence getSequence(InputStream stream)
 772         throws InvalidMidiDataException, IOException {
 773 
 774         List providers = getMidiFileReaders();
 775         Sequence sequence = null;
 776 
 777         for(int i = 0; i < providers.size(); i++) {
 778             MidiFileReader reader = (MidiFileReader) providers.get(i);
 779             try {
 780                 sequence = reader.getSequence( stream ); // throws IOException
 781                 break;
 782             } catch (InvalidMidiDataException e) {
 783                 continue;
 784             }
 785         }
 786 
 787         if( sequence==null ) {
 788             throw new InvalidMidiDataException("could not get sequence from input stream");
 789         } else {
 790             return sequence;
 791         }
 792     }
 793 
 794 
 795     /**
 796      * Obtains a MIDI sequence from the specified URL.  The URL must
 797      * point to valid MIDI file data for a file type recognized
 798      * by the system.
 799      * <p>
 800      * This operation can only succeed for files of a type which can be parsed
 801      * by an installed file reader.  It may fail with an InvalidMidiDataException
 802      * even for valid files if no compatible file reader is installed.  It
 803      * will also fail with an InvalidMidiDataException if a compatible file reader
 804      * is installed, but encounters errors while constructing the <code>Sequence</code>
 805      * object from the file data.
 806      *
 807      * @param url the URL from which the <code>Sequence</code> should be
 808      * constructed
 809      * @return a <code>Sequence</code> object based on the MIDI file data
 810      * pointed to by the URL
 811      * @throws InvalidMidiDataException if the URL does not point to valid MIDI
 812      * file data recognized by the system
 813      * @throws IOException if an I/O exception occurs while accessing the URL
 814      */
 815     public static Sequence getSequence(URL url)
 816         throws InvalidMidiDataException, IOException {
 817 
 818         List providers = getMidiFileReaders();
 819         Sequence sequence = null;
 820 
 821         for(int i = 0; i < providers.size(); i++) {
 822             MidiFileReader reader = (MidiFileReader) providers.get(i);
 823             try {
 824                 sequence = reader.getSequence( url ); // throws IOException
 825                 break;
 826             } catch (InvalidMidiDataException e) {
 827                 continue;
 828             }
 829         }
 830 
 831         if( sequence==null ) {
 832             throw new InvalidMidiDataException("could not get sequence from URL");
 833         } else {
 834             return sequence;
 835         }
 836     }
 837 
 838 
 839     /**
 840      * Obtains a MIDI sequence from the specified <code>File</code>.
 841      * The <code>File</code> must point to valid MIDI file data
 842      * for a file type recognized by the system.
 843      * <p>
 844      * This operation can only succeed for files of a type which can be parsed
 845      * by an installed file reader.  It may fail with an InvalidMidiDataException
 846      * even for valid files if no compatible file reader is installed.  It
 847      * will also fail with an InvalidMidiDataException if a compatible file reader
 848      * is installed, but encounters errors while constructing the <code>Sequence</code>
 849      * object from the file data.
 850      *
 851      * @param file the <code>File</code> from which the <code>Sequence</code>
 852      * should be constructed
 853      * @return a <code>Sequence</code> object based on the MIDI file data
 854      * pointed to by the File
 855      * @throws InvalidMidiDataException if the File does not point to valid MIDI
 856      * file data recognized by the system
 857      * @throws IOException if an I/O exception occurs
 858      */
 859     public static Sequence getSequence(File file)
 860         throws InvalidMidiDataException, IOException {
 861 
 862         List providers = getMidiFileReaders();
 863         Sequence sequence = null;
 864 
 865         for(int i = 0; i < providers.size(); i++) {
 866             MidiFileReader reader = (MidiFileReader) providers.get(i);
 867             try {
 868                 sequence = reader.getSequence( file ); // throws IOException
 869                 break;
 870             } catch (InvalidMidiDataException e) {
 871                 continue;
 872             }
 873         }
 874 
 875         if( sequence==null ) {
 876             throw new InvalidMidiDataException("could not get sequence from file");
 877         } else {
 878             return sequence;
 879         }
 880     }
 881 
 882 
 883     /**
 884      * Obtains the set of MIDI file types for which file writing support is
 885      * provided by the system.
 886      * @return array of unique file types.  If no file types are supported,
 887      * an array of length 0 is returned.
 888      */
 889     public static int[] getMidiFileTypes() {
 890 
 891         List providers = getMidiFileWriters();
 892         Set allTypes = new HashSet();
 893 
 894         // gather from all the providers
 895 
 896         for (int i = 0; i < providers.size(); i++ ) {
 897             MidiFileWriter writer = (MidiFileWriter) providers.get(i);
 898             int[] types = writer.getMidiFileTypes();
 899             for (int j = 0; j < types.length; j++ ) {
 900                 allTypes.add(new Integer(types[j]));
 901             }
 902         }
 903         int resultTypes[] = new int[allTypes.size()];
 904         int index = 0;
 905         Iterator iterator = allTypes.iterator();
 906         while (iterator.hasNext()) {
 907             Integer integer = (Integer) iterator.next();
 908             resultTypes[index++] = integer.intValue();
 909         }
 910         return resultTypes;
 911     }
 912 
 913 
 914     /**
 915      * Indicates whether file writing support for the specified MIDI file type
 916      * is provided by the system.
 917      * @param fileType the file type for which write capabilities are queried
 918      * @return <code>true</code> if the file type is supported,
 919      * otherwise <code>false</code>
 920      */
 921     public static boolean isFileTypeSupported(int fileType) {
 922 
 923         List providers = getMidiFileWriters();
 924 
 925         for (int i = 0; i < providers.size(); i++ ) {
 926             MidiFileWriter writer = (MidiFileWriter) providers.get(i);
 927             if( writer.isFileTypeSupported(fileType)) {
 928                 return true;
 929             }
 930         }
 931         return false;
 932     }
 933 
 934 
 935     /**
 936      * Obtains the set of MIDI file types that the system can write from the
 937      * sequence specified.
 938      * @param sequence the sequence for which MIDI file type support
 939      * is queried
 940      * @return the set of unique supported file types.  If no file types are supported,
 941      * returns an array of length 0.
 942      */
 943     public static int[] getMidiFileTypes(Sequence sequence) {
 944 
 945         List providers = getMidiFileWriters();
 946         Set allTypes = new HashSet();
 947 
 948         // gather from all the providers
 949 
 950         for (int i = 0; i < providers.size(); i++ ) {
 951             MidiFileWriter writer = (MidiFileWriter) providers.get(i);
 952             int[] types = writer.getMidiFileTypes(sequence);
 953             for (int j = 0; j < types.length; j++ ) {
 954                 allTypes.add(new Integer(types[j]));
 955             }
 956         }
 957         int resultTypes[] = new int[allTypes.size()];
 958         int index = 0;
 959         Iterator iterator = allTypes.iterator();
 960         while (iterator.hasNext()) {
 961             Integer integer = (Integer) iterator.next();
 962             resultTypes[index++] = integer.intValue();
 963         }
 964         return resultTypes;
 965     }
 966 
 967 
 968     /**
 969      * Indicates whether a MIDI file of the file type specified can be written
 970      * from the sequence indicated.
 971      * @param fileType the file type for which write capabilities
 972      * are queried
 973      * @param sequence the sequence for which file writing support is queried
 974      * @return <code>true</code> if the file type is supported for this
 975      * sequence, otherwise <code>false</code>
 976      */
 977     public static boolean isFileTypeSupported(int fileType, Sequence sequence) {
 978 
 979         List providers = getMidiFileWriters();
 980 
 981         for (int i = 0; i < providers.size(); i++ ) {
 982             MidiFileWriter writer = (MidiFileWriter) providers.get(i);
 983             if( writer.isFileTypeSupported(fileType,sequence)) {
 984                 return true;
 985             }
 986         }
 987         return false;
 988     }
 989 
 990 
 991     /**
 992      * Writes a stream of bytes representing a file of the MIDI file type
 993      * indicated to the output stream provided.
 994      * @param in sequence containing MIDI data to be written to the file
 995      * @param fileType the file type of the file to be written to the output stream
 996      * @param out stream to which the file data should be written
 997      * @return the number of bytes written to the output stream
 998      * @throws IOException if an I/O exception occurs
 999      * @throws IllegalArgumentException if the file format is not supported by
1000      * the system
1001      * @see #isFileTypeSupported(int, Sequence)
1002      * @see     #getMidiFileTypes(Sequence)
1003      */
1004     public static int write(Sequence in, int fileType, OutputStream out) throws IOException {
1005 
1006         List providers = getMidiFileWriters();
1007         //$$fb 2002-04-17: Fix for 4635287: Standard MidiFileWriter cannot write empty Sequences
1008         int bytesWritten = -2;
1009 
1010         for (int i = 0; i < providers.size(); i++ ) {
1011             MidiFileWriter writer = (MidiFileWriter) providers.get(i);
1012             if( writer.isFileTypeSupported( fileType, in ) ) {
1013 
1014                 bytesWritten = writer.write(in, fileType, out);
1015                 break;
1016             }
1017         }
1018         if (bytesWritten == -2) {
1019             throw new IllegalArgumentException("MIDI file type is not supported");
1020         }
1021         return bytesWritten;
1022     }
1023 
1024 
1025     /**
1026      * Writes a stream of bytes representing a file of the MIDI file type
1027      * indicated to the external file provided.
1028      * @param in sequence containing MIDI data to be written to the file
1029      * @param type the file type of the file to be written to the output stream
1030      * @param out external file to which the file data should be written
1031      * @return the number of bytes written to the file
1032      * @throws IOException if an I/O exception occurs
1033      * @throws IllegalArgumentException if the file type is not supported by
1034      * the system
1035      * @see #isFileTypeSupported(int, Sequence)
1036      * @see     #getMidiFileTypes(Sequence)
1037      */
1038     public static int write(Sequence in, int type, File out) throws IOException {
1039 
1040         List providers = getMidiFileWriters();
1041         //$$fb 2002-04-17: Fix for 4635287: Standard MidiFileWriter cannot write empty Sequences
1042         int bytesWritten = -2;
1043 
1044         for (int i = 0; i < providers.size(); i++ ) {
1045             MidiFileWriter writer = (MidiFileWriter) providers.get(i);
1046             if( writer.isFileTypeSupported( type, in ) ) {
1047 
1048                 bytesWritten = writer.write(in, type, out);
1049                 break;
1050             }
1051         }
1052         if (bytesWritten == -2) {
1053             throw new IllegalArgumentException("MIDI file type is not supported");
1054         }
1055         return bytesWritten;
1056     }
1057 
1058 
1059 
1060     // HELPER METHODS
1061 
1062     private static List getMidiDeviceProviders() {
1063         return getProviders(MidiDeviceProvider.class);
1064     }
1065 
1066 
1067     private static List getSoundbankReaders() {
1068         return getProviders(SoundbankReader.class);
1069     }
1070 
1071 
1072     private static List getMidiFileWriters() {
1073         return getProviders(MidiFileWriter.class);
1074     }
1075 
1076 
1077     private static List getMidiFileReaders() {
1078         return getProviders(MidiFileReader.class);
1079     }
1080 
1081 
1082     /** Attempts to locate and return a default MidiDevice of the specified
1083      * type.
1084      *
1085      * This method wraps {@link #getDefaultDevice}. It catches the
1086      * <code>IllegalArgumentException</code> thrown by
1087      * <code>getDefaultDevice</code> and instead throws a
1088      * <code>MidiUnavailableException</code>, with the catched
1089      * exception chained.
1090      *
1091      * @param deviceClass The requested device type, one of Synthesizer.class,
1092      * Sequencer.class, Receiver.class or Transmitter.class.
1093      * @throws  MidiUnavalableException on failure.
1094      */
1095     private static MidiDevice getDefaultDeviceWrapper(Class deviceClass)
1096         throws MidiUnavailableException{
1097         try {
1098             return getDefaultDevice(deviceClass);
1099         } catch (IllegalArgumentException iae) {
1100             MidiUnavailableException mae = new MidiUnavailableException();
1101             mae.initCause(iae);
1102             throw mae;
1103         }
1104     }
1105 
1106 
1107     /** Attempts to locate and return a default MidiDevice of the specified
1108      * type.
1109      *
1110      * @param deviceClass The requested device type, one of Synthesizer.class,
1111      * Sequencer.class, Receiver.class or Transmitter.class.
1112      * @throws  IllegalArgumentException on failure.
1113      */
1114     private static MidiDevice getDefaultDevice(Class deviceClass) {
1115         List providers = getMidiDeviceProviders();
1116         String providerClassName = JDK13Services.getDefaultProviderClassName(deviceClass);
1117         String instanceName = JDK13Services.getDefaultInstanceName(deviceClass);
1118         MidiDevice device;
1119 
1120         if (providerClassName != null) {
1121             MidiDeviceProvider defaultProvider = getNamedProvider(providerClassName, providers);
1122             if (defaultProvider != null) {
1123                 if (instanceName != null) {
1124                     device = getNamedDevice(instanceName, defaultProvider, deviceClass);
1125                     if (device != null) {
1126                         return device;
1127                     }
1128                 }
1129                 device = getFirstDevice(defaultProvider, deviceClass);
1130                 if (device != null) {
1131                     return device;
1132                 }
1133             }
1134         }
1135 
1136         /* Provider class not specified or cannot be found, or
1137            provider class specified, and no appropriate device available or
1138            provider class and instance specified and instance cannot be found or is not appropriate */
1139         if (instanceName != null) {
1140             device = getNamedDevice(instanceName, providers, deviceClass);
1141             if (device != null) {
1142                 return device;
1143             }
1144         }
1145 
1146         /* No default are specified, or if something is specified, everything
1147            failed. */
1148         device = getFirstDevice(providers, deviceClass);
1149         if (device != null) {
1150             return device;
1151         }
1152         throw new IllegalArgumentException("Requested device not installed");
1153     }
1154 
1155 
1156 
1157     /** Return a MidiDeviceProcider of a given class from the list of
1158         MidiDeviceProviders.
1159 
1160         @param providerClassName The class name of the provider to be returned.
1161         @param provider The list of MidiDeviceProviders that is searched.
1162         @return A MidiDeviceProvider of the requested class, or null if none
1163         is found.
1164     */
1165     private static MidiDeviceProvider getNamedProvider(String providerClassName, List providers) {
1166         for(int i = 0; i < providers.size(); i++) {
1167             MidiDeviceProvider provider = (MidiDeviceProvider) providers.get(i);
1168             if (provider.getClass().getName().equals(providerClassName)) {
1169                 return provider;
1170             }
1171         }
1172         return null;
1173     }
1174 
1175 
1176     /** Return a MidiDevice with a given name from a given MidiDeviceProvider.
1177         @param deviceName The name of the MidiDevice to be returned.
1178         @param provider The MidiDeviceProvider to check for MidiDevices.
1179         @param deviceClass The requested device type, one of Synthesizer.class,
1180         Sequencer.class, Receiver.class or Transmitter.class.
1181 
1182         @return A MidiDevice matching the requirements, or null if none is found.
1183     */
1184     private static MidiDevice getNamedDevice(String deviceName,
1185                                              MidiDeviceProvider provider,
1186                                              Class deviceClass) {
1187         MidiDevice device;
1188         // try to get MIDI port
1189         device = getNamedDevice(deviceName, provider, deviceClass,
1190                                  false, false);
1191         if (device != null) {
1192             return device;
1193         }
1194 
1195         if (deviceClass == Receiver.class) {
1196             // try to get Synthesizer
1197             device = getNamedDevice(deviceName, provider, deviceClass,
1198                                      true, false);
1199             if (device != null) {
1200                 return device;
1201             }
1202         }
1203 
1204         return null;
1205     }
1206 
1207 
1208     /** Return a MidiDevice with a given name from a given MidiDeviceProvider.
1209       @param deviceName The name of the MidiDevice to be returned.
1210       @param provider The MidiDeviceProvider to check for MidiDevices.
1211       @param deviceClass The requested device type, one of Synthesizer.class,
1212       Sequencer.class, Receiver.class or Transmitter.class.
1213 
1214       @return A MidiDevice matching the requirements, or null if none is found.
1215      */
1216     private static MidiDevice getNamedDevice(String deviceName,
1217                                              MidiDeviceProvider provider,
1218                                              Class deviceClass,
1219                                              boolean allowSynthesizer,
1220                                              boolean allowSequencer) {
1221         MidiDevice.Info[] infos = provider.getDeviceInfo();
1222         for (int i = 0; i < infos.length; i++) {
1223             if (infos[i].getName().equals(deviceName)) {
1224                 MidiDevice device = provider.getDevice(infos[i]);
1225                 if (isAppropriateDevice(device, deviceClass,
1226                                         allowSynthesizer, allowSequencer)) {
1227                     return device;
1228                 }
1229             }
1230         }
1231         return null;
1232     }
1233 
1234 
1235     /** Return a MidiDevice with a given name from a list of
1236         MidiDeviceProviders.
1237         @param deviceName The name of the MidiDevice to be returned.
1238         @param providers The List of MidiDeviceProviders to check for
1239         MidiDevices.
1240         @param deviceClass The requested device type, one of Synthesizer.class,
1241         Sequencer.class, Receiver.class or Transmitter.class.
1242         @return A Mixer matching the requirements, or null if none is found.
1243     */
1244     private static MidiDevice getNamedDevice(String deviceName,
1245                                              List providers,
1246                                              Class deviceClass) {
1247         MidiDevice device;
1248         // try to get MIDI port
1249         device = getNamedDevice(deviceName, providers, deviceClass,
1250                                  false, false);
1251         if (device != null) {
1252             return device;
1253         }
1254 
1255         if (deviceClass == Receiver.class) {
1256             // try to get Synthesizer
1257             device = getNamedDevice(deviceName, providers, deviceClass,
1258                                      true, false);
1259             if (device != null) {
1260                 return device;
1261             }
1262         }
1263 
1264         return null;
1265     }
1266 
1267 
1268     /** Return a MidiDevice with a given name from a list of
1269         MidiDeviceProviders.
1270         @param deviceName The name of the MidiDevice to be returned.
1271         @param providers The List of MidiDeviceProviders to check for
1272         MidiDevices.
1273         @param deviceClass The requested device type, one of Synthesizer.class,
1274         Sequencer.class, Receiver.class or Transmitter.class.
1275         @return A Mixer matching the requirements, or null if none is found.
1276      */
1277     private static MidiDevice getNamedDevice(String deviceName,
1278                                              List providers,
1279                                              Class deviceClass,
1280                                              boolean allowSynthesizer,
1281                                              boolean allowSequencer) {
1282         for(int i = 0; i < providers.size(); i++) {
1283             MidiDeviceProvider provider = (MidiDeviceProvider) providers.get(i);
1284             MidiDevice device = getNamedDevice(deviceName, provider,
1285                                                deviceClass,
1286                                                allowSynthesizer,
1287                                                allowSequencer);
1288             if (device != null) {
1289                 return device;
1290             }
1291         }
1292         return null;
1293     }
1294 
1295 
1296     /** From a given MidiDeviceProvider, return the first appropriate device.
1297         @param provider The MidiDeviceProvider to check for MidiDevices.
1298         @param deviceClass The requested device type, one of Synthesizer.class,
1299         Sequencer.class, Receiver.class or Transmitter.class.
1300         @return A MidiDevice is considered appropriate, or null if no
1301         appropriate device is found.
1302     */
1303     private static MidiDevice getFirstDevice(MidiDeviceProvider provider,
1304                                              Class deviceClass) {
1305         MidiDevice device;
1306         // try to get MIDI port
1307         device = getFirstDevice(provider, deviceClass,
1308                                 false, false);
1309         if (device != null) {
1310             return device;
1311         }
1312 
1313         if (deviceClass == Receiver.class) {
1314             // try to get Synthesizer
1315             device = getFirstDevice(provider, deviceClass,
1316                                     true, false);
1317             if (device != null) {
1318                 return device;
1319             }
1320         }
1321 
1322         return null;
1323     }
1324 
1325 
1326     /** From a given MidiDeviceProvider, return the first appropriate device.
1327         @param provider The MidiDeviceProvider to check for MidiDevices.
1328         @param deviceClass The requested device type, one of Synthesizer.class,
1329         Sequencer.class, Receiver.class or Transmitter.class.
1330         @return A MidiDevice is considered appropriate, or null if no
1331         appropriate device is found.
1332      */
1333     private static MidiDevice getFirstDevice(MidiDeviceProvider provider,
1334                                              Class deviceClass,
1335                                              boolean allowSynthesizer,
1336                                              boolean allowSequencer) {
1337         MidiDevice.Info[] infos = provider.getDeviceInfo();
1338         for (int j = 0; j < infos.length; j++) {
1339             MidiDevice device = provider.getDevice(infos[j]);
1340             if (isAppropriateDevice(device, deviceClass,
1341                                     allowSynthesizer, allowSequencer)) {
1342                 return device;
1343             }
1344         }
1345         return null;
1346     }
1347 
1348 
1349     /** From a List of MidiDeviceProviders, return the first appropriate
1350         MidiDevice.
1351         @param providers The List of MidiDeviceProviders to search.
1352         @param deviceClass The requested device type, one of Synthesizer.class,
1353         Sequencer.class, Receiver.class or Transmitter.class.
1354         @return A MidiDevice that is considered appropriate, or null
1355         if none is found.
1356     */
1357     private static MidiDevice getFirstDevice(List providers,
1358                                              Class deviceClass) {
1359         MidiDevice device;
1360         // try to get MIDI port
1361         device = getFirstDevice(providers, deviceClass,
1362                                 false, false);
1363         if (device != null) {
1364             return device;
1365         }
1366 
1367         if (deviceClass == Receiver.class) {
1368             // try to get Synthesizer
1369             device = getFirstDevice(providers, deviceClass,
1370                                     true, false);
1371             if (device != null) {
1372                 return device;
1373             }
1374         }
1375 
1376         return null;
1377     }
1378 
1379 
1380     /** From a List of MidiDeviceProviders, return the first appropriate
1381         MidiDevice.
1382         @param providers The List of MidiDeviceProviders to search.
1383         @param deviceClass The requested device type, one of Synthesizer.class,
1384         Sequencer.class, Receiver.class or Transmitter.class.
1385         @return A MidiDevice that is considered appropriate, or null
1386         if none is found.
1387      */
1388     private static MidiDevice getFirstDevice(List providers,
1389                                              Class deviceClass,
1390                                              boolean allowSynthesizer,
1391                                              boolean allowSequencer) {
1392         for(int i = 0; i < providers.size(); i++) {
1393             MidiDeviceProvider provider = (MidiDeviceProvider) providers.get(i);
1394             MidiDevice device = getFirstDevice(provider, deviceClass,
1395                                                allowSynthesizer,
1396                                                allowSequencer);
1397             if (device != null) {
1398                 return device;
1399             }
1400         }
1401         return null;
1402     }
1403 
1404 
1405     /** Checks if a MidiDevice is appropriate.
1406         If deviceClass is Synthesizer or Sequencer, a device implementing
1407         the respective interface is considered appropriate. If deviceClass
1408         is Receiver or Transmitter, a device is considered appropriate if
1409         it implements neither Synthesizer nor Transmitter, and if it can
1410         provide at least one Receiver or Transmitter, respectively.
1411 
1412         @param device the MidiDevice to test
1413         @param allowSynthesizer if true, Synthesizers are considered
1414         appropriate. Otherwise only pure MidiDevices are considered
1415         appropriate (unless allowSequencer is true). This flag only has an
1416         effect for deviceClass Receiver and Transmitter. For other device
1417         classes (Sequencer and Synthesizer), this flag has no effect.
1418         @param allowSequencer if true, Sequencers are considered
1419         appropriate. Otherwise only pure MidiDevices are considered
1420         appropriate (unless allowSynthesizer is true). This flag only has an
1421         effect for deviceClass Receiver and Transmitter. For other device
1422         classes (Sequencer and Synthesizer), this flag has no effect.
1423         @return true if the device is considered appropriate according to the
1424         rules given above, false otherwise.
1425     */
1426     private static boolean isAppropriateDevice(MidiDevice device,
1427                                                Class deviceClass,
1428                                                boolean allowSynthesizer,
1429                                                boolean allowSequencer) {
1430         if (deviceClass.isInstance(device)) {
1431             // This clause is for deviceClass being either Synthesizer
1432             // or Sequencer.
1433             return true;
1434         } else {
1435             // Now the case that deviceClass is Transmitter or
1436             // Receiver. If neither allowSynthesizer nor allowSequencer is
1437             // true, we require device instances to be
1438             // neither Synthesizer nor Sequencer, since we only want
1439             // devices representing MIDI ports.
1440             // Otherwise, the respective type is accepted, too
1441             if ( (! (device instanceof Sequencer) &&
1442                   ! (device instanceof Synthesizer) ) ||
1443                  ((device instanceof Sequencer) && allowSequencer) ||
1444                  ((device instanceof Synthesizer) && allowSynthesizer)) {
1445                 // And of cource, the device has to be able to provide
1446                 // Receivers or Transmitters.
1447                 if ((deviceClass == Receiver.class &&
1448                      device.getMaxReceivers() != 0) ||
1449                     (deviceClass == Transmitter.class &&
1450                      device.getMaxTransmitters() != 0)) {
1451                     return true;
1452                 }
1453             }
1454         }
1455         return false;
1456     }
1457 
1458 
1459     /**
1460      * Obtains the set of services currently installed on the system
1461      * using sun.misc.Service, the SPI mechanism in 1.3.
1462      * @return a List of instances of providers for the requested service.
1463      * If no providers are available, a List of length 0 will be returned.
1464      */
1465     private static List getProviders(Class providerClass) {
1466         return JDK13Services.getProviders(providerClass);
1467     }
1468 }