package data.ooimpl;

import java.util.*;

import java.io.Serializable;

import data.events.*;
import data.*;

import log.*;

import util.*;

/**
  * Pure Java implementation of the {@link DataBasket} interface.
  *
  * <p>This DataBasket implementation can be used together with the {@link CatalogImpl Catalog} and
  * {@link StockImpl Stock implementations} that come with the framework as well as with any other data
  * container that manages all its internal structure on its own, instead of delegating them, e.g. to a data
  * base. For a data base backing you should use other implementations of {@link Stock}, {@link Catalog} and
  * {@link DataBasket}, but these are not yet part of the framework.</p>
  *
  * @author Steffen Zschaler
  * @version 2.0 14/06/1999
  * @since v2.0
  */
public class DataBasketImpl extends Object implements ListenableDataBasket {

  /**
    * Internal helper class used by {@link DataBasketImpl}, representing a subbasket of a {@link DataBasket}.
    *
    * <p>This class has been made protected so that framework users be able to subclass it should the need
    * arise.</p>
    *
    * @author Steffen Zschaler
    * @version 2.0 14/06/1999
    * @since v2.0
    */
  protected static class SubDataBasket implements Serializable {

    /**
      * The entries contained in this subbasket.
      *
      * <p>This is a map of maps of lists of {@link DataBasketEntry DataBasketEntries}.</p>
      *
      * @serial
      */
    private Map m_mpmpldbeCategories = new HashMap();

    /**
      * The owner of this subbasket.
      *
      * @serial
      */
    private DataBasketImpl m_dbiOwner;

    /**
      * Create a new subbasket.
      *
      * @param dbiOwner the DataBasketImpl instance owning this subbasket.
      */
    public SubDataBasket (DataBasketImpl dbiOwner) {
      super();

      m_dbiOwner = dbiOwner;
    }

    /**
      * Check whether the given Object is equal to this subbasket.
      *
      * <p>This is overridden to mean identity, because for subbaskets equality and identity are really the
      * same.</p>
      *
      * @override Never
      */
    public boolean equals (Object o) {
      return this == o;
    }

    /**
      * Commit all items in this subbasket that match the condition.
      *
      * @param dbc the condition that must be matched.
      *
      * @see DataBasketEntry#commit
      *
      * @override Never
      */
    public void commit (DataBasketCondition dbc) {
      DataBasketEntry dbe = null;

      for (Iterator i = iterator (dbc, true, true); i.hasNext();) {
        try {
          dbe = (DataBasketEntry) i.next();

          if (!dbe.isHandled()) {
            dbe.commit();
          }

          i.remove();
        }
        catch (Throwable t) {
          System.err.println ("Exception during commit of <" + dbe + ">:");
          t.printStackTrace();
          System.err.println ("Continuing committing any other DataBasketEntries.");
        }
      }
    }

    /**
      * Rollback all entries in this subbasket that match the condition.
      *
      * @param dbc the condition to be matched.
      *
      * @see DataBasketEntry#rollback
      *
      * @override Never
      */
    public void rollback (DataBasketCondition dbc) {
      DataBasketEntry dbe = null;

      for (Iterator i = iterator (dbc, true, true); i.hasNext();) {
        try {
          dbe = (DataBasketEntry) i.next();

          if (!dbe.isHandled()) {
            dbe.rollback();
          }

          i.remove();
        }
        catch (Throwable t) {
          System.err.println ("Exception during rollback of <" + dbe + ">:");
          t.printStackTrace();
          System.err.println ("Continuing rolling back any other DataBasketEntries.");
        }
      }
    }

