package data.ooimpl;

import java.util.*;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;

import java.io.*;

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

import util.*;

/**
  * Pure Java implementation of the {@link Stock} interface.
  *
  * <p>StockImpl Stocks can only work together with {@link CatalogImpl} {@link Catalog Catalogs},
  * {@link StockItemImpl} {@link StockItem StockItems} and {@link DataBasketImpl}
  * {@link DataBasket DataBaskets}.</p>
  *
  * @author Steffen Zschaler
  * @version 2.0 19/08/1999
  * @since v2.0
  */
public abstract class StockImpl extends StockItemImpl implements Stock,
                                                                 ListenableStock,
                                                                 NameContext,
                                                                 SelfManagingDBESource,
                                                                 SelfManagingDBEDestination {

  /**
    * The Catalog that is associated to this Stock.
    *
    * @serial
    */
  protected CatalogImpl m_ciCatalog;

  /**
    * The DataBasket that determines the visibility of the catalog associated with this Stock.
    *
    * <p>If <code>null</code>, the associated Catalog is visible with any DataBasket, otherwise it
    * is only visible with the DataBasket specified in this field. Requests for the Catalog using
    * a different DataBasket will the be rejected by throwing a DataBasketConflictException.</p>
    *
    * @serial
    */
  protected DataBasket m_dbCatalogValidator;
  
  /**
    * The listeners listening for events from this Stock.
    *
    * @serial
    */
  protected ListenerHelper m_lhListeners = new ListenerHelper();

  /**
    * The map of items that are actually contained in the Stock.
    *
    * @serial
    */
  private Map m_mpItems = new HashMap();

  /**
    * The map of items that have been temporaryly added to the Stock.
    *
    * @serial
    */
  private Map m_mpTemporaryAddedItems = new HashMap();

  /**
    * The map of items that have been temporaryly removed from the Stock.
    *
    * @serial
    */
  private Map m_mpTemporaryRemovedItems = new HashMap();

  /**
    * The map of items that are currently being edited.
    *
    * @serial
    */
  private Map m_mpEditingItems = new HashMap();

  /**
    * The map of items that have been removed from the Stock to ensure referential integrity.
    *
    * @serial
    */
  private Map m_mpRefIntegrItems = new HashMap();

  /**
    * A map storing information about name changes in CatalogItems.
    *
    * <p>Key: new name.<br>
    * Value: old name.</p>
    *
    * <p>This map is needed for referential integrity.</p>
    *
    * @serial
    */
  private Map m_mpRefIntegrEdit = new HashMap();     // stores information about name changes in CatalogItems: key: new name; entry: old name

  /**
    * The monitor synchronizing access to the Stock's contents.
    */
  private transient Object m_oItemsLock;

  /**
    * Get the monitor synchronizing access to the Stock's contents.
    *
    * @override Never
    */
  protected final Object getItemsLock() {
    if (m_oItemsLock == null) {
      m_oItemsLock = new Object();
    }

    return m_oItemsLock;
  }

  /**
    * PropertyChangeListener that reacts to name changes in CatalogItems that are currently being edited.
    * Updates {@link #getRefIntegrEditContainer}.
    *
    * @author Steffen Zschaler
    * @version 2.0 19/08/1999
    * @since v2.0
    */
  class CatalogItemNameListener implements PropertyChangeListener, SerializableListener {
    /**
      * @override Never Instead override {@link #nameChangeOccurred}.
      */
    public void propertyChange (PropertyChangeEvent e) {
      if (e.getPropertyName().equals (NAME_PROPERTY)) {
        synchronized (getItemsLock()) {
          String sOld = (String) e.getOldValue();
          String sNew = (String) e.getNewValue();

          nameChangeOccurred (sOld, sNew);
        }
      }
    }

    /**
      * Notification that a CatalogItem's name changed.
      *
      * <p>This will update all data structures necessary to ensure Stock and Catalog are properly linked
      * together.</p>
      *
      * @override Sometimes
      */
    protected void nameChangeOccurred (String sOld,
                                       String sNew) {
      Object oData = getTemporaryAddedItemsContainer().remove (sOld);
      if (oData != null) {
        getTemporaryAddedItemsContainer().put (sNew, oData);
      }

      oData = getItemsContainer().remove (sOld);
      if (oData != null) {
        getItemsContainer().put (sNew, oData);
      }

      oData = getTemporaryRemovedItemsContainer().remove (sOld);
      if (oData != null) {
        getTemporaryRemovedItemsContainer().put (sNew, oData);
      }

      oData = getRefIntegrItemsContainer().remove (sOld);
      if (oData != null) {
        getRefIntegrItemsContainer().put (sNew, oData);
      }

      getRefIntegrEditContainer().put (sNew, getRefIntegrEditContainer().remove (sOld));
    }
  }

  /**
    * The listener that listens to name changes in CatalogItems in the associated Catalog.
    *
    * @serial
    */
  protected CatalogItemNameListener m_cinlCatalogItemNameListener = new CatalogItemNameListener();

  /**
    * Listens for editing events from the Catalog. Installs {@link #m_cinlCatalogItemNameListener} if
    * necessary. Also prevents editing, if there are StockItems in DataBaskets, so that we don't get any
    * problems with name changes. Note however, that it might be possible to first edit a CatalogItem and then
    * remove corresponding StockItems which might cause the same problem! This is not provided for in the
    * Framework.
    *
    * @serial
    */
  protected final CatalogChangeListener m_cclEditListener = new CatalogChangeAdapter() {
    public void canEditCatalogItem (CatalogChangeEvent e)
      throws VetoException {
      synchronized (getItemsLock()) {
        String sKey = e.getAffectedItem().getName();

        if ((getTemporaryAddedItemsContainer().containsKey (sKey)) ||
            (getTemporaryRemovedItemsContainer().containsKey (sKey))) {
          throw new VetoException ("Stock \"" + getName() + "\": Having StockItems with key \"" + sKey + "\" in DataBaskets.");
        }
      }
    }
    public void editingCatalogItem (CatalogChangeEvent e) {
      synchronized (getItemsLock()) {
        String sKey = e.getAffectedItem().getName();
        getRefIntegrEditContainer().put (sKey, sKey);

        ((CatalogItemImpl) e.getAffectedItem()).addNameListener (m_cinlCatalogItemNameListener);
      }
    }

    public void rollbackEditCatalogItem (CatalogChangeEvent e) {
      synchronized (getItemsLock()) {
        String sCurKey = e.getAffectedItem().getName();
        String sKey = (String) getRefIntegrEditContainer().remove (sCurKey);

        Object oData = getTemporaryAddedItemsContainer().remove (sCurKey);
        if (oData != null) {
          getTemporaryAddedItemsContainer().put (sKey, oData);
        }

        oData = getItemsContainer().remove (sCurKey);
        if (oData != null) {
          getItemsContainer().put (sKey, oData);
        }

        oData = getTemporaryRemovedItemsContainer().remove (sCurKey);
        if (oData != null) {
          getTemporaryRemovedItemsContainer().put (sKey, oData);
        }

        oData = getRefIntegrItemsContainer().remove (sCurKey);
        if (oData != null) {
          getRefIntegrItemsContainer().put (sKey, oData);
        }

        ((CatalogItemImpl) e.getAffectedItem()).removeNameListener (m_cinlCatalogItemNameListener);
      }
    }

    public void commitEditCatalogItem (CatalogChangeEvent e) {
      synchronized (getItemsLock()) {
        // clean up
        getRefIntegrEditContainer().remove (e.getAffectedItem().getName());

        ((CatalogItemImpl) e.getAffectedItem()).removeNameListener (m_cinlCatalogItemNameListener);
      }
    }
  };

  /**
    * The original Stock, if this is a shallow copy that was created for editing purposes. A SoftReference is
    * used so that it can be removed when the last reference to the creator was deleted.
    */
  private transient java.lang.ref.SoftReference m_srsiEditCreator = null;

  /**
    * Listens to the parent Stock of the creator, if this is a shallow copy that was created for editing
    * purposes. It will cut the connection if the editing is either commited or rolled back.
    *
    * @serial
    */
  private final StockChangeListener m_sclEditingListener = new StockChangeAdapter() {
    public void commitEditStockItems (StockChangeEvent e) {
      if (e.getAffectedKey() == getName()) {
        for (Iterator i = e.getAffectedItems(); i.hasNext();) {
          if (i.next() == StockImpl.this) {
            ((StockImpl) e.getSource()).removeStockChangeListener (this);
            return;
          }
        }
      }
    }

    public void rollbackEditStockItems (StockChangeEvent e) {
      if (e.getAffectedKey() == getName()) {
        for (Iterator i = e.getAffectedItems(); i.hasNext();) {
          if (i.next() == StockImpl.this) {
            ((StockImpl) e.getSource()).removeStockChangeListener (this);

            ((StockImpl) m_srsiEditCreator.get()).removeStockChangeListener (m_sclEditCreatorListener);
            m_srsiEditCreator = null;

            return;
          }
        }
      }
    }
  };

  /**
    * Listens to the creator, if this is a shallow copy that was created for editing purposes. This is to
    * follow with any events that the creator might trigger.
    *
    * @serial
    */
  protected StockChangeListener m_sclEditCreatorListener;

  /**
    * First writes the default serializable data and then the reference to the edit creator, if any.
    */
  private void writeObject (ObjectOutputStream oos) throws IOException {
    oos.defaultWriteObject();

    if (m_srsiEditCreator != null) {
      oos.writeObject (m_srsiEditCreator.get());
    }
    else {
      oos.writeObject (null);
    }
  }

  /**
    * First reads the default serializable data and then re-establishes the reference to the edit creator, if
    * any.
    */
  private void readObject (ObjectInputStream ois) throws IOException, ClassNotFoundException {
    ois.defaultReadObject();

    Object oEditCreator = ois.readObject();

    if (oEditCreator != null) {
      m_srsiEditCreator = new java.lang.ref.SoftReference (oEditCreator);
    }
  }

  /**
    * Create a new StockImpl.
    *
    * @param sName the name of the new Stock.
    * @param ciRef the Catalog that is referenced by this Stock.
    */
  public StockImpl (String sName, CatalogImpl ciRef) {
    super (sName);

    if (ciRef == null)
      throw new NullPointerException ("Catalog must not be null!");

    internalSetCatalog (ciRef);
  }

  /**
    * Set the Catalog that this Stock refers to. This method is only used internally.
    *
    * <p>{@link #m_dbCatalogValidator} will be set to <cod>null</code>.
    *
    * @param ciRef the Catalog to refer to from now on.
    *
    * @override Never
    */
  protected void internalSetCatalog (CatalogImpl ciRef) {
    if (m_ciCatalog != null) {
      m_ciCatalog.removeCatalogChangeListener (m_cclEditListener);
    }

    m_ciCatalog = ciRef;

    if (m_ciCatalog != null) {
      m_ciCatalog.addCatalogChangeListener (m_cclEditListener);
    }
    
    m_dbCatalogValidator = null;
  }

  /**
    * Get the map of items that are actually contained in the Stock.
    *
    * @override Never
    */
  protected Map getItemsContainer() {
    return m_mpItems;
  }

  /**
    * Get the map of items that have been temporaryly added to the Stock.
    *
    * @override Never
    */
  protected Map getTemporaryAddedItemsContainer() {
    return m_mpTemporaryAddedItems;
  }

  /**
    * Get the map of items that have been temporaryly removed from the Stock.
    *
    * @override Never
    */
  protected Map getTemporaryRemovedItemsContainer() {
    return m_mpTemporaryRemovedItems;
  }

  /**
    * Get the map of items that are currently being edited.
    *
    * @override Never
    */
  protected Map getEditingItemsContainer() {
    return m_mpEditingItems;
  }

  /**
    * Get the map of items that have been removed from the Stock to ensure referential integrity.
    *
    * @override Never
    */
  protected Map getRefIntegrItemsContainer() {
    return m_mpRefIntegrItems;
  }

  /**
    * Get the map storing information about name changes in CatalogItems.
    *
    * @override Never
    */
  protected Map getRefIntegrEditContainer() {
    return m_mpRefIntegrEdit;
  }

  /**
    * Set the map of items that are actually contained in the Stock.
    *
    * @override Never
    */
  protected void setItemsContainer (Map mp) {
    m_mpItems = mp;
  }

  /**
    * Set the map of items that have been temporaryly added to the Stock.
    *
    * @override Never
    */
  protected void setTemporaryAddedItemsContainer (Map mp) {
    m_mpTemporaryAddedItems = mp;
  }

  /**
    * Set the map of items that have been temporaryly removed from the Stock.
    *
    * @override Never
    */
  protected void setTemporaryRemovedItemsContainer (Map mp) {
    m_mpTemporaryRemovedItems = mp;
  }

  /**
    * Set the map of items that are currently being edited.
    *
    * @override Never
    */
  protected void setEditingItemsContainer (Map mp) {
    m_mpEditingItems = mp;
  }

  /**
    * Set the map of items that have been removed from the Stock to ensure referential integrity.
    *
    * @override Never
    */
  protected void setRefIntegrItemsContainer (Map mp) {
    m_mpRefIntegrItems = mp;
  }

  /**
    * Set the map storing information about name changes in CatalogItems.
    *
    * @override Never
    */
  protected void setRefIntegrEditContainer (Map mp) {
    m_mpRefIntegrEdit = mp;
  }

  // Stock interface methods

  /**
    * Add the contents of a Stock to this Stock. The method calls {@link Stock#add} for each item in the source
    * Stock so the same constraints apply and the same exceptions may be thrown.
    *
    * @override Never
    *
    * @param st the Stock whose contents is to be added to this Stock.
    * @param db the DataBasket relative to which to perform the actions. <code>addStock</code> will add all
    * items from the source Stock that are visible using this DataBasket. Must be either <code>null</code> or
    * a descendant of {@link DataBasketImpl}.
    * @param fRemove if true, the items will be removed from the source Stock prior to adding them to this
    * Stock. Otherwise, they will be cloned prior to adding them to the Stock.
    */
  public void addStock (Stock st, DataBasket db, boolean fRemove) {
    if (st.getCatalog (db) != getCatalog (db)) {
      throw new CatalogConflictException();
    }

    Object oLock = ((db == null)?(new Object()):(((DataBasketImpl) db).getDBIMonitor()));

    synchronized (oLock) {
      synchronized (getItemsLock()) {
        Object oLock2 = ((st instanceof StockImpl)?(((StockImpl) st).getItemsLock()):(oLock));

        synchronized (oLock2) {
          for (Iterator i = st.iterator (db, false); i.hasNext();) {
            try {
              StockItem si = (StockItem) i.next();

              if (fRemove) {
                i.remove();
              }
              else {
                si = (StockItem) si.clone();
              }

              add (si, db);
            }
            catch (ConcurrentModificationException e) { break; }
            catch (UnsupportedOperationException e) {
              // ignore any items that could not be removed from their source
              continue;
            }
          }
        }
      }
    }
  }

  /**
    * Check whether the Stock contains an item with the given key.
    *
    * <p>Equivalent to:<code>({@link #countItems} (sKey, db) > 0)</code>.</p>
    *
    * @override Never
    *
    * @param sKey the key for which to check containment.
    * @param db the DataBasket used to check visibility. Must be either <code>null</code> or a descendant of
    * {@link DataBasketImpl}.
    */
  public boolean contains (String sKey, DataBasket db) {
    return (countItems (sKey, db) > 0);
  }

  /**
    * Check whether the Stock contains the given StockItem.
    *
    * <p>Return true if the Stock contains a StockItem that is equal to the given one.</p>
    *
    * @param si the StockItem for which to check containment.
    * @param db the DataBasket used to check visibility. Must be either <code>null</code> or a descendant of
    * {@link DataBasketImpl}.
    *
    * @override Never
    */
  public boolean contains (StockItem si, DataBasket db) {
    Object oLock = ((db == null)?(new Object()):(((DataBasketImpl) db).getDBIMonitor()));

    synchronized (oLock) {
      synchronized (getItemsLock()) {
        for (Iterator i = get (si.getName(), db, false); i.hasNext();) {
          StockItem si2 = (StockItem) i.next();

          if (si.equals (si2)) {
            return true;
          }
        }

        return false;
      }
    }
  }

  /**
    * Check whether the given Stock is completely contained in this Stock.
    *
    * <p>Calls {@link #contains(data.StockItem, data.DataBasket)} for each item in the given Stock.</p>
    *
    * @override Never
    *
    * @param st the Stock for which to check containment.
    * @param db the DataBasket used to determine visibility. Must be either <code>null</code> or a descendant of
    * {@link DataBasketImpl}.
    */
  public boolean containsStock (Stock st, DataBasket db) {
    Object oLock = ((db == null)?(new Object()):(((DataBasketImpl) db).getDBIMonitor()));

    synchronized (oLock) {
      synchronized (getItemsLock()) {
        Object oLock2 = ((st instanceof StockImpl)?(((StockImpl) st).getItemsLock()):(oLock));

        synchronized (oLock2) {
          for (Iterator i = st.iterator (db, false); i.hasNext();) {
            if (!contains ((StockItem) i.next(), db)) {
              return false;
            }
          }

          return true;
        }
      }
    }
  }

  /**
    * Iterate all items in the Stock.
    *
    * <p>This method, together with {@link Stock#get} is the only way of accessing the individual
    * {@link StockItem StockItems} contained in a Stock. The iterator will deliver all items that are visible
    * using the given DataBasket. Depending on the <code>fForEdit</code> parameter, the items will be retrieved
    * in different ways. See {@link DataBasket} for an explanation of the different possibilities.</p>
    *
    * <p><code>canEditStockItems</code> and <code>editingStockItems</code> events will be fired if
    * <code>fForEdit</code> == true. {@link VetoException VetoExceptions} will be converted into
    * <code>UnSupportedOperationException</code>s.</p>
    *
    * @override Never
    *
    * @param db the DataBasket relative to which to retrieve the StockItems. Must be either <code>null</code> or a descendant of
    * {@link DataBasketImpl}.
    * @param fForEdit if true, the StockItems will be retrieved for editing.
    */
  public Iterator iterator (final DataBasket db, final boolean fForEdit) {
    class I implements Iterator {
      private StockImpl m_stiOwner;
      private Iterator m_iKeys;
      private Iterator m_iItems;

      public I (StockImpl stiOwner) {
        super();

        m_stiOwner = stiOwner;

        m_iKeys = m_stiOwner.keySet (db).iterator();
      }

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

      public Object next() {
        if (!findNext()) {
          throw new NoSuchElementException ("No more elements in Stock.");
        }

        return m_iItems.next();
      }

      public void remove() {
        if (m_iItems == null) {
          throw new IllegalStateException();
        }

        m_iItems.remove();
      }

      private boolean findNext() {
        if (m_iItems == null) {
          if (m_iKeys.hasNext()) {
            m_iItems = m_stiOwner.get ((String) m_iKeys.next(), db, fForEdit);
          }
          else {
            return false;
          }
        }

        while ((m_iItems.hasNext()) ||
               (m_iKeys.hasNext())) {
          if (m_iItems.hasNext()) {
            return true;
          }

          m_iItems = m_stiOwner.get ((String) m_iKeys.next(), db, fForEdit);
        }

        return false;
      }
    }

    return new I (this);
  }

  /**
    * Return the set of keys for which {@link StockItem StockItems} are visible using the given DataBasket.
    *
    * <p>The returned set is static and gives the state of the Stock at the time of the call. It will not
    * automatically update when the contents of the Stock changes.</p>
    *
    * @param db the DataBasket used for determining visibility. Must be either <code>null</code> or a descendant of
    * {@link DataBasketImpl}.
    *
    * @override Never
    */
  public Set keySet (DataBasket db) {
    Object oLock = ((db == null)?(new Object()):(((DataBasketImpl) db).getDBIMonitor()));

    synchronized (oLock) {
      synchronized (getItemsLock()) {
        Set stKeys = new TreeSet (getItemsContainer().keySet());

        for (Iterator i = getTemporaryAddedItemsContainer().keySet().iterator(); i.hasNext();) {
          String sKey = (String) i.next();

          if ((!stKeys.contains (sKey)) &&
              (countItems (sKey, db) > 0)) {
            stKeys.add (sKey);
          }
        }

        return stKeys;
      }
    }
  }

  /**
    * Sum up the Stock.
    *
    * <p>The method will determine the value of each {@link CatalogItem} in the associated Catalog and
    * {@link Value#multiplyAccumulating(int) multiply} this by
    * {@link Stock#countItems the number of StockItems for the respective key}. These products will be
    * {@link Value#addAccumulating added up} and the resulting total will be returned.</p>
    *
    * @override Never
    *
    * @param db the DataBasket that is used to determine visibility. Must be either <code>null</code> or a
    * descendant of {@link DataBasketImpl}.
    * @param civ the CatalogItemValue used for determining the value of a CatalogItem.
    * @param vInit the initial value. The sum of the Stock will be added to this value.
    *
    * @return the resulting total. Usually the returned object is the same as the one passed as
    * <code>vInit</code>, only with a changed value.
    */
  public Value sumStock (DataBasket db, CatalogItemValue civ, Value vInit) {
    Object oLock = ((db == null)?(new Object()):(((DataBasketImpl) db).getDBIMonitor()));

    synchronized (oLock) {
      synchronized (getItemsLock()) {
        Set stKeys = keySet (db);

        for (Iterator i = stKeys.iterator(); i.hasNext();) {
          String sKey = (String) i.next();

          try {
            vInit.addAccumulating (civ.getValue (getCatalog (db).get (sKey, db, false)).multiply (countItems (sKey, db)));
          }
          catch (VetoException ex) {}
        }

        return vInit;
      }
    }
  }

  /**
    * Increase the {@link #sumStock Stock's value} by a given value.
    *
    * <p>The method will try to break the given value as exactly as possible into StockItems that will be
    * added to the Stock. The actual algorithm used for breaking up the value will be determined by the last
    * parameter. The return value of the method will specify the remaining value that could not be represented
    * by StockItems by the given algorithm.</p>
    *
    * @param db the DataBasket relative to which to perform the operation. Must be either <code>null</code> or
    * a descendant of {@link DataBasketImpl}.
    * @param vTarget the value by which to increase the Stock's total value.
    * @param sfvc the strategy used to fill the Stock.
    *
    * @return the value that remained and could not be represented by StockItems.
    *
    * @override Never
    */
  public Value fillStockWithValue (DataBasket db, Value vTarget, StockFromValueCreator sfvc) {
    Object oLock = ((db != null)?(((DataBasketImpl) db).getDBIMonitor()):(new Object()));

    synchronized (oLock) {
      synchronized (getItemsLock()) {
        return sfvc.fillStock (this, vTarget, db);
      }
    }
  }

  /**
    * Get the size of this Stock. I.e. calculate the number of StockItems that can be seen when using the
    * given DataBasket.
    *
    * @param db the DataBasket used to determine visibility. Must be either <code>null</code> or a descendant of
    * {@link DataBasketImpl}.
    *
    * @override Never
    */
  public int size (DataBasket db) {
    Object oLock = ((db == null)?(new Object()):(((DataBasketImpl) db).getDBIMonitor()));

    synchronized (oLock) {
      synchronized (getItemsLock()) {
        Set stKeys = new HashSet (getItemsContainer().keySet());
        stKeys.addAll (getTemporaryAddedItemsContainer().keySet());

        int nCount = 0;

        for (Iterator i = stKeys.iterator(); i.hasNext();) {
          String sKey = (String) i.next();

          nCount += countItems (sKey, db);
        }

        return nCount;
      }
    }
  }

  /**
    * Get the Catalog associated to this Stock.
    *
    * @override Never
    */
  public Catalog getCatalog (DataBasket db) {
    if ((m_dbCatalogValidator == null) || // All DataBaskets OK
         (m_dbCatalogValidator == db)) {   // Only one specific DataBasket may pass          
      return m_ciCatalog;
    }
    else
      throw new DataBasketConflictException ("Cannot access catalog that is currently being edited in another transaction.");
  }

  /**
    * Create a shallow clone of the Stock. In contrast to a {@link #clone deep clone}, the individual items in
    * the Stock are not cloned.
    *
    * @override Never Instead, override {@link #createPeer} and/or {@link #fillShallowClone}.
    */
  public StockItemImpl getShallowClone() {
    StockImpl sti = createPeer();

    fillShallowClone (sti);

    // Attach as listener to this Stock
    sti.m_srsiEditCreator = new java.lang.ref.SoftReference (this);
    addStockChangeListener (sti.m_sclEditCreatorListener);
    if (getStock() != null) {
      ((StockImpl) getStock()).addStockChangeListener (sti.m_sclEditingListener);
    }

    return sti;
  }

  /**
    * Hook method called to fill the given shallow clone of this Stock.
    *
    * @override Sometimes Normally you do not have to override this method.
    */
  protected void fillShallowClone (StockImpl stiClone) {
    synchronized (getItemsLock()) {
      synchronized (stiClone.getItemsLock()) {
        stiClone.setItemsContainer (new HashMap (getItemsContainer()));
        stiClone.setTemporaryAddedItemsContainer (new HashMap (getTemporaryAddedItemsContainer()));
        stiClone.setTemporaryRemovedItemsContainer (new HashMap (getTemporaryRemovedItemsContainer()));
        stiClone.setEditingItemsContainer (new HashMap (getEditingItemsContainer()));
        stiClone.setRefIntegrItemsContainer (new HashMap (getRefIntegrItemsContainer()));
        stiClone.setRefIntegrEditContainer (new HashMap (getRefIntegrEditContainer()));
      }
    }
  }

  /**
    * Create and return a deep clone of the Stock. In contrast to a {@link #getShallowClone shallow clone}, the
    * individual items themselves are also cloned.
    *
    * @override Never Instead override {@link #createPeer}.
    */
  public Object clone() {
    StockImpl sti = createPeer();

    sti.addStock (this, null, false);

    return sti;
  }

  /**
    * Create an empty Stock with the same name, associated Catalog and class.
    *
    * @override Always
    */
  protected abstract StockImpl createPeer();

  /**
    * Set the Stock that contains this Stock.
    *
    * @override Never
    */
  protected void setStock (StockImpl sti) {
    super.setStock (sti);

    if (sti != null) {
      internalSetCatalog ((CatalogImpl) getAssociatedItem (sti.m_dbCatalogValidator)); //12/11/2000-sz9: Changed to use m_dbCatalogValidator
    }
  }

  /**
    * Compare this StockItem to the given object.
    *
    * @override Always The default implementation will assume <code>o</code> to be a StockItem and will
    * compare the names. Stocks, however, will always be greater than StockItems.
    *
    * @exception ClassCastException if the given object cannot be converted into a {@link StockItem}.
    */
  public int compareTo (Object o) {
    if (o instanceof Stock) {
      return super.compareTo (o);
    }
    else {
      return 1; // Stocks are always greater than StockItems
    }
  }

  // NameContext interface methods
  /**
    * Check a name change of a StockItem that is contained in this Stock.
    *
    * <p>Stocks will not allow name changes of StockItem, as a principle.</p>
    *
    * @override Never
    */
  public void checkNameChange (DataBasket db, String sOldName, String sNewName)
    throws NameContextException {

    throw new NameContextException ("StockItem names cannot be changed when they are part of a Stock.");
  }

  /**
    * Stocks will not allow name changes of StockItem, as a principle.
    *
    * @override Never
    */
  public void nameHasChanged (DataBasket db, String sOldName, String sNewName) {}

  /**
    * Stocks will not allow name changes of StockItem, as a principle.
    *
    * @override Never
    */
  public Object getNCMonitor() {
    return new Object();
  }

  /**
    * Helper method to be called in the beginning of commitAdd and rollbackRemove. Tries to maintain
    * referential integrity by trying to make sure that a CatalogItem exists for the the StockItems that will
    * be brought into the Stock. Must be called from within
    * <code>synchronized ({@link #getItemsLock}) {}</code> before any other operation.
    *
    * @override Never
    */
  protected void prepareReferentialIntegrity (DataBasket db, DataBasketEntry dbe) {
    // try to achieve referential integrity by first trying to make sure, there'll be a CatalogItem for these
    // StockItems in the corresponding Catalog.
    DataBasketCondition dbc = new DataBasketConditionImpl (CatalogItemImpl.CATALOG_ITEM_MAIN_KEY,
                                                           dbe.getSecondaryKey(),
                                                           (CatalogImpl) getCatalog (db),
                                                           null,
                                                           null);
    DataBasketEntry dbeRemovedCI = db.get (dbc);

    dbc =  new DataBasketConditionImpl (CatalogItemImpl.CATALOG_ITEM_MAIN_KEY,
                                        dbe.getSecondaryKey(),
                                        null,
                                        (CatalogImpl) getCatalog (db),
                                        null);
    DataBasketEntry dbeAddedCI = db.get (dbc);

    if (((dbeRemovedCI == null) ||
         (dbeAddedCI == null)) &&
        (dbeRemovedCI != dbeAddedCI)) {

      if (dbeRemovedCI != null) {
        dbeRemovedCI.rollback();
      }
      else {
        dbeAddedCI.commit();
      }
    }
    // if both dbeRemovedCI and dbeAddedCI are not null, we cannot decide which is correct and must therefore
    // live with that bit of inconsistency. However, as long as there's no corresponding CatalogItem, the
    // StockItems will not be visible.
  }

  // ListenableStock interface methods
  /**
    * Add a listener to receive events when the Stock's contents change.
    *
    * @override Never
    */
  public void addStockChangeListener (StockChangeListener scl) {
    m_lhListeners.add (StockChangeListener.class, scl);
  }

  /**
    * Remove a listener that received events when the Stock's contents changed.
    *
    * @override Never
    */
  public void removeStockChangeListener(StockChangeListener scl) {
    m_lhListeners.remove (StockChangeListener.class, scl);
  }

  /**
    * Fire an event to all listeners that showed an interest in this Stock.
    *
    * @override Never
    */
  protected void fireStockItemsAdded (StockChangeEvent e) {
    Object[] listeners = m_lhListeners.getListenerList();

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == StockChangeListener.class) {

        ((StockChangeListener) listeners[i+1]).addedStockItems (e);
      }
    }
  }

  /**
    * Fire an event to all listeners that showed an interest in this Stock.
    *
    * @override Never
    */
  protected void fireStockItemsAddCommit (StockChangeEvent e) {
    Object[] listeners = m_lhListeners.getListenerList();

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == StockChangeListener.class) {

        ((StockChangeListener) listeners[i+1]).commitAddStockItems (e);
      }
    }
  }

  /**
    * Fire an event to all listeners that showed an interest in this Stock.
    *
    * @override Never
    */
  protected void fireStockItemsAddRollback (StockChangeEvent e) {
    Object[] listeners = m_lhListeners.getListenerList();

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == StockChangeListener.class) {

        ((StockChangeListener) listeners[i+1]).rollbackAddStockItems (e);
      }
    }
  }

  /**
    * Fire an event to all listeners that showed an interest in this Stock.
    *
    * @override Never
    */
  protected void fireStockItemsRemoved (StockChangeEvent e) {
    Object[] listeners = m_lhListeners.getListenerList();

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == StockChangeListener.class) {

        ((StockChangeListener) listeners[i+1]).removedStockItems (e);
      }
    }
  }

  /**
    * Fire an event to all listeners that showed an interest in this Stock.
    *
    * @override Never
    */
  protected void fireStockItemsRemoveCommit (StockChangeEvent e) {
    Object[] listeners = m_lhListeners.getListenerList();

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == StockChangeListener.class) {

        ((StockChangeListener) listeners[i+1]).commitRemoveStockItems (e);
      }
    }
  }

  /**
    * Fire an event to all listeners that showed an interest in this Stock.
    *
    * @override Never
    */
  protected void fireStockItemsRemoveRollback (StockChangeEvent e) {
    Object[] listeners = m_lhListeners.getListenerList();

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == StockChangeListener.class) {

        ((StockChangeListener) listeners[i+1]).rollbackRemoveStockItems (e);
      }
    }
  }

  /**
    * Fire an event to all listeners that showed an interest in this Stock.
    *
    * @override Never
    */
  protected void fireCanRemoveStockItems (StockChangeEvent e)
    throws VetoException {
    Object[] temp = m_lhListeners.getListenerList();
    Object[] listeners = new Object[temp.length];
    System.arraycopy (temp, 0, listeners, 0, temp.length);

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == StockChangeListener.class) {
        try {
          ((StockChangeListener) listeners[i+1]).canRemoveStockItems (e);
        }
        catch (VetoException ex) {
          for (int j = i; j < listeners.length; j += 2) {
            if (listeners[j] == CatalogChangeListener.class) {
              ((StockChangeListener) listeners[j + 1]).noRemoveStockItems (e);
            }
          }

          throw ex;
        }
      }
    }
  }

  /**
    * Fire an event to all listeners that showed an interest in this Stock.
    *
    * @override Never
    */
  protected void fireCanEditStockItems (StockChangeEvent e)
    throws VetoException {
    Object[] temp = m_lhListeners.getListenerList();
    Object[] listeners = new Object[temp.length];
    System.arraycopy (temp, 0, listeners, 0, temp.length);

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == StockChangeListener.class) {
        try {
          ((StockChangeListener) listeners[i+1]).canEditStockItems (e);
        }
        catch (VetoException ex) {
          for (int j = i; j < listeners.length; j += 2) {
            if (listeners[j] == CatalogChangeListener.class) {
              ((StockChangeListener) listeners[j + 1]).noRemoveStockItems (e);
            }
          }

          throw ex;
        }
      }
    }
  }

  /**
    * Fire an event to all listeners that showed an interest in this Stock.
    *
    * @override Never
    */
  protected void fireEditingStockItems (StockChangeEvent e) {
    Object[] listeners = m_lhListeners.getListenerList();

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == StockChangeListener.class) {

        ((StockChangeListener) listeners[i+1]).editingStockItems (e);
      }
    }
  }

  /**
    * Fire an event to all listeners that showed an interest in this Stock.
    *
    * @override Never
    */
  protected void fireStockItemsEditCommit (StockChangeEvent e) {
    Object[] listeners = m_lhListeners.getListenerList();

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == StockChangeListener.class) {

        ((StockChangeListener) listeners[i+1]).commitEditStockItems (e);
      }
    }
  }

  /**
    * Fire an event to all listeners that showed an interest in this Stock.
    *
    * @override Never
    */
  protected void fireStockItemsEditRollback (StockChangeEvent e) {
    Object[] listeners = m_lhListeners.getListenerList();

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == StockChangeListener.class) {

        ((StockChangeListener) listeners[i+1]).rollbackEditStockItems (e);
      }
    }
  }
  
  /**
    * Helper method used to maintain StockImpl - CatalogImpl links in nested Stocks/Catalogs. For internal use only.
    *
    * @param db the DataBasket that protecting this activity.
    * @param nAction the action that occurred. Can be either {@link #COMMIT_ACTION}, {@link #ROLLBACK_ACTION},
    * {@link #STARTEDIT_ACTION}.
    */
  void relinkCatalog (DataBasket db, int nAction) {
    super.relinkCatalog (db, nAction);
    
    switch (nAction) {
      case STARTEDIT_ACTION:
        /**
          * A starting catalog editing operation requires us to set the catalog to the catalog being 
          * edited and to remember the databasket coordinating the editing.
          */
        internalSetCatalog ((CatalogImpl) getAssociatedItem (db));
        m_dbCatalogValidator = db;
        
        break;
      case COMMIT_ACTION:
        /*
         * Commiting a catalog editing action only requires to forget the DataBasket used to edit the 
         * Catalog, as it will no longer be contained in it and all other relinks will have been 
         * performed.
         */
        if (db == m_dbCatalogValidator) {
          m_dbCatalogValidator = null;
        }
        
        break;
      case ROLLBACK_ACTION:
        /*
         * Rolling back a catalog editing action requires us to set the catalog based on our parent's catalog
         * validator, reset the catalog validator and update our children if we'returna StoringStock. The latter
         * will be done in StoringStockImpl.relinkCatalog.
         */
        if (db == m_dbCatalogValidator) {
          DataBasket dbParent = ((StockImpl) getStock()).m_dbCatalogValidator;
          
          if (dbParent != null) {
            // This normally shouldn't happen, but I am not sure that it really is an error situation,
            // so just trace it...
            Debug.print ("In data.ooimpl.StockImpl.relinkCatalog (db, ROLLBACK_ACTION): parent databasket is not null!", -1);
          }
          
          internalSetCatalog ((CatalogImpl) getAssociatedItem (dbParent));
          
          m_dbCatalogValidator = null;
        }
        
        break;
    }
  }
}