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