    /**
      * Iterate all entries in the subbasket that match the given condition.
      *
      * <p>The condition applies to the returned iterator only, it will not be influenced by any future calls
      * to <code>iterator()</code>.</p>
      *
      * @param dbc the condition returned items will have to match. <code>null</code> means match all.
      * @param fAllowRemove if true,the returned iterator's {@link java.util.Iterator#remove remove()} method
      * will be enabled. An iterator with its <code>remove()</code> method enabled must never be made publicly
      * accessible outside of the {@link DataBasketImpl DataBasket}.
      * @param fShowHandled if true, the iterator will include items that return true from their
      * {@link DataBasketEntry#isHandled} method. Such an iterator must never be made publicly accessible
      * outside of the {@link DataBasketImpl DataBasket}.
      *
      * @return an iterator that will iterate over all entries in this subbasket that match the given
      * condition. The iterator will support the <code>remove()</code> method, if <code>fAllowRemove</code> is
      * true.
      *
      * @override Never
      */
    public Iterator iterator (final DataBasketCondition dbc,
                              final boolean fAllowRemove,
                              final boolean fShowHandled) {

      // The iterator to be returned
      class I implements Iterator {

        private Map m_mpmpldbeCategories;
        private Iterator m_iCategories;
        private Map m_mpldbeSubCategories;
        private Iterator m_iSubCategories;
        private Iterator m_iItems;

        private DataBasketEntry m_dbeCurrent = null;
        private DataBasketEntry m_dbeNext = null;

        public I (Map mpmpldbeCategories) {
          super();

          m_mpmpldbeCategories = mpmpldbeCategories;

          // Use a TreeSet to sort the main keys alphabetically.
          m_iCategories = new TreeSet (m_mpmpldbeCategories.keySet()).iterator();
        }

        public boolean hasNext() {
          return findNext (false);
        }

        public Object next() {
          if (!findNext (true)) {
            throw new NoSuchElementException();
          }

          return m_dbeCurrent;
        }

        public void remove() {
          if (!fAllowRemove) {
            throw new UnsupportedOperationException();
          }
          else {
            if (m_iItems == null) {
              throw new IllegalStateException();
            }

            m_iItems.remove();
            m_dbeCurrent.setOwner (null);
            m_dbiOwner.fireDBERemoved (m_dbeCurrent);
          }
        }

        private boolean findNext (boolean fGet) {
          // traverse the hierarchy to find the next item that applies
          // if fGet == true, put the next valid item into m_dbeCurrent
          do {
            if (m_iSubCategories != null) {
              if (checkSubCategories (fGet)) {
                return true;
              }
            }
            else {
              m_iSubCategories = new Iterator() {
                public boolean hasNext() { return false; }
                public Object next() { return null; }
                public void remove() {}
              };
            }

            while ((m_iCategories.hasNext()) &&
                   (!m_iSubCategories.hasNext())) {
              String sCategoryID = (String) m_iCategories.next();

              if (dbc != null) {
                if ((dbc.getMainKey() == null) ||
                    (dbc.getMainKey().equals (sCategoryID))) {
                  m_mpldbeSubCategories = (Map) m_mpmpldbeCategories.get (sCategoryID);
                  m_iSubCategories = m_mpldbeSubCategories.keySet().iterator();
                }
              }
              else {
                m_mpldbeSubCategories = (Map) m_mpmpldbeCategories.get (sCategoryID);
                m_iSubCategories = m_mpldbeSubCategories.keySet().iterator();
              }
            }
          } while (m_iSubCategories.hasNext());

          return false;
        }

        private boolean checkSubCategories (boolean fGet) {
          do {
            if (m_iItems != null) {
              if (checkItems (fGet)) {
                return true;
              }
            }
            else {
              m_iItems = new Iterator() {
                public boolean hasNext() { return false; }
                public Object next() { return null; }
                public void remove() {}
              };
            }

            while ((m_iSubCategories.hasNext()) &&
                   (!m_iItems.hasNext())) {
              String sSubCategoryID = (String) m_iSubCategories.next();

              if (dbc != null) {
                if ((dbc.getSecondaryKey() == null) ||
                    (dbc.getSecondaryKey().equals (sSubCategoryID))) {
                  List ldbeSubCategory = (List) m_mpldbeSubCategories.get (sSubCategoryID);
                  m_iItems = ldbeSubCategory.iterator();
                }
              }
              else {
                List ldbeSubCategory = (List) m_mpldbeSubCategories.get (sSubCategoryID);
                m_iItems = ldbeSubCategory.iterator();
              }
            }
          } while (m_iItems.hasNext());

          return false;
        }

        private boolean checkItems (boolean fGet) {
          if (m_dbeNext != null) {
            if (fGet) {
              m_dbeCurrent = m_dbeNext;
              m_dbeNext = null;
            }

            return true;
          }

          while (m_iItems.hasNext()) {
            DataBasketEntry dbe = (DataBasketEntry) m_iItems.next();

            if ((dbe.isHandled()) &&
                (!fShowHandled)) {
              continue;
            }

            if (dbc != null) {
              if ((dbc.getSource() != null) &&
                  (dbc.getSource() != dbe.getSource())) {
                continue;
              }

              if ((dbc.getDestination() != null) &&
                  (dbc.getDestination() != dbe.getDestination())) {
                continue;
              }

              if (((dbc.getValue() != null) &&
                   (dbc.getValue() == dbe.getValue())) ||
                  ((dbc.getValue() == null) &&
                   (dbc.match (dbe)))) {

                if (!fGet) {
                  m_dbeNext = dbe;
                }
                else {
                  m_dbeCurrent = dbe;
                }

                return true;
              }
            }
            else {
              if (!fGet) {
                m_dbeNext = dbe;
              }
              else {
                m_dbeCurrent = dbe;
              }

              return true;
            }
          }

          return false;
        }
      }

      return new I (m_mpmpldbeCategories);
    }

