package sale;

import java.io.*;

import users.User;

import sale.stdforms.MsgForm;

import data.DataBasket;
import data.Stock;
import data.Catalog;

import log.*;

/**
  * A process. Processes are used to manipulate data in {@link DataBasket DataBaskets}, {@link Stock Stocks},
  * {@link Catalog Catalogs}, and any other data structures you might want to use.
  *
  * <p>Processes are viewed as finite deterministic automata, internally represented by a directed graph. The
  * nodes of this graph are called {@link Gate Gates}, the edges are {@link Transition Transitions}.</p>
  *
  * <p>Processes are persistent, i.e. they are automatically restored when the system comes up again after
  * having been down for some time.</p>
  *
  * <p>A process can be suspended whenever it is at a gate. When a process is asked to
  * {@link #suspend suspend()} while it is in a transition, it will get suspended only at the next gate. For
  * this to be feasible, transitions must be rather short, and, in particular, must not comprise any user
  * interaction.</p>
  *
  * @author Steffen Zschaler
  * @version 2.0 17/08/1999
  * @since v2.0
  */
public abstract class SaleProcess implements LogContext,
                                             Loggable,
                                             ProcessErrorCodes,
                                             Serializable {

  /**
    * The name of this process. Used to identify the process, esp. for debug purposes.
    *
    * @serial
    */
  private String m_sName;

  /**
    * The context in which this process runs. Used for all user communication, log and
    * data access.
    *
    * @serial
    */
  private ProcessContext m_pcContext;

  /**
    * The monitor synchronizing access to the process context.
    */
  private transient Object m_oContextLock;

  /**
    * Return the monitor synchronizing access to the process context.
    *
    * @override Never
    */
  private Object getContextLock() {
    if (m_oContextLock == null) {
      m_oContextLock = new Object();
    }

    return m_oContextLock;
  }

  /**
    * The DataBasket used to implement transactional behavior.
    *
    * @serial
    */
  private DataBasket m_dbWorkBasket;

  /**
    * The previous log context of the current DataBasket.
    *
    * @serial
    */
  private LogContext m_lcOldBasketContext;

  /**
    * The monitor synchronizing access to the DataBasket.
    */
  private transient Object m_oBasketLock;

  /**
    * Return the monitor synchronizing access to the DataBasket.
    *
    * @override Never
    */
  private Object getBasketLock() {
    if (m_oBasketLock == null) {
      m_oBasketLock = new Object();
    }

    return m_oBasketLock;
  }

  /**
    * The current gate, if any.
    *
    * @serial
    */
  protected Gate m_gCurGate;

  /**
    * The current transition, if any.
    *
    * @serial
    */
  protected Transition m_tCurTransition;

  /**
    * The process' main thread.
    */
  private transient Thread m_trdMain = null;

  /**
    * Flag indicating whether the process is currently suspended.
    *
    * @serial
    */
  private boolean m_fSuspended = false;

  /**
    * Flag indicating whether the process has been resumed.
    *
    * @serial
    */
  private boolean m_fResumed = false;

  /**
    * Last error condition.
    *
    * @serial
    */
  private int m_nErrorCode = 0;

  /**
    * Additional information concerning the cause of the last error.
    *
    * @serial <strong>Attention:</strong> This may be a common cause of mistake, when objects handed in as
    * error descriptions are not serializable!
    */
  private Object m_oErrorExtraInfo = null;

  /**
    * Count error nesting, so that we do not run into infinite loops.
    *
    * @serial
    */
  private int m_nErrorNesting = 0;

  /**
    * Create a new SaleProcess with a given name.
    *
    * @param sName the name of this process.
    */
  public SaleProcess (String sName) {
    super();

    m_sName = sName;
  }

  /**
    * Return the name of this process.
    *
    * @override Never
    *
    * @return the name of the process.
    */
  public String getName() {
    return m_sName;
  }

  /**
    * Attach a ProcessContext to this process.
    *
    * @override Never
    *
    * @param pcNew the process context to be attached.
    *
    * @return the previously attached process context, if any.
    */
  public ProcessContext attach (ProcessContext pcNew) {
    synchronized (getContextLock()) {
      ProcessContext pc = m_pcContext;

      m_pcContext = pcNew;

      if (isAlive()) {
        // if the process is currently alive, we have to
        // unregister the process with the old context...
        if (pc != null) {
          pc.processFinished (this);
        }

        //...and register it with the new context
        if (m_pcContext != null) {
          m_pcContext.processStarted (this);
        }
      }

      return pc;
    }
  }

  /**
    * Detach and return the current process context.
    *
    * @override Never
    */
  public ProcessContext detachContext() {
    return attach ((ProcessContext) null);
  }

  /**
    * Return the process context attached to this process.
    *
    * @override Never
    */
  public ProcessContext getContext() {
    synchronized (getContextLock()) {
      return m_pcContext;
    }
  }

  /**
    * Attach the DataBaskte that is going to be used to implement transactional
    * behavior for this Process.
    *
    * @override Never
    *
    * @param dbNew the DataBasket to be attached.
    *
    * @return the previously attached DataBasket, if any.
    */
  public DataBasket attach (DataBasket dbNew) {
    synchronized (getBasketLock()) {
      DataBasket db = m_dbWorkBasket;

      if (m_dbWorkBasket != null) {
        m_dbWorkBasket.setLogContext (m_lcOldBasketContext);
      }

      m_dbWorkBasket = dbNew;

      if (m_dbWorkBasket != null) {
        m_lcOldBasketContext = m_dbWorkBasket.setLogContext (this);
      }

      return db;
    }
  }

  /**
    * Detach and return the current DataBasket.
    *
    * @override Never
    */
  public DataBasket detachBasket() {
    return attach ((DataBasket) null);
  }

  /**
    * Get the currently attached DataBasket.
    *
    * @override Never
    */
  public DataBasket getBasket() {
    synchronized (getBasketLock()) {
      return m_dbWorkBasket;
    }
  }

  /**
    * Return true if this Process can be stopped with a subsequent <code>quit()</code>
    * command.
    *
    * @override Sometimes The default implementation will return <code>!fContextDestroy</code>, so that the
    * process can only be quitted, if it will be possible to resume it afterwards.
    *
    * @param fContextDestroy true, if the quit request was issued due to a destroyal of
    * the process' context. If false you can assume that it will be possible to restore
    * and resume the Process after it had been <code>quit</code>ted.
    *
    * @return Currently returns <code>!fContextDestroy</code>, so that the process can
    * only be quitted, if it will be possible to resume it afterwards.
    */
  public boolean canQuit (boolean fContextDestroy) {
    return !fContextDestroy;
  }

  /**
    * Internal error helper, used to cancel a transition or gate when it calls error().
    *
    * @author Steffen Zschaler
    * @version 2.0 17/08/1999
    * @since v2.0
    */
  private class ProcessErrorError extends Error {
    public ProcessErrorError() {
      super();
    }
  }

  /**
    * Raise an error in this process.
    *
    * <p>First calls {@link #printErrorInfo} to inform the user of the error condition,
    * then cancels the process by jumping to the &quot;error&quot; gate.</p>
    *
    * <p><strong>Attention:</strong>This method must only be called from within a gate or
    * a transition of this process. If called from any other environment, unpredictable
    * behavior will result.</p>
    *
    * @override Never
    *
    * @param nErrorCode the error code.
    */
  public void error (int nErrorCode) {
    error (nErrorCode, (Object) null);
  }

  /**
    * Raise an error in this process.
    *
    * <p>First calls {@link #printErrorInfo} to inform the user of the error condition,
    * then cancels the process by jumping to the &quot;error&quot; gate.</p>
    *
    * <p><strong>Attention:</strong>This method must only be called from within a gate or
    * a transition of this process. If called from any other environment, unpredictable
    * behavior will result.</p>
    *
    * @override Never
    *
    * @param nErrorCode the error code.
    * @param oExtraInfo additional information that explains the cause of the error.
    */
  public void error (int nErrorCode, Object oExtraInfo) {
    m_nErrorCode = nErrorCode;
    m_oErrorExtraInfo = oExtraInfo;
    m_nErrorNesting++;

    //printErrorInfo (nErrorCode, oExtraInfo); --> called at the error gate!

    throw new ProcessErrorError();
  }

  /**
    * Raise an error in this process.
    *
    * <p>First calls {@link #printErrorInfo} to inform the user of the error condition,
    * then cancels the process by jumping to the &quot;error&quot; gate.</p>
    *
    * <p><strong>Attention:</strong>This method must only be called from within a gate or
    * a transition of this process. If called from any other environment, unpredictable
    * behavior will result.</p>
    *
    * @override Never
    *
    * @param nErrorCode the error code.
    * @param tExtraInfo the exception that caused the error.
    */
  public void error (int nErrorCode, Throwable tExtraInfo) {

    java.io.ByteArrayOutputStream bos = new java.io.ByteArrayOutputStream();
    java.io.PrintWriter pw = new java.io.PrintWriter (bos);

    tExtraInfo.printStackTrace (pw);

    pw.close();

    error (nErrorCode, bos.toString());
  }

  /**
    * Print error information to inform the user of an error condition.
    *
    * <p>Calls {@link #getErrorMsg} to resolve the error code into an error message.
    * All occurences of &quot;%o&quot; in the error message are then replaced by the
    * extra information's string representation.</p>
    *
    * <p>If the context has a {@link ProcessContext#hasUseableDisplay useable display}
    * a {@link sale.stdforms.MsgForm} is displayed containing the error message.
    * Otherwise, the error message is printed to the standard error stream.</p>
    *
    * <p>This method is never called directly, but rather called by the Framework as
    * appropriate. Thus, it is assured, that user communication takes place at a gate
    * only, even in the case of an error.</p>
    *
    * @override Never
    *
    * @param nErrorCode the error code.
    * @param oExtraInfo additional information concerning the cause of the error.
    */
  protected void printErrorInfo (int nErrorCode, Object oExtraInfo) {
    String sErrorMsg = getErrorMsg (nErrorCode);

    if (oExtraInfo != null) {
      if (sErrorMsg.indexOf ("%o") >= 0) {
        int nIndex;
        while ((nIndex = sErrorMsg.indexOf("%o")) >= 0) {

          String sTemp = sErrorMsg.substring (0, nIndex) + oExtraInfo;
          if (nIndex < sErrorMsg.length() - 2) {
            sTemp += sErrorMsg.substring (nIndex + 2);
          }

          sErrorMsg = sTemp;
        }
      }
      else {
        sErrorMsg += "\nAdditional Information: " + oExtraInfo;
      }
    }
    else {
      if (sErrorMsg.indexOf ("%o") >= 0) {
        int nIndex;

        while ((nIndex = sErrorMsg.indexOf("%o")) >= 0) {

          String sTemp = sErrorMsg.substring (0, nIndex) + "<>";
          if (nIndex < sErrorMsg.length() - 2) {
            sTemp += sErrorMsg.substring (nIndex + 2);
          }

          sErrorMsg = sTemp;
        }
      }
    }

    if ((m_pcContext != null) &&
        (m_pcContext.hasUseableDisplay (this))) {
      try {
        m_pcContext.setFormSheet (this, new MsgForm ("Error", sErrorMsg));
      }
      catch (InterruptedException e) {}
    }
    else {
      System.err.println ("Error in process <" + getName() + ">:\n");
      System.err.println (sErrorMsg);
    }
  }

  /**
    * Return a readable version of the error message.
    *
    * @override Sometimes Override this method whenever you define new error codes for a process.
    *
    * @param nErrorCode the error code.
    *
    * @return a readable version of the error message.
    */
  public String getErrorMsg (int nErrorCode) {
    switch (nErrorCode) {
      case ERR_INTERNAL:
        return "An internal error occured. " +
               "Please file a bug report to your programmer team.\n" +
               "Error message (Please provide this message with your bug report):\n\n%o";
      case NOT_ENOUGH_ELEMENTS_ERROR:
        return "Sorry, not enough elements.";
      case REMOVE_VETO_EXCEPTION:
        return "Sorry, couldn't delete.";
      case DUPLICATE_KEY_EXCEPTION:
        return "Element does already exist.";
      case DATABASKET_CONFLICT_ERROR:
        return "Your action stands in conflict with an action of another user.";
      default:
        return "Error no. " + nErrorCode + " occured.\nProcess cancelled.";
    }
  }

  /**
    * Suspend the process.
    *
    * <p>This method will suspend the process at the nearest gate. The method will block
    * until the process was suspended.</p>
    *
    * @override Never
    *
    * @exception InterruptedException if an interrupt occurs in the calling thread while
    * waiting for the process to suspend.
    */
  public synchronized void suspend()
    throws InterruptedException {
    if (m_fSuspended) {
      // already done.
      return;
    }

    m_fSuspended = true;

    Thread trdMain = m_trdMain;
    if (trdMain != null) {
      trdMain.interrupt();

      trdMain.join(); // wait for main thread to finish
                      // allow InterruptedException, if any, to propagate upwards
    }
  }

  /**
    * Return true if this process is currently suspended.
    *
    * <p><strong>Attention:</strong>This method is not synchronized. This allows you to
    * call it from within a gate or transition without fear for deadlocks, but there
    * might be certain rare circumstances where the several <code>isXXX()</code>
    * methods' return values give an seemingly inconsistent picture.</p>
    *
    * @override Never
    */
  public final boolean isSuspended() {
    return m_fSuspended;
  }

  /**
    * Resume a previously suspended process.
    *
    * <p>This method will resume the process at the gate at which it was suspended.</p>
    *
    * @override Never
    */
  public synchronized void resume() {
    m_fResumed = true;

    start();
  }

  /**
    * Return true if this process has been resumed from a previous suspended state.
    *
    * <p><strong>Attention:</strong>This method is not synchronized. This allows you to
    * call it from within a gate or transition without fear for deadlocks, but there
    * might be certain rare circumstances where the several <code>isXXX()</code>
    * methods' return values give an seemingly inconsistent picture.</p>
    *
    * @override Never
    */
  public final boolean isResumed() {
    return m_fResumed;
  }

  /**
    * Start the process.
    *
    * @override Never
    */
  public synchronized void start() {
    if (m_trdMain == null) {
      m_fSuspended = false;

      if (!m_fResumed) {
        m_gCurGate = getInitialGate();
      }

      m_trdMain = new Thread ("SaleProcess thread: <" + getName() + ">.main()") {
        public void run() {
          main();
        }
      };

      m_trdMain.start();
    }
  }

  /**
    * Return true if this process has been started, but has not yet died.
    *
    * <p>In contrast to {@link #isRunning()} <code>isAlive()</code> will also
    * return true for a process that has been suspended.</p>
    *
    * <p><strong>Attention:</strong>This method is not synchronized. This allows you to
    * call it from within a gate or transition without fear for deadlocks, but there
    * might be certain rare circumstances where the several <code>isXXX()</code>
    * methods' return values give an seemingly inconsistent picture.</p>
    *
    * @override Never
    */
  public final boolean isAlive() {
    return isRunning() ||
           isSuspended();
  }

  /**
    * Return true if this process is currently running. The process is running, if it
    * has been started, but not yet stopped nor suspended.
    *
    * <p><strong>Attention:</strong>This method is not synchronized. This allows you to
    * call it from within a gate or transition without fear for deadlocks, but there
    * might be certain rare circumstances where the several <code>isXXX()</code>
    * methods' return values give an seemingly inconsistent picture.</p>
    *
    * @override Never
    */
  public final boolean isRunning() {
    return (m_trdMain != null);
  }

  /**
    * A thread providing an exception firewall for all subprocesses of a process.
    */
  private class SubProcess extends Thread {

    /**
      * The activity to be guarded by the exception firewall.
      */
    private Runnable m_rSubProcess;

    /**
      * The parent process.
      */
    private SaleProcess m_pParent;

    /**
      * Create a new subprocess.
      */
    public SubProcess (String sName,
                       SaleProcess pParent,
                       Runnable rSubProcess) {
      super (sName);

      m_pParent = pParent;
      m_rSubProcess = rSubProcess;
    }

    /**
      * The exception firewall.
      */
    public void run() {
      try {
        m_rSubProcess.run();
      }
      catch (ProcessErrorError pe) {
        // silently stop the subprocess
      }
      catch (ThreadDeath td) {
        throw td;
      }
      catch (Throwable t) {
        try {
          m_pParent.error (ERR_INTERNAL, t);
        }
        catch (ProcessErrorError pe) {
          // silently stop the subprocess
        }
      }
    }
  }

  /**
    * The gate that is responsible for printing error messages.
    */
  private class PrintErrorGate implements Gate {

    private int m_nCode;
    private Object m_oInfo;
    private int m_nErrNesting;

    public PrintErrorGate (int nErrorNesting) {
      super();

      m_nCode = m_nErrorCode;
      m_oInfo = m_oErrorExtraInfo;
      m_nErrNesting = nErrorNesting;
    }

    public Transition getNextTransition (SaleProcess p, User u) {
      printErrorInfo (m_nCode, m_oInfo);

      return new GateChangeTransition (p.getErrorGate (m_nErrNesting));
    }
  }

  /**
    * The central control loop of the process.
    *
    * @override Never
    */
  private void main() {

    int nCurErrorNesting = m_nErrorNesting;

    try {
      // initialization
      if (!m_fResumed) {
        m_pcContext.processStarted (this);
      }

      try {
        onResumeOrStart (m_fResumed);
      }
      catch (ProcessErrorError pe) {
        nCurErrorNesting = m_nErrorNesting;
        m_gCurGate = new PrintErrorGate (m_nErrorNesting);
      }

      // the actual control loop
      while ((m_gCurGate != null) &&
             (!m_fSuspended)) {
        // Loop initialization
        m_tCurTransition = null;

        // Let the gate figure out the next Transition.
        // This is interruptible.
        Thread trdGate = new SubProcess ("SaleProcess thread: <" + getName() + ">.gateHandler",
                                         this,
                                         new Runnable() {
          public void run() {
            try {
              m_tCurTransition = m_gCurGate.getNextTransition (SaleProcess.this,
                                                               ((m_pcContext != null) ?
                                                                (m_pcContext.getCurrentUser (SaleProcess.this)):
                                                                (null)));

              if (m_fSuspended) {
                m_tCurTransition = null;
              }
            }
            catch (InterruptedException e) {
              util.Debug.print ("Caught interrupt in Gate handler.", -1);
              m_tCurTransition = null;
            }
          }
        });

        trdGate.start();

        while (trdGate.isAlive()) {  // zzz (see below!)
          try {
            trdGate.join();
          }
          catch (InterruptedException e) {
            util.Debug.print ("Caught interrupt in main process handler.", -1);

            if (m_fSuspended) {
              util.Debug.print ("In main process handler: Handing interrupt on to gate handler.", -1);

              trdGate.interrupt();

              // we don't need to wait for trdGate to die here, as we will simply enter the loop at zzz again
            }
          }
        }

        util.Debug.print ("In main process handler: Gate handler died.", -1);

        if (m_fSuspended) {
          // if the process was suspended, break the control loop.
          break;
        }

        if (m_nErrorNesting != nCurErrorNesting) {
          // an error occurred: jump to "error" gate.
          nCurErrorNesting = m_nErrorNesting;
          m_gCurGate = new PrintErrorGate (m_nErrorNesting);

          continue;
        }

        // Leave the gate and do a Transition.
        // This is non-interruptible, except on error conditions.
        Thread trdTransition = new SubProcess ("SaleProcess thread: <" + getName() + ">.transitionHandler",
                                               this,
                                               new Runnable() {
          public void run() {
            m_gCurGate = m_tCurTransition.perform (SaleProcess.this,
                                                   ((m_pcContext != null) ?
                                                    (m_pcContext.getCurrentUser (SaleProcess.this)):
                                                    (null)));
          }
        });

        trdTransition.start();

        while (trdTransition.isAlive()) {
          try {
            trdTransition.join();
          }
          catch (InterruptedException e) {
            // In a Transition we don't want to be interrupted, so just go on waiting.
            // The m_fSuspended flag will be set by the suspend() call.
            continue;
          }
        }

        if (m_nErrorNesting != nCurErrorNesting) {
          // an error occurred: jump to "error" gate.
          nCurErrorNesting = m_nErrorNesting;
          m_gCurGate = new PrintErrorGate (m_nErrorNesting);

          continue;
        }
      }

      if (m_fSuspended) {
        // special cleanup on suspend() calls.
        try {
          onSuspended();
        }
        catch (ProcessErrorError pe) {
          nCurErrorNesting = m_nErrorNesting;
          m_gCurGate = new PrintErrorGate (m_nErrorNesting);
        }
      }
    }
    catch (Throwable t) {
      System.err.println ("Exception occured in process " + getName() + ":\n");
      t.printStackTrace();
    }
    finally {
      try {
        onFinished();
      }
      catch (ProcessErrorError pe) {
        if (m_fSuspended) {
          // on any error only jump to the "error" gate if the process has been suspended
          // otherwise just forget about the error !
          nCurErrorNesting = m_nErrorNesting;
          m_gCurGate = new PrintErrorGate (m_nErrorNesting);
        }
      }
      catch (ThreadDeath td) {}
      catch (Throwable t) {
        System.err.println ("Exception occured in process " + getName() + ", onFinished() sequence:\n");
        t.printStackTrace();
      }

      // make sure, this is always done
      if (!isSuspended()) {
        m_pcContext.processFinished (this);
      }
      m_fResumed = false;
      m_trdMain = null;
    }
  }

  /**
    * Hook method called on every start or resume of the process. Should perform any
    * global process initializiation.
    *
    * <p>This method is called in the process' main thread and any uncaught exception
    * raised in this method will lead to the process being stopped.</p>
    *
    * @override Sometimes Override this method if you need special initialization code.
    *
    * @param fIsResume true if the process has not been started afresh, but rather has
    * been resumed.
    */
  protected void onResumeOrStart (boolean fIsResume) {}

  /**
    * Hook method called whenever the process was suspended. This method is called
    * in the process' main thread and any uncaught exception raised in this method
    * will be reported from that thread.</p>
    *
    * <p>This method is called in the process' main thread and any uncaught exception
    * raised in this method will lead to the process being stopped.</p>
    *
    * @override Sometimes Override this method if you need special cleanup code for suspended processes.
    */
  protected void onSuspended() {}

  /**
    * Hook method called whenever the process was finished, independently of whether
    * the process was really finished or just suspended.
    *
    * <p>You can find out whether the process was just suspended by calling
    * {@link #isSuspended isSuspended()}.</p>
    *
    * <p>This method is called in the process' main thread and any uncaught exception
    * raised in this method will be reported in this thread. This method must
    * <strong>not</strong> call {@link #error error()}, however.</p>
    *
    * @override Sometimes Override this method if you need special cleanup code at the end of a process.
    */
  protected void onFinished() {}

  /**
    * Quit the process at the nearest gate. The process will jump to the
    * &quot;quit&quot; gate.
    *
    * <p><code>quit()</code> first calls <code>suspend()</code> then sets the current
    * gate to be the &quot;quit&quot; gate and finally <code>resume()</code>-s the
    * process.</p>
    *
    * @override Never
    *
    * @param fWaitQuit if true, quit will block until the process ended.
    *
    * @exception InterruptedException if an interrupt occured in the calling thread
    * while waiting for the process to quit.
    *
    * @see #getQuitGate
    */
  public synchronized void quit (boolean fWaitQuit)
    throws InterruptedException {

    if (!isAlive()) {
      return;
    }

    try {
      suspend();
    }
    catch (InterruptedException e) {}

    m_gCurGate = getQuitGate();

    resume();

    if (fWaitQuit) {
      // copy to avoid NullPointerExceptions
      Thread trdMain = m_trdMain;

      if (trdMain != null) {
        // wait for the process to finish.
        trdMain.join();
      }
    }
  }

  /**
    * A log entry describing a process that was executed.
    *
    * <p>The default implementation will only give the name of the process and when it was logged.</p>
    *
    * @author Steffen Zschaler
    * @version 2.0 14/07/1999
    * @since v2.0
    */
  public static class ProcessLogEntry extends LogEntry {

    /**
      * The name of the process that this log entry describes.
      *
      * @serial
      */
    protected String m_sProcessName;

    /**
      * Create a new ProcessLogEntry.
      */
    public ProcessLogEntry (SaleProcess p) {
      super();

      m_sProcessName = p.getName();
    }

    /**
      * Return the name of the process that this log entry describes.
      *
      * @override Never
      */
    public String getProcessName() {
      return m_sProcessName;
    }

    /**
      * Return descriptive information for this LogEntry.
      *
      * @override Always
      */
    public String toString() {
      return "Process \"" + getProcessName() + "\" logged on " + getLogDate() + ".";
    }
  }

  /**
    * Return information that describes the process for logging purposes.
    *
    * @override Always The default implementation produces a log entry that will simply give the name of the
    * process and the time when logging happened.
    *
    * @see ProcessLogEntry
    */
  public LogEntry getLogData() {
    return new ProcessLogEntry (this);
  }

  /**
    * Logs the given data to a log file. If the process is in a process context, the data is logged to the
    * process context's log file. Otherwise, the global log file is used.
    *
    * @override Sometimes Override if you want to personalize log entries from the DataBasket.
    *
    * @param la the event to be logged.
    *
    * @exception LogNoOutputStreamException if no OutputStream has been
    * specified for the log file.
    * @exception IOException if an IOException occurs when writing to the
    * log file.
    */
  public void log (Loggable la)
    throws LogNoOutputStreamException,
           IOException {
    if (getContext() != null) {
      getContext().log (this, la);
    }
    else {
      Log.getGlobalLog().log (la);
    }
  }

  /**
    * Return the gate at which the process currently stands or which it just left.
    *
    * @override Never
    */
  public Gate getCurrentGate() {
    return m_gCurGate;
  }

  /**
    * Return the initial gate for this process.
    *
    * <p>By the time this method gets called, you can assume that the {@link #getBasket working basket} and the
    * {@link #getContext process context} have been properly initialized.</p>
    *
    * @override Always The process will start at the gate that is returned by this method. Therefore,
    * in order to do anything sensible, you must override this method.
    */
  protected abstract Gate getInitialGate();

  /**
    * Return the gate to jump to when quitting the process.
    *
    * <p>Transitions starting at this gate will usually perform a rollback and will then
    * jump to the &quot;stop&quot; gate.</p>
    *
    * @override Sometimes As a default, returns the &quot;rollback&quot; gate.
    *
    * @see #getStopGate
    * @see #getRollbackGate
    */
  public Gate getQuitGate() {
    return getRollbackGate();
  }

  /**
    * Return the gate to jump to when an {@link #error error} occurs.
    *
    * <p>Transition starting at this gate can perform any specific error handling and
    * should then arrive at the &quot;rollback&quot; gate.</p>
    *
    * <p>When this method is called, {@link #getCurrentGate} will still deliver the
    * last valid gate.</p>
    *
    * @override Sometimes As a default returns the &quot;rollback&quot; gate, unless nErrorNesting is greater
    * than 1, in which case <code>null</code> is returned to indicate the end of the process.
    *
    * @param nErrorNesting a value that indicates nested errors. This value increases with
    * every new error, so that values greater than 1 indicate errors that occured while
    * other errors where handled.
    *
    * @return As a default returns the &quot;rollback&quot; gate, unless nErrorNesting is
    * greater than 1, in which case <code>null</code> is returned to indicate the end of
    * the process.
    */
  protected Gate getErrorGate (int nErrorNesting) {
    if (nErrorNesting <= 1) {
      return getRollbackGate();
    }
    else {
      return null;
    }
  }

  /**
    * Return the gate to jump to when performing a rollback.
    *
    * <p>Transitions starting from this gate must roll back any data structures the process used.</p>
    *
    * @override Sometimes As a default returns a gate with a transition that will roll back the DataBasket
    * attached to the process and will eventually jump to the {@link #getLogGate &quot;log&quot; gate}.
    *
    * @see #getStopGate
    */
  public Gate getRollbackGate() {
    return new Gate() {
      public Transition getNextTransition (SaleProcess p, User u) {
        return new Transition() {
          public Gate perform (SaleProcess p, User u) {
            DataBasket db = p.getBasket();

            if (db != null) {
              db.rollback();
            }

            return p.getLogGate();
          }
        };
      }
    };
  }

  /**
    * Return the gate to jump to when performing a commit.
    *
    * <p>Transitions starting from this gate must commit any data structures the process used.</p>
    *
    * @override Sometimes As a default returns a gate with a transition that will commit the DataBasket
    * attached to the process and will eventually jump to the {@link #getLogGate &quot;log&quot; gate}.
    *
    * @see #getStopGate
    */
  public Gate getCommitGate() {
    return new Gate() {
      public Transition getNextTransition (SaleProcess p, User u) {
        return new Transition() {
          public Gate perform (SaleProcess p, User u) {
            DataBasket db = p.getBasket();

            if (db != null) {
              db.commit();
            }

            return p.getLogGate();
          }
        };
      }
    };
  }

  /**
    * Return the gate that the process must jump to if it wishes to be logged before finishing.
    *
    * <p>Transitions from this gate should {@link Log#log log} the process into a log file of their choice and
    * then proceed to the {@link #getStopGate &quot;stop&quot; gate}.</p>
    *
    * @override Sometimes As a default returns a gate with a transition that will log the process using the
    * process context's {@link ProcessContext#log log()} method.</p>
    */
  public Gate getLogGate() {
    return new Gate() {
      public Transition getNextTransition (SaleProcess p, User u) {
        return new Transition() {
          public Gate perform (SaleProcess p, User u) {
            try {
              p.log (p);
            }
            catch (java.io.IOException ioe) {
              throw new Error ("Exception occurred while logging process: " + ioe);
            }

            return p.getStopGate();
          }
        };
      }
    };
  }

  /**
    * Return the last gate that this process should be at.
    *
    * <p> Transitions from this gate must return <code>null</code> instead of a next gate.</p>
    *
    * @override Sometimes As a default just return <code>null</code>, indicating no further processing is to be
    * performed.</p>
    */
  public Gate getStopGate() {
    return null;
  }

  /**
    * A LogEntryFilter that will accept only such {@link LogEntry LogEntries} that stem from a process.
    */
  public static final LogEntryFilter LOGENTRYFILTER_PROCESSES_ONLY = new LogEntryFilter() {
    public boolean accept (LogEntry le) {
      return (le instanceof ProcessLogEntry);
    }
  };
}