public abstract class AbortableLooper {
  AbortedException abortedException = new AbortedException();
  Runnable runner;
  volatile boolean beenAborted = false;
  volatile boolean beenKilled = false;

  public AbortableLooper() {
    this.runner = new Runnable() { public void run() { loop(); } };
  }

  public void start() {
    this.beenKilled = false;
    Thread thread = new Thread(this.runner);
    thread.setDaemon(true);
    thread.start();
  }

  public void stop() {
    this.beenKilled = true;
    this.abort();
  }

  void loop() {
    boolean exceptionThrown = false;
    while (! this.beenKilled) {
      if (! exceptionThrown) { this.waitAborted(); }
      try {
        this.run();
        exceptionThrown = false;
      }
      catch (AbortedException abe) {
        exceptionThrown = true;
      }
    }
  }

  public void abort() {
    if (this.beenAborted) return;
    this.beenAborted = true;
    synchronized (this) { this.notify(); }
  }
    
  protected void hasBeenAborted() throws AbortedException {
    if (! this.beenAborted) return;
    synchronized (this) {
      this.beenAborted = false;
    }
    throw this.abortedException;
  }

  synchronized void waitAborted() {
    while (! this.beenAborted) {
      try { this.wait(); } catch (InterruptedException ie) { }
    }
    this.beenAborted = false;
  }

  public abstract void run() throws AbortedException;
}