    /**
      * Sum up the values of all entries in this subbasket that match the condition.
      *
      * @param dbc the condition to be matched.
      * @param bev an helper object used to determine the value of each matching DataBasketEntry.
      * @param vInit the value that is to be used for adding up. All adding is performed by calling
      * {@link Value#addAccumulating} on this object.
      *
      * @return the sum in <code>vInit</code>.
      *
      * @override Never
      */
    public Value sumSubBasket (DataBasketCondition dbc, BasketEntryValue bev, Value vInit) {
      for (Iterator i = iterator (dbc, false, false); i.hasNext();) {
        vInit.addAccumulating ((Value) bev.getEntryValue ((DataBasketEntry) i.next()).clone());
      }

      return vInit;
    }

    /**
      * Put a {@link DataBasketEntry} into the subbasket.
      *
      * @param dbe the entry to be put
      *
      * @see DataBasketImpl#put
      *
      * @override Never
      */
    public void put (DataBasketEntryImpl dbe) {
      Map mpldbeCategory = (Map) m_mpmpldbeCategories.get (dbe.getMainKey());

      if (mpldbeCategory == null) {
        mpldbeCategory = new HashMap();
        m_mpmpldbeCategories.put (dbe.getMainKey(), mpldbeCategory);
      }

      List ldbeSubCategory = (List) mpldbeCategory.get (dbe.getSecondaryKey());

      if (ldbeSubCategory == null) {
        ldbeSubCategory = new LinkedList();
        mpldbeCategory.put (dbe.getSecondaryKey(), ldbeSubCategory);
      }

      ldbeSubCategory.add (dbe);

      dbe.setOwner (m_dbiOwner);

      m_dbiOwner.log (PUT_ACTION, dbe);
      m_dbiOwner.fireDBEAdded (dbe);
    }

    /**
      * Get the first entry in this subbasket that matches the condition, if any.
      *
      * @param dbc the condition to be matched
      *
      * @return the matching entry, if any.
      *
      * @see DataBasketImpl#get
      *
      * @override Never
      */
    public DataBasketEntry get (DataBasketCondition dbc) {
      Iterator i = iterator (dbc, false, false);

      if (i.hasNext()) {
        return (DataBasketEntry) i.next();
      }
      else {
        return null;
      }
    }
  }

  /**
    * The subbaskets of this DataBasket.
    *
    * @serial
    */
  protected Map m_mpsdbChildren = new HashMap();

  /**
    * The monitor used to synchronize access to the list of children.
    */
  private transient Object m_oChildrenLock;

  /**
    * Return the monitor used to synchronize access to the list of children.
    *
    * @override Never
    */
  protected final Object getChildrenLock() {
    if (m_oChildrenLock == null) {
      m_oChildrenLock = new Object();
    }

    return m_oChildrenLock;
  }

  /**
    * The current subbasket.
    *
    * @serial
    */
  private SubDataBasket m_sdbCurrent;

  /**
    * The monitor used to synchronize access to the current subbasket.
    */
  private transient Object m_oCurrentLock;

  /**
    * Return the monitor used to synchronize access to the current subbasket.
    *
    * @override Never
    */
  private final Object getCurrentLock() {
    if (m_oCurrentLock == null) {
      m_oCurrentLock = new Object();
    }

    return m_oCurrentLock;
  }

  /**
    * The current log context.
    *
    * @serial
    */
  private LogContext m_lcLog;

  /**
    * The current log mode.
    *
    * @serial
    */
  private int m_nLogMode = LOG_MODE_NONE;

  /**
    * The monitor synchronizing access to the log related attributes.
    */
  private transient Object m_oLogLock;

  /**
    * Get the monitor synchronizing access to the log related attributes.
    *
    * @override Never
    */
  private final Object getLogLock() {
    if (m_oLogLock == null) {
      m_oLogLock = new Object();
    }

    return m_oLogLock;
  }

  /**
    * The listeners currently listening for events from this DataBasket.
    *
    * @serial
    */
  protected ListenerHelper m_lhListeners = new ListenerHelper();

  /**
    * Create a new DataBasketImpl.
    */
  public DataBasketImpl() {
    super();

    setCurrentSubBasket (DEFAULTSUBBASKET_NAME);
  }

  /**
    * Rollback the contents of all subbaskets of this DataBasket.
    *
    * @override Never
    */
  public void rollback() {
    rollback ((DataBasketCondition) null);
  }

