package data.ooimpl;

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

import java.util.*;

/**
  * Pure Java implementation of the {@link StoringStock} interface.
  *
  * @author Steffen Zschaler
  * @version 2.0 19/08/1999
  * @since v2.0
  */
public class StoringStockImpl extends StockImpl implements StoringStock {

  /**
    * Modification counter. Increases by one with every structural modification.
    *
    * @serial
    */
  protected int m_nModCount = Integer.MIN_VALUE;

  /**
    * Listens to the Stock's Catalog to ensure referential integrity.
    *
    * @serial
    */
  protected CatalogChangeListener m_cclReferentialIntegrityListener;

  /**
    * true, if StockImpl's CatalogItemNameListener was already replaced by an enhanced version.
    * Used internally only.
    *
    * @serial
    */
  private boolean m_fChangedCatalogItemNameListener = false;

  /**
    * Enhanced CatalogItemNameListener, updating the names of the actual StockItems whenever a name change
    * occurs.
    *
    * @author Steffen Zschaler
    * @version 2.0 19/08/1999
    * @since v2.0
    */
  class SSICatalogItemNameListener extends CatalogItemNameListener {
    protected void nameChangeOccurred (String sOld, String sNew) {
      super.nameChangeOccurred (sOld, sNew);

      List lAdded = (List) getTemporaryAddedItemsContainer().get (sNew);
      if (lAdded != null) {
        for (Iterator i = lAdded.iterator(); i.hasNext();) {
          StockItemImpl sii = (StockItemImpl) i.next();

          sii.internalSetName (sNew);
        }
      }

      List lItems = (List) getItemsContainer().get (sNew);
      if (lItems != null) {
        for (Iterator i = lItems.iterator(); i.hasNext();) {
          StockItemImpl sii = (StockItemImpl) i.next();

          sii.internalSetName (sNew);
        }
      }

      List lRemoved = (List) getTemporaryRemovedItemsContainer().get (sNew);
      if (lRemoved != null) {
        for (Iterator i = lRemoved.iterator(); i.hasNext();) {
          StockItemImpl sii = (StockItemImpl) i.next();

          sii.internalSetName (sNew);
        }
      }

      List lRefIntegr = (List) getRefIntegrItemsContainer().get (sNew);
      if (lRefIntegr != null) {
        for (Iterator i = lRefIntegr.iterator(); i.hasNext();) {
          StockItemImpl sii = (StockItemImpl) i.next();

          sii.internalSetName (sNew);
        }
      }
    }
  }

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

