// ============================================================================
// File:               $File$
//
// Project:            DXF viewer
//
// Purpose:            A Runnable which knows of exceptions.
//
// Author:             Rammi
//
// Copyright Notice:   (c) 2003  Rammi (rammi@caff.de)
//                     This code is in the public domain.
//                     Use at own risk.
//                     No guarantees given.
//
// Latest change:      $Date$
//
// History:	       $Log$
//=============================================================================

package de.caff.util;

import javax.swing.*;
import java.util.*;

/**
 *  A Runnable which knows of exceptions.
 *  @author <a href="mailto:rammi@caff.de">Rammi</a>
 *  @version $Revision$
 */
public abstract class Worker
        implements Runnable
{
  /** The exception catched (if any). */
  private Throwable catched;
  /** The listeners for finished work. */
  private List<KnockOffListener> knockOffListeners = new LinkedList<KnockOffListener>();
  /** Shall this worker stop? */
  private boolean   stopped = false;

  /**
   *  Default constructor.
   */
  public Worker()
  {
  }

  /**
   *  Constructor taking an intial knock off listener.
   *  @param listener knock off listener
   */
  public Worker(KnockOffListener listener)
  {
    addKnockOffListener(listener);
  }



  /**
   * When an object implementing interface <code>Runnable</code> is used
   * to create a thread, starting the thread causes the object's
   * <code>run</code> method to be called in that separately executing
   * thread.
   * <p>
   * The general contract of the method <code>run</code> is that it may
   * take any action whatsoever.
   *
   * @see     Thread#run()
   */
  public void run()
  {
    try {
      execute();
    } catch (Throwable x) {
      catched = x;
    } finally {
      informKnockOffListeners();
    }
  }

  /**
   *  Implement this in extending classes to do the work.
   *  @throws Throwable any exception thrown during work
   */
  protected abstract void execute() throws Throwable;

  /**
   *  Get the thrwoable catched during work, if there happened one.
   *  @return the throwable or <code>null</code> if there didn't occure any
   */
  public Throwable getCatched()
  {
    return catched;
  }

  /**
   *  This throws the catched exception if there occured one.
   *  @throws Throwable exception catched during execute of work
   */
  public void rethrow()
          throws Throwable
  {
    if (catched != null) {
      throw catched;
    }
  }

  /**
   *  Add a knock off listener.
   *  The knock off listener will be called in the AWT event thread after the worker has finished.
   *  @param listener new listener
   */
  public void addKnockOffListener(KnockOffListener listener)
  {
    if (listener != null) {
      synchronized (knockOffListeners) {
        knockOffListeners.add(listener);
      }
    }
  }

  /**
   *  Remove a knock off listener.
   *  @param listener listener to remove
   */
  public void removeKnockOffListener(KnockOffListener listener)
  {
    if (listener != null) {
      synchronized (knockOffListeners) {
        knockOffListeners.remove(listener);
      }
    }
  }

  /**
   *  Call all knockoff listeners.
   */
  protected void informKnockOffListeners()
  {
    invokeInEventDispatchThread(new Runnable() {
      public void run()
      {
        Collection knockOffListenersCopy;
        synchronized (knockOffListeners) {
          knockOffListenersCopy = new ArrayList<KnockOffListener>(knockOffListeners);
        }
        for (Iterator iterator = knockOffListenersCopy.iterator(); iterator.hasNext();) {
          KnockOffListener listener = (KnockOffListener)iterator.next();
          listener.knockedOff(Worker.this);
        }
      }
    });
  }

  /**
   *  Removes all KnockOffListeners and sets the internal state to stopped.
   *  Implementing classes may check the {@link #isStopped()} method regularly
   *  and stop executing when it returns true.
   *  <p>
   *  This is a workaround for the broken behavior of the Thread.stop() method.
   */
  public void stop()
  {
    stop(true);
  }

  /**
   *  Sets the internal state to stopped.
   *  Implementing classes may check the {@link #isStopped()} method regularly
   *  and stop executing when the stop state is true.
   *  <p>
   *  This is a workaround for the broken behavior of the Thread.stop() method.
   *  @param removeListeners remove the listeners before stopping so they are not called when the worker finishes?
   *                         This is usually a good idea.
   */
  public void stop(boolean removeListeners)
  {
    if (removeListeners) {
      synchronized (knockOffListeners) {
        knockOffListeners.clear();
      }
    }
    synchronized (this) {
      stopped = true;
    }
  }

  /**
   *  Was the worker stopped?
   *  Implementing classes may check this method regularly
   *  and stop executing when it returns <code>true</code>.
   *  <p>
   *  This is a workaround for the broken behavior of the Thread.stop() method.
   *  @return was on of the stop methods called?
   *  @see #stop()
   *  @see #stop(boolean) 
   *
   */
  public synchronized boolean isStopped()
  {
    return stopped;
  }

  /**
   *  Invoke in the AWT thread.
   *  @param run runnable which has to be invoked in the AWT event thread
   */
  public static void invokeInEventDispatchThread(Runnable run)
  {
    if (SwingUtilities.isEventDispatchThread()) {
      run.run();
    }
    else {
      SwingUtilities.invokeLater(run);
    }
  }

}