  /**
    * Commit the contents of all subbaskets of this DataBasket.
    *
    * @override Never
    */
  public void commit() {
    commit ((DataBasketCondition) null);
  }

  /**
    * Rollback all items in all subbaskets that do match the given condition.
    *
    * @param dbc the condition to be matched. <code>null</code> means rollback
    * unconditionally.
    *
    * @override Never
    */
  public void rollback (DataBasketCondition dbc) {
    synchronized (getChildrenLock()) {
      synchronized (getLogLock()) {
        for (Iterator i = m_mpsdbChildren.values().iterator(); i.hasNext();) {
          ((SubDataBasket) i.next()).rollback (dbc);
        }

        clean();
      }
    }
  }

  /**
    * Commit all items in all subbaskets that do match the given condition.
    *
    * @param dbc the condition to be matched. <code>null</code> means commit
    * unconditionally.
    *
    * @override Never
    */
  public void commit (DataBasketCondition dbc) {
    synchronized (getChildrenLock()) {
      synchronized (getLogLock()) {
        for (Iterator i = m_mpsdbChildren.values().iterator(); i.hasNext();) {
          ((SubDataBasket) i.next()).commit (dbc);
        }

        clean();
      }
    }
  }

  /**
    * Rollback all entries in a given subbasket.
    *
    * @param sName the name of the subbasket.
    *
    * @override Never
    */
  public void rollbackSubBasket (String sName) {
    SubDataBasket sdb = null;

    synchronized (getChildrenLock()) {
      sdb = (SubDataBasket) m_mpsdbChildren.get (sName);

      synchronized (getLogLock()) {
        if (sdb != null) {
          sdb.rollback (null);
        }

        clean();
      }
    }
  }

  /**
    * Rollback the current subbasket's contents.
    *
    * @override Never
    */
  public void rollbackCurrentSubBasket() {
    SubDataBasket sdbCurrent = null;

    synchronized (getCurrentLock()) {
      sdbCurrent = m_sdbCurrent;
    }

    if (sdbCurrent != null) {
      synchronized (getChildrenLock()) {
        synchronized (getLogLock()) {
          sdbCurrent.rollback (null);

          clean();
        }
      }
    }
  }

  /**
    * Commit all entries in a given subbasket.
    *
    * @param sName the name of the subbasket.
    *
    * @override Never
    */
  public void commitSubBasket (String sName) {
    SubDataBasket sdb = null;

    synchronized (getChildrenLock()) {
      sdb = (SubDataBasket) m_mpsdbChildren.get (sName);

      synchronized (getLogLock()) {
        if (sdb != null) {
          sdb.commit (null);
        }

        clean();
      }
    }
  }

  /**
    * Commit the current subbasket's contents.
    *
    * @override Never
    */
  public void commitCurrentSubBasket() {
    SubDataBasket sdbCurrent = null;

    synchronized (getCurrentLock()) {
      sdbCurrent = m_sdbCurrent;
    }

    if (sdbCurrent != null) {
      synchronized (getChildrenLock()) {
        synchronized (getLogLock()) {
          sdbCurrent.commit (null);

          clean();
        }
      }
    }
  }

  /**
    * Set the current subbasket.
    *
    * @param sName the name of the new current subbasket. If the subbasket does not yet exist, it
    * is created prior to being made the current subbasket.
    *
    * @override Never
    */
  public void setCurrentSubBasket(String sName) {
    synchronized (getChildrenLock()) {
      if (!m_mpsdbChildren.containsKey (sName)) {
        SubDataBasket sdb = new SubDataBasket (this);
        m_mpsdbChildren.put (sName, sdb);
      }

      synchronized (getCurrentLock()) {
        m_sdbCurrent = (SubDataBasket) m_mpsdbChildren.get (sName);
      }
    }
  }

