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