Path: blob/master/src/java.desktop/share/classes/com/sun/media/sound/AbstractMidiDevice.java
41161 views
/*1* Copyright (c) 1999, 2020, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package com.sun.media.sound;2627import java.util.ArrayList;28import java.util.Collections;29import java.util.List;3031import javax.sound.midi.InvalidMidiDataException;32import javax.sound.midi.MidiDevice;33import javax.sound.midi.MidiDeviceReceiver;34import javax.sound.midi.MidiDeviceTransmitter;35import javax.sound.midi.MidiMessage;36import javax.sound.midi.MidiUnavailableException;37import javax.sound.midi.Receiver;38import javax.sound.midi.Transmitter;394041/**42* Abstract AbstractMidiDevice class representing functionality shared by43* MidiInDevice and MidiOutDevice objects.44*45* @author David Rivas46* @author Kara Kytle47* @author Matthias Pfisterer48* @author Florian Bomers49*/50abstract class AbstractMidiDevice implements MidiDevice, ReferenceCountingDevice {5152private ArrayList<Receiver> receiverList;5354private TransmitterList transmitterList;5556// lock to protect receiverList and transmitterList57// from simultaneous creation and destruction58// reduces possibility of deadlock, compared to59// synchronizing to the class instance60private final Object traRecLock = new Object();6162// DEVICE ATTRIBUTES6364private final MidiDevice.Info info;6566// DEVICE STATE6768private volatile boolean open;69private int openRefCount;7071/** List of Receivers and Transmitters that opened the device implicitely.72*/73private List<Object> openKeepingObjects;7475/**76* This is the device handle returned from native code.77*/78protected volatile long id;7980/**81* Constructs an AbstractMidiDevice with the specified info object.82* @param info the description of the device83*/84/*85* The initial mode and only supported mode default to OMNI_ON_POLY.86*/87protected AbstractMidiDevice(MidiDevice.Info info) {88this.info = info;89openRefCount = 0;90}9192// MIDI DEVICE METHODS9394@Override95public final MidiDevice.Info getDeviceInfo() {96return info;97}9899/** Open the device from an application program.100* Setting the open reference count to -1 here prevents Transmitters and Receivers that101* opened the device implicitly from closing it. The only way to close the device after102* this call is a call to close().103*/104@Override105public final void open() throws MidiUnavailableException {106synchronized(this) {107openRefCount = -1;108doOpen();109}110}111112/** Open the device implicitly.113* This method is intended to be used by AbstractReceiver114* and BasicTransmitter. Actually, it is called by getReceiverReferenceCounting() and115* getTransmitterReferenceCounting(). These, in turn, are called by MidiSytem on calls to116* getReceiver() and getTransmitter(). The former methods should pass the Receiver or117* Transmitter just created as the object parameter to this method. Storing references to118* these objects is necessary to be able to decide later (when it comes to closing) if119* R/T's are ones that opened the device implicitly.120*121* @object The Receiver or Transmitter instance that triggered this implicit open.122*/123private void openInternal(Object object) throws MidiUnavailableException {124synchronized(this) {125if (openRefCount != -1) {126openRefCount++;127getOpenKeepingObjects().add(object);128}129// double calls to doOpens() will be catched by the open flag.130doOpen();131}132}133134private void doOpen() throws MidiUnavailableException {135synchronized(this) {136if (! isOpen()) {137implOpen();138open = true;139}140}141}142143@Override144public final void close() {145synchronized (this) {146doClose();147openRefCount = 0;148}149}150151/** Close the device for an object that implicitely opened it.152* This method is intended to be used by Transmitter.close() and Receiver.close().153* Those methods should pass this for the object parameter. Since Transmitters or Receivers154* do not know if their device has been opened implicitely because of them, they call this155* method in any case. This method now is able to seperate Receivers/Transmitters that opened156* the device implicitely from those that didn't by looking up the R/T in the157* openKeepingObjects list. Only if the R/T is contained there, the reference count is158* reduced.159*160* @param object The object that might have been opening the device implicitely (for now,161* this may be a Transmitter or receiver).162*/163public final void closeInternal(Object object) {164synchronized(this) {165if (getOpenKeepingObjects().remove(object)) {166if (openRefCount > 0) {167openRefCount--;168if (openRefCount == 0) {169doClose();170}171}172}173}174}175176public final void doClose() {177synchronized(this) {178if (isOpen()) {179implClose();180open = false;181}182}183}184185@Override186public final boolean isOpen() {187return open;188}189190protected void implClose() {191synchronized (traRecLock) {192if (receiverList != null) {193// close all receivers194for(int i = 0; i < receiverList.size(); i++) {195receiverList.get(i).close();196}197receiverList.clear();198}199if (transmitterList != null) {200// close all transmitters201transmitterList.close();202}203}204}205206/**207* This implementation always returns -1.208* Devices that actually provide this should over-ride209* this method.210*/211@Override212public long getMicrosecondPosition() {213return -1;214}215216/** Return the maximum number of Receivers supported by this device.217Depending on the return value of hasReceivers(), this method returns either 0 or -1.218Subclasses should rather override hasReceivers() than override this method.219*/220@Override221public final int getMaxReceivers() {222if (hasReceivers()) {223return -1;224} else {225return 0;226}227}228229/** Return the maximum number of Transmitters supported by this device.230Depending on the return value of hasTransmitters(), this method returns either 0 or -1.231Subclasses should override hasTransmitters().232*/233@Override234public final int getMaxTransmitters() {235if (hasTransmitters()) {236return -1;237} else {238return 0;239}240}241242/** Retrieve a Receiver for this device.243This method returns the value returned by createReceiver(), if it doesn't throw244an exception. Subclasses should rather override createReceiver() than override245this method.246If createReceiver returns a Receiver, it is added to the internal list247of Receivers (see getReceiversList)248*/249@Override250public final Receiver getReceiver() throws MidiUnavailableException {251Receiver receiver;252synchronized (traRecLock) {253receiver = createReceiver(); // may throw MidiUnavailableException254getReceiverList().add(receiver);255}256return receiver;257}258259@Override260@SuppressWarnings("unchecked") // Cast of result of clone261public final List<Receiver> getReceivers() {262List<Receiver> recs;263synchronized (traRecLock) {264if (receiverList == null) {265recs = Collections.unmodifiableList(new ArrayList<Receiver>(0));266} else {267recs = Collections.unmodifiableList268((List<Receiver>) (receiverList.clone()));269}270}271return recs;272}273274/**275* This implementation uses createTransmitter, which may throw an exception.276* If a transmitter is returned in createTransmitter, it is added to the internal277* TransmitterList278*/279@Override280public final Transmitter getTransmitter() throws MidiUnavailableException {281Transmitter transmitter;282synchronized (traRecLock) {283transmitter = createTransmitter(); // may throw MidiUnavailableException284getTransmitterList().add(transmitter);285}286return transmitter;287}288289@Override290@SuppressWarnings("unchecked") // Cast of result of clone291public final List<Transmitter> getTransmitters() {292List<Transmitter> tras;293synchronized (traRecLock) {294if (transmitterList == null295|| transmitterList.transmitters.size() == 0) {296tras = Collections.unmodifiableList(new ArrayList<Transmitter>(0));297} else {298tras = Collections.unmodifiableList((List<Transmitter>) (transmitterList.transmitters.clone()));299}300}301return tras;302}303304final long getId() {305return id;306}307308// REFERENCE COUNTING309310/** Retrieve a Receiver and open the device implicitly.311This method is called by MidiSystem.getReceiver().312*/313@Override314public final Receiver getReceiverReferenceCounting()315throws MidiUnavailableException {316/* Keep this order of commands! If getReceiver() throws an exception,317openInternal() should not be called!318*/319Receiver receiver;320synchronized (traRecLock) {321receiver = getReceiver();322AbstractMidiDevice.this.openInternal(receiver);323}324return receiver;325}326327/** Retrieve a Transmitter and open the device implicitly.328This method is called by MidiSystem.getTransmitter().329*/330@Override331public final Transmitter getTransmitterReferenceCounting()332throws MidiUnavailableException {333/* Keep this order of commands! If getTransmitter() throws an exception,334openInternal() should not be called!335*/336Transmitter transmitter;337synchronized (traRecLock) {338transmitter = getTransmitter();339AbstractMidiDevice.this.openInternal(transmitter);340}341return transmitter;342}343344/** Return the list of objects that have opened the device implicitely.345*/346private synchronized List<Object> getOpenKeepingObjects() {347if (openKeepingObjects == null) {348openKeepingObjects = new ArrayList<>();349}350return openKeepingObjects;351}352353// RECEIVER HANDLING METHODS354355/** Return the internal list of Receivers, possibly creating it first.356*/357private List<Receiver> getReceiverList() {358synchronized (traRecLock) {359if (receiverList == null) {360receiverList = new ArrayList<>();361}362}363return receiverList;364}365366/** Returns if this device supports Receivers.367Subclasses that use Receivers should override this method to368return true. They also should override createReceiver().369370@return true, if the device supports Receivers, false otherwise.371*/372protected boolean hasReceivers() {373return false;374}375376/** Create a Receiver object.377throwing an exception here means that Receivers aren't enabled.378Subclasses that use Receivers should override this method with379one that returns objects implementing Receiver.380Classes overriding this method should also override hasReceivers()381to return true.382*/383protected Receiver createReceiver() throws MidiUnavailableException {384throw new MidiUnavailableException("MIDI IN receiver not available");385}386387// TRANSMITTER HANDLING388389/** Return the internal list of Transmitters, possibly creating it first.390*/391final TransmitterList getTransmitterList() {392synchronized (traRecLock) {393if (transmitterList == null) {394transmitterList = new TransmitterList();395}396}397return transmitterList;398}399400/** Returns if this device supports Transmitters.401Subclasses that use Transmitters should override this method to402return true. They also should override createTransmitter().403404@return true, if the device supports Transmitters, false otherwise.405*/406protected boolean hasTransmitters() {407return false;408}409410/** Create a Transmitter object.411throwing an exception here means that Transmitters aren't enabled.412Subclasses that use Transmitters should override this method with413one that returns objects implementing Transmitters.414Classes overriding this method should also override hasTransmitters()415to return true.416*/417protected Transmitter createTransmitter() throws MidiUnavailableException {418throw new MidiUnavailableException("MIDI OUT transmitter not available");419}420421protected abstract void implOpen() throws MidiUnavailableException;422423/**424* close this device if discarded by the garbage collector.425*/426@Override427@SuppressWarnings("deprecation")428protected final void finalize() {429close();430}431432/** Base class for Receivers.433Subclasses that use Receivers must use this base class, since it434contains magic necessary to manage implicit closing the device.435This is necessary for Receivers retrieved via MidiSystem.getReceiver()436(which opens the device implicitely).437*/438abstract class AbstractReceiver implements MidiDeviceReceiver {439private volatile boolean open = true;440441442/** Deliver a MidiMessage.443This method contains magic related to the closed state of a444Receiver. Therefore, subclasses should not override this method.445Instead, they should implement implSend().446*/447@Override448public final synchronized void send(final MidiMessage message,449final long timeStamp) {450if (!open) {451throw new IllegalStateException("Receiver is not open");452}453implSend(message, timeStamp);454}455456abstract void implSend(MidiMessage message, long timeStamp);457458/** Close the Receiver.459* Here, the call to the magic method closeInternal() takes place.460* Therefore, subclasses that override this method must call461* 'super.close()'.462*/463@Override464public final void close() {465open = false;466synchronized (AbstractMidiDevice.this.traRecLock) {467AbstractMidiDevice.this.getReceiverList().remove(this);468}469AbstractMidiDevice.this.closeInternal(this);470}471472@Override473public final MidiDevice getMidiDevice() {474return AbstractMidiDevice.this;475}476477final boolean isOpen() {478return open;479}480} // class AbstractReceiver481482483/**484* Transmitter base class.485* This class especially makes sure the device is closed if it486* has been opened implicitly by a call to MidiSystem.getTransmitter().487* The logic of doing so is actually in closeInternal().488*489* Also, it has some optimizations regarding sending to the Receivers,490* for known Receivers, and managing itself in the TransmitterList.491*/492class BasicTransmitter implements MidiDeviceTransmitter {493494private Receiver receiver = null;495TransmitterList tlist = null;496497protected BasicTransmitter() {498}499500private void setTransmitterList(TransmitterList tlist) {501this.tlist = tlist;502}503504@Override505public final void setReceiver(Receiver receiver) {506if (tlist != null && this.receiver != receiver) {507tlist.receiverChanged(this, this.receiver, receiver);508this.receiver = receiver;509}510}511512@Override513public final Receiver getReceiver() {514return receiver;515}516517/** Close the Transmitter.518* Here, the call to the magic method closeInternal() takes place.519* Therefore, subclasses that override this method must call520* 'super.close()'.521*/522@Override523public final void close() {524AbstractMidiDevice.this.closeInternal(this);525if (tlist != null) {526tlist.receiverChanged(this, this.receiver, null);527tlist.remove(this);528tlist = null;529}530}531532@Override533public final MidiDevice getMidiDevice() {534return AbstractMidiDevice.this;535}536537} // class BasicTransmitter538539/**540* a class to manage a list of transmitters.541*/542final class TransmitterList {543544private final ArrayList<Transmitter> transmitters = new ArrayList<>();545private MidiOutDevice.MidiOutReceiver midiOutReceiver;546547// how many transmitters must be present for optimized548// handling549private int optimizedReceiverCount = 0;550551552private void add(Transmitter t) {553synchronized(transmitters) {554transmitters.add(t);555}556if (t instanceof BasicTransmitter) {557((BasicTransmitter) t).setTransmitterList(this);558}559}560561private void remove(Transmitter t) {562synchronized(transmitters) {563int index = transmitters.indexOf(t);564if (index >= 0) {565transmitters.remove(index);566}567}568}569570private void receiverChanged(BasicTransmitter t,571Receiver oldR,572Receiver newR) {573synchronized(transmitters) {574// some optimization575if (midiOutReceiver == oldR) {576midiOutReceiver = null;577}578if (newR != null) {579if ((newR instanceof MidiOutDevice.MidiOutReceiver)580&& (midiOutReceiver == null)) {581midiOutReceiver = ((MidiOutDevice.MidiOutReceiver) newR);582}583}584optimizedReceiverCount =585((midiOutReceiver!=null)?1:0);586}587// more potential for optimization here588}589590591/** closes all transmitters and empties the list */592void close() {593synchronized (transmitters) {594for(int i = 0; i < transmitters.size(); i++) {595transmitters.get(i).close();596}597transmitters.clear();598}599}600601602603/**604* Send this message to all receivers605* status = packedMessage & 0xFF606* data1 = (packedMessage & 0xFF00) >> 8;607* data1 = (packedMessage & 0xFF0000) >> 16;608*/609void sendMessage(int packedMessage, long timeStamp) {610try {611synchronized(transmitters) {612int size = transmitters.size();613if (optimizedReceiverCount == size) {614if (midiOutReceiver != null) {615midiOutReceiver.sendPackedMidiMessage(packedMessage, timeStamp);616}617} else {618for (int i = 0; i < size; i++) {619Receiver receiver = transmitters.get(i).getReceiver();620if (receiver != null) {621if (optimizedReceiverCount > 0) {622if (receiver instanceof MidiOutDevice.MidiOutReceiver) {623((MidiOutDevice.MidiOutReceiver) receiver).sendPackedMidiMessage(packedMessage, timeStamp);624} else {625receiver.send(new FastShortMessage(packedMessage), timeStamp);626}627} else {628receiver.send(new FastShortMessage(packedMessage), timeStamp);629}630}631}632}633}634} catch (InvalidMidiDataException e) {635// this happens when invalid data comes over the wire. Ignore it.636}637}638639void sendMessage(byte[] data, long timeStamp) {640try {641synchronized(transmitters) {642int size = transmitters.size();643for (int i = 0; i < size; i++) {644Receiver receiver = transmitters.get(i).getReceiver();645if (receiver != null) {646//$$fb 2002-04-02: SysexMessages are mutable, so647// an application could change the contents of this object,648// or try to use the object later. So we can't get around object creation649// But the array need not be unique for each FastSysexMessage object,650// because it cannot be modified.651receiver.send(new FastSysexMessage(data), timeStamp);652}653}654}655} catch (InvalidMidiDataException e) {656// this happens when invalid data comes over the wire. Ignore it.657return;658}659}660661/**662* Send this message to all transmitters.663*/664void sendMessage(MidiMessage message, long timeStamp) {665if (message instanceof FastShortMessage) {666sendMessage(((FastShortMessage) message).getPackedMsg(), timeStamp);667return;668}669synchronized(transmitters) {670int size = transmitters.size();671if (optimizedReceiverCount == size) {672if (midiOutReceiver != null) {673midiOutReceiver.send(message, timeStamp);674}675} else {676for (int i = 0; i < size; i++) {677Receiver receiver = transmitters.get(i).getReceiver();678if (receiver != null) {679//$$fb 2002-04-02: ShortMessages are mutable, so680// an application could change the contents of this object,681// or try to use the object later.682// We violate this spec here, to avoid costly (and gc-intensive)683// object creation for potentially hundred of messages per second.684// The spec should be changed to allow Immutable MidiMessages685// (i.e. throws InvalidStateException or so in setMessage)686receiver.send(message, timeStamp);687}688}689}690}691}692} // TransmitterList693}694695696