1 /*
   2  * Copyright (c) 1999, 2015, 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 border=0>
  78  *  <caption>MIDI System Property Keys</caption>
  79  *  <tr>
  80  *   <th>Property Key</th>
  81  *   <th>Interface</th>
  82  *   <th>Affected Method</th>
  83  *  </tr>
  84  *  <tr>
  85  *   <td>{@code javax.sound.midi.Receiver}</td>
  86  *   <td>{@link Receiver}</td>
  87  *   <td>{@link #getReceiver}</td>
  88  *  </tr>
  89  *  <tr>
  90  *   <td>{@code javax.sound.midi.Sequencer}</td>
  91  *   <td>{@link Sequencer}</td>
  92  *   <td>{@link #getSequencer}</td>
  93  *  </tr>
  94  *  <tr>
  95  *   <td>{@code javax.sound.midi.Synthesizer}</td>
  96  *   <td>{@link Synthesizer}</td>
  97  *   <td>{@link #getSynthesizer}</td>
  98  *  </tr>
  99  *  <tr>
 100  *   <td>{@code javax.sound.midi.Transmitter}</td>
 101  *   <td>{@link Transmitter}</td>
 102  *   <td>{@link #getTransmitter}</td>
 103  *  </tr>
 104  * </table>
 105  *
 106  * The property value consists of the provider class name and the device name,
 107  * separated by the hash mark (&quot;#&quot;). The provider class name is the
 108  * fully-qualified name of a concrete
 109  * {@link MidiDeviceProvider MIDI device provider} class. The device name is
 110  * matched against the {@code String} returned by the {@code getName} method of
 111  * {@code MidiDevice.Info}. Either the class name, or the device name may be
 112  * omitted. If only the class name is specified, the trailing hash mark is
 113  * optional.
 114  * <p>
 115  * If the provider class is specified, and it can be successfully retrieved from
 116  * the installed providers, the list of {@code MidiDevice.Info} objects is
 117  * retrieved from the provider. Otherwise, or when these devices do not provide
 118  * a subsequent match, the list is retrieved from {@link #getMidiDeviceInfo} to
 119  * contain all available {@code MidiDevice.Info} objects.
 120  * <p>
 121  * If a device name is specified, the resulting list of {@code MidiDevice.Info}
 122  * objects is searched: the first one with a matching name, and whose
 123  * {@code MidiDevice} implements the respective interface, will be returned. If
 124  * no matching {@code MidiDevice.Info} object is found, or the device name is
 125  * not specified, the first suitable device from the resulting list will be
 126  * returned. For Sequencer and Synthesizer, a device is suitable if it
 127  * implements the respective interface; whereas for Receiver and Transmitter, a
 128  * device is suitable if it implements neither Sequencer nor Synthesizer and
 129  * provides at least one Receiver or Transmitter, respectively.
 130  * <p>
 131  * For example, the property {@code javax.sound.midi.Receiver} with a value
 132  * {@code "com.sun.media.sound.MidiProvider#SunMIDI1"} will have the following
 133  * consequences when {@code getReceiver} is called: if the class
 134  * {@code com.sun.media.sound.MidiProvider} exists in the list of installed MIDI
 135  * device providers, the first {@code Receiver} device with name
 136  * {@code "SunMIDI1"} will be returned. If it cannot be found, the first
 137  * {@code Receiver} from that provider will be returned, regardless of name. If
 138  * there is none, the first {@code Receiver} with name {@code "SunMIDI1"} in the
 139  * list of all devices (as returned by {@code getMidiDeviceInfo}) will be
 140  * returned, or, if not found, the first {@code Receiver} that can be found in
 141  * the list of all devices is returned. If that fails, too, a
 142  * {@code MidiUnavailableException} is thrown.
 143  *
 144  * @author Kara Kytle
 145  * @author Florian Bomers
 146  * @author Matthias Pfisterer
 147  */
 148 public class MidiSystem {
 149 
 150     /**
 151      * Private no-args constructor for ensuring against instantiation.
 152      */
 153     private MidiSystem() {
 154     }
 155 
 156     /**
 157      * Obtains an array of information objects representing the set of all MIDI
 158      * devices available on the system. A returned information object can then
 159      * be used to obtain the corresponding device object, by invoking
 160      * {@link #getMidiDevice(MidiDevice.Info) getMidiDevice}.
 161      *
 162      * @return an array of {@code MidiDevice.Info} objects, one for each
 163      *         installed MIDI device. If no such devices are installed, an array
 164      *         of length 0 is returned.
 165      */
 166     public static MidiDevice.Info[] getMidiDeviceInfo() {
 167         final List<MidiDevice.Info> allInfos = new ArrayList<>();
 168         for (final MidiDeviceProvider provider : getMidiDeviceProviders()) {
 169             Collections.addAll(allInfos, provider.getDeviceInfo());
 170         }
 171         return allInfos.toArray(new MidiDevice.Info[allInfos.size()]);
 172     }
 173 
 174     /**
 175      * Obtains the requested MIDI device.
 176      *
 177      * @param  info a device information object representing the desired device
 178      * @return the requested device
 179      * @throws MidiUnavailableException if the requested device is not available
 180      *         due to resource restrictions
 181      * @throws IllegalArgumentException if the info object does not represent a
 182      *         MIDI device installed on the system
 183      * @throws NullPointerException if {@code info} is {@code null}
 184      * @see #getMidiDeviceInfo
 185      */
 186     public static MidiDevice getMidiDevice(final MidiDevice.Info info)
 187             throws MidiUnavailableException {
 188         Objects.requireNonNull(info);
 189         for (final MidiDeviceProvider provider : getMidiDeviceProviders()) {
 190             if (provider.isDeviceSupported(info)) {
 191                 return provider.getDevice(info);
 192             }
 193         }
 194         throw new IllegalArgumentException(String.format(
 195                 "Requested device not installed: %s", info));
 196     }
 197 
 198     /**
 199      * Obtains a MIDI receiver from an external MIDI port or other default
 200      * device. The returned receiver always implements the
 201      * {@code MidiDeviceReceiver} interface.
 202      * <p>
 203      * If the system property {@code javax.sound.midi.Receiver} is defined or it
 204      * is defined in the file "sound.properties", it is used to identify the
 205      * device that provides the default receiver. For details, refer to the
 206      * {@link MidiSystem class description}.
 207      * <p>
 208      * If a suitable MIDI port is not available, the Receiver is retrieved from
 209      * an installed synthesizer.
 210      * <p>
 211      * If a native receiver provided by the default device does not implement
 212      * the {@code MidiDeviceReceiver} interface, it will be wrapped in a wrapper
 213      * class that implements the {@code MidiDeviceReceiver} interface. The
 214      * corresponding {@code Receiver} method calls will be forwarded to the
 215      * native receiver.
 216      * <p>
 217      * If this method returns successfully, the {@link MidiDevice MidiDevice}
 218      * the {@code Receiver} belongs to is opened implicitly, if it is not
 219      * already open. It is possible to close an implicitly opened device by
 220      * calling {@link Receiver#close close} on the returned {@code Receiver}.
 221      * All open {@code Receiver} instances have to be closed in order to release
 222      * system resources hold by the {@code MidiDevice}. For a detailed
 223      * description of open/close behaviour see the class description of
 224      * {@link MidiDevice MidiDevice}.
 225      *
 226      * @return the default MIDI receiver
 227      * @throws MidiUnavailableException if the default receiver is not available
 228      *         due to resource restrictions, or no device providing receivers is
 229      *         installed in the system
 230      */
 231     public static Receiver getReceiver() throws MidiUnavailableException {
 232         // may throw MidiUnavailableException
 233         MidiDevice device = getDefaultDeviceWrapper(Receiver.class);
 234         Receiver receiver;
 235         if (device instanceof ReferenceCountingDevice) {
 236             receiver = ((ReferenceCountingDevice) device).getReceiverReferenceCounting();
 237         } else {
 238             receiver = device.getReceiver();
 239         }
 240         if (!(receiver instanceof MidiDeviceReceiver)) {
 241             receiver = new MidiDeviceReceiverEnvelope(device, receiver);
 242         }
 243         return receiver;
 244     }
 245 
 246     /**
 247      * Obtains a MIDI transmitter from an external MIDI port or other default
 248      * source. The returned transmitter always implements the
 249      * {@code MidiDeviceTransmitter} interface.
 250      * <p>
 251      * If the system property {@code javax.sound.midi.Transmitter} is defined or
 252      * it is defined in the file "sound.properties", it is used to identify the
 253      * device that provides the default transmitter. For details, refer to the
 254      * {@link MidiSystem class description}.
 255      * <p>
 256      * If a native transmitter provided by the default device does not implement
 257      * the {@code MidiDeviceTransmitter} interface, it will be wrapped in a
 258      * wrapper class that implements the {@code MidiDeviceTransmitter}
 259      * interface. The corresponding {@code Transmitter} method calls will be
 260      * forwarded to the native transmitter.
 261      * <p>
 262      * If this method returns successfully, the {@link MidiDevice MidiDevice}
 263      * the {@code Transmitter} belongs to is opened implicitly, if it is not
 264      * already open. It is possible to close an implicitly opened device by
 265      * calling {@link Transmitter#close close} on the returned
 266      * {@code Transmitter}. All open {@code Transmitter} instances have to be
 267      * closed in order to release system resources hold by the
 268      * {@code MidiDevice}. For a detailed description of open/close behaviour
 269      * see the class description of {@link MidiDevice MidiDevice}.
 270      *
 271      * @return the default MIDI transmitter
 272      * @throws MidiUnavailableException if the default transmitter is not
 273      *         available due to resource restrictions, or no device providing
 274      *         transmitters is installed in the system
 275      */
 276     public static Transmitter getTransmitter() throws MidiUnavailableException {
 277         // may throw MidiUnavailableException
 278         MidiDevice device = getDefaultDeviceWrapper(Transmitter.class);
 279         Transmitter transmitter;
 280         if (device instanceof ReferenceCountingDevice) {
 281             transmitter = ((ReferenceCountingDevice) device).getTransmitterReferenceCounting();
 282         } else {
 283             transmitter = device.getTransmitter();
 284         }
 285         if (!(transmitter instanceof MidiDeviceTransmitter)) {
 286             transmitter = new MidiDeviceTransmitterEnvelope(device, transmitter);
 287         }
 288         return transmitter;
 289     }
 290 
 291     /**
 292      * Obtains the default synthesizer.
 293      * <p>
 294      * If the system property {@code javax.sound.midi.Synthesizer} is defined or
 295      * it is defined in the file "sound.properties", it is used to identify the
 296      * default synthesizer. For details, refer to the
 297      * {@link MidiSystem class description}.
 298      *
 299      * @return the default synthesizer
 300      * @throws MidiUnavailableException if the synthesizer is not available due
 301      *         to resource restrictions, or no synthesizer is installed in the
 302      *         system
 303      */
 304     public static Synthesizer getSynthesizer() throws MidiUnavailableException {
 305         // may throw MidiUnavailableException
 306         return (Synthesizer) getDefaultDeviceWrapper(Synthesizer.class);
 307     }
 308 
 309     /**
 310      * Obtains the default {@code Sequencer}, connected to a default device. The
 311      * returned {@code Sequencer} instance is connected to the default
 312      * {@code Synthesizer}, as returned by {@link #getSynthesizer}. If there is
 313      * no {@code Synthesizer} available, or the default {@code Synthesizer}
 314      * cannot be opened, the {@code sequencer} is connected to the default
 315      * {@code Receiver}, as returned by {@link #getReceiver}. The connection is
 316      * made by retrieving a {@code Transmitter} instance from the
 317      * {@code Sequencer} and setting its {@code Receiver}. Closing and
 318      * re-opening the sequencer will restore the connection to the default
 319      * device.
 320      * <p>
 321      * This method is equivalent to calling {@code getSequencer(true)}.
 322      * <p>
 323      * If the system property {@code javax.sound.midi.Sequencer} is defined or
 324      * it is defined in the file "sound.properties", it is used to identify the
 325      * default sequencer. For details, refer to the
 326      * {@link MidiSystem class description}.
 327      *
 328      * @return the default sequencer, connected to a default Receiver
 329      * @throws MidiUnavailableException if the sequencer is not available due to
 330      *         resource restrictions, or there is no {@code Receiver} available
 331      *         by any installed {@code MidiDevice}, or no sequencer is installed
 332      *         in the system
 333      * @see #getSequencer(boolean)
 334      * @see #getSynthesizer
 335      * @see #getReceiver
 336      */
 337     public static Sequencer getSequencer() throws MidiUnavailableException {
 338         return getSequencer(true);
 339     }
 340 
 341     /**
 342      * Obtains the default {@code Sequencer}, optionally connected to a default
 343      * device.
 344      * <p>
 345      * If {@code connected} is true, the returned {@code Sequencer} instance is
 346      * connected to the default {@code Synthesizer}, as returned by
 347      * {@link #getSynthesizer}. If there is no {@code Synthesizer} available, or
 348      * the default {@code Synthesizer} cannot be opened, the {@code sequencer}
 349      * is connected to the default {@code Receiver}, as returned by
 350      * {@link #getReceiver}. The connection is made by retrieving a
 351      * {@code Transmitter} instance from the {@code Sequencer} and setting its
 352      * {@code Receiver}. Closing and re-opening the sequencer will restore the
 353      * connection to the default device.
 354      * <p>
 355      * If {@code connected} is false, the returned {@code Sequencer} instance is
 356      * not connected, it has no open {@code Transmitters}. In order to play the
 357      * sequencer on a MIDI device, or a {@code Synthesizer}, it is necessary to
 358      * get a {@code Transmitter} and set its {@code Receiver}.
 359      * <p>
 360      * If the system property {@code javax.sound.midi.Sequencer} is defined or
 361      * it is defined in the file "sound.properties", it is used to identify the
 362      * default sequencer. For details, refer to the
 363      * {@link MidiSystem class description}.
 364      *
 365      * @param  connected whether or not the returned {@code Sequencer} is
 366      *         connected to the default {@code Synthesizer}
 367      * @return the default sequencer
 368      * @throws MidiUnavailableException if the sequencer is not available due to
 369      *         resource restrictions, or no sequencer is installed in the
 370      *         system, or if {@code connected} is true, and there is no
 371      *         {@code Receiver} available by any installed {@code MidiDevice}
 372      * @see #getSynthesizer
 373      * @see #getReceiver
 374      * @since 1.5
 375      */
 376     public static Sequencer getSequencer(boolean connected)
 377         throws MidiUnavailableException {
 378         Sequencer seq = (Sequencer) getDefaultDeviceWrapper(Sequencer.class);
 379 
 380         if (connected) {
 381             // IMPORTANT: this code needs to be synch'ed with
 382             //            all AutoConnectSequencer instances,
 383             //            (e.g. RealTimeSequencer) because the
 384             //            same algorithm for synth retrieval
 385             //            needs to be used!
 386 
 387             Receiver rec = null;
 388             MidiUnavailableException mue = null;
 389 
 390             // first try to connect to the default synthesizer
 391             try {
 392                 Synthesizer synth = getSynthesizer();
 393                 if (synth instanceof ReferenceCountingDevice) {
 394                     rec = ((ReferenceCountingDevice) synth).getReceiverReferenceCounting();
 395                 } else {
 396                     synth.open();
 397                     try {
 398                         rec = synth.getReceiver();
 399                     } finally {
 400                         // make sure that the synth is properly closed
 401                         if (rec == null) {
 402                             synth.close();
 403                         }
 404                     }
 405                 }
 406             } catch (MidiUnavailableException e) {
 407                 // something went wrong with synth
 408                 if (e instanceof MidiUnavailableException) {
 409                     mue = e;
 410                 }
 411             }
 412             if (rec == null) {
 413                 // then try to connect to the default Receiver
 414                 try {
 415                     rec = MidiSystem.getReceiver();
 416                 } catch (Exception e) {
 417                     // something went wrong. Nothing to do then!
 418                     if (e instanceof MidiUnavailableException) {
 419                         mue = (MidiUnavailableException) e;
 420                     }
 421                 }
 422             }
 423             if (rec != null) {
 424                 seq.getTransmitter().setReceiver(rec);
 425                 if (seq instanceof AutoConnectSequencer) {
 426                     ((AutoConnectSequencer) seq).setAutoConnect(rec);
 427                 }
 428             } else {
 429                 if (mue != null) {
 430                     throw mue;
 431                 }
 432                 throw new MidiUnavailableException("no receiver available");
 433             }
 434         }
 435         return seq;
 436     }
 437 
 438     /**
 439      * Constructs a MIDI sound bank by reading it from the specified stream. The
 440      * stream must point to a valid MIDI soundbank file. In general, MIDI
 441      * soundbank providers may need to read some data from the stream before
 442      * determining whether they support it. These parsers must be able to mark
 443      * the stream, read enough data to determine whether they support the
 444      * stream, and, if not, reset the stream's read pointer to its original
 445      * position. If the input stream does not support this, this method may fail
 446      * with an {@code IOException}.
 447      *
 448      * @param  stream the source of the sound bank data
 449      * @return the sound bank
 450      * @throws InvalidMidiDataException if the stream does not point to valid
 451      *         MIDI soundbank data recognized by the system
 452      * @throws IOException if an I/O error occurred when loading the soundbank
 453      * @throws NullPointerException if {@code stream} is {@code null}
 454      * @see InputStream#markSupported
 455      * @see InputStream#mark
 456      */
 457     public static Soundbank getSoundbank(final InputStream stream)
 458             throws InvalidMidiDataException, IOException {
 459         Objects.requireNonNull(stream);
 460 
 461         SoundbankReader sp = null;
 462         Soundbank s = null;
 463 
 464         List<SoundbankReader> providers = getSoundbankReaders();
 465 
 466         for(int i = 0; i < providers.size(); i++) {
 467             sp = providers.get(i);
 468             s = sp.getSoundbank(stream);
 469 
 470             if( s!= null) {
 471                 return s;
 472             }
 473         }
 474         throw new InvalidMidiDataException("cannot get soundbank from stream");
 475 
 476     }
 477 
 478     /**
 479      * Constructs a {@code Soundbank} by reading it from the specified URL. The
 480      * URL must point to a valid MIDI soundbank file.
 481      *
 482      * @param  url the source of the sound bank data
 483      * @return the sound bank
 484      * @throws InvalidMidiDataException if the URL does not point to valid MIDI
 485      *         soundbank data recognized by the system
 486      * @throws IOException if an I/O error occurred when loading the soundbank
 487      * @throws NullPointerException if {@code url} is {@code null}
 488      */
 489     public static Soundbank getSoundbank(final URL url)
 490             throws InvalidMidiDataException, IOException {
 491         Objects.requireNonNull(url);
 492 
 493         SoundbankReader sp = null;
 494         Soundbank s = null;
 495 
 496         List<SoundbankReader> providers = getSoundbankReaders();
 497 
 498         for(int i = 0; i < providers.size(); i++) {
 499             sp = providers.get(i);
 500             s = sp.getSoundbank(url);
 501 
 502             if( s!= null) {
 503                 return s;
 504             }
 505         }
 506         throw new InvalidMidiDataException("cannot get soundbank from stream");
 507 
 508     }
 509 
 510     /**
 511      * Constructs a {@code Soundbank} by reading it from the specified
 512      * {@code File}. The {@code File} must point to a valid MIDI soundbank file.
 513      *
 514      * @param  file the source of the sound bank data
 515      * @return the sound bank
 516      * @throws InvalidMidiDataException if the {@code File} does not point to
 517      *         valid MIDI soundbank data recognized by the system
 518      * @throws IOException if an I/O error occurred when loading the soundbank
 519      * @throws NullPointerException if {@code file} is {@code null}
 520      */
 521     public static Soundbank getSoundbank(final File file)
 522             throws InvalidMidiDataException, IOException {
 523         Objects.requireNonNull(file);
 524 
 525         SoundbankReader sp = null;
 526         Soundbank s = null;
 527 
 528         List<SoundbankReader> providers = getSoundbankReaders();
 529 
 530         for(int i = 0; i < providers.size(); i++) {
 531             sp = providers.get(i);
 532             s = sp.getSoundbank(file);
 533 
 534             if( s!= null) {
 535                 return s;
 536             }
 537         }
 538         throw new InvalidMidiDataException("cannot get soundbank from stream");
 539     }
 540 
 541     /**
 542      * Obtains the MIDI file format of the data in the specified input stream.
 543      * The stream must point to valid MIDI file data for a file type recognized
 544      * by the system.
 545      * <p>
 546      * This method and/or the code it invokes may need to read some data from
 547      * the stream to determine whether its data format is supported. The
 548      * implementation may therefore need to mark the stream, read enough data to
 549      * determine whether it is in a supported format, and reset the stream's
 550      * read pointer to its original position. If the input stream does not
 551      * permit this set of operations, this method may fail with an
 552      * {@code IOException}.
 553      * <p>
 554      * This operation can only succeed for files of a type which can be parsed
 555      * by an installed file reader. It may fail with an
 556      * {@code InvalidMidiDataException} even for valid files if no compatible
 557      * file reader is installed. It will also fail with an
 558      * {@code InvalidMidiDataException} if a compatible file reader is
 559      * installed, but encounters errors while determining the file format.
 560      *
 561      * @param  stream the input stream from which file format information should
 562      *         be extracted
 563      * @return an {@code MidiFileFormat} object describing the MIDI file format
 564      * @throws InvalidMidiDataException if the stream does not point to valid
 565      *         MIDI file data recognized by the system
 566      * @throws IOException if an I/O exception occurs while accessing the
 567      *         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     @SuppressWarnings("unchecked")
1027     private static List<MidiDeviceProvider> getMidiDeviceProviders() {
1028         return (List<MidiDeviceProvider>) getProviders(MidiDeviceProvider.class);
1029     }
1030 
1031     @SuppressWarnings("unchecked")
1032     private static List<SoundbankReader> getSoundbankReaders() {
1033         return (List<SoundbankReader>) getProviders(SoundbankReader.class);
1034     }
1035 
1036     @SuppressWarnings("unchecked")
1037     private static List<MidiFileWriter> getMidiFileWriters() {
1038         return (List<MidiFileWriter>) getProviders(MidiFileWriter.class);
1039     }
1040 
1041     @SuppressWarnings("unchecked")
1042     private static List<MidiFileReader> getMidiFileReaders() {
1043         return (List<MidiFileReader>) getProviders(MidiFileReader.class);
1044     }
1045 
1046     /**
1047      * Attempts to locate and return a default MidiDevice of the specified type.
1048      * This method wraps {@link #getDefaultDevice}. It catches the
1049      * {@code IllegalArgumentException} thrown by {@code getDefaultDevice} and
1050      * instead throws a {@code MidiUnavailableException}, with the catched
1051      * exception chained.
1052      *
1053      * @param  deviceClass The requested device type, one of Synthesizer.class,
1054      *         Sequencer.class, Receiver.class or Transmitter.class
1055      * @throws MidiUnavailableException on failure
1056      */
1057     private static MidiDevice getDefaultDeviceWrapper(Class<?> deviceClass)
1058         throws MidiUnavailableException{
1059         try {
1060             return getDefaultDevice(deviceClass);
1061         } catch (IllegalArgumentException iae) {
1062             MidiUnavailableException mae = new MidiUnavailableException();
1063             mae.initCause(iae);
1064             throw mae;
1065         }
1066     }
1067 
1068     /**
1069      * Attempts to locate and return a default MidiDevice of the specified type.
1070      *
1071      * @param  deviceClass The requested device type, one of Synthesizer.class,
1072      *         Sequencer.class, Receiver.class or Transmitter.class
1073      * @throws IllegalArgumentException on failure
1074      */
1075     private static MidiDevice getDefaultDevice(Class<?> deviceClass) {
1076         List<MidiDeviceProvider> providers = getMidiDeviceProviders();
1077         String providerClassName = JDK13Services.getDefaultProviderClassName(deviceClass);
1078         String instanceName = JDK13Services.getDefaultInstanceName(deviceClass);
1079         MidiDevice device;
1080 
1081         if (providerClassName != null) {
1082             MidiDeviceProvider defaultProvider = getNamedProvider(providerClassName, providers);
1083             if (defaultProvider != null) {
1084                 if (instanceName != null) {
1085                     device = getNamedDevice(instanceName, defaultProvider, deviceClass);
1086                     if (device != null) {
1087                         return device;
1088                     }
1089                 }
1090                 device = getFirstDevice(defaultProvider, deviceClass);
1091                 if (device != null) {
1092                     return device;
1093                 }
1094             }
1095         }
1096 
1097         /* Provider class not specified or cannot be found, or
1098            provider class specified, and no appropriate device available or
1099            provider class and instance specified and instance cannot be found or is not appropriate */
1100         if (instanceName != null) {
1101             device = getNamedDevice(instanceName, providers, deviceClass);
1102             if (device != null) {
1103                 return device;
1104             }
1105         }
1106 
1107         /* No default are specified, or if something is specified, everything
1108            failed. */
1109         device = getFirstDevice(providers, deviceClass);
1110         if (device != null) {
1111             return device;
1112         }
1113         throw new IllegalArgumentException("Requested device not installed");
1114     }
1115 
1116     /**
1117      * Return a MidiDeviceProvider of a given class from the list of
1118      * MidiDeviceProviders.
1119      *
1120      * @param  providerClassName The class name of the provider to be returned
1121      * @param  providers The list of MidiDeviceProviders that is searched
1122      * @return A MidiDeviceProvider of the requested class, or null if none is
1123      *         found
1124      */
1125     private static MidiDeviceProvider getNamedProvider(String providerClassName,
1126                                                        List<MidiDeviceProvider> providers) {
1127         for(int i = 0; i < providers.size(); i++) {
1128             MidiDeviceProvider provider = providers.get(i);
1129             if (provider.getClass().getName().equals(providerClassName)) {
1130                 return provider;
1131             }
1132         }
1133         return null;
1134     }
1135 
1136     /**
1137      * Return a MidiDevice with a given name from a given MidiDeviceProvider.
1138      *
1139      * @param  deviceName The name of the MidiDevice to be returned
1140      * @param  provider The MidiDeviceProvider to check for MidiDevices
1141      * @param  deviceClass The requested device type, one of Synthesizer.class,
1142      *         Sequencer.class, Receiver.class or Transmitter.class
1143      * @return A MidiDevice matching the requirements, or null if none is found
1144      */
1145     private static MidiDevice getNamedDevice(String deviceName,
1146                                              MidiDeviceProvider provider,
1147                                              Class<?> deviceClass) {
1148         MidiDevice device;
1149         // try to get MIDI port
1150         device = getNamedDevice(deviceName, provider, deviceClass,
1151                                  false, false);
1152         if (device != null) {
1153             return device;
1154         }
1155 
1156         if (deviceClass == Receiver.class) {
1157             // try to get Synthesizer
1158             device = getNamedDevice(deviceName, provider, deviceClass,
1159                                      true, false);
1160             if (device != null) {
1161                 return device;
1162             }
1163         }
1164 
1165         return null;
1166     }
1167 
1168     /**
1169      * Return a MidiDevice with a given name from a given MidiDeviceProvider.
1170      *
1171      * @param  deviceName The name of the MidiDevice to be returned
1172      * @param  provider The MidiDeviceProvider to check for MidiDevices
1173      * @param  deviceClass The requested device type, one of Synthesizer.class,
1174      *         Sequencer.class, Receiver.class or Transmitter.class
1175      * @return A MidiDevice matching the requirements, or null if none is found
1176      */
1177     private static MidiDevice getNamedDevice(String deviceName,
1178                                              MidiDeviceProvider provider,
1179                                              Class<?> deviceClass,
1180                                              boolean allowSynthesizer,
1181                                              boolean allowSequencer) {
1182         MidiDevice.Info[] infos = provider.getDeviceInfo();
1183         for (int i = 0; i < infos.length; i++) {
1184             if (infos[i].getName().equals(deviceName)) {
1185                 MidiDevice device = provider.getDevice(infos[i]);
1186                 if (isAppropriateDevice(device, deviceClass,
1187                                         allowSynthesizer, allowSequencer)) {
1188                     return device;
1189                 }
1190             }
1191         }
1192         return null;
1193     }
1194 
1195     /**
1196      * Return a MidiDevice with a given name from a list of MidiDeviceProviders.
1197      *
1198      * @param  deviceName The name of the MidiDevice to be returned
1199      * @param  providers The List of MidiDeviceProviders to check for
1200      *         MidiDevices
1201      * @param  deviceClass The requested device type, one of Synthesizer.class,
1202      *         Sequencer.class, Receiver.class or Transmitter.class
1203      * @return A Mixer matching the requirements, or null if none is found
1204      */
1205     private static MidiDevice getNamedDevice(String deviceName,
1206                                              List<MidiDeviceProvider> providers,
1207                                              Class<?> deviceClass) {
1208         MidiDevice device;
1209         // try to get MIDI port
1210         device = getNamedDevice(deviceName, providers, deviceClass,
1211                                  false, false);
1212         if (device != null) {
1213             return device;
1214         }
1215 
1216         if (deviceClass == Receiver.class) {
1217             // try to get Synthesizer
1218             device = getNamedDevice(deviceName, providers, deviceClass,
1219                                      true, false);
1220             if (device != null) {
1221                 return device;
1222             }
1223         }
1224 
1225         return null;
1226     }
1227 
1228     /**
1229      * Return a MidiDevice with a given name from a list of MidiDeviceProviders.
1230      *
1231      * @param  deviceName The name of the MidiDevice to be returned
1232      * @param  providers The List of MidiDeviceProviders to check for
1233      *         MidiDevices
1234      * @param  deviceClass The requested device type, one of Synthesizer.class,
1235      *         Sequencer.class, Receiver.class or Transmitter.class
1236      * @return A Mixer matching the requirements, or null if none is found
1237      */
1238     private static MidiDevice getNamedDevice(String deviceName,
1239                                              List<MidiDeviceProvider> providers,
1240                                              Class<?> deviceClass,
1241                                              boolean allowSynthesizer,
1242                                              boolean allowSequencer) {
1243         for(int i = 0; i < providers.size(); i++) {
1244             MidiDeviceProvider provider = providers.get(i);
1245             MidiDevice device = getNamedDevice(deviceName, provider,
1246                                                deviceClass,
1247                                                allowSynthesizer,
1248                                                allowSequencer);
1249             if (device != null) {
1250                 return device;
1251             }
1252         }
1253         return null;
1254     }
1255 
1256     /**
1257      * From a given MidiDeviceProvider, return the first appropriate device.
1258      *
1259      * @param  provider The MidiDeviceProvider to check for MidiDevices
1260      * @param  deviceClass The requested device type, one of Synthesizer.class,
1261      *         Sequencer.class, Receiver.class or Transmitter.class
1262      * @return A MidiDevice is considered appropriate, or null if no appropriate
1263      *         device is found
1264      */
1265     private static MidiDevice getFirstDevice(MidiDeviceProvider provider,
1266                                              Class<?> deviceClass) {
1267         MidiDevice device;
1268         // try to get MIDI port
1269         device = getFirstDevice(provider, deviceClass,
1270                                 false, false);
1271         if (device != null) {
1272             return device;
1273         }
1274 
1275         if (deviceClass == Receiver.class) {
1276             // try to get Synthesizer
1277             device = getFirstDevice(provider, deviceClass,
1278                                     true, false);
1279             if (device != null) {
1280                 return device;
1281             }
1282         }
1283 
1284         return null;
1285     }
1286 
1287     /**
1288      * From a given MidiDeviceProvider, return the first appropriate device.
1289      *
1290      * @param  provider The MidiDeviceProvider to check for MidiDevices
1291      * @param  deviceClass The requested device type, one of Synthesizer.class,
1292      *         Sequencer.class, Receiver.class or Transmitter.class
1293      * @return A MidiDevice is considered appropriate, or null if no appropriate
1294      *         device is found
1295      */
1296     private static MidiDevice getFirstDevice(MidiDeviceProvider provider,
1297                                              Class<?> deviceClass,
1298                                              boolean allowSynthesizer,
1299                                              boolean allowSequencer) {
1300         MidiDevice.Info[] infos = provider.getDeviceInfo();
1301         for (int j = 0; j < infos.length; j++) {
1302             MidiDevice device = provider.getDevice(infos[j]);
1303             if (isAppropriateDevice(device, deviceClass,
1304                                     allowSynthesizer, allowSequencer)) {
1305                 return device;
1306             }
1307         }
1308         return null;
1309     }
1310 
1311     /**
1312      * From a List of MidiDeviceProviders, return the first appropriate
1313      * MidiDevice.
1314      *
1315      * @param  providers The List of MidiDeviceProviders to search
1316      * @param  deviceClass The requested device type, one of Synthesizer.class,
1317      *         Sequencer.class, Receiver.class or Transmitter.class
1318      * @return A MidiDevice that is considered appropriate, or null if none is
1319      *         found
1320      */
1321     private static MidiDevice getFirstDevice(List<MidiDeviceProvider> providers,
1322                                              Class<?> deviceClass) {
1323         MidiDevice device;
1324         // try to get MIDI port
1325         device = getFirstDevice(providers, deviceClass,
1326                                 false, false);
1327         if (device != null) {
1328             return device;
1329         }
1330 
1331         if (deviceClass == Receiver.class) {
1332             // try to get Synthesizer
1333             device = getFirstDevice(providers, deviceClass,
1334                                     true, false);
1335             if (device != null) {
1336                 return device;
1337             }
1338         }
1339 
1340         return null;
1341     }
1342 
1343     /**
1344      * From a List of MidiDeviceProviders, return the first appropriate
1345      * MidiDevice.
1346      *
1347      * @param  providers The List of MidiDeviceProviders to search
1348      * @param  deviceClass The requested device type, one of Synthesizer.class,
1349      *         Sequencer.class, Receiver.class or Transmitter.class
1350      * @return A MidiDevice that is considered appropriate, or null if none is
1351      *         found
1352      */
1353     private static MidiDevice getFirstDevice(List<MidiDeviceProvider> providers,
1354                                              Class<?> deviceClass,
1355                                              boolean allowSynthesizer,
1356                                              boolean allowSequencer) {
1357         for(int i = 0; i < providers.size(); i++) {
1358             MidiDeviceProvider provider = providers.get(i);
1359             MidiDevice device = getFirstDevice(provider, deviceClass,
1360                                                allowSynthesizer,
1361                                                allowSequencer);
1362             if (device != null) {
1363                 return device;
1364             }
1365         }
1366         return null;
1367     }
1368 
1369     /**
1370      * Checks if a MidiDevice is appropriate. If deviceClass is Synthesizer or
1371      * Sequencer, a device implementing the respective interface is considered
1372      * appropriate. If deviceClass is Receiver or Transmitter, a device is
1373      * considered appropriate if it implements neither Synthesizer nor
1374      * Transmitter, and if it can provide at least one Receiver or Transmitter,
1375      * respectively.
1376      *
1377      * @param  device the MidiDevice to test
1378      * @param  allowSynthesizer if true, Synthesizers are considered
1379      *         appropriate. Otherwise only pure MidiDevices are considered
1380      *         appropriate (unless allowSequencer is true). This flag only has
1381      *         an effect for deviceClass Receiver and Transmitter. For other
1382      *         device classes (Sequencer and Synthesizer), this flag has no
1383      *         effect.
1384      * @param  allowSequencer if true, Sequencers are considered appropriate.
1385      *         Otherwise only pure MidiDevices are considered appropriate
1386      *         (unless allowSynthesizer is true). This flag only has an effect
1387      *         for deviceClass Receiver and Transmitter. For other device
1388      *         classes (Sequencer and Synthesizer), this flag has no effect.
1389      * @return true if the device is considered appropriate according to the
1390      *         rules given above, false otherwise
1391      */
1392     private static boolean isAppropriateDevice(MidiDevice device,
1393                                                Class<?> deviceClass,
1394                                                boolean allowSynthesizer,
1395                                                boolean allowSequencer) {
1396         if (deviceClass.isInstance(device)) {
1397            // This clause is for deviceClass being either Synthesizer
1398             // or Sequencer.
1399             return true;
1400         } else {
1401             // Now the case that deviceClass is Transmitter or
1402             // Receiver. If neither allowSynthesizer nor allowSequencer is
1403             // true, we require device instances to be
1404             // neither Synthesizer nor Sequencer, since we only want
1405             // devices representing MIDI ports.
1406             // Otherwise, the respective type is accepted, too
1407             if ( (! (device instanceof Sequencer) &&
1408                   ! (device instanceof Synthesizer) ) ||
1409                  ((device instanceof Sequencer) && allowSequencer) ||
1410                  ((device instanceof Synthesizer) && allowSynthesizer)) {
1411                 // And of cource, the device has to be able to provide
1412                 // Receivers or Transmitters.
1413                 if ((deviceClass == Receiver.class &&
1414                      device.getMaxReceivers() != 0) ||
1415                     (deviceClass == Transmitter.class &&
1416                      device.getMaxTransmitters() != 0)) {
1417                     return true;
1418                 }
1419             }
1420         }
1421         return false;
1422     }
1423 
1424     /**
1425      * Obtains the set of services currently installed on the system using the
1426      * SPI mechanism in 1.3.
1427      *
1428      * @return a List of instances of providers for the requested service. If no
1429      *         providers are available, a List of length 0 will be returned.
1430      */
1431      private static List<?> getProviders(Class<?> providerClass) {
1432          return JDK13Services.getProviders(providerClass);
1433     }
1434 }