  /**
    * Iterate all entries in all subbaskets that match the condition.
    *
    * @param dbc the condition to be matched.
    *
    * @return an iterator that iterates all the entries in the DataBasket that match the condition. The
    * iterator will not support the {@link java.util.Iterator#remove remove()} method.
    *
    * @override Never
    */
  public Iterator iterator (final DataBasketCondition dbc) {
    class I implements Iterator {
      private Iterator m_iChildKeys;
      private Iterator m_iSubBasketItems;
      private DataBasket m_dbSource;

      public I (Iterator iChildKeys,
                DataBasket dbSource) {
        super();

        m_iChildKeys = iChildKeys;
        m_dbSource = dbSource;
      }

      public boolean hasNext() {
        return findNextItem();
      }

      public Object next() {
        if (!findNextItem()) {
          throw new NoSuchElementException();
        }

        return m_iSubBasketItems.next();
      }

      public void remove() {
        throw new UnsupportedOperationException();
      }

      private boolean findNextItem() {
        while (((m_iSubBasketItems == null) ||
                (!m_iSubBasketItems.hasNext())) &&
               (m_iChildKeys.hasNext())){
          // try next subbasket
          String sNextBasket = (String) m_iChildKeys.next();
          m_iSubBasketItems = m_dbSource.subBasketIterator (sNextBasket, dbc);
        }

        if ((m_iSubBasketItems == null) ||
            (!m_iSubBasketItems.hasNext())) {
          // did not find valid next subbasket
          return false;
        }

        return true;
      }
    }

    return new I (m_mpsdbChildren.keySet().iterator(), this);
  }

  /**
    * Iterate all entries in a given subbasket that match the given condition.
    *
    * @param dbc the condition to be matched.
    *
    * @return an iterator that iterates all the entries in the given subbasket that match the condition. The
    * iterator will not support the {@link java.util.Iterator#remove remove()} method.
    *
    * @override Never
    */
  public Iterator subBasketIterator (String sName, DataBasketCondition dbc) {
    synchronized (getChildrenLock()) {
      SubDataBasket sdb = (SubDataBasket) m_mpsdbChildren.get (sName);

      return sdb.iterator (dbc, false, false);
    }
  }

  /**
    * Sum up all entries in the DataBasket that match the given condition.
    *
    * @param dbc the condition to be matched.
    * @param bev an object used for determining the value of a {@link DataBasketEntry}.
    * @param vInit the value that is to be used for adding up. All adding is performed by calling
    * {@link Value#addAccumulating} on this object.
    *
    * @return the sum in <code>vInit</code>.
    *
    * @override Never
    */
  public Value sumBasket (DataBasketCondition dbc, BasketEntryValue bev, Value vInit) {
    synchronized (getChildrenLock()) {
      for (Iterator i = m_mpsdbChildren.values().iterator(); i.hasNext();) {
        SubDataBasket sdb = (SubDataBasket) i.next();
        sdb.sumSubBasket (dbc, bev, vInit);
      }
    }

    return vInit;
  }

  /**
    * Sum up all entries in a given subbasket that match the given condition.
    *
    * @param sName the name of the subbasket that is to be summed up.
    * @param dbc the condition to be matched.
    * @param bev an object used for determining the value of a {@link DataBasketEntry}.
    * @param vInit the value that is to be used for adding up. All adding is performed by calling
    * {@link Value#addAccumulating} on this object.
    *
    * @return the sum in <code>vInit</code>.
    *
    * @override Never
    */
  public Value sumSubBasket (String sName, DataBasketCondition dbc, BasketEntryValue bev, Value vInit) {
    SubDataBasket sdb = null;

    synchronized (getChildrenLock()) {
      sdb = (SubDataBasket) m_mpsdbChildren.get (sName);

      return sdb.sumSubBasket (dbc, bev, vInit);
    }
  }

  /**
    * Sum up all entries in the current subbasket that match the given condition.
    *
    * @param dbc the condition to be matched.
    * @param bev an object used for determining the value of a {@link DataBasketEntry}.
    * @param vInit the value that is to be used for adding up. All adding is performed by calling
    * {@link Value#addAccumulating} on this object.
    *
    * @return the sum in <code>vInit</code>.
    *
    * @override Never
    */
  public Value sumCurrentSubBasket (DataBasketCondition dbc, BasketEntryValue bev, Value vInit) {
    synchronized (getCurrentLock()) {
      return m_sdbCurrent.sumSubBasket (dbc, bev, vInit);
    }
  }

  /**
    * Put a DataBasketEntry into the current subbasket. DataBasketEntries that are to be put into a
    * DataBasketImpl must be instances of, or of subclasses of, {@link DataBasketEntryImpl}.
    *
    * @param dbe the entry to be put in.
    *
    * @exception ClassCastException if <code>! (dbe instanceof {@link DataBasketEntryImpl})</code>.
    *
    * @override Never
    */
  public void put (DataBasketEntry dbe) {
    synchronized (getCurrentLock()) {
      synchronized (getChildrenLock()) {
        synchronized (getLogLock()) {
          m_sdbCurrent.put ((DataBasketEntryImpl) dbe);
        }
      }
    }
  }

