1/*
2 * Copyright (c) 1999, 2016, 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
26package com.sun.media.sound;
27
28import java.util.Map;
29import java.util.Vector;
30import java.util.WeakHashMap;
31
32import javax.sound.sampled.AudioSystem;
33import javax.sound.sampled.Control;
34import javax.sound.sampled.Line;
35import javax.sound.sampled.LineEvent;
36import javax.sound.sampled.LineListener;
37import javax.sound.sampled.LineUnavailableException;
38
39/**
40 * AbstractLine
41 *
42 * @author Kara Kytle
43 */
44abstract 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
160        if (Printer.trace) Printer.trace("> "+getClass().getName()+" (AbstractLine): setOpen(" + open + ")  this.open: " + this.open);
161
162        boolean sendEvents = false;
163        long position = getLongFramePosition();
164
165        synchronized (this) {
166            if (this.open != open) {
167                this.open = open;
168                sendEvents = true;
169            }
170        }
171
172        if (sendEvents) {
173            if (open) {
174                sendEvents(new LineEvent(this, LineEvent.Type.OPEN, position));
175            } else {
176                sendEvents(new LineEvent(this, LineEvent.Type.CLOSE, position));
177            }
178        }
179        if (Printer.trace) Printer.trace("< "+getClass().getName()+" (AbstractLine): setOpen(" + open + ")  this.open: " + this.open);
180    }
181
182    /**
183     * Send line events.
184     */
185    final void sendEvents(LineEvent event) {
186        getEventDispatcher().sendAudioEvents(event, listeners);
187    }
188
189    /**
190     * This is an error in the API: getFramePosition
191     * should return a long value. At CD quality,
192     * the int value wraps around after 13 hours.
193     */
194    public final int getFramePosition() {
195        return (int) getLongFramePosition();
196    }
197
198    /**
199     * Return the frame position in a long value
200     * This implementation returns AudioSystem.NOT_SPECIFIED.
201     */
202    public long getLongFramePosition() {
203        return AudioSystem.NOT_SPECIFIED;
204    }
205
206    // $$kk: 06.03.99: returns the mixer used in construction.
207    // this is a hold-over from when there was a public method like
208    // this on line and should be fixed!!
209    final AbstractMixer getMixer() {
210        return mixer;
211    }
212
213    final EventDispatcher getEventDispatcher() {
214        // create and start the global event thread
215        //TODO  need a way to stop this thread when the engine is done
216        final ThreadGroup tg = Thread.currentThread().getThreadGroup();
217        synchronized (dispatchers) {
218            EventDispatcher eventDispatcher = dispatchers.get(tg);
219            if (eventDispatcher == null) {
220                eventDispatcher = new EventDispatcher();
221                dispatchers.put(tg, eventDispatcher);
222                eventDispatcher.start();
223            }
224            return eventDispatcher;
225        }
226    }
227
228    @Override
229    public abstract void open() throws LineUnavailableException;
230    @Override
231    public abstract void close();
232}
233