/**
* Copyright 1998-2001 Sun Microsystems, Inc.
*
* See the file "license.terms" for information on usage and
* redistribution of this file, and for a DISCLAIMER OF ALL
* WARRANTIES.
*/
package com.sun.speech.engine;
import java.util.Collection;
import java.util.Iterator;
import javax.speech.AudioException;
import javax.speech.AudioManager;
import javax.speech.Engine;
import javax.speech.EngineEvent;
import javax.speech.EngineException;
import javax.speech.EngineListener;
import javax.speech.EngineModeDesc;
import javax.speech.EngineProperties;
import javax.speech.EngineStateError;
import javax.speech.SpeechEvent;
import javax.speech.VocabManager;
/**
* Supports the JSAPI 1.0 <code>Engine</code> interface.
* Actual JSAPI implementations might want to extend or modify this
* implementation.
*/
abstract public class BaseEngine implements Engine, SpeechEventDispatcher {
/**
* A bitmask holding the current state of this <code>Engine</code>.
*/
protected long engineState;
/**
* An <code>Object</code> used for synchronizing access to
* <code>engineState</code>.
* @see #engineState
*/
protected Object engineStateLock;
/**
* List of <code>EngineListeners</code> registered for
* <code>EngineEvents</code> on this <code>Engine</code>.
*/
protected Collection engineListeners;
/**
* The <code>AudioManager</code> for this <code>Engine</code>.
*/
protected AudioManager audioManager = null;
/**
* The <code>EngineModeDesc</code> for this <code>Engine</code>.
*/
protected EngineModeDesc engineModeDesc = null;
/**
* The <code>EngineProperties</code> for this <code>Engine</code>.
*/
protected EngineProperties engineProperties = null;
/**
* Utility state for clearing the <code>engineState</code>.
*/
protected final static long CLEAR_ALL_STATE = ~(0L);
/**
* Creates a new <code>Engine</code> in the
* <code>DEALLOCATED</code> state.
*/
public BaseEngine() {
this(null);
}
/**
* Creates a new <code>Engine</code> in the
* <code>DEALLOCATED</code> state.
*
* @param desc the operating mode of this <code>Engine</code>
*/
public BaseEngine(EngineModeDesc desc) {
engineModeDesc = desc;
engineListeners = new java.util.ArrayList();
engineState = DEALLOCATED;
engineStateLock = new Object();
engineProperties = createEngineProperties();
}
/**
* Returns a or'ed set of flags indicating the current state of
* this <code>Engine</code>.
*
* <p>An <code>EngineEvent</code> is issued each time this
* <code>Engine</code> changes state.
*
* <p>The <code>getEngineState</code> method can be called successfully
* in any <code>Engine</code> state.
*
* @return the current state of this <code>Engine</code>
*
* @see #getEngineState
* @see #waitEngineState
*/
public long getEngineState() {
return engineState;
}
/**
* Blocks the calling thread until this <code>Engine</code>
* is in a specified state.
*
* <p>All state bits specified in the <code>state</code> parameter
* must be set in order for the method to return, as defined
* for the <code>testEngineState</code> method. If the <code>state</code>
* parameter defines an unreachable state
* (e.g. <code>PAUSED | RESUMED</code>) an exception is thrown.
*
* <p>The <code>waitEngineState</code> method can be called successfully
* in any <code>Engine</code> state.
*
* @param state a bitmask of the state to wait for
*
* @see #testEngineState
* @see #getEngineState
*
* @throws InterruptedException
* if another thread has interrupted this thread.
* @throws IllegalArgumentException
* if the specified state is unreachable
*/
public void waitEngineState(long state)
throws InterruptedException, IllegalArgumentException {
synchronized (engineStateLock) {
while (!testEngineState(state))
engineStateLock.wait();
}
}
/**
* Returns <code>true</code> if this state of this
* <code>Engine</code> matches the specified state.
*
* <p>The test performed is not an exact match to the current
* state. Only the specified states are tested. For
* example the following returns true only if the
* <code>Synthesizer</code> queue is empty, irrespective
* of the pause/resume and allocation states.
*
* <PRE>
* if (synth.testEngineState(Synthesizer.QUEUE_EMPTY)) ...
* </PRE>
*
* <p>The <code>testEngineState</code> method is equivalent to:
*
* <PRE>
* if ((engine.getEngineState() & state) == state)
* </PRE>
*
* <p>The <code>testEngineState</code> method can be called
* successfully in any <code>Engine</code> state.
*
* @param state a bitmask of the states to test for
*
* @return <code>true</code> if this <code>Engine</code> matches
* <code>state</code>; otherwise <code>false</code>
* @throws IllegalArgumentException
* if the specified state is unreachable
*/
public boolean testEngineState(long state)
throws IllegalArgumentException {
return ((getEngineState() & state) == state);
}
/**
* Updates this <code>Engine</code> state by clearing defined bits,
* then setting other specified bits.
*
* @return a length-2 array with old and new state values.
*/
protected long[] setEngineState(long clear, long set) {
long states[] = new long[2];
synchronized (engineStateLock) {
states[0] = engineState;
engineState = engineState & (~clear);
engineState = engineState | set;
states[1] = engineState;
engineStateLock.notifyAll();
}
return states;
}
/**
* Allocates the resources required for this <code>Engine</code> and
* puts it into the <code>ALLOCATED</code> state. When this method
* returns successfully the <code>ALLOCATED</code> bit of this
* <code>Engine</code> state is set, and the
* <code>testEngineState(Engine.ALLOCATED)</code> method returns
* <code>true</code>.
*
* <p>During the processing of the method, this <code>Engine</code> is
* temporarily in the <code>ALLOCATING_RESOURCES</code> state.
*
* @see #deallocate
*
* @throws EngineException if this <code>Engine</code> cannot be allocated
* @throws EngineStateError if this <code>Engine</code> is in the
* <code>DEALLOCATING_RESOURCES</code> state
*/
public void allocate() throws EngineException, EngineStateError {
if (testEngineState(ALLOCATED)) {
return;
}
long[] states = setEngineState(CLEAR_ALL_STATE, ALLOCATING_RESOURCES);
postEngineAllocatingResources(states[0], states[1]);
handleAllocate();
}
/**
* Called from the <code>allocate</code> method. Override this in
* subclasses.
*
* @see #allocate
*
* @throws EngineException if problems are encountered
*/
abstract protected void handleAllocate() throws EngineException;
/**
* Frees the resources of this <code>Engine</code> that were
* acquired during allocation and during operation and return this
* <code>Engine</code> to the <code>DEALLOCATED</code>. When this
* method returns the <code>DEALLOCATED</code> bit of this
* <code>Engine</code> state is set so the
* <code>testEngineState(Engine.DEALLOCATED)</code> method returns
* <code>true</code>.
*
* <p>During the processing of the method, this
* <code>Engine</code> is temporarily in the
* <code>DEALLOCATING_RESOURCES</code> state.
*
* <p>A deallocated engine can be re-started with a subsequent
* call to <code>allocate</code>.
*
* @see #allocate
*
* @throws EngineException if this <code>Engine</code> cannot be
* deallocated
* @throws EngineStateError if this <code>Engine</code> is in the
* <code>ALLOCATING_RESOURCES</code> state
*/
public void deallocate() throws EngineException, EngineStateError {
if (testEngineState(DEALLOCATED)) {
return;
}
long[] states = setEngineState(CLEAR_ALL_STATE,
DEALLOCATING_RESOURCES);
postEngineDeallocatingResources(states[0], states[1]);
handleDeallocate();
}
/**
* Called from the <code>deallocate</code> method. Override this in
* subclasses.
*
* @throws EngineException if this <code>Engine</code> cannot be
* deallocated.
*/
abstract protected void handleDeallocate() throws EngineException;
/**
* Pauses the audio stream for this <code>Engine</code> and put
* this <code>Engine</code> into the <code>PAUSED</code> state.
*
* @throws EngineStateError if this <code>Engine</code> is in the
* <code>DEALLOCATING_RESOURCES</code> or
* <code>DEALLOCATED</code> state.
*/
public void pause() throws EngineStateError {
synchronized (engineStateLock) {
checkEngineState(DEALLOCATED | DEALLOCATING_RESOURCES);
if (testEngineState(PAUSED)) {
return;
}
handlePause();
long[] states = setEngineState(RESUMED, PAUSED);
postEnginePaused(states[0], states[1]);
}
}
/**
* Called from the <code>pause</code> method. Override this in subclasses.
*/
abstract protected void handlePause();
/**
* Resumes the audio stream for this <code>Engine</code> and put
* this <code>Engine</code> into the <code>RESUMED</code> state.
*
* @throws AudioException if unable to gain access to the audio channel
* @throws EngineStateError if this <code>Engine</code> is in the
* <code>DEALLOCATING_RESOURCES</code> or
* <code>DEALLOCATED</code> state
*/
public void resume() throws AudioException, EngineStateError {
synchronized (engineStateLock) {
checkEngineState(DEALLOCATED | DEALLOCATING_RESOURCES);
if (testEngineState(RESUMED))
return;
handleResume();
long[] states = setEngineState(PAUSED, RESUMED);
postEngineResumed(states[0], states[1]);
}
}
/**
* Called from the <code>resume</code> method. Override in subclasses.
*/
abstract protected void handleResume();
/**
* Returns an object that provides management of the audio input
* or output of this <code>Engine</code>.
*
* @return the audio manader for this <code>Engine</code>
*/
public AudioManager getAudioManager() {
if (audioManager == null) {
audioManager = new BaseAudioManager();
}
return audioManager;
}
/**
* Returns an object that provides management of the vocabulary for
* this <code>Engine</code>. Returns <code>null</code> if this
* <code>Engine</code> does not support vocabulary management.
*
* @return the vocabulary manager of this <code>Engine</code>
*
* @throws EngineStateError if this <code>Engine</code> in the
* <code>DEALLOCATING_RESOURCES</code> or
* <code>DEALLOCATED</code> state
*/
public VocabManager getVocabManager() throws EngineStateError {
return null;
}
/**
* Gets the <code>EngineProperties</code> of this <code>Engine</code>.
* Must be set in subclasses.
*
* @return the <code>EngineProperties</code> of this <code>Engine</code>.
*/
public EngineProperties getEngineProperties() {
return engineProperties;
}
/**
* Gets the current operating properties and mode of
* this <code>Engine</code>.
*
* @return the operating mode of this <code>Engine</code>
*
* @throws SecurityException
*/
public EngineModeDesc getEngineModeDesc() throws SecurityException {
return engineModeDesc;
}
/**
* Sets the current operating properties and mode of
* this <code>Engine</code>.
*
* @param desc the new operating mode of this <code>Engine</code>
*/
protected void setEngineModeDesc(EngineModeDesc desc) {
engineModeDesc = desc;
}
/**
* Requests notification of <code>EngineEvents</code> from this
* <code>Engine</code>.
*
* @param listener the listener to add.
*/
public void addEngineListener(EngineListener listener) {
synchronized (engineListeners) {
if (!engineListeners.contains(listener)) {
engineListeners.add(listener);
}
}
}
/**
* Removes an <code>EngineListener</code> from the list of
* <code>EngineListeners</code>.
*
* @param listener the listener to remove.
*/
public void removeEngineListener(EngineListener listener) {
synchronized (engineListeners) {
engineListeners.remove(listener);
}
}
/**
* Utility function that generates an
* <code>ENGINE_ALLOCATED</code> event and posts it
* to the event queue. Eventually
* <code>fireEngineAllocated</code> will be called
* by the <code>dispatchSpeechEvent</code> as a result of this
* action.
*
* @param oldState the old state of this <code>Engine</code>
* @param newState the new state of this <code>Engine</code>
*
* @see #fireEngineAllocated
* @see #dispatchSpeechEvent
*/
protected void postEngineAllocated(long oldState, long newState) {
SpeechEventUtilities.postSpeechEvent(
this,
new EngineEvent(
this,
EngineEvent.ENGINE_ALLOCATED,
oldState, newState));
}
/**
* Utility function that sends an <code>ENGINE_ALLOCATED</code>
* event to all <code>EngineListeners</code> registered with this
* <code>Engine</code>. Called by <code>dispatchSpeechEvent</code>.
*
* @param event the <code>ENGINE_ALLOCATED</code> event
*
* @see #postEngineAllocated
* @see #dispatchSpeechEvent
*/
public void fireEngineAllocated(EngineEvent event) {
if (engineListeners == null) {
return;
}
Iterator iterator = engineListeners.iterator();
while (iterator.hasNext()) {
EngineListener el = (EngineListener) iterator.next();
el.engineAllocated(event);
}
}
/**
* Utility function that generates an
* <code>ENGINE_ALLOCATING_RESOURCES</code> event and
* posts it to the event queue. Eventually
* <code>fireEngineAllocatingResources</code>
* will be called by <code>dispatchSpeechEvent</code> as a
* result of this action.
*
* @param oldState the old state of this <code>Engine</code>
* @param newState the new state of this <code>Engine</code>
*
* @see #fireEngineAllocatingResources
* @see #dispatchSpeechEvent
*/
protected void postEngineAllocatingResources(long oldState,
long newState) {
SpeechEventUtilities.postSpeechEvent(
this,
new EngineEvent(
this,
EngineEvent.ENGINE_ALLOCATING_RESOURCES,
oldState, newState));
}
/**
* Utility function that sends an
* <code>ENGINE_ALLOCATING_RESOURCES</code> event to all
* <code>EngineListeners</code> registered with this
* <code>Engine</code>. Called by <code>dispatchSpeechEvent</code>.
*
* @param event the <code>ENGINE_ALLOCATING_RESOURCES</code> event
*
* @see #postEngineAllocatingResources
* @see #dispatchSpeechEvent
*/
public void fireEngineAllocatingResources(EngineEvent event) {
if (engineListeners == null) {
return;
}
Iterator iterator = engineListeners.iterator();
while (iterator.hasNext()) {
EngineListener el = (EngineListener) iterator.next();
el.engineAllocatingResources(event);
}
}
/**
* Utility function that generates an
* <code>ENGINE_DEALLOCATED</code> event and posts it
* to the event queue. Eventually
* <code>fireEngineDeallocated</code> will be called
* by <code>dispatchSpeechEvent</code> as a result of this action.
*
* @param oldState the old state of this <code>Engine</code>
* @param newState the new state of this <code>Engine</code>
*
* @see #fireEngineDeallocated
* @see #dispatchSpeechEvent
*/
protected void postEngineDeallocated(long oldState, long newState) {
SpeechEventUtilities.postSpeechEvent(
this,
new EngineEvent(
this,
EngineEvent.ENGINE_DEALLOCATED,
oldState, newState));
}
/**
* Utility function that sends an
* <code>ENGINE_DEALLOCATED</code> event to all
* <code>EngineListeners</code> registered with this
* <code>Engine</code>. Called by <code>dispatchSpeechEvent</code>.
*
* @param event the <code>ENGINE_DEALLOCATED</code> event
*
* @see #postEngineDeallocated
* @see #dispatchSpeechEvent
*/
public void fireEngineDeallocated(EngineEvent event) {
if (engineListeners == null) {
return;
}
Iterator iterator = engineListeners.iterator();
while (iterator.hasNext()) {
EngineListener el = (EngineListener) iterator.next();
el.engineDeallocated(event);
}
}
/**
* Utility function that generates
* <code>ENGINE_DEALLOCATING_RESOURCES</code> event and
* posts it to the event queue. Eventually
* <code>fireEngineAllocatingResources</code> will be called
* by <code>dispatchSpeechEvent</code> as a result of this action.
*
* @param oldState the old state of this <code>Engine</code>
* @param newState the new state of this <code>Engine</code>
*
* @see #fireEngineDeallocatingResources
* @see #dispatchSpeechEvent
*/
protected void postEngineDeallocatingResources(long oldState,
long newState) {
SpeechEventUtilities.postSpeechEvent(
this,
new EngineEvent(
this,
EngineEvent.ENGINE_DEALLOCATING_RESOURCES,
oldState, newState));
}
/**
* Utility function that sends a
* <code>ENGINE_DEALLOCATING_RESOURCES</code> event to all
* <code>EngineListeners</code> registered with this
* <code>Engine</code>. Called by <code>dispatchSpeechEvent</code>.
*
* @param event the <code>ENGINE_DEALLOCATING_RESOURCES</code> event
*
* @see #postEngineDeallocatingResources
* @see #dispatchSpeechEvent
*/
public void fireEngineDeallocatingResources(EngineEvent event) {
if (engineListeners == null) {
return;
}
Iterator iterator = engineListeners.iterator();
while (iterator.hasNext()) {
EngineListener el = (EngineListener) iterator.next();
el.engineDeallocatingResources(event);
}
}
/**
* Utility function that generates an
* <code>ENGINE_PAUSED</code> event and posts it
* to the event queue. Eventually
* <code>fireEnginePaused</code> will be called
* by <code>dispatchSpeechEvent</code> as a result of this action.
*
* @param oldState the old state of this <code>Engine</code>
* @param newState the new state of this <code>Engine</code>
*
* @see #fireEnginePaused
* @see #dispatchSpeechEvent
*/
protected void postEnginePaused(long oldState, long newState) {
SpeechEventUtilities.postSpeechEvent(
this,
new EngineEvent(
this,
EngineEvent.ENGINE_PAUSED,
oldState, newState));
}
/**
* Utility function that sends an <code>ENGINE_PAUSED</code> event
* to all
* <code>EngineListeners</code> registered with this
* <code>Engine</code>. Called by <code>dispatchSpeechEvent</code>.
*
* @param event the <code>ENGINE_PAUSED</code> event
*
* @see #postEnginePaused
* @see #dispatchSpeechEvent
*/
public void fireEnginePaused(EngineEvent event) {
if (engineListeners == null) {
return;
}
Iterator iterator = engineListeners.iterator();
while (iterator.hasNext()) {
EngineListener el = (EngineListener) iterator.next();
el.enginePaused(event);
}
}
/**
* Utility function that generates an <code>ENGINE_RESUMED</code>
* event and posts it to the event queue. Eventually
* <code>fireEngineResumed</code> will be called
* by <code>dispatchSpeechEvent</code> as a result of this action.
*
* @param oldState the old state of this <code>Engine</code>
* @param newState the new state of this <code>Engine</code>
*
* @see #fireEngineResumed
* @see #dispatchSpeechEvent
*/
protected void postEngineResumed(long oldState, long newState) {
SpeechEventUtilities.postSpeechEvent(
this,
new EngineEvent(
this,
EngineEvent.ENGINE_RESUMED,
oldState, newState));
}
/**
* Utility function that sends an <code>ENGINE_RESUMED</code> event
* to all
* <code>EngineListeners</code> registered with this
* <code>Engine</code>. Called by <code>dispatchSpeechEvent</code>.
*
* @param event the <code>ENGINE_RESUMED</code> event
*
* @see #postEngineResumed
* @see #dispatchSpeechEvent
*/
public void fireEngineResumed(EngineEvent event) {
if (engineListeners == null) {
return;
}
Iterator iterator = engineListeners.iterator();
while (iterator.hasNext()) {
EngineListener el = (EngineListener) iterator.next();
el.engineResumed(event);
}
}
/**
* Factory constructor for EngineProperties object.
*
* @return a <code>BaseEngineProperties</code> object specific to
* a subclass.
*/
abstract protected BaseEngineProperties createEngineProperties();
/**
* Convenience method that throws an <code>EngineStateError</code>
* if any of the bits in the passed state are set in the
* <code>state</code>.
*
* @param state the <code>Engine</code> state to check
*
* @throws EngineStateError if any of the bits in the passed state
* are set in the <code>state</code>
*/
protected void checkEngineState(long state) throws EngineStateError {
long currentState = getEngineState();
if ((currentState & state) != 0) {
throw new EngineStateError
("Invalid EngineState: expected=("
+ stateToString(state) + ") current state=("
+ stateToString(currentState) + ")");
}
}
/**
* Returns a <code>String</code> of the names of all the
* <code>Engine</code> states in the given <code>Engine</code>
* state.
*
* @param state the bitmask of states
*
* @return a <code>String</code> containing the names of all the
* states set in <code>state</code>
*/
protected String stateToString(long state) {
StringBuffer buf = new StringBuffer();
if ((state & Engine.DEALLOCATED) != 0)
buf.append(" DEALLOCATED ");
if ((state & Engine.ALLOCATING_RESOURCES) != 0)
buf.append(" ALLOCATING_RESOURCES ");
if ((state & Engine.ALLOCATED) != 0)
buf.append(" ALLOCATED ");
if ((state & Engine.DEALLOCATING_RESOURCES) != 0)
buf.append(" DEALLOCATING_RESOURCES ");
if ((state & Engine.PAUSED) != 0)
buf.append(" PAUSED ");
if ((state & Engine.RESUMED) != 0)
buf.append(" RESUMED ");
return buf.toString();
}
/**
* Dispatches a <code>SpeechEvent</code>.
* The dispatcher should notify all <code>EngineListeners</code>
* from this method. The <code>SpeechEvent</code> was added
* via the various post methods of this class.
*
* @param event the <code>SpeechEvent</code> to dispatch
*
* @see #postEngineAllocatingResources
* @see #postEngineAllocated
* @see #postEngineDeallocatingResources
* @see #postEngineDeallocated
* @see #postEnginePaused
* @see #postEngineResumed
*/
public void dispatchSpeechEvent(SpeechEvent event) {
switch (event.getId()) {
case EngineEvent.ENGINE_ALLOCATED:
fireEngineAllocated((EngineEvent) event);
break;
case EngineEvent.ENGINE_ALLOCATING_RESOURCES:
fireEngineAllocatingResources((EngineEvent) event);
break;
case EngineEvent.ENGINE_DEALLOCATED:
fireEngineDeallocated((EngineEvent) event);
break;
case EngineEvent.ENGINE_DEALLOCATING_RESOURCES:
fireEngineDeallocatingResources((EngineEvent) event);
break;
//case EngineEvent.ENGINE_ERROR:
//fireEngineError((EngineErrorEvent) event);
//break;
case EngineEvent.ENGINE_PAUSED:
fireEnginePaused((EngineEvent) event);
break;
case EngineEvent.ENGINE_RESUMED:
fireEngineResumed((EngineEvent) event);
break;
}
}
/**
* Returns the engine name and mode for debug purposes.
*
* @return the engine name and mode.
*/
public String toString() {
return getEngineModeDesc().getEngineName() +
":" + getEngineModeDesc().getModeName();
}
}