  /**
    * Exchange a DataBasketEntry with another.
    *
    * @param dbeOrg the original DataBasketEntry, to be replaced.
    * @param dbeNew the replacement.
    *
    * @override Never
    */
  public void exchange (final DataBasketEntry dbeOrg, DataBasketEntry dbeNew) {
    DataBasketCondition dbc = new DataBasketConditionImpl (dbeOrg.getMainKey(),
                                                           dbeOrg.getSecondaryKey(),
                                                           dbeOrg.getSource(),
                                                           dbeOrg.getDestination(),
                                                           null) {
      public boolean match (DataBasketEntry dbe) {
        return (dbe == dbeOrg);
      }
    };

    synchronized (getChildrenLock()) {
      synchronized (getLogLock()) {
        for (Iterator i = m_mpsdbChildren.values().iterator(); i.hasNext();) {
          SubDataBasket sdb = (SubDataBasket) i.next();

          Iterator i1 = sdb.iterator (dbc, true, false);
          if (i1.hasNext()) {
            DataBasketEntryImpl dbe = (DataBasketEntryImpl) i1.next();

            i1.remove();

            log (EXCHANGE_REMOVE_ACTION, dbe);
            sdb.put ((DataBasketEntryImpl) dbeNew);

            return;
          }
        }

        put (dbeNew);
      }
    }
  }

  /**
    * Get the first entry in the DataBasket that matches the condition.
    *
    * @param dbc the condition
    *
    * @override Never
    */
  public DataBasketEntry get (DataBasketCondition dbc) {
    synchronized (getChildrenLock()) {
      Iterator i = iterator (dbc);
      if (i.hasNext()) {
        return (DataBasketEntry) i.next();
      }
      else {
        return null;
      }
    }
  }

  /**
    * Check whether any entries matching a particular condition are contained in the DataBasket.
    *
    * @param dbc the condition
    *
    * @return true if an entry that matches the condition could be found.
    *
    * @override Never
    */
  public boolean contains (DataBasketCondition dbc) {
    synchronized (getChildrenLock()) {
      return (iterator (dbc).hasNext());
    }
  }

  /**
    * Set the log context for this DataBasket.
    *
    * <p>All operations as defined through {@link #setLogMode} will be logged using the given log context. If
    * the current log context is <code>null</code> no logging of any kind will occur.</p>
    *
    * <p>The actual LogEntries written are defined by the {@link DataBasketEntryImpl DataBasketEntries} that
    * participate in the activities that are logged. The DataBasket will wrap those LogEntries into LogEntries
    * that give the type of operation performed.</p>
    *
    * @param lcNew the new log context
    *
    * @return the previous log context, if any.
    *
    * @see #log
    *
    * @override Never
    */
  public LogContext setLogContext (LogContext lcNew) {
    synchronized (getLogLock()) {
      LogContext lc = m_lcLog;

      m_lcLog = lcNew;

      return lc;
    }
  }

  /**
    * Set the log mode for this DataBasket.
    *
    * <p>The current log mode decides what operations on the DataBasket are being logged. The default value is
    * {@link DataBasket#LOG_MODE_NONE}, indicating that no logging occurs. Other possibilities are:</p>
    *
    * <ul>
    *   <li><strong>{@link DataBasket#LOG_MODE_ALL}</strong> All operations on the DataBasket are being logged.
    *   </li>
    *   <li><strong>{@link DataBasket#LOG_MODE_COMMITS_ONLY}</strong> Only commits are being logged. There will
    *       be one entry for each single step in the commit process.</li>
    *   <li><strong>{@link DataBasket#LOG_MODE_ROLLBACKS_ONLY}</strong> Only rollbacks are being logged. There
    *       will be one entry for each single step in the rollback process.</li>
    * </ul>
    *
    * <p>The actual LogEntries written are defined by the {@link DataBasketEntryImpl DataBasketEntries} that
    * participate in the activities that are logged. The DataBasket will wrap those LogEntries into LogEntries
    * that give the type of operation performed.</p>
    *
    * @param nLogMode the new log mode.
    *
    * @return the previous log mode.
    *
    * @see #log
    *
    * @override Never
    */
  public int setLogMode (int nLogMode) {
    synchronized (getLogLock()) {
      int n = m_nLogMode;

      m_nLogMode = nLogMode;

      return n;
    }
  }

  /**
    * Return the current log mode of the DataBasket. For information on the possible values and their meaning,
    * please refer to {@link #setLogMode}.
    *
    * @override Never
    */
  public int getLogMode() {
    synchronized (getLogLock()) {
      return m_nLogMode;
    }
  }

  /**
    * Action constant for {@link #log}.
    */
  public static final int PUT_ACTION = 0;

  /**
    * Action constant for {@link #log}.
    */
  public static final int EXCHANGE_REMOVE_ACTION = 1;

