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