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