  /**
    * Action constant for {@link #log}.
    */
  public static final int COMMIT_ACTION = 2;

  /**
    * Action constant for {@link #log}.
    */
  public static final int ROLLBACK_ACTION = 3;

  /**
    * A LogEntryFilter that will accept only LogEntries that were produced by a DataBasketImpl.
    */
  public static final LogEntryFilter LOGENTRYFILTER_DATABASKETIMPLACTIONS = new LogEntryFilter() {
    public boolean accept (LogEntry le) {
      return (le instanceof DataBasketImplLogEntry);
    }
  };

  /**
    * A LogEntryFilter that will accept only LogEntries that were not produced by a DataBasketImpl.
    */
  public static final LogEntryFilter LOGENTRYFILTER_NO_DATABASKETIMPLACTIONS = new LogEntryFilter() {
    public boolean accept (LogEntry le) {
      return !(le instanceof DataBasketImplLogEntry);
    }
  };

  /**
    * A LogEntryFilter that will accept only LogEntries that were produced by a DataBasketImpl and that
    * describe a <code>put</code> action.
    */
  public static final LogEntryFilter LOGENTRYFILTER_PUT_ACTIONS = new LogEntryFilter() {
    public boolean accept (LogEntry le) {
      return ((le instanceof DataBasketImplLogEntry) &&
              (((DataBasketImplLogEntry) le).getAction() == PUT_ACTION));
    }
  };

  /**
    * A LogEntryFilter that will accept only LogEntries that were produced by a DataBasketImpl and that
    * describe a <code>exchange_remove</code> action.
    */
  public static final LogEntryFilter LOGENTRYFILTER_EXCHANGE_REMOVE_ACTIONS = new LogEntryFilter() {
    public boolean accept (LogEntry le) {
      return ((le instanceof DataBasketImplLogEntry) &&
              (((DataBasketImplLogEntry) le).getAction() == EXCHANGE_REMOVE_ACTION));
    }
  };

  /**
    * A LogEntryFilter that will accept only LogEntries that were produced by a DataBasketImpl and that
    * describe a <code>commit</code> action.
    */
  public static final LogEntryFilter LOGENTRYFILTER_COMMIT_ACTIONS = new LogEntryFilter() {
    public boolean accept (LogEntry le) {
      return ((le instanceof DataBasketImplLogEntry) &&
              (((DataBasketImplLogEntry) le).getAction() == COMMIT_ACTION));
    }
  };

  /**
    * A LogEntryFilter that will accept only LogEntries that were produced by a DataBasketImpl and that
    * describe a <code>rollback</code> action.
    */
  public static final LogEntryFilter LOGENTRYFILTER_ROLLBACK_ACTIONS = new LogEntryFilter() {
    public boolean accept (LogEntry le) {
      return ((le instanceof DataBasketImplLogEntry) &&
              (((DataBasketImplLogEntry) le).getAction() == ROLLBACK_ACTION));
    }
  };

  /**
    * A LogEntry that describes an action on a DataBasket.
    *
    * @see DataBasketImpl#log
    *
    * @author Steffen Zschaler
    * @version 2.0 14/07/1999
    * @since v2.0
    */
  public static class DataBasketImplLogEntry extends LogEntry {

    /**
      * The log entry describing the actual action.
      *
      * @serial
      */
    private LogEntry m_leData;

    /**
      * The action code. One of PUT_ACTION, EXCHANGE_REMOVE_ACTION, COMMIT_ACTION, ROLLBACK_ACTION.
      *
      * @serial
      */
    private int m_nAction;

    /**
      * Helper array for converting action codes into Strings.
      */
    private static String[] s_asActionNames = {"PUT_ACTION",
                                               "EXCHANGE_REMOVE_ACTION",
                                               "COMMIT_ACTION",
                                               "ROLLBACK_ACTION"};

    /**
      * Create a new DataBasketImplLogEntry.
      *
      * @param The action code. One of {@link DataBasketImpl#PUT_ACTION},
      * {@link DataBasketImpl#EXCHANGE_REMOVE_ACTION}, {@link DataBasketImpl#COMMIT_ACTION},
      * {@link DataBasketImpl#ROLLBACK_ACTION}.
      * @param leData The log entry describing the actual action.
      */
    public DataBasketImplLogEntry (int nAction,
                                   LogEntry leData) {
      super();

      m_nAction = nAction;
      m_leData = leData;
    }

    /**
      * Get the action code.
      *
      * @override Never
      */
    public int getAction() {
      return m_nAction;
    }

    /**
      * Get the name of the action.
      *
      * @override Never
      */
    public String getActionName() {
      return s_asActionNames[getAction()];
    }

