gt:         if (currentState == Unrealized || currentState == Realizing)
context: package net.sf.fmj.ejmf.toolkit.media;
  
  import java.util.*;
  import java.util.logging.*;
  
  import javax.media.*;
  
  import net.sf.fmj.ejmf.toolkit.controls.*;
  import net.sf.fmj.media.Log;
  import net.sf.fmj.utility.*;
  
  /**
   * The AbstractController class provides a basic implementation of a
   * javax.media.Controller. Subclasses should implement the following abstract
   * "do" methods to transition their Controller:
   * <p>
   * <UL>
   * <LI>doRealize()</LI>
   * <LI>doPrefetch()</LI>
   * <LI>doSyncStart()</LI>
   * <LI>doDeallocate()</LI>
   * <LI>doStop()</LI>
   * </UL>
   * <p>
   * Follow these rules when implementing these methods:
   * <p>
   * <OL>
   * <LI>Do not return until the state change is complete. Once the state change
   * is complete, return ASAP.</LI>
   * <p>
   * <LI>Do not call one another. They will be called in the correct order at the
   * correct time.</LI>
   * <p>
   * <LI>Do not set the current or target states. They are set automatically.</LI>
   * <p>
   * <LI>Do not post any TransitionEvents. They are posted automatically.</LI>
   * <p>
   * <LI>Do not call any of the Clock routines. They will be called automatically.
   * </LI>
   * <p>
   * <LI>Return true if successful. If unsuccessful, post an appropriate
   * ControllerErrorEvent and return false.</LI>
   * <p>
   * <LI>When the end of the media has been reached, call endOfMedia(). This will
   * post an EndOfMediaEvent and set the appropriate states. Do not post an
   * EndOfMediaEvent in any other way.</LI>
   * </OL>
   * <p>
   * Other abstact methods that should be implemented are:
   * <p>
   * <UL>
   * <LI>doClose()</LI>
   * <LI>doSetMediaTime()</LI>
   * <LI>doSetRate()</LI>
   * </UL>
   *
   * <p>
   * From the book: Essential JMF, Gordon, Talley (ISBN 0130801046). Used with
   * permission.
   * </p>
   *
   * @see AbstractPlayer
   *
   * @author Steve Talley
   */
  
  public abstract class AbstractController extends AbstractClock implements
          Controller
  {
      private static final Logger logger = LoggerSingleton.logger;
  
      private int previousState;
      private int currentState = Unrealized;
      private int targetState;
  
      private StopTimeMonitor stopTimeMonitor;
  
      private ControllerEventQueue eventqueue;
  
      private ThreadQueue threadqueue;
      private Object threadqueueMutex = new Object();
  
      private Vector controls = new Vector();
      private Vector listeners = new Vector();
  
      /**
       * Construct a AbstractController.
       */
      public AbstractController()
      {
          super();
  
          eventqueue = new ControllerEventQueue(listeners,
                  "ControllerEventQueue for " + this);
          stopTimeMonitor = new StopTimeMonitor(this, "StopTimeMonitor for "
                  + this);
  
          // TODO: do not start in constructor
          // constructor sometimes gets called for a controller that will never be
          // used, and these threads just hang around.
          eventqueue.start();
          stopTimeMonitor.start();
  
          // mgodehardt: the threads get stopped in deallocate
          // threadqueue is created when used first thru getThreadQueue
          addControl(new RateControl(this));
      }
  
      // //////////////////////////////////////////////////////////
      //
      // Abstract methods
      //
      // //////////////////////////////////////////////////////////
  
      /**
       * Add a Control to this Controller.
       *
       * @param newControl
       *            The Control to add.
       */
      public void addControl(Control newControl)
      {
          synchronized (controls)
          {
              if (!controls.contains(newControl))
              {
                  controls.addElement(newControl);
              }
          }
      }
  
      /**
       * Specify a ControllerListener to which this Controller will send events.
       *
       * @param listener
       *            The listener to which the Controller will post events.
       */
      @Override
      public void addControllerListener(ControllerListener listener)
      {
          synchronized (listeners)
          {
              if (!listeners.contains(listener))
              {
                  listeners.addElement(listener);
              }
          }
      }
  
      /**
       * For a given time-base start time, block until the AbstractController
       * should be started. For a given time- base start time (t), this method
       * will get the AbstractController's start latency (l) and block until (t -
       * l). This method is useful for implementations of doSyncStart().
       * <p>
       * If the time-base time (t - l) has already passed, return immediately.
       */
      public void blockUntilStart(Time t)
      {
          // Calculate start latency. If unknown, asssume zero.
          Time latencyTime = getStartLatency();
          long latency;
  
          if (latencyTime == LATENCY_UNKNOWN)
          {
              latency = 0;
          } else
          {
              latency = latencyTime.getNanoseconds();
          }
  
          long start = t.getNanoseconds();
          long now = getTimeBase().getNanoseconds();
          long delay = (start - latency - now) / 1000000;
  
          // Wait for the syncTime
  
          if (delay > 0)
          {
              try
              {
                  Thread.sleep(delay);
              } catch (InterruptedException e)
              {
              }
          }
      }
  
      /**
       * Close the Controller. Release resources held by this Controller.
       * Subclasses should implement doClose() to add additional functionality.
       */
      @Override
      public synchronized final void close()
      {
          // Stop the Controller in case it is Started
          stop();
  
          // Call implementation-specific functionality
          doClose();
  
          // Set some resources to null
          controls = null;
  
          // Post a ControllerClosedEvent
          postControllerClosedEvent();
      }
  
      /**
       * Deallocate Controller on current thread. Subclasses should override
       * doDeallocate() to do the actual work to transition the Controller. After
       * ensuring state prerequisites, this method will call doDeallocate(). If
       * doDeallocate() returns true, then the Controller is placed in the
       * appropriate state and a DeallocateCompleteEvent is posted. Otherwise, it
       * is assumed that the controller has posted a ControllerErrorEvent
       * detailing the reasons for it's failure.
       * <p>
       * Synchronous method -- return when transition complete
       */
      @Override
      public final synchronized void deallocate()
      {
          int state;
  
          // Enforce state prereq
          if (currentState == Started)
          {
              // It's illegal to use deallocate on a started controller, but we
              // can handle this rather than falling over.
              Log.annotate(this, "Deallocate called on started controller");
              Log.dumpStack(new ClockStartedError(
                        "deallocate() cannot be called on a started Controller"));
              this.stop();
          }
  
          // Kill any forward-transitioning thread
          synchronized (threadqueueMutex)
          {
              if (threadqueue != null)
              {
                  threadqueue.stopThreads();
              }
          }
  
          // Do the actual deallocating. If this returns false,
          // the deallocate was unsuccessful. Rely on the Controller
          // to post the ControllerErrorEvent and return without
          // modifying the current or target states.
  
          if (doDeallocate())
          {
              // The deallocate was successful
  
              // Return to previous state as dictated by the spec
              if (currentState == Unrealized || currentState == Realizing)
              {
                  state = Unrealized;
              } else
              {
                  state = Realized;
              }
  
              // Set current and target states and post event
              setState(state);
              setTargetState(state);
              postDeallocateEvent();
          }
  
          // mgodehardt: cleanup here
          synchronized (threadqueueMutex)
          {
              if (null != threadqueue)
              {
                  threadqueue.close();
              }
              threadqueue = null;
          }
  
          if (null != stopTimeMonitor)
          {
              stopTimeMonitor.close();
          }
          stopTimeMonitor = null;
  
          if (null != eventqueue)
          {
              eventqueue.close();
          }
      }
  
      /**
       * Close the Controller. Typically this method will release as many
       * resources as possible, especially those that may be needed by other
       * Controllers.
       * <p>
       * This method should not be called directly. Instead, call close().
       */
      public abstract void doClose();
  
      /**
       * Implement to deallocate the Controller.
       * <p>
       * This method should not be called directly. Instead, call prefetch().
       *
       * @return True if successful, false otherwise.
       */
      public abstract boolean doDeallocate();
  
      /**
       * Implement to prefetch the Controller.
       * <p>
       * This method should not be called directly. Instead, call prefetch().
       *
       * @return True if successful, false otherwise.
       */
      public abstract boolean doPrefetch();
  
      // //////////////////////////////////////////////////////
      //
      // javax.media.Clock methods
      //
      // //////////////////////////////////////////////////////
  
      /**
       * Implement to realize the Controller.
       * <p>
       * This method should not be called directly. Instead, call realize().
       *
       * @return True if successful, false otherwise.
       */
      public abstract boolean doRealize();
  
      /**
       * Override to provide implementation-specific functionality. When this
       * method is called, it is guaranteed that the Controller is Stopped and
       * that the given time is within the Controller's duration.
       * <p>
       * This method should not be called directly. Instead, call setMediaTime().
       *
       * @param t
       *            The media time to set
       */
      public abstract void doSetMediaTime(Time t);
  
      /**
       * Override to provide implementation-specific functionality. When this
       * method is called, it is guaranteed that the Controller is Stopped.
       * <p>
       * This method should not be called directly. Instead, call setRate().
       *
       * @param rate
       *            The requested rate to set
       *
       * @return The actual rate that was set
       */
      public abstract float doSetRate(float rate);
  
      /**
       * Implement to stop the Controller.
       * <p>
       * This method should not be called directly. Instead, call stop().
       *
       * @return True if successful, false otherwise.
       */
      public abstract boolean doStop();
  
      /**
       * Implement to start the Controller.
       * <p>
       * This method should not be called directly. Instead, call syncStart().
       *
       * @return True if successful, false otherwise.
       */
      public abstract boolean doSyncStart(Time t);
  
      /**
       * Indicates to the framework that the end of media has been reached. Marks
       * the media time, sets the current and target states to Prefetched, and
       * posts an EndOfMediaEvent.
       *
       * @exception ClockStoppedException
       *                If the AbstractController is not in the Started state.
       */
      protected synchronized void endOfMedia() throws ClockStoppedException
      {
          // Enforce state prereq
          if (currentState != Started)
          {
              throw new ClockStoppedException();
          }
  
          // Stop the Clock
          super.stop();
  
          // Set the state and post an EndOfMediaEvent
          setState(Prefetched);
          setTargetState(Prefetched);
          postEndOfMediaEvent();
      }
  
      // //////////////////////////////////////////////////////////
      //
      // javax.media.Duration methods
      //
      // //////////////////////////////////////////////////////////
  
      /**
       * Get the Control that supports the class or interface specified. The full
       * class or interface name should be specified. Null is returned if the
       * Control is not supported.
       *
       * @return Control for the given class or interface name, or null if no such
       *         Control is supported.
       */
      @Override
      public Control getControl(String forName)
      {
          Class<?> c;
  
          try
          {
              c = Class.forName(forName);
          } catch (Exception e)
          {
              return null;
          }
  
          synchronized (controls)
          {
              for (int i = 0, n = controls.size(); i < n; i++)
              {
                  Control control = (Control) controls.elementAt(i);
                  if (c.isInstance(control))
                  {
                      return control;
                  }
              }
          }
  
          return null;
      }
  
      // //////////////////////////////////////////////////////////
      //
      // javax.media.Controller methods
      //
      // //////////////////////////////////////////////////////////
  
      /**
       * Get a list of the Control objects that this Controller supports. If there
       * are no controls, an array of length zero is returned.
       *
       * @return A list of Controller Controls.
       *
       */
      @Override
      public Control[] getControls()
      {
          Control[] array;
          synchronized (controls)
          {
              array = new Control[controls.size()];
              controls.copyInto(array);
          }
          return array;
      }
  
      /**
       * Returns DURATION_UNKNOWN. This method should be overridden to report a
       * more precise duration.
       */
      @Override
      public Time getDuration()
      {
          return DURATION_UNKNOWN;
      }
  
      /**
       * Calculates the current media time based on the current time-base time,
       * the time-base start time, the media start time, and the rate.
       *
       * @return The current media time
       */
      @Override
      public synchronized Time getMediaTime()
      {
          Time mediaTime = super.getMediaTime();
          Time duration = getDuration();
  
          // Compare media time with duration
          if (duration != DURATION_UNKNOWN && duration != DURATION_UNBOUNDED
                  && mediaTime.getNanoseconds() > duration.getNanoseconds())
          {
              return duration;
          }
  
          return mediaTime;
      }
  
      /**
       * Get the previous state of this Controller.
       */
      public int getPreviousState()
      {
          return previousState;
      }
  
      /**
       * Returns LATENCY_UNKNOWN. This method should be overridden to report a
       * more precise start latency.
       */
      @Override
      public Time getStartLatency()
      {
          if (currentState == Unrealized || currentState == Realizing)
          {
              throw new NotRealizedError(
                      "Cannot get start latency from an unrealized Controller.");
          }
  
          return LATENCY_UNKNOWN;
      }
  
      /**
       * Get the current state of this Controller.
       */
      @Override
      public int getState()
      {
          return currentState;
      }
  
      /**
       * Get the target state of this Controller.
       */
      @Override
      public int getTargetState()
      {
          return targetState;
      }
  
      /**
       * Gets the ThreadQueue object for this AbstractController.
       */
      protected ThreadQueue getThreadQueue()
      {
          synchronized (threadqueueMutex)
          {
              if (null == threadqueue)
              {
                  threadqueue = new ThreadQueue("ThreadQueue for " + this);
                  threadqueue.start();
              }
              return threadqueue;
          }
      }
  
      /**
       * Get the TimeBase that this Controller is using.
       */
      @Override
      public synchronized TimeBase getTimeBase()
      {
  