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