    /**
      * Get the log entry describing the actual action.
      *
      * @override Never
      */
    public LogEntry getData() {
      return m_leData;
    }

    /**
      * A short descriptive text of the log entry.
      *
      * @override Never
      */
    public String toString() {
      return getActionName() + " performed on " + getData();
    }
  }

  /**
    * Log the given event wrapped in a LogEntry that describes the action.
    *
    * <p>If the action needs to be logged in the current log mode, a new {@link DataBasketImplLogEntry} is
    * created that wraps the LogEntry produced by the given Loggable. This LogEntry is then logged using the
    * current LogContext.</p>
    *
    * @see #setLogContext
    * @see #setLogMode
    *
    * @override Never
    */
  public void log (int nAction,
                   Loggable la) {
    synchronized (getLogLock()) {
      if (m_lcLog != null) {
        switch (m_nLogMode) {
        case LOG_MODE_NONE:
          return;
        case LOG_MODE_COMMITS_ONLY:
          if (nAction != COMMIT_ACTION) {
            return;
          }

          break;
        case LOG_MODE_ROLLBACKS_ONLY:
          if (nAction != ROLLBACK_ACTION) {
            return;
          }
        }

        final DataBasketImplLogEntry dbile = new DataBasketImplLogEntry (nAction, la.getLogData());

        try {
          m_lcLog.log (new Loggable() {
            public LogEntry getLogData() {
              return dbile;
            }
          });
        }
        catch (java.io.IOException ioe) {
          ioe.printStackTrace();
        }
      }
    }
  }

  /**
    * Return the monitor used to synchronize access to the internal data structures of the DataBasket. Whenever
    * a container wants to access a DataBasket implemented in this way, it must <i>first</i> synchronize over
    * the DataBasket's monitor and <i>then</i> synchronize over its own monitor(s).
    *
    * @override Never
    */
  public Object getDBIMonitor() {
    return getChildrenLock();
  }

  /**
    * Remove any items that have already been {@link DataBasketEntry#isHandled handled}, but so far have not
    * been removed from the DataBasket.
    *
    * <p>This method is usually called by the framework only. It assumes, that it is called from within a
    * <code>synchronized</code> block synchronizing on the monitor returned by {@link #getDBIMonitor}.</p>
    *
    * @override Never
    */
  protected void clean() {
    // Remove any items that where handled because of dependencies
    for (Iterator i = m_mpsdbChildren.values().iterator(); i.hasNext();) {
      Iterator j = ((SubDataBasket) i.next()).iterator (null, true, true);

      while (j.hasNext()) {
        if (((DataBasketEntry) j.next()).isHandled()) {
          j.remove();
        }
      }
    }
  }

  // ListenableDataBasket interface methods

  /**
    * Add a listener that will receive events when the DataBasket's contents change.
    *
    * @override Never
    */
  public void addDataBasketListener (DataBasketListener dbl) {
    m_lhListeners.add (DataBasketListener.class, dbl);
  }

  /**
    * Remove a listener that received events when the DataBasket's contents changed.
    *
    * @override Never
    */
  public void removeDataBasketListener (DataBasketListener dbl) {
    m_lhListeners.remove (DataBasketListener.class, dbl);
  }

  /**
    * Fire an event to all listeners listening to this DataBasket.
    *
    * @override Never
    */
  public void fireDataBasketChanged() {
    Object[] listeners = m_lhListeners.getListenerList();
    DataBasketEvent e = null;

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == DataBasketListener.class) {
        if (e == null) {
          e = new DataBasketEvent (this, null);
        }

        ((DataBasketListener) listeners[i+1]).dataBasketChanged (e);
      }
    }
  }

  /**
    * Fire an event to all listeners listening to this DataBasket.
    *
    * @override Never
    */
  protected void fireDBEAdded (DataBasketEntry dbe) {
    Object[] listeners = m_lhListeners.getListenerList();
    DataBasketEvent e = null;

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == DataBasketListener.class) {
        if (e == null) {
          e = new DataBasketEvent (this, dbe);
        }

        ((DataBasketListener) listeners[i+1]).addedDBE (e);
      }
    }
  }

  /**
    * Fire an event to all listeners listening to this DataBasket.
    *
    * @override Never
    */
  protected void fireDBERemoved (DataBasketEntry dbe) {
    Object[] listeners = m_lhListeners.getListenerList();
    DataBasketEvent e = null;

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == DataBasketListener.class) {
        if (e == null) {
          e = new DataBasketEvent (this, dbe);
        }

        ((DataBasketListener) listeners[i+1]).removedDBE (e);
      }
    }
  }
}