package data.ooimpl;

import java.util.*;
import java.io.*;

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

import util.*;

/**
  * Pure Java implementation of the {@link Catalog} interface.
  *
  * <p>CatalogImpl can only work together with DataBaskets that are descendants of {@link DataBasketImpl}.</p>
  *
  * @author Steffen Zschaler
  * @version 2.0 19/08/1999
  * @since v2.0
  */
public class CatalogImpl extends CatalogItemImpl implements Catalog,
                                                            ListenableCatalog,
                                                            NameContext,
                                                            SelfManagingDBESource,
                                                            SelfManagingDBEDestination {

  /**
    * The listeners that registered to be informed of changes in this Catalog's contents.
    *
    * @serial
    */
  protected ListenerHelper m_lhListeners = new ListenerHelper();

  /**
    * Modification counter. Will be increased by one for each structural modification.
    *
    * @serial
    */
  protected int m_nModCount = Integer.MIN_VALUE;

  /**
    * The items in this catalog.
    *
    * @serial
    */
  private Map m_mpciItems = new HashMap();

  /**
    * The items that have been temporaryly removed from this Catalog.
    *
    * @serial
    */
  private Map m_mpciTemporaryRemovedItems = new HashMap();

  /**
    * The items that have been temporaryly added to this Catalog.
    *
    * @serial
    */
  private Map m_mpciTemporaryAddedItems = new HashMap();

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

  /**
    * The original Catalog, if this is a clone created for editing.
    *
    * <p>SoftReference, so that garbage collector will take care of it if the EditCreator is no longer used AND
    * is no longer referenced by any DataBasket or anything else.</p>
    */
  private transient java.lang.ref.SoftReference m_srciEditCreator = null;

  /**
    * Listener listening to the creator's parent Catalog to know when editing is finished, if this is a
    * shallow clone created for editing.
    *
    * @serial
    */
  private final CatalogChangeListener m_cclEditingListener = new CatalogChangeAdapter() {
    public void commitEditCatalogItem (CatalogChangeEvent e) {
      if (e.getAffectedItem() == CatalogImpl.this) {
        ((CatalogImpl) e.getSource()).removeCatalogChangeListener (this);
      }
    }

    public void rollbackEditCatalogItem (CatalogChangeEvent e) {
      if (e.getAffectedItem() == CatalogImpl.this) {
        ((CatalogImpl) e.getSource()).removeCatalogChangeListener (this);

        ((CatalogImpl) m_srciEditCreator.get()).removeCatalogChangeListener (m_cclEditCreatorListener);
        m_srciEditCreator = null;
      }
    }
  };

  /**
    * Listener listening to the creator to follow with any commits or rollbacks, if this is a shallow clone
    * created for editing.
    *
    * @serial
    */
  private final CatalogChangeListener m_cclEditCreatorListener = new CatalogChangeAdapter() {
    public void commitedAddCatalogItem (CatalogChangeEvent e) {
      synchronized (getItemsLock()) {
        if (getTemporaryAddedItemsContainer().containsKey (e.getAffectedItem().getName())) {
          getTemporaryAddedItemsContainer().remove (e.getAffectedItem().getName());
          getItemsContainer().put (e.getAffectedItem().getName(), e.getAffectedItem());

          m_nModCount++;

          ((CatalogItemImpl) e.getAffectedItem()).setCatalog (CatalogImpl.this);
          fireCatalogItemAddCommit (e.getAffectedItem(), e.getBasket());
        }
      }
    }

    public void rolledbackAddCatalogItem (CatalogChangeEvent e) {
      synchronized (getItemsLock()) {
        if (getTemporaryAddedItemsContainer().containsKey (e.getAffectedItem().getName())) {
          getTemporaryAddedItemsContainer().remove (e.getAffectedItem().getName());

          m_nModCount++;

          ((CatalogItemImpl) e.getAffectedItem()).setCatalog (null);
          fireCatalogItemAddRollback (e.getAffectedItem(), e.getBasket());
        }
      }
    }

    public void canRemoveCatalogItem (CatalogChangeEvent e) throws VetoException {
      throw new VetoException ("Please use the editable version of the Catalog for that.");
    }

    public void commitedRemoveCatalogItem (CatalogChangeEvent e) {
      synchronized (getItemsLock()) {
        if (getTemporaryRemovedItemsContainer().containsKey (e.getAffectedItem().getName())) {
          getTemporaryRemovedItemsContainer().remove (e.getAffectedItem().getName());

          m_nModCount++;

          ((CatalogItemImpl) e.getAffectedItem()).setCatalog (null);
          fireCatalogItemRemoveCommit (e.getAffectedItem(), e.getBasket());
        }
      }
    }

    public void rolledbackRemoveCatalogItem (CatalogChangeEvent e) {
      synchronized (getItemsLock()) {
        if (getTemporaryRemovedItemsContainer().containsKey (e.getAffectedItem().getName())) {
          getTemporaryRemovedItemsContainer().remove (e.getAffectedItem().getName());
          getItemsContainer().put (e.getAffectedItem().getName(), e.getAffectedItem());

          m_nModCount++;

          ((CatalogItemImpl) e.getAffectedItem()).setCatalog (CatalogImpl.this);
          fireCatalogItemRemoveRollback (e.getAffectedItem(), e.getBasket());
        }
      }
    }

    public void canEditCatalogItem (CatalogChangeEvent e) throws VetoException {
      throw new VetoException ("Please use the editable version of the Catalog for that.");
    }

    public void commitEditCatalogItem (CatalogChangeEvent e) {
      synchronized (getItemsLock()) {
        if (getEditingItemsContainer().get (e.getAffectedItem().getName()) == e.getAffectedItem()) {
          getEditingItemsContainer().remove (e.getAffectedItem().getName());

          fireCommitEditCatalogItem (e.getAffectedItem(), e.getBasket());
        }
      }
    }

    public void rollbackEditCatalogItem (CatalogChangeEvent e) {
      synchronized (getItemsLock()) {
        if (getEditingItemsContainer().get (e.getAffectedItem().getName()) == e.getAffectedItem()) {
          getEditingItemsContainer().remove (e.getAffectedItem().getName());

          fireRollbackEditCatalogItem (e.getAffectedItem(), e.getBasket());
        }
      }
    }
  };

  /**
    * Monitor synchronizing access to the several items maps.
    */
  private transient Object m_oItemsLock;
  /**
    * Get the monitor synchronizing access to the several items maps.
    *
    * @override Never
    */
  protected final Object getItemsLock() {
    if (m_oItemsLock == null) {
      m_oItemsLock = new Object();
    }

    return m_oItemsLock;
  }

  /**
    * 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_srciEditCreator != null) {
      oos.writeObject (m_srciEditCreator.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_srciEditCreator = new java.lang.ref.SoftReference (oEditCreator);
    }
  }

  /**
    * Create a new, initially empty CatalogImpl.
    *
    * @param sName the name of the Catalog.
    */
  public CatalogImpl (String sName) {
    super (sName);
  }

  /**
    * Get the map of items that are completely contained in this Catalog.
    *
    * @override Never
    */
  protected Map getItemsContainer() {
    return m_mpciItems;
  }

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

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

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

  /**
    * Set the map of items that are completely contained in this Catalog.
    *
    * <p>Must be called from within lock on {@link #getItemsLock}.</p>
    *
    * @override Never
    */
  private void setItemsContainer (Map mpNew) {
    m_mpciItems = mpNew;
  }

  /**
    * Set the map of items that have been temporaryly removed from this Catalog.
    *
    * <p>Must be called from within lock on {@link #getItemsLock}.</p>
    *
    * @override Never
    */
  private void setTemporaryRemovedItemsContainer (Map mpNew) {
    m_mpciTemporaryRemovedItems = mpNew;
  }

  /**
    * Set the map of items that have been temporaryly added to this Catalog.
    *
    * <p>Must be called from within lock on {@link #getItemsLock}.</p>
    *
    * @override Never
    */
  private void setTemporaryAddedItemsContainer (Map mpNew) {
    m_mpciTemporaryAddedItems = mpNew;
  }

  /**
    * Set the map of items that are currently being edited.
    *
    * <p>Must be called from within lock on {@link #getItemsLock}.</p>
    *
    * @override Never
    */
  private void setEditingItemsContainer (Map mpNew) {
    m_mpciEditingItems = mpNew;
  }

  // Catalog interface methods

  /**
    * Add the given item to the Catalog.
    *
    * @override Never
    *
    * @param ci the CatalogItem to be added.
    * @param db the DataBasket relative to which to perform the operation. Must be <code>null</code> or a
    * descendant of {@link DataBasketImpl}.
    *
    * @exception NotEditableException if the Catalog is currently not editable.
    * @exception DuplicateKeyException if a CatalogItem of the same name does already exist in the Catalog.
    * @exception DataBasketConflictException if the CatalogItem cannot be added because an item of the same
    * name has already been added/removed using another DataBasket.
    */
  public void add (final CatalogItem ci, DataBasket db) {
    if (!isEditable()) {
      throw new NotEditableException();
    }

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

    synchronized (oLock) {
      synchronized (getItemsLock()) {
        if (getItemsContainer().containsKey (ci.getName())) {
          throw new DuplicateKeyException ("Key " + ci.getName() + " already existent in Catalog " + getName() + ".");
        }

        if (getTemporaryAddedItemsContainer().containsKey (ci.getName())) {
          if ((db != null) &&
              (db.contains (new DataBasketConditionImpl (CATALOG_ITEM_MAIN_KEY,
                                                         ci.getName(),
                                                         null,
                                                         this,
                                                         ci)))) {
            // hey dude, that's your own responsibility here
            throw new DuplicateKeyException ("Key " + ci.getName() + " already existent in Catalog " + getName() + ".");
          }
          else {
            // sorry, someone was faster than you
            throw new DataBasketConflictException ("CatalogItem with key " + ci.getName() + " already added to Catalog " + getName() + " using a different DataBasket.");
          }
        }

        if (getTemporaryRemovedItemsContainer().containsKey (ci.getName())) {

          DataBasketCondition dbc = new DataBasketConditionImpl (CATALOG_ITEM_MAIN_KEY,
                                                                 ci.getName(),
                                                                 this,
                                                                 null,
                                                                 ci);

          if ((db != null) &&
              (db.contains (dbc))) {

            CatalogItemDataBasketEntry cidbe = (CatalogItemDataBasketEntry) db.get (dbc);

            if (cidbe.getDestination() == null) {
              // This is a rollback action.
              db.rollback (dbc);

              m_nModCount++;
            }
            else {
              // Cannot rollback a removed item that was added to another Catalog. We might end up having one CatalogItem
              // in several Catalogs...
              throw new DataBasketConflictException ("CatalogItem with key " + ci.getName() + " already removed from Catalog " + getName() + " and added to another Catalog.");
            }

            return;
          }
          else {
            // sorry, someone was faster than you
            throw new DataBasketConflictException ("CatalogItem with key " + ci.getName() + " already removed from Catalog " + getName() + " using a different DataBasket.");
          }
        }

        // everything properly checked -> add the stuff
        if (db != null) {
          getTemporaryAddedItemsContainer().put (ci.getName(), ci);

          DataBasketCondition dbc = new DataBasketConditionImpl (CATALOG_ITEM_MAIN_KEY,
                                                                 ci.getName(),
                                                                 null,
                                                                 null,
                                                                 null) {
            public boolean match (DataBasketEntry dbe) {
              return ((dbe.getDestination() == null) &&
                      (dbe.getSource() != null) &&
                      (dbe.getValue() == ci));
            }
          };

          if (db.contains (dbc)) {
            // a copy action...
            CatalogItemDataBasketEntry cidbe = (CatalogItemDataBasketEntry) db.get (dbc);

            cidbe.setDestination (this);

            if (db instanceof ListenableDataBasket) {
              ((ListenableDataBasket) db).fireDataBasketChanged();
            }
          }
          else {
            db.put (new CatalogItemDataBasketEntry (null, this, cii));
          }
        }
        else {
          // null databasket, so add directly
          getItemsContainer().put (ci.getName(), ci);
        }

        m_nModCount++;

        cii.setCatalog (this);

        fireCatalogItemAdded (cii, db);
      }
    }
  }

  /**
    * Remove the given item from the Catalog.
    *
    * @override Never
    *
    * @param ci the CatalogItem to be removed.
    * @param db the DataBasket relative to which to perform the operation. Must be <code>null</code> or a
    * descendant of {@link DataBasketImpl}.
    *
    * @exception NotEditableException if the Catalog is currently not editable.
    * @exception VetoException if one of the listeners vetos the removal.
    * @exception DataBasketConflictException if the CatalogItem cannot be removed because an item of the same
    * name has already been added using another DataBasket.
    */
  // MAY HAVE TO CHECK THAT ci IS REALLY CONTAINED IN THE CATALOG (AND NOT JUST ITS KEY)
  public CatalogItem remove (final CatalogItem ci, DataBasket db)
    throws VetoException {
    if (!isEditable()) {
      throw new NotEditableException();
    }

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

    synchronized (oLock) {
      synchronized (getItemsLock()) {
        if (getTemporaryAddedItemsContainer().containsKey (ci.getName())) {
          // the CatalogItem has been added to this Catalog, but only temporarily up to now

          // Check whether the adding was really about the same item (not just the same key)
          DataBasketCondition dbc = new DataBasketConditionImpl (CATALOG_ITEM_MAIN_KEY,
                                                                 ci.getName(),
                                                                 null,
                                                                 this,
                                                                 ci);

          if ((db != null) &&
              (db.contains (dbc))) {

            CatalogItemDataBasketEntry cidbe = (CatalogItemDataBasketEntry) db.get (dbc);

            fireCanRemoveCatalogItem (ci, db);

            if (cidbe.getSource() == null) {
              // This is a rollback
              db.rollback (dbc);
            }
            else {
              // only rollback the destination part of the the databasketentry
              cidbe.setDestination (null);

              getTemporaryAddedItemsContainer().remove (ci.getName());

              m_nModCount++;

              fireCatalogItemAddRollback (ci, db);

              if (db instanceof ListenableDataBasket) {
                ((ListenableDataBasket) db).fireDataBasketChanged();
              }
            }

            cii.setCatalog (null);

            return ci;
          }
          else {
            // someone was faster than you...
            throw new DataBasketConflictException ("CatalogItem " + ci.getName() + " has only been temporaryly added to Catalog " + getName() + " using a different DataBasket, and can therefore not be removed.");
          }
        }

        if (getTemporaryRemovedItemsContainer().containsKey (ci.getName())) {
          throw new DataBasketConflictException ("CatalogItem " + ci.getName() + " already removed from Catalog " + getName() + ".");
        }

        if (!getItemsContainer().containsKey (ci.getName())) {
          // the CatalogItem is not in the Catalog and never was, too.
          return null;
        }

        // item is in items container and must be transferred into temporaryly removed items

        fireCanRemoveCatalogItem (ci, db);

        if (db != null) {
          getItemsContainer().remove (ci.getName());
          getTemporaryRemovedItemsContainer().put (ci.getName(), ci);

          DataBasketCondition dbc = new DataBasketConditionImpl (CATALOG_ITEM_MAIN_KEY,
                                                                 ci.getName(),
                                                                 null,
                                                                 null,
                                                                 null) {
            public boolean match (DataBasketEntry dbe) {
              return ((dbe.getSource() == null) &&
                      (dbe.getValue() == ci));
            }
          };

          if (db.contains (dbc)) {
            ((CatalogItemDataBasketEntry) db.get (dbc)).setSource (this);

            if (db instanceof ListenableDataBasket) {
              ((ListenableDataBasket) db).fireDataBasketChanged();
            }
          }
          else {
            db.put (new CatalogItemDataBasketEntry (this, null, (CatalogItemImpl) ci));
          }
        }
        else {
          getItemsContainer().remove (ci.getName());
        }

        m_nModCount++;

        cii.setCatalog (null);

        fireCatalogItemRemoved (ci, db);

        return ci;
      }
    }
  }

  /**
    * Remove the indicated item from the Catalog.
    *
    * @override Never
    *
    * @param sKey the key of the CatalogItem to be removed.
    * @param db the DataBasket relative to which to perform the operation. Must be <code>null</code> or a
    * descendant of {@link DataBasketImpl}.
    *
    * @exception NotEditableException if the Catalog is currently not editable.
    * @exception VetoException if one of the listeners vetos the removal.
    * @exception DataBasketConflictException if the CatalogItem cannot be removed because an item of the same
    * name has already been added using another DataBasket.
    */
  public CatalogItem remove (String sKey, DataBasket db)
    throws VetoException {
    if (!isEditable()) {
      throw new NotEditableException();
    }

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

    synchronized (oLock) {
      synchronized (getItemsLock()) {
        CatalogItem ci = null;

        if (getItemsContainer().containsKey (sKey)) {
          ci = (CatalogItem) getItemsContainer().get (sKey);
        }
        else {
          if (getTemporaryAddedItemsContainer().containsKey (sKey)) {
            ci = (CatalogItem) getTemporaryAddedItemsContainer().get (sKey);
          }
          else {
            if (getTemporaryRemovedItemsContainer().containsKey (sKey)) {
              throw new DataBasketConflictException ("CatalogItem " + sKey + " already removed from Catalog " + getName() + ".");
            }
            else {
              return null;
            }
          }
        }

        return remove (ci, db);
      }
    }
  }

  /**
    * Get the indicated item from the Catalog.
    *
    * @override Never
    *
    * @param sKey the key for which to retrieve the item.
    * @param db the DataBasket relative to which to perform the operation. Must be <code>null</code> or a
    * descendant of {@link DataBasketImpl}.
    * @param fForEdit if true, the item will be retrieved for editing. In this case, a number of events is fired:
    * <ol>
    *   <li><code>canEditCatalogItem</code> with the original item.</li>
    *   <li>a {@link CatalogItemImpl#getShallowClone shallow clone} of the item is created.</li>
    *   <li><code>editingCatalogItem</code> with the newly created clone.</li>
    *   <li><code>addCatalogItem</code> with the newly created clone and <code>removeCatalogItem</code> with
    *       the original item.</li>
    *   <li>The newly created clone is returned for editing.</li>
    * </ol>
    *
    * @exception NotEditableException if the Catalog is not currently editable, but an attempt is made to edit
    * one of its items.
    * @exception VetoException if one of the listeners vetos the editing.
    * @exception DataBasketConflictException if the CatalogItem cannot be retrieved because it is not visible
    * to users of the given DataBasket.
    */
  public CatalogItem get (String sKey, DataBasket db, boolean fForEdit)
    throws VetoException {
      
    // If we aren't editable ourselves, but try to edit an item: TOO BAD!  
    if ((!isEditable()) &&
        (fForEdit)) {
      throw new NotEditableException();
    }

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

    synchronized (oLock) {
      synchronized (getItemsLock()) {
        // First check, whether item in question was just added by some transaction
        if (getTemporaryAddedItemsContainer().containsKey (sKey)) {
          // It was, so it should only be visible to that transaction
          CatalogItem ci = (CatalogItem) getTemporaryAddedItemsContainer().get (sKey);

          if (db != null) {
            // Search in db for ci.
            DataBasketCondition dbc = new DataBasketConditionImpl (CATALOG_ITEM_MAIN_KEY,
                                                                   sKey,
                                                                   null,
                                                                   this,
                                                                   ci);

            DataBasketEntry dbe = db.get (dbc);

            if (dbe != null) {

              if (fForEdit) {
                if (dbe.getSource() != null) {
                  // This is a little too complex for us...
                  throw new DataBasketConflictException ("Cannot edit a catalogitem that was removed temporarily from its Catalog.");
                }
                else {
                  if (!getEditingItemsContainer().containsKey (sKey)) {
                    fireCanEditCatalogItem (ci, db);

                    // No need for cloning, was added temporarily only anyway...
                    getEditingItemsContainer().put (sKey, ci);

                    fireEditingCatalogItem (ci, db);
                  }

                  return ci;
                }
              }
              else {
                return ci;
              }
            }
            else {
              return null;
            }
          }
          else {
            return null;
          }
        }

        // Item is not currently under control of any transaction. Does it exist at all?
        if (getItemsContainer().containsKey (sKey)) {
          // Yup! Prepare for retrieval
          CatalogItem ci = (CatalogItem) getItemsContainer().get (sKey);

          if ((db != null) &&
              (fForEdit)) {
            // Prepare for editing

            fireCanEditCatalogItem (ci, db);

            // Create shallow clone, which will be editable.
            CatalogItemImpl cci = ((CatalogItemImpl) ci).getShallowClone();

            // Reorganize containers
            getItemsContainer().remove (sKey);
            getTemporaryRemovedItemsContainer().put (sKey, ci);
            getTemporaryAddedItemsContainer().put (sKey, cci);
            
            // Mark as editable
            getEditingItemsContainer().put (sKey, cci);
            
            // Store undo/commit data in db
            db.put (new CatalogItemDataBasketEntry (this, null, (CatalogItemImpl) ci));
            db.put (new CatalogItemDataBasketEntry (null, this, cci));

            // Relink catalog structure
            ((CatalogItemImpl) ci).setCatalog (null);
            ((CatalogItemImpl) cci).setCatalog (this);

            // Fire all events
            fireEditingCatalogItem (cci, db);
            fireCatalogItemRemoved (ci, db);
            fireCatalogItemAdded (cci, db);

            // Notify iterators of change
            m_nModCount++;

            return cci;
          }
          else {
            return ci;
          }
        }
      }
    }

    return null;
  }

  /**
    * Check whether the Catalog contains a certain CatalogItem.
    *
    * <p>Will return true only if an item of the given key is contained in the Catalog and if that item is
    * visible to users of the given DataBasket.</p>
    *
    * @override Never
    *
    * @param sKey the key for which to check containment.
    * @param db the DataBasket that defines visibility of items. Must be <code>null</code> or a
    * descendant of {@link DataBasketImpl}.
    */
  public boolean contains (String sKey, DataBasket db) {
    Object oLock = (db == null)?(new Object()):(((DataBasketImpl) db).getDBIMonitor());

    synchronized (oLock) {
      synchronized (getItemsLock()) {
        if (getItemsContainer().containsKey (sKey)) {
          return true;
        }

        if ((db != null) &&
            (getTemporaryAddedItemsContainer().containsKey (sKey))) {
          CatalogItem ci = (CatalogItem) getTemporaryAddedItemsContainer().get (sKey);

          DataBasketCondition dbc = new DataBasketConditionImpl (CATALOG_ITEM_MAIN_KEY,
                                                                 sKey,
                                                                 null,
                                                                 this,
                                                                 ci);

          return (db.contains (dbc));
        }

        return false;
      }
    }
  }

  /**
    * Return an iterator of all items in the Catalog.
    *
    * <p>The iterator will conceptually call {@link #get} for each CatalogItem, using the given parameters.</p>
    *
    * @override Never
    *
    * @param db the DataBasket that defines visibility.
    * @param fForEdit if true, the items are retrieved for editing. VetoException will be converted into
    * <code>UnsupportedOperationException</code>s.
    */
  public Iterator iterator (final DataBasket db, final boolean fForEdit) {
    final Object oLock = ((db != null)?(((DataBasketImpl) db).getDBIMonitor()):(new Object()));

    return new Iterator() {
      private Iterator m_iItems;
      private int m_nExpectedModCount;
      private CatalogItem m_ciCurrent;
      {
        synchronized (oLock) {
          synchronized (getItemsLock()) {
            m_iItems = keySet (db).iterator();

            m_nExpectedModCount = m_nModCount;
          }
        }
      }

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

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

            String sKey = (String) m_iItems.next();

            try {
              m_ciCurrent = CatalogImpl.this.get (sKey, db, fForEdit);

              if (fForEdit) {
                m_nExpectedModCount = m_nModCount;
              }
            }
            catch (VetoException ve) {
              throw new UnsupportedOperationException ("VETO: " + ve);
            }

            return m_ciCurrent;
          }
        }
      }

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

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

            try {
              CatalogImpl.this.remove (m_ciCurrent, db);

              m_nExpectedModCount = m_nModCount;

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

              throw new UnsupportedOperationException();
            }
          }
        }
      }
    };
  }

  /**
    * Get a set of all keys currently in the Catalog.
    *
    * <p>This will retrieve a static set that gives the state of the Catalog at the time of the call.</p>
    *
    * @param db the DataBasket used to determine visibility of elements. Must be <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 stReturn = new TreeSet (getItemsContainer().keySet());

        if (db != null) {
          for (Iterator i = getTemporaryAddedItemsContainer().values().iterator(); i.hasNext();) {
            final CatalogItem ci = (CatalogItem) i.next();

            if (!stReturn.contains (ci.getName())) {
              DataBasketCondition dbc = new DataBasketConditionImpl (CATALOG_ITEM_MAIN_KEY,
                                                                     ci.getName(),
                                                                     null,
                                                                     this,
                                                                     ci);
              if (db.contains (dbc)) {
                stReturn.add (ci.getName());
              }
            }
          }
        }

        return stReturn;
      }
    }
  }

  /**
    * Calculate the size of the Catalog. I.e. count the CatalogItems that are visible to users of the given
    * DataBasket.
    *
    * @override Never
    *
    * @param db the DataBasket used to determine visibility. Must be <code>null</code> or a
    * descendant of {@link DataBasketImpl}.
    */
  public int size (DataBasket db) {
    return keySet (db).size();
  }

  /**
    * Set the Catalog that contains this Catalog.
    *
    * @override Never
    */
  void setCatalog (CatalogImpl ci) {
    super.setCatalog (ci);

    if (ci != null) {
      // necessary, so that children of clones prepared for editing will always know who there
      // correct parents are.
      for (Iterator i = getItemsContainer().values().iterator(); i.hasNext();) {
        ((CatalogItemImpl) i.next()).setCatalog (this);
      }

      for (Iterator i = getTemporaryAddedItemsContainer().values().iterator(); i.hasNext();) {
        ((CatalogItemImpl) i.next()).setCatalog (this);
      }
    }
  }

  /**
    * Create a shallow clone of this Catalog. A shallow clone means that the individual items themselves will
    * not be cloned.
    *
    * @override Never Instead override {@link #createPeer}.
    */
  protected CatalogItemImpl getShallowClone() {
    CatalogImpl ci = createPeer();

    synchronized (getItemsLock()) {
      synchronized (ci.getItemsLock()) {
        ci.setItemsContainer (new HashMap (getItemsContainer()));
        ci.setEditingItemsContainer (new HashMap (getEditingItemsContainer()));
        ci.setTemporaryAddedItemsContainer (new HashMap (getTemporaryAddedItemsContainer()));
        ci.setTemporaryRemovedItemsContainer (new HashMap (getTemporaryRemovedItemsContainer()));
      }
    }

    // attach as listener to this CatalogImpl
    ci.m_srciEditCreator = new java.lang.ref.SoftReference (this);
    addCatalogChangeListener (ci.m_cclEditCreatorListener);
    if (getCatalog() != null) {
      ((CatalogImpl) getCatalog()).addCatalogChangeListener (ci.m_cclEditingListener);
    }

    return ci;
  }

  /**
    * Create and return an empty CatalogImpl of the same name and class.
    *
    * @override Always
    */
  protected CatalogImpl createPeer() {
    return new CatalogImpl (getName());
  }

  /**
    * Return a {@link String} representation of this Catalog.
    *
    * @override Sometimes
    */
  public String toString() {
    synchronized (getItemsLock()) {
      String sReturn = "Catalog \"" + getName() + "\" [";

      boolean fFirst = true;
      for (Iterator i = iterator (null, false); i.hasNext();) {
        sReturn += ((fFirst)?(""):(", ")) + i.next();
        fFirst = false;
      }

      return sReturn + "]";
    }
  }

  // SelfManagingDBESource interface methods

  /**
    * Commit the removal of a CatalogItem.
    */
  public void commitRemove (DataBasket db, DataBasketEntry dbe) {
    // The databasket already locks on its own monitor, so we just lock on ours.
    synchronized (getItemsLock()) {
      if (dbe.getSource() == this) {
        CatalogItemImpl cii = (CatalogItemImpl) dbe.getValue();

        getTemporaryRemovedItemsContainer().remove (cii.getName());

        if (cii.getCatalog() == this) {
          cii.setCatalog (null);
        }

        ((CatalogItemDataBasketEntry) dbe).setSource (null);

        fireCatalogItemRemoveCommit (cii, db);
      }
    }
  }

  /**
    * Roll back the removal of a CatalogItem.
    */
  public void rollbackRemove (DataBasket db, DataBasketEntry dbe) {
    // the databasket already locks on its own monitor, so we just lock on ours.
    synchronized (getItemsLock()) {
      if (dbe.getSource() == this) {
        CatalogItemImpl cii = (CatalogItemImpl) dbe.getValue();

        if (getTemporaryRemovedItemsContainer().get (cii.getName()) == cii) {
          getTemporaryRemovedItemsContainer().remove (cii.getName());
          getItemsContainer().put (cii.getName(), cii);
          cii.setCatalog (this);

          m_nModCount++;

          fireCatalogItemRemoveRollback (cii, db);
        }

        ((CatalogItemDataBasketEntry) dbe).setSource (null);
      }
    }
  }

  // SelfManagingDBEDestination interface methods
  /**
    * Commit the adding of a CatalogItem. In addition to the <code>addedCatalogItemCommit</code> event this
    * may trigger an <code>editingCatalogItemCommit</code> event with the CatalogItem that has been edited.
    */
  public void commitAdd (DataBasket db, DataBasketEntry dbe) {
    // the databasket already locks on its own monitor, so we just lock on ours.
    synchronized (getItemsLock()) {
      if (dbe.getDestination() == this) {
        CatalogItemImpl cii = (CatalogItemImpl) dbe.getValue();

        if (getTemporaryAddedItemsContainer().get (cii.getName()) == cii) {
          getTemporaryAddedItemsContainer().remove (cii.getName());

          getItemsContainer().put (cii.getName(), cii);

          cii.setCatalog (this);

          m_nModCount++;

          fireCatalogItemAddCommit (cii, db);

          if (getEditingItemsContainer().remove (cii.getName()) != null) {
            fireCommitEditCatalogItem (cii, db);
          }
        }

        ((CatalogItemDataBasketEntry) dbe).setDestination (null);
      }
    }
  }

  /**
    * Roll back the adding of a CatalogItem. In addition to the <code>addedCatalogItemRollback</code> event
    * this may trigger an <code>editingCatalogItemRollback</code> event with the CatalogItem that has been
    * edited.
    */
  public void rollbackAdd (DataBasket db, DataBasketEntry dbe) {
    // the databasket already locks on its own monitor, so we just lock on ours.
    synchronized (getItemsLock()) {
      if (dbe.getDestination() == this) {
        CatalogItemImpl cii = (CatalogItemImpl) dbe.getValue();

        getTemporaryAddedItemsContainer().remove (cii.getName());

        if (cii.getCatalog() == this) {
          cii.setCatalog (null);
        }

        ((CatalogItemDataBasketEntry) dbe).setDestination (null);

        m_nModCount++;

        fireCatalogItemAddRollback (cii, db);

        if (getEditingItemsContainer().remove (cii.getName()) != null) {
          fireRollbackEditCatalogItem (cii, db);
        }
      }
    }
  }

  // NameContext interface methods
  /**
    * Check a name change of a CatalogItem in this Catalog.
    *
    * <p>The name change will be allowed if the item is editable and the new name can be guaranteed to be
    * unique.</p>
    *
    * @override Sometimes Override to enforce stricter naming conventions.
    */
  public void checkNameChange (DataBasket db, String sOldName, String sNewName)
    throws NameContextException {

    if (sOldName == sNewName) return;

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

    synchronized (oLock) {
      synchronized (getItemsLock()) {
        if (!getEditingItemsContainer().containsKey (sOldName)) {
          throw new NameContextException ("Item must be made editable before you can change its name!");
        }

        if ((getTemporaryRemovedItemsContainer().containsKey (sNewName)) ||
            (getItemsContainer().containsKey (sNewName)) ||
            (getTemporaryAddedItemsContainer().containsKey (sNewName))) {
          throw new NameContextException ("Name conflict: name \"" + sNewName + "\" already existent (though maybe temproarily removed!)");
        }

        DataBasketCondition dbc = new DataBasketConditionImpl (CATALOG_ITEM_MAIN_KEY,
                                                               sOldName,
                                                               null,
                                                               this,
                                                               null);

        if ((db == null) ||
            (!db.contains (dbc))) {
          throw new NameContextException ("DataBasket conflict: No corresponding item with name \"" + sOldName + "\" in the given DataBasket.");
        }
      }
    }
  }

  /**
    * Synchronize the Catalog's internal data with the name change.
    *
    * @override Never
    */
  public void nameHasChanged (DataBasket db, String sOldName, String sNewName) {
    if (sOldName == sNewName) return;

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

    synchronized (oLock) {
      synchronized (getItemsLock()) {
        getEditingItemsContainer().put (sNewName, getEditingItemsContainer().remove (sOldName));
        getTemporaryAddedItemsContainer().put (sNewName, getTemporaryAddedItemsContainer().remove (sOldName));

        DataBasketCondition dbc = new DataBasketConditionImpl (CATALOG_ITEM_MAIN_KEY,
                                                               sOldName,
                                                               null,
                                                               this,
                                                               null);
        DataBasketEntry dbe = db.get (dbc);
        db.exchange (dbe, new CatalogItemDataBasketEntry (null, this, (CatalogItemImpl) dbe.getValue()));

        m_nModCount++;
      }
    }
  }

  /**
    * Return the monitor used to synchronize access to the Catalog's internal data.
    *
    * @override Never
    */
  public final Object getNCMonitor() { return getItemsLock(); }

  // ListenableCatalog interface methods

  /**
    * Add a listener that listens for changes in this Catalog's contents.
    *
    * @override Never
    *
    * @param ccl the listener
    */
  public void addCatalogChangeListener (CatalogChangeListener ccl) {
    m_lhListeners.add (CatalogChangeListener.class, ccl);
  }

  /**
    * Remove a listener that listened for changes in this Catalog's contents.
    *
    * @override Never
    *
    * @param ccl the listener
    */
  public void removeCatalogChangeListener(CatalogChangeListener ccl) {
    m_lhListeners.remove (CatalogChangeListener.class, ccl);
  }

  /**
    * Fire an event to all listeners listening to this Catalog.
    *
    * @override Never
    */
  protected void fireCatalogItemAdded (CatalogItem ci, DataBasket db) {
    Object[] listeners = m_lhListeners.getListenerList();
    CatalogChangeEvent cce = null;

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == CatalogChangeListener.class) {
        if (cce == null) {
          cce = new CatalogChangeEvent (this, ci, db);
        }

        ((CatalogChangeListener) listeners[i+1]).addedCatalogItem (cce);
      }
    }
  }

  /**
    * Fire an event to all listeners listening to this Catalog.
    *
    * @override Never
    */
  protected void fireCatalogItemAddCommit (CatalogItem ci, DataBasket db) {
    Object[] listeners = m_lhListeners.getListenerList();
    CatalogChangeEvent cce = null;

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == CatalogChangeListener.class) {
        if (cce == null) {
          cce = new CatalogChangeEvent (this, ci, db);
        }

        ((CatalogChangeListener) listeners[i+1]).commitedAddCatalogItem (cce);
      }
    }
  }

  /**
    * Fire an event to all listeners listening to this Catalog.
    *
    * @override Never
    */
  protected void fireCatalogItemAddRollback (CatalogItem ci, DataBasket db) {
    Object[] listeners = m_lhListeners.getListenerList();
    CatalogChangeEvent cce = null;

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == CatalogChangeListener.class) {
        if (cce == null) {
          cce = new CatalogChangeEvent (this, ci, db);
        }

        ((CatalogChangeListener) listeners[i+1]).rolledbackAddCatalogItem (cce);
      }
    }
  }

  /**
    * Fire an event to all listeners listening to this Catalog.
    *
    * @override Never
    */
  protected void fireCatalogItemRemoved (CatalogItem ci, DataBasket db) {
    Object[] listeners = m_lhListeners.getListenerList();
    CatalogChangeEvent cce = null;

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == CatalogChangeListener.class) {
        if (cce == null) {
          cce = new CatalogChangeEvent (this, ci, db);
        }

        ((CatalogChangeListener) listeners[i+1]).removedCatalogItem (cce);
      }
    }
  }

  /**
    * Fire an event to all listeners listening to this Catalog.
    *
    * @override Never
    */
  protected void fireCatalogItemRemoveCommit (CatalogItem ci, DataBasket db) {
    Object[] listeners = m_lhListeners.getListenerList();
    CatalogChangeEvent cce = null;

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == CatalogChangeListener.class) {
        if (cce == null) {
          cce = new CatalogChangeEvent (this, ci, db);
        }

        ((CatalogChangeListener) listeners[i+1]).commitedRemoveCatalogItem (cce);
      }
    }
  }

  /**
    * Fire an event to all listeners listening to this Catalog.
    *
    * @override Never
    */
  protected void fireCatalogItemRemoveRollback (CatalogItem ci, DataBasket db) {
    Object[] listeners = m_lhListeners.getListenerList();
    CatalogChangeEvent cce = null;

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == CatalogChangeListener.class) {
        if (cce == null) {
          cce = new CatalogChangeEvent (this, ci, db);
        }

        ((CatalogChangeListener) listeners[i+1]).rolledbackRemoveCatalogItem (cce);
      }
    }
  }

  /**
    * Fire an event to all listeners listening to this Catalog.
    *
    * @override Never
    */
  protected void fireCanRemoveCatalogItem (CatalogItem ci, DataBasket db)
    throws VetoException {
    Object[] temp = m_lhListeners.getListenerList();
    Object[] listeners = new Object[temp.length];
    System.arraycopy (temp, 0, listeners, 0, temp.length);

    CatalogChangeEvent cce = null;

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == CatalogChangeListener.class) {
        if (cce == null) {
          cce = new CatalogChangeEvent (this, ci, db);
        }

        try {
          ((CatalogChangeListener) listeners[i+1]).canRemoveCatalogItem (cce);
        }
        catch (VetoException e) {
          for (int j = i; j < listeners.length; j += 2) {
            if (listeners[j] == CatalogChangeListener.class) {
              ((CatalogChangeListener) listeners[j + 1]).noRemoveCatalogItem (cce);
            }
          }

          throw e;
        }
      }
    }
  }

  /**
    * Fire an event to all listeners listening to this Catalog.
    *
    * @override Never
    */
  protected void fireCanEditCatalogItem (CatalogItem ci, DataBasket db)
    throws VetoException {
    Object[] temp = m_lhListeners.getListenerList();
    Object[] listeners = new Object[temp.length];
    System.arraycopy (temp, 0, listeners, 0, temp.length);

    CatalogChangeEvent cce = null;

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == CatalogChangeListener.class) {
        if (cce == null) {
          cce = new CatalogChangeEvent (this, ci, db);
        }

        try {
          ((CatalogChangeListener) listeners[i+1]).canEditCatalogItem (cce);
        }
        catch (VetoException e) {
          for (int j = i; j < listeners.length; j += 2) {
            if (listeners[j] == CatalogChangeListener.class) {
              ((CatalogChangeListener) listeners[j + 1]).noEditCatalogItem (cce);
            }
          }

          throw e;
        }
      }
    }
  }

  /**
    * Fire an event to all listeners listening to this Catalog.
    *
    * @override Never
    */
  protected void fireEditingCatalogItem (CatalogItem ci, DataBasket db) {
    Object[] listeners = m_lhListeners.getListenerList();

    CatalogChangeEvent cce = null;

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == CatalogChangeListener.class) {
        if (cce == null) {
          cce = new CatalogChangeEvent (this, ci, db);
        }

        ((CatalogChangeListener) listeners[i+1]).editingCatalogItem (cce);
      }
    }
  }

  /**
    * Fire an event to all listeners listening to this Catalog.
    *
    * @override Never
    */
  protected void fireCommitEditCatalogItem (CatalogItem ci, DataBasket db) {
    Object[] listeners = m_lhListeners.getListenerList();

    CatalogChangeEvent cce = null;

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == CatalogChangeListener.class) {
        if (cce == null) {
          cce = new CatalogChangeEvent (this, ci, db);
        }

        ((CatalogChangeListener) listeners[i+1]).commitEditCatalogItem (cce);
      }
    }
  }

  /**
    * Fire an event to all listeners listening to this Catalog.
    *
    * @override Never
    */
  protected void fireRollbackEditCatalogItem (CatalogItem ci, DataBasket db) {
    Object[] listeners = m_lhListeners.getListenerList();

    CatalogChangeEvent cce = null;

    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == CatalogChangeListener.class) {
        if (cce == null) {
          cce = new CatalogChangeEvent (this, ci, db);
        }

        ((CatalogChangeListener) listeners[i+1]).rollbackEditCatalogItem (cce);
      }
    }
  }
}