    // Enhanced version.
    m_sclEditCreatorListener = new StockChangeAdapter() {
      public void commitAddStockItems (StockChangeEvent e) {
        synchronized (getItemsLock()) {
          StockItemImpl sii = (StockItemImpl) e.getAffectedItems().next();

          List lAdded = (List) getTemporaryAddedItemsContainer().get (sii.getName());

          if (lAdded == null) {
            return;
          }

          if (lAdded.remove (sii)) {
            List lItems = (List) getItemsContainer().get (sii.getName());

            if (lItems == null) {
              lItems = new LinkedList();
              getItemsContainer().put (sii.getName(), lItems);
            }
            lItems.add (sii);

            sii.setStock (StoringStockImpl.this);

            fireStockItemsAddCommit (new StoringStockChangeEvent (StoringStockImpl.this,
                                                                  sii,
                                                                  e.getBasket()));
          }
        }
      }

      public void rollbackAddStockItems (StockChangeEvent e) {
        synchronized (getItemsLock()) {
          StockItemImpl sii = (StockItemImpl) e.getAffectedItems().next();

          List lAdded = (List) getTemporaryAddedItemsContainer().get (sii.getName());

          if (lAdded == null) {
            return;
          }

          if (lAdded.remove (sii)) {
            if (sii.getStock() == StoringStockImpl.this) {
              sii.setStock (null);
            }

            fireStockItemsAddRollback (new StoringStockChangeEvent (StoringStockImpl.this,
                                                                    sii,
                                                                    e.getBasket()));
          }
        }
      }

      public void canRemoveStockItems (StockChangeEvent e) throws VetoException {
        throw new VetoException ("Please use the editable version for this!");
      }

      public void commitRemoveStockItems (StockChangeEvent e) {
        synchronized (getItemsLock()) {
          StockItemImpl sii = (StockItemImpl) e.getAffectedItems().next();

          List lRemoved = (List) getTemporaryRemovedItemsContainer().get (sii.getName());

          if (lRemoved == null) {
            return;
          }

          if (lRemoved.remove (sii)) {
            if (sii.getStock() == StoringStockImpl.this) {
              sii.setStock (null);
            }

            fireStockItemsRemoveCommit (new StoringStockChangeEvent (StoringStockImpl.this,
                                                                     sii,
                                                                     e.getBasket()));
          }
        }
      }

      public void rollbackRemoveStockItems (StockChangeEvent e) {
        synchronized (getItemsLock()) {
          StockItemImpl sii = (StockItemImpl) e.getAffectedItems().next();

          List lRemoved = (List) getTemporaryRemovedItemsContainer().get (sii.getName());

          if (lRemoved == null) {
            return;
          }

          if (lRemoved.remove (sii)) {
            List lItems = (List) getItemsContainer().get (sii.getName());

            if (lItems == null) {
              lItems = new LinkedList();
              getItemsContainer().put (sii.getName(), lItems);
            }
            lItems.add (sii);

            sii.setStock (StoringStockImpl.this);

            fireStockItemsRemoveCommit (new StoringStockChangeEvent (StoringStockImpl.this,
                                                                     sii,
                                                                     e.getBasket()));
          }
        }
      }

      public void canEditStockItems (StockChangeEvent e) throws VetoException {
        throw new VetoException ("Please use the editable version for this!");
      }

      public void commitEditStockItems (StockChangeEvent e) {
        synchronized (getItemsLock()) {
          StockItemImpl sii = (StockItemImpl) e.getAffectedItems().next();

          List lEditing = (List) getEditingItemsContainer().get (sii.getName());

          if (lEditing == null) {
            return;
          }

          if (lEditing.remove (sii)) {
            fireStockItemsEditCommit (new StoringStockChangeEvent (StoringStockImpl.this,
                                                                   sii,
                                                                   e.getBasket()));
          }
        }
      }

      public void rollbackEditStockItems (StockChangeEvent e) {
        synchronized (getItemsLock()) {
          StockItemImpl sii = (StockItemImpl) e.getAffectedItems().next();

          List lEditing = (List) getEditingItemsContainer().get (sii.getName());

          if (lEditing == null) {
            return;
          }

          if (lEditing.remove (sii)) {
            fireStockItemsEditRollback (new StoringStockChangeEvent (StoringStockImpl.this,
                                                                     sii,
                                                                     e.getBasket()));
          }
        }
      }
    };
  }

  /**
    * Overridden because of referential integrity.
    *
    * @override Never
    */
  protected void internalSetCatalog (CatalogImpl ciRef) {
    if (m_ciCatalog != null) {
      m_ciCatalog.removeCatalogChangeListener (m_cclReferentialIntegrityListener);
    }

    if (!m_fChangedCatalogItemNameListener) {
      m_fChangedCatalogItemNameListener = true;

      m_cinlCatalogItemNameListener = new SSICatalogItemNameListener();
    }

    super.internalSetCatalog (ciRef);

    if (m_ciCatalog != null) {
      if (m_cclReferentialIntegrityListener == null) {
        initReferentialIntegrityListener();
      }

      m_ciCatalog.addCatalogChangeListener (m_cclReferentialIntegrityListener);
    }
  }

  /**
    * Internal helper function.
    *
    * @override Never
    */
  private void initReferentialIntegrityListener() {
    m_cclReferentialIntegrityListener = new CatalogChangeAdapter() {
      public void canRemoveCatalogItem (CatalogChangeEvent e)
        throws VetoException {
        // DataBasket already locks on its monitor
        synchronized (getItemsLock()) {
          String sKey = e.getAffectedItem().getName();
          DataBasket db = e.getBasket();

          List lAdded = (List) getTemporaryAddedItemsContainer().get (sKey);
          if ((lAdded != null) &&
              (lAdded.size() > 0)) {
            throw new VetoException ("Stock \"" + getName() + "\": Having temporarily added items of key \"" + sKey + "\".");
          }

          List lRemoved = (List) getTemporaryRemovedItemsContainer().get (sKey);
          if (lRemoved != null) {
            if ((db == null) &&
                (lRemoved.size() > 0)) {
              throw new VetoException ("Stock \"" + getName() + "\": Having temporarily removed items that are in a different DataBasket.");
            }

            for (Iterator i = lRemoved.iterator(); i.hasNext();) {
              StockItem si = (StockItem) i.next();

              DataBasketCondition dbc = new DataBasketConditionImpl (STOCK_ITEM_MAIN_KEY,
                                                                     sKey,
                                                                     StoringStockImpl.this,
                                                                     null,
                                                                     si);
              if (!db.contains (dbc)) {
                throw new VetoException ("Stock \"" + getName() + "\": Having temporarily removed items that are in a different DataBasket.");
              }
            }
          }

          List lItems = (List) getItemsContainer().get (sKey);
          if ((lItems != null) &&
              (lItems.size() > 0)) {
            List lRefIntegr = new LinkedList (lItems);
            getRefIntegrItemsContainer().put (sKey, lRefIntegr);

            for (Iterator i = lRefIntegr.iterator(); i.hasNext();) {
              remove ((StockItem) i.next(), db);
            }
          }
        }
      }

      public void noRemoveCatalogItem (CatalogChangeEvent e) {
        synchronized (getItemsLock()) {
          String sKey = e.getAffectedItem().getName();
          DataBasket db = e.getBasket();

          List lRefIntegr = (List) getRefIntegrItemsContainer().remove (sKey);
          if (lRefIntegr != null) {
            for (Iterator i = lRefIntegr.iterator(); i.hasNext();) {
              add ((StockItem) i.next(), db);
            }
          }
        }
      }

      public void removedCatalogItem (CatalogChangeEvent e) {
        synchronized (getItemsLock()) {
          // clean up
          getRefIntegrItemsContainer().remove (e.getAffectedItem().getName());
        }
      }

      public void commitedRemoveCatalogItem (CatalogChangeEvent e) {
        synchronized (getItemsLock()) {
          if (!((Catalog) e.getSource()).contains (e.getAffectedItem().getName(), e.getBasket())) {
            ciGoneForEver (e);
          }
        }
      }

      public void rollbackAddCatalogItem (CatalogChangeEvent e) {
        synchronized (getItemsLock()) {
          DataBasketCondition dbc = new DataBasketConditionImpl (CatalogItemImpl.CATALOG_ITEM_MAIN_KEY,
                                                                 e.getAffectedItem().getName(),
                                                                 (CatalogImpl) e.getSource(),
                                                                 null,
                                                                 null);

          if (!e.getBasket().contains (dbc)) {
            ciGoneForEver (e);
          }
        }
      }

      private void ciGoneForEver (CatalogChangeEvent e) {
        String sKey = e.getAffectedItem().getName();
        DataBasket db = e.getBasket();

        if (getTemporaryAddedItemsContainer().containsKey (sKey)) {
          DataBasketCondition dbc = new DataBasketConditionImpl (STOCK_ITEM_MAIN_KEY,
                                                                 sKey,
                                                                 null,
                                                                 StoringStockImpl.this,
                                                                 null);

          // Rollback all items temporaryly added to this Stock
          // StoringStocks produce DataBasketEntries that may have both source and dest set,
          // so we must rollback only the destination part.
          for (Iterator i = db.iterator (dbc); i.hasNext();) {
            ((StoringStockItemDBEntry) i.next()).rollbackDestination();
          }
        }

        getItemsContainer().remove (sKey);
        getRefIntegrItemsContainer().remove (sKey);

        if (getTemporaryRemovedItemsContainer().containsKey (sKey)) {
          DataBasketCondition dbc = new DataBasketConditionImpl (STOCK_ITEM_MAIN_KEY,
                                                                 sKey,
                                                                 StoringStockImpl.this,
                                                                 null,
                                                                 null);

          // Commit all items temporaryly removed from this Stock
          // StoringStocks produce DataBasketEntries that may have both source and dest set,
          // so we must commit only the source part.
          for (Iterator i = db.iterator (dbc); i.hasNext();) {
            ((StoringStockItemDBEntry) i.next()).commitSource();
          }
        }
      }

      // The actual instance of the associated Catalog will have changed, for children of the given key that
      // are Stocks.
      public void editingCatalogItem (CatalogChangeEvent e) {
        relinkCatalog (e, STARTEDIT_ACTION);
      }

      // The actual instance of the associated Catalog will have to be changed back.
      public void rollbackEditCatalogItem (CatalogChangeEvent e) {
        relinkCatalog (e, ROLLBACK_ACTION);
      }

      public void commitEditCatalogItem (CatalogChangeEvent e) {
        relinkCatalog (e, COMMIT_ACTION);
      }

      void relinkCatalog (CatalogChangeEvent e, int nAction) {
        DataBasket db = e.getBasket();

        synchronized (getItemsLock()) {
          List l = (List) getItemsContainer().get (e.getAffectedItem().getName());

          if (l != null) {
            for (Iterator i = l.iterator(); i.hasNext();) {
              StockItemImpl sii = (StockItemImpl) i.next();

              sii.relinkCatalog (db, nAction);
            }
          }

          l = (List) getTemporaryAddedItemsContainer().get (e.getAffectedItem().getName());

          if (l != null) {
            for (Iterator i = l.iterator(); i.hasNext();) {
              StockItemImpl sii = (StockItemImpl) i.next();

              sii.relinkCatalog (db, nAction);
            }
          }
        }
      }

      public void rollbackRemoveCatalogItem (CatalogChangeEvent e) {
        reEstablishStockCatalogLink (e.getAffectedItem().getName());
      }
    };
  }

  /**
    * Private helper function re-establishing the Stock-Catalog connection if any items in this Stock should be
    * Stocks themselves.
    *
    * @override Never
    */
  private void reEstablishStockCatalogLink (String sKey) {
    synchronized (getItemsLock()) {
      List lAdded = (List) getTemporaryAddedItemsContainer().get (sKey);
      if (lAdded != null) {
        for (Iterator i = lAdded.iterator(); i.hasNext();) {
          // we call setStock again, which for other Stocks will adjust their Catalog link accordingly.
          ((StockItemImpl) i.next()).setStock (StoringStockImpl.this);
        }
      }

      List lItems = (List) getItemsContainer().get (sKey);
      if (lItems != null) {
        for (Iterator i = lItems.iterator(); i.hasNext();) {
          // we call setStock again, which for other Stocks will adjust their Catalog link accordingly.
          ((StockItemImpl) i.next()).setStock (StoringStockImpl.this);
        }
      }
    }
  }

  // Stock interface methods

  /**
    * Add an item to the Stock.
    *
    * <p>The item will only be visible to users of the same DataBasket. Only after a {@link DataBasket#commit}
    * was performed on the DataBasket, the item will become visible to other users.</p>
    *
    * <p>A <code>addedStockItems</code> event will be fired.</p>
    *
    * @param si the item to be added.
    * @param db the DataBasket relative to which the item will be added. Must be either <code>null</code> or a
    * descendant of {@link DataBasketImpl}.
    *
    * @override Never
    *
    * @exception CatalogConflictException if the items key is not contained in the corresponding {@link Catalog}.
    * @exception DataBasketConflictException if the item has already been added/removed using another DataBasket.
    */
  public void add (StockItem si, DataBasket db) {
    Object oLock = ((db != null)?(((DataBasketImpl) db).getDBIMonitor()):(new Object()));

    synchronized (oLock) {
      synchronized (getItemsLock()) {
        if ((getCatalog (db) != null) &&
            (!getCatalog (db).contains (si.getName(), db))) {
          throw new CatalogConflictException ("Couldn't find key \"" + si.getName() + "\" in Catalog \"" + getCatalog (db).getName() + "\"");
        }

        List lAdded = (List) getTemporaryAddedItemsContainer().get (si.getName());
        if ((lAdded != null) &&
            (lAdded.contains (si))) {
          throw new DataBasketConflictException ("Cannot add item that has already been added.");
        }

        List lItems = (List) getItemsContainer().get (si.getName());
        if ((lItems != null) &&
            (lItems.contains (si))) {
          return;
        }

        List lRemoved = (List) getTemporaryRemovedItemsContainer().get (si.getName());
        if ((lRemoved != null) &&
            (lRemoved.contains (si))) {

          DataBasketCondition dbc = DataBasketConditionImpl.specificStockItem ((StockItem) lRemoved.get (lRemoved.indexOf (si)));

          if ((db == null) ||
              (!db.contains (dbc))) {
            throw new DataBasketConflictException ("Cannot add item that was removed using a different DataBasket.");
          }
          else {
            DataBasketEntry dbe = db.get (dbc);

            if (dbe.getDestination() == null) {
              // just rollback the prior remove action!

              db.rollback (dbc);

              return;
            }
            else {
              throw new DataBasketConflictException ("Cannot add item that was removed and added to another Stock!");
            }
          }
        }

        // all checked, so add the stuff
        if (db != null) {
          if (lAdded == null) {
            lAdded = new LinkedList();
            getTemporaryAddedItemsContainer().put (si.getName(), lAdded);
          }

          lAdded.add (si);

          // put information into databasket! Make sure there's only one DBE for each StockItem!
          DataBasketCondition dbc = DataBasketConditionImpl.specificStockItem (si);
          StockItemDBEntry sidbe = (StockItemDBEntry) db.get (dbc);

          if (sidbe != null) {
            db.exchange (sidbe, new StoringStockItemDBEntry ((StoringStockImpl) sidbe.getSource(), this, (StockItemImpl) si));
          }
          else {
            db.put (new StoringStockItemDBEntry (null, this, (StockItemImpl) si));
          }

        }
        else {
          if (lItems == null) {
            lItems = new LinkedList();
            getItemsContainer().put (si.getName(), lItems);
          }

          lItems.add (si);
        }

        m_nModCount++;

        ((StockItemImpl) si).setStock (this);
        fireStockItemsAdded (new StoringStockChangeEvent (this, (StockItemImpl) si, db));
      }
    }
  }

  /**
    * Iterate all items with a given key.
    *
    * <p>This method, together with {@link Stock#iterator} is the only way of accessing the individual
    * {@link StockItem StockItems} contained in a Stock. The iterator will deliver all items that have the
    * specified key and 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 sKey the key for which to retrieve the StockItems.
    * @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 get (final String sKey, final DataBasket db, final boolean fForEdit) {
    final Object oLock = ((db != null)?(((DataBasketImpl) db).getDBIMonitor()):(new Object()));

    class I implements Iterator {
      private Iterator m_iItems;
      private int m_nExpectedModCount;

      private StockItemImpl m_siiCurrent;

      public I() {
        super();

        synchronized (oLock) {
          synchronized (getItemsLock()) {
            List lItems = (List) getItemsContainer().get (sKey);

            if (lItems != null) {
              lItems = new LinkedList (lItems);
            }
            else {
              lItems = new LinkedList();
            }

            if (db != null) {
              DataBasketCondition dbc = new DataBasketConditionImpl (STOCK_ITEM_MAIN_KEY,
                                                                     sKey,
                                                                     null,
                                                                     StoringStockImpl.this,
                                                                     null);
              for (Iterator i = db.iterator (dbc); i.hasNext();) {
                DataBasketEntry dbe = (DataBasketEntry) i.next();


                lItems.add (dbe.getValue());
              }
            }

            m_iItems = lItems.iterator();
            m_nExpectedModCount = m_nModCount;
          }
        }
      }

      public boolean hasNext() {
        return m_iItems.hasNext();
      }

      public Object next() {
        synchronized (oLock) {
          synchronized (getItemsContainer()) {
            if (m_nExpectedModCount != m_nModCount) {
              throw new ConcurrentModificationException();
            }

            m_siiCurrent = (StockItemImpl) m_iItems.next();

            if ((fForEdit) &&
                (db != null)) {

              List lAdded = (List) getTemporaryAddedItemsContainer().get (sKey);
              if ((lAdded != null) &&
                  (lAdded.contains (m_siiCurrent))) {
                return m_siiCurrent;
              }

              try {
                fireCanEditStockItems (new StoringStockChangeEvent (StoringStockImpl.this, m_siiCurrent, db));
              }
              catch (VetoException ve) {
                return null;
              }

              List lItems = (List) getItemsContainer().get (sKey);
              lItems.remove (m_siiCurrent);
              if (lItems.size() == 0) {
                getItemsContainer().remove (sKey);
              }

              List lRemoved = (List) getTemporaryRemovedItemsContainer().get (sKey);
              if (lRemoved == null) {
                lRemoved = new LinkedList();
                getTemporaryRemovedItemsContainer().put (sKey, lRemoved);
              }
              lRemoved.add (m_siiCurrent);

              StockItemImpl siiRemoved = m_siiCurrent;
              m_siiCurrent = siiRemoved.getShallowClone();

              if (lAdded == null) {
                lAdded = new LinkedList();
                getTemporaryAddedItemsContainer().put (sKey, lAdded);
              }
              lAdded.add (m_siiCurrent);

              List lEdit = (List) getEditingItemsContainer().get (sKey);
              if (lEdit == null) {
                lEdit = new LinkedList();
                getEditingItemsContainer().put (sKey, lEdit);
              }
              lEdit.add (m_siiCurrent);

              siiRemoved.setStock (null);
              m_siiCurrent.setStock (StoringStockImpl.this);

              // put information into databasket, making sure there's only one entry per StockItem
              DataBasketCondition dbc = DataBasketConditionImpl.specificStockItem (siiRemoved);
              StockItemDBEntry sidbe = (StockItemDBEntry) db.get (dbc);

              if (sidbe != null) {
                db.exchange (sidbe, new StoringStockItemDBEntry (StoringStockImpl.this, (StoringStockImpl) sidbe.getDestination(), siiRemoved));
              }
              else {
                db.put (new StoringStockItemDBEntry (StoringStockImpl.this, null, siiRemoved));
              }

              db.put (new StoringStockItemDBEntry (null, StoringStockImpl.this, m_siiCurrent));

              fireEditingStockItems (new StoringStockChangeEvent (StoringStockImpl.this, m_siiCurrent, db));
              fireStockItemsRemoved (new StoringStockChangeEvent (StoringStockImpl.this, siiRemoved, db));
              fireStockItemsAdded (new StoringStockChangeEvent (StoringStockImpl.this, m_siiCurrent, db));

              m_nExpectedModCount = (++m_nModCount);
            }

            return m_siiCurrent;
          }
        }
      }

      public void remove() {
        synchronized (oLock) {
          synchronized (getItemsLock()) {
            if (m_nModCount != m_nExpectedModCount) {
              throw new ConcurrentModificationException();
            }

            if (m_siiCurrent == null) {
              throw new IllegalStateException();
            }

            try {
              StoringStockImpl.this.remove (m_siiCurrent, db);

              m_nExpectedModCount = m_nModCount;

              m_siiCurrent = null;
            }
            catch (VetoException ve) {
              m_siiCurrent = null;

              throw new UnsupportedOperationException ("VETO: " + ve);
            }
          }
        }
      }
    }

    if ((getCatalog (db) != null) &&
        (!getCatalog (db).contains (sKey, db))) {
      return new Iterator() {
        public boolean hasNext() {
          return false;
        }

        public Object next() {
          throw new NoSuchElementException();
        }

        public void remove() {}
      };
    }

    return new I();
  }

  /**
    * Count the StockItems with a given key that are visible using a given DataBasket.
    *
    * @override Never
    *
    * @param sKey the key for which to count the StockItems.
    * @param db the DataBasket that is used to determine visibility. Must be either <code>null</code> or a
    * descendant of {@link DataBasketImpl}.
    */
  public int countItems (String sKey, DataBasket db) {
    Object oLock = ((db != null)?(((DataBasketImpl) db).getDBIMonitor()):(new Object()));

    synchronized (oLock) {
      synchronized (getItemsLock()) {
        if ((getCatalog (db) != null) &&
            (!getCatalog (db).contains (sKey, db))) {
          return 0;
        }

        int nCount = 0;

        List lItems = (List) getItemsContainer().get (sKey);

        if (lItems != null) {
          nCount += lItems.size();
        }

        if ((getTemporaryAddedItemsContainer().containsKey (sKey)) &&
            (db != null)) {
          DataBasketCondition dbc = new DataBasketConditionImpl (STOCK_ITEM_MAIN_KEY,
                                                                 sKey,
                                                                 null,
                                                                 this,
                                                                 null);
          BasketEntryValue bev = BasketEntryValues.COUNT_ITEMS;
          IntegerValue ivCount = new IntegerValue (0);

          db.sumBasket (dbc, bev, ivCount);

          nCount += ivCount.getValue().intValue();
        }

        return nCount;
      }
    }
  }

  /**
    * Remove one StockItem with the specified key from the Stock.
    *
    * <p>If there are any StockItems with the specified key, one will be removed. There is no guarantee as to
    * which StockItem will be removed. The removed item, if any, will be returned.</p>
    *
    * <p><code>canRemoveStockItems</code> and <code>removedStockItems</code> events will be fired.</p>
    *
    * @override Never
    *
    * @param sKey the key for which to remove an item.
    * @param db the DataBasket relative to which to remove the item. Must be either <code>null</code> or a
    * descendant of {@link DataBasketImpl}.
    *
    * @return the removed item
    *
    * @exception VetoException if a listener vetoed the removal.
    * @exception DataBasketConflictException if the item cannot be removed due to conflicts from DataBasket
    * usage.
    */
  public StockItem remove (String sKey, DataBasket db)
    throws VetoException {
    Object oLock = ((db != null)?(((DataBasketImpl) db).getDBIMonitor()):(new Object()));

    synchronized (oLock) {
      synchronized (getItemsLock()) {
        List lAdded = (List) getTemporaryAddedItemsContainer().get (sKey);

        if ((lAdded != null) &&
            (db != null)) {
          DataBasketCondition dbc = new DataBasketConditionImpl (STOCK_ITEM_MAIN_KEY,
                                                                 sKey,
                                                                 null,
                                                                 this,
                                                                 null);

          StockItemDBEntry sidbe = (StockItemDBEntry) db.get (dbc);

          if (sidbe != null) {
            return remove ((StockItem) sidbe.getValue(), db);
          }
        }

        List lItems = (List) getItemsContainer().get (sKey);

        if (lItems != null) {
          /*
           * 06/27/2000-STEFFEN: Had to add checking for lItems.size here, as apparently I sometimes
           * keep the vector even if it is empty.
           * I don't think, it should do that, but I need to check again.
           * Checked, apparently remove (si, db) also doesn't clean up the list. This is pretty memory
           * ineffective, but needs some effort to fix it. For the moment, just worked around it.
           */
          if (lItems.size () > 0) {
            return remove ((StockItem) lItems.get (lItems.size() - 1), db);
          }
        }
      }
    }

    return null;
  }

  /**
    * Remove the given StockItem from the Stock.
    *
    * <p>If the given StockItem is contained in the Stock, it will be removed. The removed item, if any, will
    * be returned.</p>
    *
    * <p><code>canRemoveStockItems</code> and <code>removedStockItems</code> events will be fired.</p>
    *
    * @override Never
    *
    * @param si the StockItem to be removed.
    * @param db the DataBasket relative to which to remove the item. Must be either <code>null</code> or a
    * descendant of {@link DataBasketImpl}.
    *
    * @return the removed item
    *
    * @exception VetoException if a listener vetoed the removal.
    * @exception DataBasketConflictException if the item cannot be removed due to conflicts from DataBasket
    * usage.
    */
  public StockItem remove (StockItem si, DataBasket db)
    throws VetoException {
    Object oLock = ((db != null)?(((DataBasketImpl) db).getDBIMonitor()):(new Object()));

    synchronized (oLock) {
      synchronized (getItemsLock()) {
        List lAdded = (List) getTemporaryAddedItemsContainer().get (si.getName());
        if ((lAdded != null) &&
            (lAdded.contains (si))) {
          DataBasketCondition dbc = DataBasketConditionImpl.specificStockItem ((StockItem) lAdded.get (lAdded.indexOf (si)));

          StockItemDBEntry sidbe = null;

          if (db != null) {
            sidbe = (StockItemDBEntry) db.get (dbc);
          }

          if (sidbe == null) {
            throw new DataBasketConflictException ("Cannot remove StockItem that was added using a different DataBasket!");
          }
          else {
            fireCanRemoveStockItems (new StoringStockChangeEvent (this, (StockItemImpl) si, db));

            if (sidbe.getSource() == null) {
              db.rollback (dbc);
            }
            else {
              // remove only the destination part of it:
              db.exchange (sidbe, new StoringStockItemDBEntry ((StoringStockImpl) sidbe.getSource(), null, (StockItemImpl) sidbe.getValue()));

              si = (StockItem) lAdded.get (lAdded.indexOf (si));
              lAdded.remove (si);

              m_nModCount++;

              fireStockItemsAddRollback (new StoringStockChangeEvent (this, (StockItemImpl) si, db));
            }

            ((StockItemImpl) si).setStock (null);

            return si;
          }
        }

        List lRemoved = (List) getTemporaryRemovedItemsContainer().get (si.getName());
        if ((lRemoved != null) &&
            (lRemoved.contains (si))) {
          throw new DataBasketConflictException ("Cannot remove an item that has already been removed!");
        }

        List lItems = (List) getItemsContainer().get (si.getName());
        if ((lItems == null) ||
            (!lItems.contains (si))) {
          return null;
        }

        // remove from items container, making sure there's always only one DataBasket entry for each stockitem

        fireCanRemoveStockItems (new StoringStockChangeEvent (this, (StockItemImpl) lItems.get (lItems.indexOf (si)), db));

        si = (StockItem) lItems.get (lItems.indexOf (si));
        lItems.remove (si);

        if (db != null) {
          if (lRemoved == null) {
            lRemoved = new LinkedList();
            getTemporaryRemovedItemsContainer().put (si.getName(), lRemoved);
          }

          lRemoved.add (si);

          DataBasketCondition dbc = DataBasketConditionImpl.specificStockItem (si);
          StockItemDBEntry sidbe = (StockItemDBEntry) db.get (dbc);

          if (sidbe != null) {
            db.exchange (sidbe, new StoringStockItemDBEntry (this, (StoringStockImpl) sidbe.getDestination(), (StockItemImpl) si));
          }
          else {
            db.put (new StoringStockItemDBEntry (this, null, (StockItemImpl) si));
          }
        }

        m_nModCount++;

        ((StockItemImpl) si).setStock (null);
        fireStockItemsRemoved (new StoringStockChangeEvent (this, (StockItemImpl) si, db));

        return si;
      }
    }
  }

  // StockImpl methods

  /**
    * Overridden to accomodate for specific usage of memory.
    *
    * @override Never
    */
  protected void fillShallowClone (StockImpl stiClone) {
    synchronized (getItemsLock()) {
      synchronized (stiClone.getItemsLock()) {
        stiClone.setItemsContainer (new HashMap());
        for (Iterator i = getItemsContainer().keySet().iterator(); i.hasNext();) {
          String sKey = (String) i.next();

          stiClone.getItemsContainer().put (sKey, ((LinkedList) getItemsContainer().get (sKey)).clone());
            // shallow clone of LinkedList
        }

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

          stiClone.getTemporaryAddedItemsContainer().put (sKey, ((LinkedList) getTemporaryAddedItemsContainer().get (sKey)).clone());
        }

        stiClone.setTemporaryRemovedItemsContainer (new HashMap());
        for (Iterator i = getTemporaryRemovedItemsContainer().keySet().iterator(); i.hasNext();) {
          String sKey = (String) i.next();

          stiClone.getTemporaryRemovedItemsContainer().put (sKey, ((LinkedList) getTemporaryRemovedItemsContainer().get (sKey)).clone());
        }

        stiClone.setEditingItemsContainer (new HashMap());
        for (Iterator i = getEditingItemsContainer().keySet().iterator(); i.hasNext();) {
          String sKey = (String) i.next();

          stiClone.getEditingItemsContainer().put (sKey, ((LinkedList) getEditingItemsContainer().get (sKey)).clone());
        }

        stiClone.setRefIntegrItemsContainer (new HashMap());
        for (Iterator i = getRefIntegrItemsContainer().keySet().iterator(); i.hasNext();) {
          String sKey = (String) i.next();

          stiClone.getRefIntegrItemsContainer().put (sKey, ((LinkedList) getRefIntegrItemsContainer().get (sKey)).clone());
        }

        stiClone.setRefIntegrEditContainer (new HashMap());
        for (Iterator i = getRefIntegrEditContainer().keySet().iterator(); i.hasNext();) {
          String sKey = (String) i.next();

          stiClone.getRefIntegrEditContainer().put (sKey, ((LinkedList) getRefIntegrEditContainer().get (sKey)).clone());
        }
      }
    }
  }

  /**
    * @override Always
    */
  protected StockImpl createPeer() {
    StoringStockImpl ssiPeer = new StoringStockImpl (getName(), m_ciCatalog);
    ssiPeer.m_dbCatalogValidator = m_dbCatalogValidator;
    
    return ssiPeer;
  }

  /**
    * Set the Stock and adjust the Catalog link for all Stocks that are contained in this Stock.
    *
    * @override Never
    */
  protected void setStock (StockImpl sti) {
    super.setStock (sti);

    if (sti != null) {
      synchronized (getItemsLock()) {
        Set stKeys = getItemsContainer().keySet();
        stKeys.addAll (getTemporaryAddedItemsContainer().keySet());

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

  /**
    * 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);

    if (nAction == ROLLBACK_ACTION) {
      // Additionally refresh the links in all child stocks.
      synchronized (getItemsLock()) {
        for (Iterator i = getItemsContainer().values().iterator(); i.hasNext();) {
          List l = (List) i.next();

          for (Iterator j = l.iterator(); j.hasNext();) {
            StockItemImpl sii = (StockItemImpl) j.next();

            sii.relinkCatalog (db, nAction);
          }
        }

        for (Iterator i = getTemporaryAddedItemsContainer().values().iterator(); i.hasNext();) {
          List l = (List) i.next();

          for (Iterator j = l.iterator(); j.hasNext();) {
            StockItemImpl sii = (StockItemImpl) j.next();

            sii.relinkCatalog (db, nAction);
          }
        }
      }
    }
  }

  // SelfManagingDBESource interface methods

  /**
    * Commit the removal of a StockItem.
    *
    * <p>A <code>commitRemoveStockItems</code> will be fired.</p>
    *
    * @override Never
    */
  public void commitRemove (DataBasket db, DataBasketEntry dbe) {
    // DataBasket is already locking on its monitor so we just lock on ours
    synchronized (getItemsLock()) {
      StockItemImpl sii = (StockItemImpl) dbe.getValue();

      List lRemoved = (List) getTemporaryRemovedItemsContainer().get (sii.getName());
      if (lRemoved != null) {
        lRemoved.remove (sii);

        if (lRemoved.size() == 0) {
          getTemporaryRemovedItemsContainer().remove (sii.getName());
        }

        if (sii.getStock() == this) {
          sii.setStock (null);
        }
        fireStockItemsRemoveCommit (new StoringStockChangeEvent (this, sii, db));
      }
    }
  }

  /**
    * Rollback the removal of a StockItem.
    *
    * <p>A <code>rollbackRemoveStockItems</code> will be fired. Also, the Stock will try to make sure, that
    * a corresponding CatalogItem exists.</p>
    *
    * @override Never
    */
  public void rollbackRemove (DataBasket db, DataBasketEntry dbe) {
    synchronized (getItemsLock()) {
      prepareReferentialIntegrity (db, dbe);

      StockItemImpl sii = (StockItemImpl) dbe.getValue();

      List lRemoved = (List) getTemporaryRemovedItemsContainer().get (sii.getName());
      if (lRemoved != null) {
        lRemoved.remove (sii);

        if (lRemoved.size() == 0) {
          getTemporaryRemovedItemsContainer().remove (sii.getName());
        }

        List lItems = (List) getItemsContainer().get (sii.getName());
        if (lItems == null) {
          lItems = new LinkedList();
          getItemsContainer().put (sii.getName(), lItems);
        }

        lItems.add (sii);

        sii.setStock (this);

        m_nModCount++;

        fireStockItemsRemoveRollback (new StoringStockChangeEvent (this, sii, db));
      }
    }
  }

  // SelfManagingDBEDestination interface methods

  /**
    * Commit the adding of a StockItem.
    *
    * <p>A <code>commitAddStockItems</code> will be fired. A <code>commitEditStockItems</code> event may be
    * fired as a consequence of this method. Also, the Stock will try to make sure, that a corresponding
    * CatalogItem exists.</p>
    *
    * @override Never
    */
  public void commitAdd (DataBasket db, DataBasketEntry dbe) {
    synchronized (getItemsLock()) {
      prepareReferentialIntegrity (db, dbe);

      StockItemImpl sii = (StockItemImpl) dbe.getValue();

      List lAdded = (List) getTemporaryAddedItemsContainer().get (sii.getName());
      if (lAdded != null) {
        lAdded.remove (sii);

        if (lAdded.size() == 0) {
          getTemporaryAddedItemsContainer().remove (sii.getName());
        }

        List lItems = (List) getItemsContainer().get (sii.getName());
        if (lItems == null) {
          lItems = new LinkedList();
          getItemsContainer().put (sii.getName(), lItems);
        }

        lItems.add (sii);

        sii.setStock (this);

        m_nModCount++;

        fireStockItemsAddCommit (new StoringStockChangeEvent (this, sii, db));

        List lEdit = (List) getEditingItemsContainer().get (sii.getName());
        if ((lEdit != null) &&
            (lEdit.remove (sii))) {
          if (lEdit.size() == 0) {
            getEditingItemsContainer().remove (sii.getName());
          }

          fireStockItemsEditCommit (new StoringStockChangeEvent (this, sii, db));
        }
      }
    }
  }

  /**
    * Rollback the adding of a StockItem.
    *
    * <p>A <code>commitAddStockItems</code> will be fired. A <code>commitEditStockItems</code> event may be
    * fired as a consequence of this method.</p>
    *
    * @override Never
    */
  public void rollbackAdd (DataBasket db, DataBasketEntry dbe) {
    synchronized (getItemsLock()) {
      StockItemImpl sii = (StockItemImpl) dbe.getValue();

      List lAdded = (List) getTemporaryAddedItemsContainer().get (sii.getName());
      if (lAdded != null) {
        lAdded.remove (sii);

        if (lAdded.size() == 0) {
          getTemporaryAddedItemsContainer().remove (sii.getName());
        }

        if (sii.getStock() == this) {
          sii.setStock (null);
        }

        m_nModCount++;

        fireStockItemsAddRollback (new StoringStockChangeEvent (this, sii, db));

        List lEdit = (List) getEditingItemsContainer().get (sii.getName());
        if ((lEdit != null) &&
            (lEdit.remove (sii))) {
          if (lEdit.size() == 0) {
            getEditingItemsContainer().remove (sii.getName());
          }

          fireStockItemsEditRollback (new StoringStockChangeEvent (this, sii, db));
        }
      }
    }
  }
}