1 /*
  2  * Copyright (c) 1999, 2021, 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 com.sun.media.sound;
 27 
 28 import java.util.Map;
 29 import java.util.Vector;
 30 import java.util.WeakHashMap;
 31 
 32 import javax.sound.sampled.AudioSystem;
 33 import javax.sound.sampled.Control;
 34 import javax.sound.sampled.Line;
 35 import javax.sound.sampled.LineEvent;
 36 import javax.sound.sampled.LineListener;
 37 import javax.sound.sampled.LineUnavailableException;
 38 
 39 /**
 40  * AbstractLine
 41  *
 42  * @author Kara Kytle
 43  */
 44 abstract class AbstractLine implements Line {
 45 
 46     protected final Line.Info info;
 47     protected Control[] controls;
 48     AbstractMixer mixer;
 49     private volatile boolean open;
 50     private final Vector<Object> listeners = new Vector<>();
 51 
 52     /**
 53      * Contains event dispatcher per thread group.
 54      */
 55     private static final Map<ThreadGroup, EventDispatcher> dispatchers =
 56             new WeakHashMap<>();
 57 
 58     /**
 59      * Constructs a new AbstractLine.
 60      * @param mixer the mixer with which this line is associated
 61      * @param controls set of supported controls
 62      */
 63     protected AbstractLine(Line.Info info, AbstractMixer mixer, Control[] controls) {
 64 
 65         if (controls == null) {
 66             controls = new Control[0];
 67         }
 68 
 69         this.info = info;
 70         this.mixer = mixer;
 71         this.controls = controls;
 72     }
 73 
 74     // LINE METHODS
 75 
 76     @Override
 77     public final Line.Info getLineInfo() {
 78         return info;
 79     }
 80 
 81     @Override
 82     public final boolean isOpen() {
 83         return open;
 84     }
 85 
 86     @Override
 87     public final void addLineListener(LineListener listener) {
 88         synchronized(listeners) {
 89             if ( ! (listeners.contains(listener)) ) {
 90                 listeners.addElement(listener);
 91             }
 92         }
 93     }
 94 
 95     /**
 96      * Removes an audio listener.
 97      * @param listener listener to remove
 98      */
 99     @Override
100     public final void removeLineListener(LineListener listener) {
101         listeners.removeElement(listener);
102     }
103 
104     /**
105      * Obtains the set of controls supported by the
106      * line.  If no controls are supported, returns an
107      * array of length 0.
108      * @return control set
109      */
110     @Override
111     public final Control[] getControls() {
112         Control[] returnedArray = new Control[controls.length];
113 
114         for (int i = 0; i < controls.length; i++) {
115             returnedArray[i] = controls[i];
116         }
117 
118         return returnedArray;
119     }
120 
121     @Override
122     public final boolean isControlSupported(Control.Type controlType) {
123         // protect against a NullPointerException
124         if (controlType == null) {
125             return false;
126         }
127 
128         for (int i = 0; i < controls.length; i++) {
129             if (controlType == controls[i].getType()) {
130                 return true;
131             }
132         }
133 
134         return false;
135     }
136 
137     @Override
138     public final Control getControl(Control.Type controlType) {
139         // protect against a NullPointerException
140         if (controlType != null) {
141 
142             for (int i = 0; i < controls.length; i++) {
143                 if (controlType == controls[i].getType()) {
144                     return controls[i];
145                 }
146             }
147         }
148 
149         throw new IllegalArgumentException("Unsupported control type: " + controlType);
150     }
151 
152     // HELPER METHODS
153 
154     /**
155      * This method sets the open state and generates
156      * events if it changes.
157      */
158     final void setOpen(boolean open) {
159         boolean sendEvents = false;
160         long position = getLongFramePosition();
161 
162         if (this.open != open) {
163             this.open = open;
164             sendEvents = true;
165         }
166 
167         if (sendEvents) {
168             if (open) {
169                 sendEvents(new LineEvent(this, LineEvent.Type.OPEN, position));
170             } else {
171                 sendEvents(new LineEvent(this, LineEvent.Type.CLOSE, position));
172             }
173         }
174     }
175 
176     /**
177      * Send line events.
178      */
179     final void sendEvents(LineEvent event) {
180         getEventDispatcher().sendAudioEvents(event, listeners);
181     }
182 
183     /**
184      * This is an error in the API: getFramePosition
185      * should return a long value. At CD quality,
186      * the int value wraps around after 13 hours.
187      */
188     public final int getFramePosition() {
189         return (int) getLongFramePosition();
190     }
191 
192     /**
193      * Return the frame position in a long value
194      * This implementation returns AudioSystem.NOT_SPECIFIED.
195      */
196     public long getLongFramePosition() {
197         return AudioSystem.NOT_SPECIFIED;
198     }
199 
200     // $$kk: 06.03.99: returns the mixer used in construction.
201     // this is a hold-over from when there was a public method like
202     // this on line and should be fixed!!
203     final AbstractMixer getMixer() {
204         return mixer;
205     }
206 
207     final EventDispatcher getEventDispatcher() {
208         // create and start the global event thread
209         //TODO  need a way to stop this thread when the engine is done
210         final ThreadGroup tg = Thread.currentThread().getThreadGroup();
211         synchronized (dispatchers) {
212             EventDispatcher eventDispatcher = dispatchers.get(tg);
213             if (eventDispatcher == null) {
214                 eventDispatcher = new EventDispatcher();
215                 dispatchers.put(tg, eventDispatcher);
216                 eventDispatcher.start();
217             }
218             return eventDispatcher;
219         }
220     }
221 
222     @Override
223     public abstract void open() throws LineUnavailableException;
224     @Override
225     public abstract void close();
226 }