/*
* @(#)BasicController.java 1.39 02/08/21
*
* Copyright (c) 1996-2002 Sun Microsystems, Inc. All rights reserved.
*/
package com.sun.media;
import java.security.*;
import java.lang.reflect.Method;
import java.lang.reflect.Constructor;
import java.util.Vector;
import java.util.Enumeration;
import java.util.EventListener;
import javax.media.*;
import com.sun.media.util.*;
import com.ms.security.PermissionID;
import com.ms.security.PolicyEngine;
/**
* Media Controller implements the basic functionalities of a
* java.media.Controller. These include:<
* <ul>
* <li> The clock calculations using the BasicClock helper class.
* <li> The RealizeWorkThread and PrefetchWorkThread to implement realize() and
* prefetch() in the correct unblocking manner.
* <li> The ListenerList to maintain the list of ControllerListener.
* <li> Two ThreadedEventQueues for incoming and outgoing ControllerEvents.
* </ul><p>
* @version 1.9, 98/11/19
*/
public abstract class BasicController implements Controller, Duration {
private int targetState = Unrealized;
protected int state = Unrealized;
private Vector listenerList = null;
private SendEventQueue sendEvtQueue;
private ConfigureWorkThread configureThread = null;
private RealizeWorkThread realizeThread = null;
private PrefetchWorkThread prefetchThread = null;
protected String processError = null;
private Clock clock; // Use the BasicClock to keep track of time
// and for some calculations.
private TimedStartThread startThread = null;
private StopTimeThread stopTimeThread = null;
private boolean interrupted = false;
private Object interruptSync = new Object();
final static int Configuring = Processor.Configuring;
final static int Configured = Processor.Configured;
private static JMFSecurity jmfSecurity = null;
private static boolean securityPrivelege=false;
private Method m[] = new Method[1];
private Class cl[] = new Class[1];
private Object args[][] = new Object[1][0];
protected boolean stopThreadEnabled = true;
static {
try {
jmfSecurity = JMFSecurityManager.getJMFSecurity();
securityPrivelege = true;
} catch (SecurityException e) {
}
}
public BasicController() {
if ( /*securityPrivelege &&*/ (jmfSecurity != null) ) {
String permission = null;
try {
if (jmfSecurity.getName().startsWith("jmf-security")) {
permission = "thread";
jmfSecurity.requestPermission(m, cl, args, JMFSecurity.THREAD);
m[0].invoke(cl[0], args[0]);
permission = "thread group";
jmfSecurity.requestPermission(m, cl, args, JMFSecurity.THREAD_GROUP);
m[0].invoke(cl[0], args[0]);
} else if (jmfSecurity.getName().startsWith("internet")) {
PolicyEngine.checkPermission(PermissionID.THREAD);
PolicyEngine.assertPermission(PermissionID.THREAD);
}
} catch (Throwable e) {
if (JMFSecurityManager.DEBUG) {
System.err.println("Unable to get " + permission +
" privilege " + e);
}
securityPrivelege = false;
// TODO: Do the right thing if permissions cannot be obtained.
// User should be notified via an event
}
}
if ( (jmfSecurity != null) && (jmfSecurity.getName().startsWith("jdk12"))) {
try {
Constructor cons = CreateWorkThreadAction.cons;
sendEvtQueue = (SendEventQueue) jdk12.doPrivM.invoke(
jdk12.ac,
new Object[] {
cons.newInstance(
new Object[] {
SendEventQueue.class,
BasicController.class,
this
})});
sendEvtQueue.setName(sendEvtQueue.getName() + ": SendEventQueue: " +
this.getClass().getName());
sendEvtQueue.start();
clock = new BasicClock();
} catch (Exception e) {
}
} else {
sendEvtQueue = new SendEventQueue(this);
sendEvtQueue.setName(sendEvtQueue.getName() + ": SendEventQueue: " +
this.getClass().getName());
sendEvtQueue.start();
clock = new BasicClock();
}
}
/**
* Subclass should define this.
* If this return true, the controller will go through the Configured
* state. For example, the Controller implementing the Processor
* should return true.
*/
protected abstract boolean isConfigurable();
/**
* Subclass can use this to switch to a different default clock.
*/
protected void setClock(Clock c) {
clock = c;
}
protected Clock getClock() {
return clock;
}
/**
* Interrupt the process.
*/
protected void interrupt() {
synchronized (interruptSync) {
interrupted = true;
interruptSync.notify();
}
}
/**
* Reset the interrupted flag.
*/
protected void resetInterrupt() {
synchronized (interruptSync) {
interrupted = false;
interruptSync.notify();
}
}
/**
* Return true if the process is interrupted.
*/
protected boolean isInterrupted() {
return interrupted;
}
/**
* The stub function to perform the steps to configure the controller.
* Call configure() for the public method.
*/
protected boolean doConfigure() {return true;}
/**
* Called when the configure() is aborted, i.e. deallocate() was called
* while realizing. Release all resources claimed previously by the
* configure() call.
* Override this to implement subclass behavior.
*/
protected void abortConfigure() {}
/**
* The stub function to perform the steps to realize the controller.
* Call realize() for the public method.
* This is called from a separately running thread. So do take
* necessary precautions to protect shared resources.
* It's OK to put an empty stub function body here.<p>
* Return true if the realize is successful. Return false and
* set the processError string if failed.<p>
* This function is not declared synchronized because first it is
* already guaranteed by realize() not to be called more than once
* simultaneously. Secondly if this is synchronized, then other
* synchronized methods, deallocate() and processEvent() will be
* blocked since they are synchronized methods.
* Override this to implement subclass behavior.
* @return true if successful.
*/
protected abstract boolean doRealize();
/**
* Called when the realize() is aborted, i.e. deallocate() was called
* while realizing. Release all resources claimed previously by the
* realize() call.
* Override this to implement subclass behavior.
*/
protected abstract void abortRealize();
/**
* The stub function to perform the steps to prefetch the controller.
* Call prefetch for the public method.
* This is called from a separately running thread. So do take
* necessary precautions to protect shared resources.
* It's OK to put an empty stub function body here.<p>
* Return true if the prefetch is successful. Return false and
* set the processError string if failed.<p>
* This function is not declared synchronized because first it is
* already guaranteed by realize() not to be called more than once
* simultaneously. Secondly if this is synchronized, then other
* synchronized methods, deallocate() and processEvent() will be
* blocked since they are synchronized methods.
* Override this to implement subclass behavior.
* @return true if successful.
*/
protected abstract boolean doPrefetch();
/**
* Called when the prefetch() is aborted, i.e. deallocate() was called
* while prefetching. Release all resources claimed previously by the
* prefetch call.
* Override this to implement subclass behavior.
*/
protected abstract void abortPrefetch();
/**
* Start immediately.
* Invoked from start(tbt) when the scheduled start time is reached.
* Use the public start(tbt) method for the public interface.
* Override this to implement subclass behavior.
*/
protected abstract void doStart();
/**
* Invoked from stop().
* Override this to implement subclass behavior.
*/
protected void doStop() {}
/**
* A subclass of this implement close to stop all threads to make
* it "finalizable", i.e., ready to be garbage collected.
*/
final public void close() {
doClose();
// Interrupt the controller. If it's in any of the state
// transition threads, the threads will be interrupted and
// that transition will be aborted.
interrupt();
if (startThread != null)
startThread.abort();
if (stopTimeThread != null)
stopTimeThread.abort();
if (sendEvtQueue != null) {
sendEvtQueue.kill();
sendEvtQueue = null;
}
}
/**
* Invoked by close() to cleanup the Controller.
* Override this to implement subclass behavior.
*/
protected void doClose() {
}
/**
* Set the timebase used by the controller.
* i.e. all media-time to time-base-time will be computed with the
* given time base.
* The subclass should implement and further specialized this method
* to do the real work. But it should also invoke this method to
* maintain the correct states.
* @param tb the time base to set to.
* @exception IncompatibleTimeBaseException is thrown if the Controller
* cannot accept the given time base.
*/
static String TimeBaseError = "Cannot set time base on an unrealized controller.";
public void setTimeBase(TimeBase tb) throws IncompatibleTimeBaseException {
if (state < Realized) {
throwError(new NotRealizedError(TimeBaseError));
}
clock.setTimeBase(tb);
}
/**
* Return a list of <b>Control</b> objects this <b>Controller</b>
* supports.
* If there are no controls, then an array of length zero
* is returned.
*
* @return list of <b>Controller</b> controls.
*/
public Control[] getControls() {
// Not implemented $$$
// Is this correct ? $$$
return new Control[0];
}
/**
* Get the <code>Control</code> that supports the
* class or interface specified. The full class
* or interface name should be specified.
* <code>Null</code> is returned if the <code>Control</code>
* is not supported.
*
* @return <code>Control</code> for the class or interface
* name.
*/
public Control getControl(String type) {
Class cls;
try {
cls = Class.forName(type);
} catch (ClassNotFoundException e) {
return null;
}
Control cs[] = getControls();
for (int i = 0; i < cs.length; i++) {
if (cls.isInstance(cs[i]))
return cs[i];
}
return null;
}
/**
* Start the controller.
* Invoke clock.start() to maintain the clock states.
* It starts a wait thread to wait for the given tbt.
* At tbt, it will wake up and call doStart().
* A subclass should implement the doStart() method to do
* the real work.
* @param tbt the timebase time to start the controller.
*/
static String SyncStartError = "Cannot start the controller before it has been prefetched.";
public void syncStart(final Time tbt) {
if (state < Prefetched) {
throwError(new NotPrefetchedError(SyncStartError));
}
clock.syncStart(tbt); // Will generate ClockStartedError if state is Started
state = Started;
setTargetState(Started);
sendEvent(new StartEvent(this, Prefetched, Started,
Started, getMediaTime(), tbt));
long timeToStop;
if ((timeToStop = checkStopTime()) < 0 ||
(stopThreadEnabled && activateStopThread(timeToStop))) {
// If the stop-time is set to a value that the Clock
// has already passed, the Clock immediately stops.
stopAtTime();
return;
}
// Schedule the start time.
// startThread will wake up at the scheduled tbt and call the
// protected doStart() method.
if ( /*securityPrivelege && */ (jmfSecurity != null) ) {
String permission = null;
try {
if (jmfSecurity.getName().startsWith("jmf-security")) {
permission = "thread";
jmfSecurity.requestPermission(m, cl, args, JMFSecurity.THREAD);
m[0].invoke(cl[0], args[0]);
permission = "thread group";
jmfSecurity.requestPermission(m, cl, args, JMFSecurity.THREAD_GROUP);
m[0].invoke(cl[0], args[0]);
} else if (jmfSecurity.getName().startsWith("internet")) {
PolicyEngine.checkPermission(PermissionID.THREAD);
PolicyEngine.assertPermission(PermissionID.THREAD);
}
} catch (Throwable e) {
if (JMFSecurityManager.DEBUG) {
System.err.println("Unable to get " + permission +
" privilege " + e);
}
securityPrivelege = false;
// TODO: Do the right thing if permissions cannot be obtained.
// User should be notified via an event
}
}
if ( (jmfSecurity != null) && (jmfSecurity.getName().startsWith("jdk12"))) {
try {
Constructor cons = CreateTimedThreadAction.cons;
startThread = (TimedStartThread) jdk12.doPrivM.invoke(
jdk12.ac,
new Object[] {
cons.newInstance(
new Object[] {
TimedStartThread.class,
BasicController.class,
this,
new Long(tbt.getNanoseconds()),
})});
startThread.setName(startThread.getName() + " ( startThread: " + this + " )");
startThread.start();
} catch (Exception e) {
}
} else {
startThread = new TimedStartThread(this, tbt.getNanoseconds());
startThread.setName(startThread.getName() + " ( startThread: " + this + " )");
startThread.start();
}
}
protected boolean syncStartInProgress() {
return (startThread != null && startThread.isAlive());
}
// Return timeToStop
private long checkStopTime() {
long stopTime = getStopTime().getNanoseconds();
if (stopTime == Long.MAX_VALUE)
return 1;
return (long) ((stopTime - getMediaTime().getNanoseconds()) / getRate());
}
// Return true if the stop time has already passed
private boolean activateStopThread(long timeToStop) {
if (getStopTime().getNanoseconds() == Long.MAX_VALUE)
return false;
if (stopTimeThread != null && stopTimeThread.isAlive()) {
stopTimeThread.abort();
stopTimeThread = null;
}
if (timeToStop > 100000000) {
if ( /*securityPrivelege &&*/ (jmfSecurity != null) ) {
String permission = null;
try {
if (jmfSecurity.getName().startsWith("jmf-security")) {
permission = "thread";
jmfSecurity.requestPermission(m, cl, args, JMFSecurity.THREAD);
m[0].invoke(cl[0], args[0]);
permission = "thread group";
jmfSecurity.requestPermission(m, cl, args, JMFSecurity.THREAD_GROUP);
m[0].invoke(cl[0], args[0]);
} else if (jmfSecurity.getName().startsWith("internet")) {
PolicyEngine.checkPermission(PermissionID.THREAD);
PolicyEngine.assertPermission(PermissionID.THREAD);
}
} catch (Throwable e) {
if (JMFSecurityManager.DEBUG) {
System.err.println("Unable to get " + permission +
" privilege " + e);
}
securityPrivelege = false;
// TODO: Do the right thing if permissions cannot be obtained.
// User should be notified via an event
}
}
if ( (jmfSecurity != null) && (jmfSecurity.getName().startsWith("jdk12"))) {
try {
Constructor cons = CreateTimedThreadAction.cons;
stopTimeThread = (StopTimeThread) jdk12.doPrivM.invoke(
jdk12.ac,
new Object[] {
cons.newInstance(
new Object[] {
StopTimeThread.class,
BasicController.class,
this,
new Long(timeToStop),
})});
stopTimeThread.start();
} catch (Exception e) { }
} else {
(stopTimeThread = new StopTimeThread(this, timeToStop)).start();
}
return false;
} else {
return true;
}
}
/**
* Stop the controller.
* Invoke clock.stop() to maintain the clock states.
* The subclass should implement and further specialized this method
* to do the real work. But it should also invoke this method to
* maintain the correct states.
*/
public void stop() {
if (state == Started || state == Prefetching) {
stopControllerOnly();
doStop();
}
}
/**
* Stop the controller.
* Invoke clock.stop() to maintain the clock states.
* The subclass should implement and further specialized this method
* to do the real work. But it should also invoke this method to
* maintain the correct states.
*/
protected void stopControllerOnly() {
if (state == Started || state == Prefetching) {
clock.stop();
state = Prefetched;
setTargetState(Prefetched);
if ( stopTimeThread != null && stopTimeThread.isAlive() &&
Thread.currentThread() != stopTimeThread ) {
stopTimeThread.abort();
}
// If start(tbt) was invoked and it hasn't reached the
// scheduled tbt yet, the startThread is spinning and
// we need to abort that.
if (startThread != null && startThread.isAlive())
startThread.abort();
}
}
/**
* Stop because stop time has been reached.
* Subclasses should override this method.
*/
protected void stopAtTime() {
stop();
setStopTime(Clock.RESET);
sendEvent(new StopAtTimeEvent(this, Started,
Prefetched,
getTargetState(),
getMediaTime()));
}
/**
* Set the stop time.
* Invoke clock.setStopTime() to maintain the clock states.
* The subclass should implement and further specialized this method
* to do the real work. But it should also invoke this method to
* maintain the correct states.
* @param t the time to stop.
*/
static String StopTimeError = "Cannot set stop time on an unrealized controller.";
public void setStopTime(Time t) {
if (state < Realized) {
throwError(new NotRealizedError(StopTimeError));
}
Time oldStopTime = getStopTime();
clock.setStopTime(t);
boolean stopTimeHasPassed = false;
if (state == Started) {
long timeToStop;
if (((timeToStop = checkStopTime()) < 0) ||
(stopThreadEnabled && activateStopThread(timeToStop)))
stopTimeHasPassed = true;
}
if ( oldStopTime.getNanoseconds() != t.getNanoseconds() ) {
sendEvent(new StopTimeChangeEvent(this, t));
}
if (stopTimeHasPassed) {
stopAtTime();
}
}
/**
* Get the preset stop time.
* @return the preset stop time.
*/
public Time getStopTime() {
return clock.getStopTime();
}
/**
* Set the media time.
* Invoke clock.setMediaTime() to maintain the clock states.
* The subclass should implement and further specialized this method
* to do the real work. But it should also invoke this method to
* maintain the correct states.
* @param now the media time to set to.
*/
static String MediaTimeError = "Cannot set media time on a unrealized controller";
public void setMediaTime(Time when) {
if (state < Realized) {
throwError(new NotRealizedError(MediaTimeError));
}
clock.setMediaTime(when);
doSetMediaTime(when);
sendEvent(new MediaTimeSetEvent(this, when));
}
protected void doSetMediaTime(Time when) {
}
/**
* Return the current media time.
* Uses the clock to do the computation. A subclass can override this
* method to do the right thing for itself.
* @return the current media time.
*/
public Time getMediaTime() {
return clock.getMediaTime();
}
/**
* Get the current media time in nanoseconds.
* @return the media time in nanoseconds.
*/
public long getMediaNanoseconds() {
return clock.getMediaNanoseconds();
}
/**
* Return the Sync Time.
* Not yet implementated.
**/
public Time getSyncTime() {
return new Time(0);
}
/**
* Get the current time base.
* @return the current time base.
*/
static String GetTimeBaseError = "Cannot get Time Base from an unrealized controller";
public TimeBase getTimeBase() {
if (state < Realized) {
throwError(new NotRealizedError(GetTimeBaseError));
}
return clock.getTimeBase();
}
/**
* Map the given media-time to time-base-time.
* @param t given media time.
* @return timebase time.
* @exception ClockStoppedException thrown if the Controller has already
* been stopped.
*/
public Time mapToTimeBase(Time t) throws ClockStoppedException {
return clock.mapToTimeBase(t);
}
/**
* Set the rate of presentation: 1.0: normal, 2.0: twice the speed.
* -2.0: twice the speed in reverse.
* Note that not all rates are supported.
* Invokes clock.setRate() to maintain the clock states.
* The subclass SHOULDN'T in general override this class.
* If necessary, it should override the behavior using the
* doSetRate method. By overriding the doSetRate method,
* subclass Conrollers can support negative rates, fractional rates
* etc., but they should guard against illegal rates from going into
* the clock calculations.
* @param factor the rate to set to.
* @return the actual rate used.
*/
static String SetRateError = "Cannot set rate on an unrealized controller.";
public float setRate(float factor) {
if (state < Realized) {
throwError(new NotRealizedError(SetRateError));
}
float oldRate = getRate();
float rateSet = doSetRate(factor);
float newRate = clock.setRate(rateSet);
if (newRate != oldRate) {
sendEvent(new RateChangeEvent(this, newRate));
}
return newRate;
}
// Override this to implement subclass behavior.
// Conrollers can override this method if they
// can support negative rates, and/or have other
// other restrictions.
protected float doSetRate(float factor) {
return factor;
}
/**
* Get the current presentation speed.
* @return the current presentation speed.
*/
public float getRate() {
return clock.getRate();
}
/**
* Get the current state of the controller.
* @return the current state of the controller.
*/
final public int getState() {
return state;
}
/**
* Set the target state.
*/
final protected /*synchronized*/ void setTargetState(int state) {
targetState = state;
}
/**
* Get the current target state.
* @return the current target state.
*/
final public int getTargetState() {
return targetState;
}
/**
* Returns the start latency.
* Don't know until the particular node is implemented.
* @return the start latency.
*/
static String LatencyError = "Cannot get start latency from an unrealized controller";
public Time getStartLatency() {
if (state < Realized) {
throwError(new NotRealizedError(LatencyError));
}
return LATENCY_UNKNOWN;
}
/**
* Return the duration of the media.
* It's unknown until we implement a particular node.
* @return the duration of the media.
*/
public Time getDuration() {
return Duration.DURATION_UNKNOWN;
}
protected void setMediaLength(long t) {
if (clock instanceof BasicClock)
((BasicClock)clock).setMediaLength(t);
}
public synchronized void configure() {
if (getTargetState() < Configured)
setTargetState(Configured);
switch(state) {
case Configured:
case Realizing:
case Realized:
case Prefetching:
case Prefetched:
case Started:
sendEvent(new ConfigureCompleteEvent(this, state, state, getTargetState()));
break;
case Configuring:
break;
case Unrealized:
state = Configuring;
sendEvent(new TransitionEvent(this, Unrealized, Configuring, getTargetState()));
if ( /*securityPrivelege &&*/ (jmfSecurity != null) ) {
String permission = null;
try {
if (jmfSecurity.getName().startsWith("jmf-security")) {
permission = "thread";
jmfSecurity.requestPermission(m, cl, args, JMFSecurity.THREAD);
m[0].invoke(cl[0], args[0]);
permission = "thread group";
jmfSecurity.requestPermission(m, cl, args, JMFSecurity.THREAD_GROUP);
m[0].invoke(cl[0], args[0]);
} else if (jmfSecurity.getName().startsWith("internet")) {
PolicyEngine.checkPermission(PermissionID.THREAD);
PolicyEngine.assertPermission(PermissionID.THREAD);
}
} catch (Throwable e) {
if (JMFSecurityManager.DEBUG) {
System.err.println("Unable to get " + permission +
" privilege " + e);
}
securityPrivelege = false;
// TODO: Do the right thing if permissions cannot be obtained.
// User should be notified via an event
}
}
if ( (jmfSecurity != null) && (jmfSecurity.getName().startsWith("jdk12"))) {
try {
Constructor cons = CreateWorkThreadAction.cons;
configureThread = (ConfigureWorkThread) jdk12.doPrivM.invoke(
jdk12.ac,
new Object[] {
cons.newInstance(
new Object[] {
ConfigureWorkThread.class,
BasicController.class,
this
})});
configureThread.setName(configureThread.getName() +
"[ " + this + " ]" +
" ( configureThread)");
configureThread.start();
} catch (Exception e) {
}
} else {
configureThread = new ConfigureWorkThread(this);
configureThread.setName(configureThread.getName() +
"[ " + this + " ]" +
" ( configureThread)");
configureThread.start();
}
}
}
/**
* Called when the controller is realized and when all the
* ConfigureCompleteEvents from down stream Controllers have been received.
* If a subclass wants to override this method, it should still
* invoke this to ensure the correct events being sent to the
* upstream Controllers.
*/
protected synchronized void completeConfigure() {
state = Configured;
sendEvent(new ConfigureCompleteEvent(this, Configuring, Configured, getTargetState()));
if (getTargetState() >= Realized) {
realize();
}
}
/**
* Called when realize() has failed.
*/
protected void doFailedConfigure() {
state = Unrealized;
setTargetState(Unrealized);
String msg = "Failed to configure";
if (processError != null)
msg = msg + ": " + processError;
sendEvent(new ResourceUnavailableEvent(this, msg));
processError = null;
}
/**
* Take the necessary steps to realize the controller.
* This is a non-blocking call. It starts a work thread to do the
* real work. The actual code to do the realizing should be written
* in doRealize(). The thread is also responsible for catching all
* the RealizeCompleteEvents from the down stream nodes. When the
* steps to realize the controller are completed and when all the
* RealizeCompleteEvents from down stream nodes have been received,
* the completeRealize() call will be invoked.
*/
public final synchronized void realize() {
if (getTargetState() < Realized)
setTargetState(Realized);
switch (state) {
case Realized:
case Prefetching:
case Prefetched:
case Started:
sendEvent(new RealizeCompleteEvent(this, state, state, getTargetState()));
break;
case Realizing:
case Configuring:
break; // $$ Nothing is done. Two realize() will result in one event
case Unrealized:
// For processors, we'll implicitly call configure.
if (isConfigurable()) {
configure();
break;
}
case Configured:
// Put it in the realizing state.
int oldState = state;
state = Realizing;
sendEvent(new TransitionEvent(this, oldState, Realizing, getTargetState()));
// Start the realize thread for this controller.
if ( /*securityPrivelege &&*/ (jmfSecurity != null) ) {
String permission = null;
try {
if (jmfSecurity.getName().startsWith("jmf-security")) {
permission = "thread";
jmfSecurity.requestPermission(m, cl, args, JMFSecurity.THREAD);
m[0].invoke(cl[0], args[0]);
permission = "thread group";
jmfSecurity.requestPermission(m, cl, args, JMFSecurity.THREAD_GROUP);
m[0].invoke(cl[0], args[0]);
} else if (jmfSecurity.getName().startsWith("internet")) {
PolicyEngine.checkPermission(PermissionID.THREAD);
PolicyEngine.assertPermission(PermissionID.THREAD);
}
} catch (Throwable e) {
if (JMFSecurityManager.DEBUG) {
System.err.println("Unable to get " + permission +
" privilege " + e);
}
securityPrivelege = false;
// TODO: Do the right thing if permissions cannot be obtained.
// User should be notified via an event
}
}
if ( (jmfSecurity != null) && (jmfSecurity.getName().startsWith("jdk12"))) {
try {
Constructor cons = CreateWorkThreadAction.cons;
realizeThread = (RealizeWorkThread) jdk12.doPrivM.invoke(
jdk12.ac,
new Object[] {
cons.newInstance(
new Object[] {
RealizeWorkThread.class,
BasicController.class,
this
})});
realizeThread.setName(realizeThread.getName() +
"[ " + this + " ]" +
" ( realizeThread)");
realizeThread.start();
} catch (Exception e) {
}
} else {
realizeThread = new RealizeWorkThread(this);
realizeThread.setName(realizeThread.getName() +
"[ " + this + " ]" +
" ( realizeThread)");
realizeThread.start();
}
break;
}
}
/**
* Called when the controller is realized and when all the
* RealizeCompleteEvents from down stream Controllers have been received.
* If a subclass wants to override this method, it should still
* invoke this to ensure the correct events being sent to the
* upstream Controllers.
*/
protected synchronized void completeRealize() {
// Send back the events to whoever is listening, most likely the
// upstream nodes.
state = Realized;
sendEvent(new RealizeCompleteEvent(this, Realizing, Realized, getTargetState()));
if (getTargetState() >= Prefetched) {
prefetch();
}
}
/**
* Called when realize() has failed.
*/
protected void doFailedRealize() {
state = Unrealized;
setTargetState(Unrealized);
String msg = "Failed to realize";
if (processError != null)
msg = msg + ": " + processError;
sendEvent(new ResourceUnavailableEvent(this, msg));
processError = null;
}
/**
* Take the necessary steps to prefetch the controller.
* This is a non-blocking call. It starts a work thread to do the
* real work. The actual code to do the realizing should be written
* in doPrefetch(). The thread is also responsible for catching all
* the PrefetchCompleteEvents from the down stream nodes. When the
* steps to prefetch the controller are completed and when all the
* PrefetchCompleteEvents from down stream nodes have been received,
* the completePrefetch() call will be invoked.
*/
public final /*synchronized*/ void prefetch() {
if (getTargetState() <= Realized)
setTargetState(Prefetched);
switch (state) {
case Prefetched:
case Started:
sendEvent(new PrefetchCompleteEvent(this, state, state, getTargetState()));
break;
case Configuring:
case Realizing:
case Prefetching:
break; // $$ Nothing is done.
case Unrealized:
case Configured:
// The controller is not realized yet, we have to implicitly
// carry out a realize().
realize();
break;
case Realized:
// Put it in the prefetching state.
// synchronized(this) {
state = Prefetching;
sendEvent(new TransitionEvent(this, Realized, Prefetching, getTargetState()));
// Start the prefetch thread for this controller.
if ( /*securityPrivelege && */ (jmfSecurity != null) ) {
String permission = null;
try {
if (jmfSecurity.getName().startsWith("jmf-security")) {
permission = "thread";
jmfSecurity.requestPermission(m, cl, args, JMFSecurity.THREAD);
m[0].invoke(cl[0], args[0]);
permission = "thread group";
jmfSecurity.requestPermission(m, cl, args, JMFSecurity.THREAD_GROUP);
m[0].invoke(cl[0], args[0]);
} else if (jmfSecurity.getName().startsWith("internet")) {
PolicyEngine.checkPermission(PermissionID.THREAD);
PolicyEngine.assertPermission(PermissionID.THREAD);
}
} catch (Throwable e) {
if (JMFSecurityManager.DEBUG) {
System.err.println("Unable to get " + permission +
" privilege " + e);
}
securityPrivelege = false;
// TODO: Do the right thing if permissions cannot be obtained.
// User should be notified via an event
}
}
if ( (jmfSecurity != null) && (jmfSecurity.getName().startsWith("jdk12"))) {
try {
Constructor cons = CreateWorkThreadAction.cons;
prefetchThread = (PrefetchWorkThread) jdk12.doPrivM.invoke(
jdk12.ac,
new Object[] {
cons.newInstance(
new Object[] {
PrefetchWorkThread.class,
BasicController.class,
this
})});
prefetchThread.setName(prefetchThread.getName() +
"[ " + this + " ]" +
" ( prefetchThread)");
prefetchThread.start();
} catch (Exception e) {
}
} else {
prefetchThread = new PrefetchWorkThread(this);
prefetchThread.setName(prefetchThread.getName() + " ( prefetchThread)");
prefetchThread.start();
}
// }
break;
}
}
/**
* Called when the controller is prefetched and when all the
* PrefetchCompleteEvents from down stream nodes have been received.
* If a subclass wants to override this method, it should still
* invoke this to ensure the correct events being sent to the
* upstream Controllers.
*/
protected /*synchronized*/ void completePrefetch() {
// Send back the events to whoever is listening, most likely the
// upstream nodes.
clock.stop();
state = Prefetched;
sendEvent(new PrefetchCompleteEvent(this, Prefetching, Prefetched, getTargetState()));
}
/**
* Called when the prefetch() has failed.
*/
protected void doFailedPrefetch() {
state = Realized;
setTargetState(Realized);
String msg = "Failed to prefetch";
if (processError != null)
msg = msg + ": " + processError;
sendEvent(new ResourceUnavailableEvent(this, msg));
processError = null;
}
/**
* Release the resouces held by the controller.
* It obeys strict rules as specified in the spec. Implement the
* abortRealize and abortPrefetch methods to actually do the work.
*
* This is a blocking call. It returns only when the Controller
* has done deallocating the resources. This should be called from
* an external thread outside of the controller. Take caution if this
* is being call from inside the Controller. It may cause deadlock.
*/
static String DeallocateError = "deallocate cannot be used on a started controller.";
final public void deallocate() {
int previousState = getState();
// It's illegal to use deallocate on a started controller.
if (state == Started) {
throwError(new ClockStartedError(DeallocateError));
}
// stop the thread even if isAlive() is false as
// we want to kill the thread that has been created
// but not scheduled to run
switch (state) {
case Configuring:
case Realizing:
interrupt();
state = Unrealized;
break;
case Prefetching:
interrupt();
state = Realized;
break;
case Prefetched:
abortPrefetch();
state = Realized;
resetInterrupt();
break;
}
setTargetState(state);
// Use by subclass to add in its own behavior.
doDeallocate();
// Wait for the interrupt to take effect.
synchronized (interruptSync) {
while (isInterrupted()) {
try {
interruptSync.wait();
} catch (InterruptedException e) {}
}
}
sendEvent(new DeallocateEvent(this, previousState, state,
state, getMediaTime()));
}
/**
* Called by deallocate().
* Subclasses should implement this for its specific behavior.
*/
protected void doDeallocate() {
}
/**
* Add a listener to the listenerList. This listener will be notified
* of this controller's event.
* This needs to be a synchronized method so as to maintain the integrity
* of the listenerList.
*/
final public void addControllerListener(ControllerListener listener) {
if (listenerList == null) {
listenerList = new Vector();
}
synchronized (listenerList) {
if (!listenerList.contains(listener)) {
listenerList.addElement(listener);
}
}
}
/**
* Remove a listener from the listener list. The listener will stop
* receiving notification from this controller.
* This needs to be a synchronized method so as to maintain the integrity
* of the listenerList.
*/
final public void removeControllerListener(ControllerListener listener) {
if (listenerList == null)
return;
synchronized (listenerList) {
if (listenerList != null) {
listenerList.removeElement(listener);
}
}
}
/**
* Send an event to the listeners listening to my events.
* The event is Queued in the sendEvtQueue which runs in a
* separate thread. This way, sendEvent() won't be blocked.
*/
final protected void sendEvent(ControllerEvent evt) {
if (sendEvtQueue != null)
sendEvtQueue.postEvent(evt);
}
/**
* An internal function to notify the listeners on the listener list
* the given event. This gets called by the sendEvtQueue's processEvent()
* callback.
* This method updates a lock on the Vector listenerList before
* enumerating it.
*/
final protected void dispatchEvent(ControllerEvent evt) {
if (listenerList == null)
return;
synchronized(listenerList) {
Enumeration list = listenerList.elements();
while (list.hasMoreElements()) {
ControllerListener listener = (ControllerListener)list.nextElement();
listener.controllerUpdate(evt);
}
}
}
protected void throwError(Error e) {
Log.dumpStack(e);
throw e;
}
}
/**
* The event queue to post outgoing events for the BasicController.
* The queue is running is a separate thread.
* This is so the posting of the events won't block the controller.
*/
class SendEventQueue extends ThreadedEventQueue {
private BasicController controller;
public SendEventQueue(BasicController c) {
controller = c;
}
/**
* Callback from the thread when there is an event to be processed.
* In this case, we call controller's dispatchEvent() to send the
* event to the listening controllers.
*/
public void processEvent(ControllerEvent evt) {
controller.dispatchEvent(evt);
}
}
/**
* An execution thread to take care of processing and waiting of
* completion events for BasicController.
* Subclass this to build PrefetchWork thread for example.
*/
abstract class StateTransitionWorkThread extends MediaThread {
BasicController controller;
Vector eventQueue = new Vector();
boolean allEventsArrived = false;
StateTransitionWorkThread() {
useControlPriority();
}
/**
* Implement this to do the real work.
*/
protected abstract boolean process();
/**
* This will be invoked when everything is ready.
* i.e., the processing is completed and all the events from down
* stream nodes have been fully captured.
*/
protected abstract void completed();
/**
* Called if the processing failed.
*/
protected abstract void failed();
/**
* Called if the processing is aborted in the middle.
*/
protected abstract void aborted();
/**
* run() method for the thread.
* Do the work, wait for every expected events to arrive, signal the
* the completion.
*/
public void run() {
controller.resetInterrupt();
try {
boolean success = process();
if (controller.isInterrupted())
aborted();
else if (success)
completed();
else
failed();
} catch (OutOfMemoryError e) {
System.err.println("Out of memory!");
}
controller.resetInterrupt();
}
}
/**
* A Thread to take care of realizing and catching of ConfigureCompleteEvents
* from down stream nodes.
*/
class ConfigureWorkThread extends StateTransitionWorkThread {
// Made this method on package private class public [for jdk1.2 security]
public ConfigureWorkThread(BasicController mc) {
controller = mc;
setName(getName() + ": " + mc);
}
protected boolean process() {
return controller.doConfigure();
}
protected void completed() {
controller.completeConfigure();
}
protected void aborted() {
controller.abortConfigure();
}
protected void failed() {
controller.doFailedConfigure();
}
}
/**
* A Thread to take care of realizing and catching of RealizeCompleteEvents
* from down stream nodes.
*/
class RealizeWorkThread extends StateTransitionWorkThread {
// Made this method on package private class public [for jdk1.2 security]
public RealizeWorkThread(BasicController mc) {
controller = mc;
setName(getName() + ": " + mc);
}
protected boolean process() {
return controller.doRealize();
}
protected void completed() {
controller.completeRealize();
}
protected void aborted() {
controller.abortRealize();
}
protected void failed() {
controller.doFailedRealize();
}
}
/**
* A Thread to take care of prefetching and catching of PrefetchCompleteEvents
* from down stream nodes.
*/
class PrefetchWorkThread extends StateTransitionWorkThread {
// Made this method on package private class public [for jdk1.2 security]
public PrefetchWorkThread(BasicController mc) {
controller = mc;
setName(getName() + ": " + mc);
}
protected boolean process() {
return controller.doPrefetch();
}
protected void completed() {
controller.completePrefetch();
}
protected void aborted() {
controller.abortPrefetch();
}
protected void failed() {
controller.doFailedPrefetch();
}
}
/**
* Start a countdown thread to perform an action at the specified time.
*/
abstract class TimedActionThread extends MediaThread {
protected BasicController controller;
protected long wakeupTime;
protected boolean aborted = false;
TimedActionThread(BasicController mc, long nanoseconds) {
controller = mc;
useControlPriority();
wakeupTime = nanoseconds;
}
protected abstract long getTime();
protected abstract void action();
public synchronized void abort() {
aborted = true;
notify();
}
public void run() {
long sleepTime, now;
while (true) {
now = getTime();
if (now >= wakeupTime || aborted)
break;
// Don't sleep more than 1 sec.
sleepTime = wakeupTime - now;
if (sleepTime > 1000000000L)
sleepTime = 1000000000L;
synchronized (this) {
try {
wait(sleepTime/1000000);
} catch (InterruptedException e) {
break;
}
}
}
if (!aborted)
action();
}
}
/**
* A Thread to schedule the start of the node.
*/
class TimedStartThread extends TimedActionThread {
// Made this method on package private class public [for jdk1.2 security]
public TimedStartThread(BasicController mc, long tbt) {
super(mc, tbt);
setName(getName() + ": TimedStartThread");
}
protected long getTime() {
return controller.getTimeBase().getNanoseconds();
}
protected void action() {
controller.doStart();
}
}
/**
* A thread to schedule the stop time.
*/
class StopTimeThread extends TimedActionThread {
// Made this method on package private class public [for jdk1.2 security]
public StopTimeThread(BasicController mc, long nanoseconds) {
super(mc, nanoseconds);
setName(getName() + ": StopTimeThread");
wakeupTime = getTime() + nanoseconds;
}
protected long getTime() {
return controller.getMediaNanoseconds();
}
protected void action() {
controller.stopAtTime();
}
}
|