You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by dp...@apache.org on 2005/06/13 10:45:50 UTC

svn commit: r190372 [1/2] - in /incubator/jackrabbit/trunk/src: java/org/apache/jackrabbit/core/ java/org/apache/jackrabbit/core/lock/ java/org/apache/jackrabbit/core/state/ test/org/apache/jackrabbit/test/api/lock/

Author: dpfister
Date: Mon Jun 13 01:45:49 2005
New Revision: 190372

URL: http://svn.apache.org/viewcvs?rev=190372&view=rev
Log:
True caching in CachingHierarchyManager

Added:
    incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/NodeStateListener.java   (with props)
Modified:
    incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/BatchedItemOperations.java
    incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/CachingHierarchyManager.java
    incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/HierarchyManagerImpl.java
    incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/ItemImpl.java
    incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/NodeImpl.java
    incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/PathMap.java
    incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/PropertyImpl.java
    incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/WorkspaceImpl.java
    incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/lock/LockManagerImpl.java
    incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/ItemState.java
    incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/NodeState.java
    incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/PropertyState.java
    incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/SessionItemStateManager.java
    incubator/jackrabbit/trunk/src/test/org/apache/jackrabbit/test/api/lock/LockTest.java

Modified: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/BatchedItemOperations.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/BatchedItemOperations.java?rev=190372&r1=190371&r2=190372&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/BatchedItemOperations.java (original)
+++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/BatchedItemOperations.java Mon Jun 13 01:45:49 2005
@@ -420,23 +420,25 @@
 
         // 3. do move operation (modify and store affected states)
 
+
         boolean renameOnly = srcParent.getUUID().equals(destParent.getUUID());
 
-        // remove from old parent
-        if (!renameOnly) {
-            target.removeParentUUID(srcParent.getUUID());
-        }
         int srcNameIndex = srcName.getIndex();
         if (srcNameIndex == 0) {
             srcNameIndex = 1;
         }
-        srcParent.removeChildNodeEntry(srcName.getName(), srcNameIndex);
 
-        // add to new parent
-        if (!renameOnly) {
+        // remove from old parent
+        if (renameOnly) {
+            destParent.renameChildNodeEntry(srcName.getName(), srcNameIndex,
+                    destName.getName());
+        } else {
+            target.removeParentUUID(srcParent.getUUID());
             target.addParentUUID(destParent.getUUID());
+
+            destParent.addChildNodeEntry(destName.getName(), target.getUUID());
+            srcParent.removeChildNodeEntry(srcName.getName(), srcNameIndex);
         }
-        destParent.addChildNodeEntry(destName.getName(), target.getUUID());
 
         // change definition (id) of target node
         NodeDef newTargetDef =

Modified: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/CachingHierarchyManager.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/CachingHierarchyManager.java?rev=190372&r1=190371&r2=190372&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/CachingHierarchyManager.java (original)
+++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/CachingHierarchyManager.java Mon Jun 13 01:45:49 2005
@@ -16,119 +16,250 @@
  */
 package org.apache.jackrabbit.core;
 
-import org.apache.commons.collections.ReferenceMap;
+import org.apache.jackrabbit.core.state.ItemState;
+import org.apache.jackrabbit.core.state.ItemStateException;
+import org.apache.jackrabbit.core.state.ItemStateManager;
+import org.apache.jackrabbit.core.state.NodeState;
+import org.apache.jackrabbit.core.state.NodeStateListener;
+import org.apache.log4j.Logger;
+import org.apache.commons.collections.map.ReferenceMap;
 
 import javax.jcr.ItemNotFoundException;
 import javax.jcr.PathNotFoundException;
 import javax.jcr.RepositoryException;
-import java.util.Map;
+import java.util.List;
+import java.util.Iterator;
+import java.util.HashSet;
+import java.util.Set;
 
 /**
- * <code>CachingHierarchyManager</code> is a simple wrapper for a
- * <code>HierarchyManager</code> that caches the <code>ItemId</code> to <code>Path</code>
- * mappings returned by the underlying <code>HierarchyManager</code> for better
- * performance.
- * <p/>
- * Please keep in mind that this cache of <code>Path</code>s is not automatically
- * updated when the underlying hierarchy is changing. Therefore it should only be
- * used with caution and in special situations (usually only locally within a
- * narrow scope) where the underlying hierarchy is not expected to change.
+ * Implementation of a <code>HierarchyManager</code> that caches paths of
+ * items.
  */
-public class CachingHierarchyManager implements HierarchyManager {
+public class CachingHierarchyManager extends HierarchyManagerImpl
+        implements NodeStateListener {
 
-    private final HierarchyManager delegatee;
+    /**
+     * Default upper limit of cached states
+     */
+    public static final int DEFAULT_UPPER_LIMIT = 100;
 
-    // map of item id to list of paths
-    private Map pathCache = new ReferenceMap(ReferenceMap.HARD, ReferenceMap.HARD);
-    private Map zombiePathCache = new ReferenceMap(ReferenceMap.HARD, ReferenceMap.HARD);
+    /**
+     * Logger instance
+     */
+    private static Logger log = Logger.getLogger(CachingHierarchyManager.class);
 
-    // map of path to item id
-    private Map idCache = new ReferenceMap(ReferenceMap.HARD, ReferenceMap.HARD);
+    /**
+     * Mapping of paths to children in the path map
+     */
+    private final PathMap pathCache = new PathMap();
 
     /**
-     * @param hierMgr
+     * Mapping of item ids to <code>LRUEntry</code> in the path map
      */
-    public CachingHierarchyManager(HierarchyManager hierMgr) {
-        delegatee = hierMgr;
-    }
+    private final ReferenceMap idCache = new ReferenceMap(
+            ReferenceMap.HARD, ReferenceMap.HARD);
 
     /**
-     * Returns the wrapped <code>HierarchyManager</code> instance
-     *
-     * @return the wrapped <code>HierarchyManager</code> instance
+     * Set of items that were moved
+     */
+    private final Set movedIds = new HashSet();
+
+    /**
+     * Cache monitor object
+     */
+    private final Object cacheMonitor = new Object();
+
+    /**
+     * Upper limit
      */
-    public HierarchyManager unwrap() {
-        return delegatee;
+    private final int upperLimit;
+
+    /**
+     * Head of LRU
+     */
+    private LRUEntry head;
+
+    /**
+     * Tail of LRU
+     */
+    private LRUEntry tail;
+
+    /**
+     * Create a new instance of this class. This hierarchy manager will not
+     * check for item states that have been moved into attic space
+     * @param rootNodeUUID root node UUID
+     * @param provider item state manager
+     * @param nsResolver namespace resolver
+     */
+    public CachingHierarchyManager(String rootNodeUUID,
+                                   ItemStateManager provider,
+                                   NamespaceResolver nsResolver) {
+        this(rootNodeUUID, provider, nsResolver, null);
     }
 
     /**
-     * Clears the cache.
+     * Create a new instance of this class.
+     * @param rootNodeUUID root node UUID
+     * @param provider item state manager
+     * @param nsResolver namespace resolver
+     * @param attic item state manager for states in the attic space
      */
-    public synchronized void clearCache() {
-        pathCache.clear();
-        zombiePathCache.clear();
-        idCache.clear();
+    public CachingHierarchyManager(String rootNodeUUID,
+                                   ItemStateManager provider,
+                                   NamespaceResolver nsResolver,
+                                   ItemStateManager attic) {
+
+        super(rootNodeUUID, provider, nsResolver, attic);
+
+        this.upperLimit = DEFAULT_UPPER_LIMIT;
     }
 
     //-----------------------------------------------------< HierarchyManager >
+
     /**
      * {@inheritDoc}
+     *
+     * Check the item indicated inside our path cache first.
      */
     public NodeId[] listParents(ItemId id)
             throws ItemNotFoundException, RepositoryException {
-        return delegatee.listParents(id);
+
+        if (id.denotesNode()) {
+            PathMap.Element element = get(id);
+            if (element != null) {
+                PathMap.Element parent = element.getParent();
+                if (parent != null) {
+                    LRUEntry entry = (LRUEntry) element.get();
+                    if (entry != null) {
+                        return new NodeId[] { (NodeId) entry.getId() };
+                    }
+                }
+            }
+        }
+        return super.listParents(id);
     }
 
     /**
      * {@inheritDoc}
+     *
+     * Check the path indicated inside our cache first.
      */
-    public ItemId[] listChildren(NodeId id)
-            throws ItemNotFoundException, RepositoryException {
-        return delegatee.listChildren(id);
+    public ItemId resolvePath(Path path)
+            throws PathNotFoundException, RepositoryException {
+
+        PathMap.Element element = map(path);
+        if (element == null) {
+            return super.resolvePath(path);
+        }
+        LRUEntry entry = (LRUEntry) element.get();
+        if (element.hasPath(path)) {
+            entry.touch();
+            return entry.getId();
+        }
+        return super.resolvePath(path, entry.getId(), element.getDepth() + 1);
     }
 
     /**
      * {@inheritDoc}
+     *
+     * Cache the intermediate item inside our cache.
      */
-    public ItemId[] listZombieChildren(NodeId id)
-            throws ItemNotFoundException, RepositoryException {
-        return delegatee.listZombieChildren(id);
+    protected ItemId resolvePath(Path path, ItemState state, int next)
+            throws PathNotFoundException, ItemStateException {
+
+        if (state.isNode() && !isCached(state.getId())) {
+            try {
+                Path.PathBuilder builder = new Path.PathBuilder();
+                Path.PathElement[] elements = path.getElements();
+                for (int i = 0; i < next; i++) {
+                    builder.addLast(elements[i]);
+                }
+                Path parentPath = builder.getPath();
+                cache(state, parentPath);
+            } catch (MalformedPathException mpe) {
+                log.warn("Failed to build path of " + state.getId(), mpe);
+            }
+        }
+        return super.resolvePath(path, state, next);
     }
 
     /**
      * {@inheritDoc}
+     *
+     * Overridden method simply checks whether we have an item matching the id
+     * and returns its path, otherwise calls base implementation.
      */
-    public synchronized ItemId resolvePath(Path path)
-            throws PathNotFoundException, RepositoryException {
-        // check cache first
-        ItemId id = (ItemId) idCache.get(path);
-        if (id != null) {
-            return id;
+    public synchronized Path getPath(ItemId id)
+            throws ItemNotFoundException, RepositoryException {
+
+        if (id.denotesNode()) {
+            PathMap.Element element = get(id);
+            if (element != null) {
+                try {
+                    return element.getPath();
+                } catch (MalformedPathException mpe) {
+                    String msg = "Failed to build path of " + id;
+                    log.debug(msg);
+                    throw new RepositoryException(msg, mpe);
+                }
+            }
         }
-        id = delegatee.resolvePath(path);
-        idCache.put(path, id);
-        return id;
+        return super.getPath(id);
     }
 
     /**
      * {@inheritDoc}
-     */
-    public synchronized Path getPath(ItemId id)
-            throws ItemNotFoundException, RepositoryException {
-        return getAllPaths(id, false)[0];
+     *
+     * Overridden method tries to find a mapping for the intermediate item
+     * <code>state</code> and add its path elements to the builder currently
+     * being used. If no mapping is found, the item is cached instead after
+     * the base implementation has been invoked.
+     */
+    protected void getPath(Path.PathBuilder builder, ItemState state)
+            throws ItemStateException, RepositoryException {
+
+        if (state.isNode()) {
+            PathMap.Element element = get(state.getId());
+            if (element != null) {
+                try {
+                    Path.PathElement[] elements = element.getPath().getElements();
+                    for (int i = elements.length - 1; i >= 0; i--) {
+                        builder.addFirst(elements[i]);
+                    }
+                    return;
+                } catch (MalformedPathException mpe) {
+                    String msg = "Failed to build path of " + state.getId();
+                    log.debug(msg);
+                    throw new RepositoryException(msg, mpe);
+                }
+            }
+        }
+
+        super.getPath(builder, state);
+
+        if (state.isNode()) {
+            try {
+                cache(state, builder.getPath());
+            } catch (MalformedPathException mpe) {
+                log.warn("Failed to build path of " + state.getId());
+            }
+        }
     }
 
     /**
      * {@inheritDoc}
      */
-    public QName getName(ItemId itemId)
+    public QName getName(ItemId id)
             throws ItemNotFoundException, RepositoryException {
-        if (itemId.denotesNode()) {
-            return getPath(itemId).getNameElement().getName();
-        } else {
-            PropertyId propId = (PropertyId) itemId;
-            return propId.getName();
+
+        if (id.denotesNode()) {
+            PathMap.Element element = get(id);
+            if (element != null) {
+                return element.getName();
+            }
         }
+        return super.getName(id);
     }
 
     /**
@@ -136,11 +267,14 @@
      */
     public int getDepth(ItemId id)
             throws ItemNotFoundException, RepositoryException {
-        if (pathCache.containsKey(id)) {
-            return getPath(id).getAncestorCount();
-        } else {
-            return delegatee.getDepth(id);
+
+        if (id.denotesNode()) {
+            PathMap.Element element = get(id);
+            if (element != null) {
+                return element.getDepth();
+            }
         }
+        return super.getDepth(id);
     }
 
     /**
@@ -148,52 +282,458 @@
      */
     public boolean isAncestor(NodeId nodeId, ItemId itemId)
             throws ItemNotFoundException, RepositoryException {
-        if (pathCache.containsKey(nodeId) && pathCache.containsKey(itemId)) {
-            // use cached Path objects rather than calling delegatee
-            try {
-                return getPath(nodeId).isAncestorOf(getPath(itemId));
-            } catch (MalformedPathException mpe) {
-                // should never get here...
-                String msg = "failed to determine degree of relationship of "
-                        + nodeId + " and " + itemId;
-                throw new ItemNotFoundException(msg, mpe);
+
+        if (itemId.denotesNode()) {
+            PathMap.Element element = get(nodeId);
+            if (element != null) {
+                PathMap.Element child = get(itemId);
+                if (child != null) {
+                    return element.isAncestorOf(child);
+                }
             }
-        } else {
-            return delegatee.isAncestor(nodeId, itemId);
         }
+        return super.isAncestor(nodeId, itemId);
     }
 
     /**
      * {@inheritDoc}
      */
-    public synchronized Path[] getAllPaths(ItemId id)
+    public Path[] getAllPaths(ItemId id, boolean includeZombies)
             throws ItemNotFoundException, RepositoryException {
-        return getAllPaths(id, false);
+
+        if (!includeZombies) {
+            return new Path[] { getPath(id) };
+        }
+        return super.getAllPaths(id, includeZombies);
     }
 
+    //----------------------------------------------------< ItemStateListener >
+
     /**
      * {@inheritDoc}
      */
-    public synchronized Path[] getAllPaths(ItemId id, boolean includeZombies)
-            throws ItemNotFoundException, RepositoryException {
-        // check cache first
-        Path[] paths;
-        if (includeZombies) {
-            paths = (Path[]) zombiePathCache.get(id);
-            if (paths != null) {
-                return paths;
+    public void stateCreated(ItemState created) {
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void stateModified(ItemState modified) {
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void stateDestroyed(ItemState destroyed) {
+        destroyed.removeListener(this);
+        evict(destroyed.getId());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void stateDiscarded(ItemState discarded) {
+        discarded.removeListener(this);
+        evict(discarded.getId());
+    }
+
+    /**
+     * Called when an <code>ItemState</code> has been overlaid by some
+     * other state that now takes its identity. This notification is sent
+     * on the state being overlaid.
+     *
+     * @param overlayer the <code>ItemState</code> that overlays this state
+     */
+    public void stateOverlaid(ItemState overlayer) {
+        if (overlayer.isNode()) {
+            overlayer.getOverlayedState().removeListener(this);
+            overlayer.addListener(this);
+        }
+    }
+
+    /**
+     * Called when an <code>ItemState</code> no longer overlayes some other
+     * item state. This notification is sent on the state overlaying another
+     * state.
+     *
+     * @param overlayer the <code>ItemState</code> that overlaid another
+     *        item state. To get the overlaid state, invoke
+     *        {@link ItemState#getOverlayedState()}
+     */
+    public void stateUncovered(ItemState overlayer) {
+        if (overlayer.isNode()) {
+            overlayer.removeListener(this);
+            overlayer.getOverlayedState().addListener(this);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void nodeAdded(NodeState state, QName name, int index, String uuid) {
+        try {
+            Path path = Path.create(getPath(state.getId()), name, index, true);
+            insert(path, new NodeId(uuid));
+        } catch (PathNotFoundException e) {
+            log.warn("Added node does not have parent, ignoring event.");
+        } catch (MalformedPathException e) {
+            log.warn("Unable to create path of " + uuid, e);
+        } catch(ItemNotFoundException e) {
+            log.warn("Unable to get path of " + state.getId(), e);
+        } catch(RepositoryException e) {
+            log.warn("Unable to get path of " + state.getId(), e);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * Generate subsequent add and remove notifications for every replacement.
+     */
+    public void nodesReplaced(NodeState state) {
+        List entries = state.getReorderedChildNodeEntries();
+        if (entries.size() == 0) {
+            return;
+        }
+
+        Iterator iter = entries.iterator();
+        while (iter.hasNext()) {
+            NodeState.ChildNodeEntry now = (NodeState.ChildNodeEntry) iter.next();
+            NodeState.ChildNodeEntry old = null;
+
+            List list = ((NodeState) state.getOverlayedState()).
+                    getChildNodeEntries(now.getUUID());
+            if (list.size() > 0) {
+                old = (NodeState.ChildNodeEntry) list.get(0);
             }
-            paths = delegatee.getAllPaths(id, includeZombies);
-            zombiePathCache.put(id, paths);
-        } else {
-            paths = (Path[]) pathCache.get(id);
-            if (paths != null) {
-                return paths;
+            if (old == null) {
+                log.warn("Reordered child node not found in old list.");
+                continue;
             }
-            paths = delegatee.getAllPaths(id, includeZombies);
-            pathCache.put(id, paths);
+
+            nodeAdded(state, now.getName(), now.getIndex(), now.getUUID());
+            nodeRemoved(state, old.getName(), old.getIndex(), old.getUUID());
         }
-        return paths;
     }
-}
 
+    /**
+     * {@inheritDoc}
+     */
+    public void nodeRemoved(NodeState state, QName name, int index, String uuid) {
+        try {
+            Path path = Path.create(getPath(state.getId()), name, index, true);
+            remove(path, new NodeId(uuid));
+        } catch (PathNotFoundException e) {
+            log.warn("Added node does not have parent, ignoring event.");
+        } catch (MalformedPathException e) {
+            log.warn("Unable to create path of " + uuid, e);
+        } catch(ItemNotFoundException e) {
+            log.warn("Unable to get path of " + state.getId(), e);
+        } catch(RepositoryException e) {
+            log.warn("Unable to get path of " + state.getId(), e);
+        }
+    }
+
+    //-------------------------------------------------------< private methods >
+
+    /**
+     * Return a cached element in the path map, given its id
+     * @param id node id
+     * @return cached element, <code>null</code> if not found
+     */
+    private PathMap.Element get(ItemId id) {
+        synchronized (cacheMonitor) {
+            LRUEntry entry = (LRUEntry) idCache.get(id);
+            if (entry != null) {
+                entry.touch();
+                return entry.getElement();
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Return the nearest cached element in the path map, given a path.
+     * The returned element is guaranteed to have an associated object that
+     * is not <code>null</code>.
+     * @param path path
+     * @return cached element, <code>null</code> if not found
+     */
+    private PathMap.Element map(Path path) {
+        synchronized (cacheMonitor) {
+            PathMap.Element element = pathCache.map(path, false);
+            while (element != null) {
+                LRUEntry entry = (LRUEntry) element.get();
+                if (entry != null) {
+                    entry.touch();
+                    return element;
+                }
+                element = element.getParent();
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Cache an item in the hierarchy given its id and path. Adds a listener
+     * for this item state to get notified about changes.
+     * @param state item state, may be <code>null</code>
+     * @param path path to item
+     */
+    private void cache(ItemState state, Path path) {
+        ItemId id = state.getId();
+
+        synchronized (cacheMonitor) {
+            if (idCache.get(id) != null) {
+                return;
+            }
+            if (idCache.size() >= upperLimit) {
+                removeLRU();
+            }
+
+            PathMap.Element element = pathCache.put(path);
+            LRUEntry entry = new LRUEntry(id, element);
+            element.set(entry);
+            idCache.put(id, entry);
+
+            state.addListener(this);
+        }
+    }
+
+    /**
+     * Remove least recently used item. Scans the LRU list from head to tail
+     * and removes the first item that has no children.
+     */
+    private void removeLRU() {
+        synchronized (cacheMonitor) {
+            LRUEntry entry = head;
+            while (entry != null) {
+                PathMap.Element element = entry.getElement();
+                if (element.getChildrenCount() == 0) {
+                    evict(entry, true);
+                    return;
+                }
+                entry = entry.getNext();
+            }
+        }
+    }
+
+    /**
+     * Return a flag indicating whether a certain element is cached.
+     * @param id item id
+     * @return <code>true</code> if the item is already cached;
+     *         <code>false</code> otherwise
+     */
+    private boolean isCached(ItemId id) {
+        synchronized (cacheMonitor) {
+            return idCache.get(id) != null;
+        }
+    }
+
+    /**
+     * Evict item from cache. Evicts the associated <code>LRUEntry</code>
+     * @param id item id
+     */
+    private void evict(ItemId id) {
+        synchronized (cacheMonitor) {
+            LRUEntry entry = (LRUEntry) idCache.get(id);
+            if (entry != null) {
+                evict(entry, true);
+            }
+        }
+    }
+
+    /**
+     * Evict item from cache
+     * @param entry LRU entry
+     * @param removeFromPathCache whether to remove from path cache
+     */
+    private void evict(LRUEntry entry, boolean removeFromPathCache) {
+        synchronized (cacheMonitor) {
+            if (removeFromPathCache) {
+                PathMap.Element element = entry.getElement();
+                evict(element);
+                element.remove();
+            } else {
+                idCache.remove(entry.getId());
+                entry.remove();
+            }
+        }
+    }
+
+    /**
+     * Evict path map element from cache. This will traverse all children
+     * of this element and evict the objects associated with them
+     * @param element path map element
+     */
+    private void evict(PathMap.Element element) {
+        synchronized (cacheMonitor) {
+            element.traverse(new PathMap.ElementVisitor() {
+                public void elementVisited(PathMap.Element element) {
+                    evict((LRUEntry) element.get(), false);
+                }
+            }, false);
+        }
+    }
+
+    /**
+     * Insert a node into the cache. This will automatically shift
+     * all indexes of sibling nodes having index greater or equal.
+     * @param path child path
+     * @param id node id
+     */
+    private void insert(Path path, ItemId id) throws PathNotFoundException {
+        synchronized (cacheMonitor) {
+            PathMap.Element element = null;
+
+            LRUEntry entry = (LRUEntry) idCache.get(id);
+            if (entry != null) {
+                element = entry.getElement();
+                element.remove();
+            }
+
+            PathMap.Element parent = pathCache.map(path.getAncestor(1), true);
+            if (parent != null) {
+                parent.insert(path.getNameElement());
+            }
+            if (element != null) {
+                pathCache.put(path, element);
+
+                /* Remember this as a move */
+                movedIds.add(id);
+            }
+        }
+    }
+
+    /**
+     * Remove an item from the cache in order to shift the indexes
+     * of items following this item.
+     * @param path child path
+     * @param id node id
+     */
+    private void remove(Path path, ItemId id) throws PathNotFoundException {
+        synchronized (cacheMonitor) {
+            /* If we remembered this as a move, ignore this event */
+            if (movedIds.remove(id)) {
+                return;
+            }
+            PathMap.Element parent = pathCache.map(path.getAncestor(1), true);
+            if (parent != null) {
+                PathMap.Element element = parent.remove(path.getNameElement());
+                if (element != null) {
+                    evict(element);
+                }
+            }
+        }
+    }
+
+    /**
+     * Entry in the LRU list
+     */
+    private class LRUEntry {
+
+        /**
+         * Previous entry
+         */
+        private LRUEntry previous;
+
+        /**
+         * Next entry
+         */
+        private LRUEntry next;
+
+        /**
+         * Item id
+         */
+        private final ItemId id;
+
+        /**
+         * Element in path map
+         */
+        private final PathMap.Element element;
+
+        /**
+         * Create a new instance of this class
+         * @param id item id
+         */
+        public LRUEntry(ItemId id, PathMap.Element element) {
+            this.id = id;
+            this.element = element;
+
+            append();
+        }
+
+        /**
+         * Append entry to end of LRU list
+         */
+        public void append() {
+            if (tail == null) {
+                head = tail = this;
+            } else {
+                previous = tail;
+                tail.next = this;
+                tail = this;
+            }
+        }
+
+        /**
+         * Remove entry from LRU list
+         */
+        public void remove() {
+            if (previous != null) {
+                previous.next = next;
+            }
+            if (next != null) {
+                next.previous = previous;
+            }
+            if (head == this) {
+                head = next;
+            }
+            if (tail == this) {
+                tail = previous;
+            }
+            previous = next = null;
+        }
+
+        /**
+         * Touch entry. Removes it from its current position in the LRU list
+         * and moves it to the end.
+         */
+        public void touch() {
+            remove();
+            append();
+        }
+
+        /**
+         * Return previous LRU entry
+         * @return previous LRU entry
+         */
+        public LRUEntry getPrevious() {
+            return previous;
+        }
+
+        /**
+         * Return next LRU entry
+         * @return next LRU entry
+         */
+        public LRUEntry getNext() {
+            return next;
+        }
+
+        /**
+         * Return item ID
+         * @return item ID
+         */
+        public ItemId getId() {
+            return id;
+        }
+
+        /**
+         * Return element in path map
+         * @return element in path map
+         */
+        public PathMap.Element getElement() {
+            return element;
+        }
+    }
+}

Modified: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/HierarchyManagerImpl.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/HierarchyManagerImpl.java?rev=190372&r1=190371&r2=190372&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/HierarchyManagerImpl.java (original)
+++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/HierarchyManagerImpl.java Mon Jun 13 01:45:49 2005
@@ -211,59 +211,83 @@
             throw new RepositoryException(msg);
         }
 
-        NodeState parentState;
+        return resolvePath(path, rootNodeId, 1);
+    }
+
+    /**
+     * Resolve a path into an item ID. Recursively invoked method that may be
+     * overridden by some subclass to either return cached responses or add
+     * response to cache.
+     * @param path full path of item to resolve
+     * @param id item id
+     * @param next next path element index to resolve
+     */
+    protected ItemId resolvePath(Path path, ItemId id, int next)
+            throws RepositoryException {
+
         try {
-            parentState = (NodeState) getItemState(rootNodeId);
+            return resolvePath(path, getItemState(id), next);
+        } catch (NoSuchItemStateException e) {
+            String msg = "failed to retrieve state of intermediary node";
+            log.debug(msg);
+            throw new RepositoryException(msg, e);
         } catch (ItemStateException e) {
-            String msg = "failed to retrieve state of root node";
+            String msg = "failed to retrieve state of intermediary node";
             log.debug(msg);
             throw new RepositoryException(msg, e);
         }
+    }
 
-        Path.PathElement[] elems = path.getElements();
-        for (int i = 1; i < elems.length; i++) {
-            Path.PathElement elem = elems[i];
-            QName name = elem.getName();
-            int index = elem.getIndex();
-            if (index == 0) {
-                index = 1;
-            }
-            if (parentState.hasChildNodeEntry(name, index)) {
-                // child node
-                NodeState.ChildNodeEntry nodeEntry =
-                        parentState.getChildNodeEntry(name, index);
-                if (i == elems.length - 1) {
-                    // last element in the path
-                    return new NodeId(nodeEntry.getUUID());
-                }
-                try {
-                    parentState =
-                            (NodeState) getItemState(new NodeId(nodeEntry.getUUID()));
-                } catch (ItemStateException e) {
-                    String msg = "failed to retrieve state of intermediary node";
-                    log.debug(msg);
-                    throw new RepositoryException(msg, e);
-                }
-                continue;
-            } else if (parentState.hasPropertyEntry(name)) {
-                // property
-                if (index > 1) {
-                    // properties can't have same name siblings
-                    throw new PathNotFoundException(safeGetJCRPath(path));
-                }
-                if (i == elems.length - 1) {
-                    return new PropertyId(parentState.getUUID(), name);
-                } else {
-                    // property is not the last element in the path
-                    throw new PathNotFoundException(safeGetJCRPath(path));
-                }
-            } else {
-                // no such item
+    /**
+     * Resolve a path into an item ID. Recursively invoked method that may be
+     * overridden by some subclass to either return cached responses or add
+     * response to cache.
+     * @param path full path of item to resolve
+     * @param state intermediate state
+     * @param next next path element index to resolve
+     */
+    protected ItemId resolvePath(Path path, ItemState state, int next)
+            throws PathNotFoundException, ItemStateException {
+
+        Path.PathElement[] elements = path.getElements();
+        if (elements.length == next) {
+            return state.getId();
+        }
+        Path.PathElement elem = elements[next];
+
+        QName name = elem.getName();
+        int index = elem.getIndex();
+        if (index == 0) {
+            index = 1;
+        }
+
+        NodeState parentState = (NodeState) state;
+        ItemId childId;
+
+        if (parentState.hasChildNodeEntry(name, index)) {
+            // child node
+            NodeState.ChildNodeEntry nodeEntry =
+                    parentState.getChildNodeEntry(name, index);
+            childId = new NodeId(nodeEntry.getUUID());
+
+        } else if (parentState.hasPropertyEntry(name)) {
+            // property
+            if (index > 1) {
+                // properties can't have same name siblings
+                throw new PathNotFoundException(safeGetJCRPath(path));
+
+            } else if (next < elements.length - 1) {
+                // property is not the last element in the path
                 throw new PathNotFoundException(safeGetJCRPath(path));
             }
-        }
 
-        throw new PathNotFoundException(safeGetJCRPath(path));
+            childId = new PropertyId(parentState.getUUID(), name);
+
+        } else {
+            // no such item
+            throw new PathNotFoundException(safeGetJCRPath(path));
+        }
+        return resolvePath(path, getItemState(childId), next + 1);
     }
 
     /**
@@ -271,58 +295,11 @@
      */
     public synchronized Path getPath(ItemId id)
             throws ItemNotFoundException, RepositoryException {
-        try {
-            Path.PathBuilder builder = new Path.PathBuilder();
-
-            ItemState state = getItemState(id);
-            String parentUUID = state.getParentUUID();
-            if (parentUUID == null) {
-                // specified id denotes the root node
-                builder.addRoot();
-                return builder.getPath();
-            }
 
-            NodeState parent = (NodeState) getItemState(new NodeId(parentUUID));
-            do {
-                if (state.isNode()) {
-                    NodeState nodeState = (NodeState) state;
-                    String uuid = nodeState.getUUID();
-                    List entries = parent.getChildNodeEntries(uuid);
-                    if (entries.isEmpty()) {
-                        String msg = "failed to build path of " + id + ": "
-                                + parent.getUUID() + " has no child entry for "
-                                + uuid;
-                        log.debug(msg);
-                        throw new RepositoryException(msg);
-                    }
-                    // if the parent has more than one child node entries pointing
-                    // to the same child node, always use the first one
-                    NodeState.ChildNodeEntry entry =
-                            (NodeState.ChildNodeEntry) entries.get(0);
-                    // add to path
-                    if (entry.getIndex() == 1) {
-                        builder.addFirst(entry.getName());
-                    } else {
-                        builder.addFirst(entry.getName(), entry.getIndex());
-                    }
-                } else {
-                    PropertyState propState = (PropertyState) state;
-                    QName name = propState.getName();
-                    // add to path
-                    builder.addFirst(name);
-                }
-                parentUUID = parent.getParentUUID();
-                if (parentUUID != null) {
-                    state = parent;
-                    parent = (NodeState) getItemState(new NodeId(parentUUID));
-                } else {
-                    parent = null;
-                    state = null;
-                }
-            } while (parent != null);
+        Path.PathBuilder builder = new Path.PathBuilder();
 
-            // add root to path
-            builder.addRoot();
+        try {
+            getPath(builder, getItemState(id));
             return builder.getPath();
         } catch (NoSuchItemStateException nsise) {
             String msg = "failed to build path of " + id;
@@ -340,6 +317,57 @@
     }
 
     /**
+     * Adds the path element of an item ID to the path currently being built.
+     * Recursively invoked method that may be overridden by some subclass to
+     * either return cached responses or add response to cache. On exit,
+     * <code>builder</code> contains the path of <code>state</code>.
+     *
+     * @param builder builder currently being used
+     * @param state item to find path of
+     */
+    protected void getPath(Path.PathBuilder builder, ItemState state)
+            throws ItemStateException, RepositoryException {
+
+        String parentUUID = state.getParentUUID();
+        if (parentUUID == null) {
+            builder.addRoot();
+            return;
+        }
+
+        NodeState parent = (NodeState) getItemState(new NodeId(parentUUID));
+        getPath(builder, parent);
+
+        if (state.isNode()) {
+            NodeState nodeState = (NodeState) state;
+            String uuid = nodeState.getUUID();
+            List entries = parent.getChildNodeEntries(uuid);
+            if (entries.isEmpty()) {
+                String msg = "failed to build path of " + state.getId() + ": "
+                        + parent.getUUID() + " has no child entry for "
+                        + uuid;
+                log.debug(msg);
+                throw new ItemNotFoundException(msg);
+            }
+            // if the parent has more than one child node entries pointing
+            // to the same child node, always use the first one
+            NodeState.ChildNodeEntry entry =
+                    (NodeState.ChildNodeEntry) entries.get(0);
+            // add to path
+            if (entry.getIndex() == 1) {
+                builder.addLast(entry.getName());
+            } else {
+                builder.addLast(entry.getName(), entry.getIndex());
+            }
+
+        } else {
+            PropertyState propState = (PropertyState) state;
+            QName name = propState.getName();
+            // add to path
+            builder.addLast(name);
+        }
+    }
+
+    /**
      * {@inheritDoc}
      */
     public QName getName(ItemId itemId)
@@ -388,15 +416,12 @@
     public int getDepth(ItemId id)
             throws ItemNotFoundException, RepositoryException {
         try {
-            int depth = 0;
             ItemState state = getItemState(id, false);
             String parentUUID = state.getParentUUID();
-            while (parentUUID != null) {
-                state = getItemState(new NodeId(parentUUID), false);
-                parentUUID = state.getParentUUID();
-                depth++;
+            if (parentUUID != null) {
+                return getDepth(new NodeId(parentUUID)) + 1;
             }
-            return depth;
+            return 0;
         } catch (NoSuchItemStateException nsise) {
             String msg = "failed to determine depth of " + id;
             log.debug(msg);
@@ -416,12 +441,11 @@
         try {
             ItemState state = getItemState(itemId, false);
             String parentUUID = state.getParentUUID();
-            while (parentUUID != null) {
+            if (parentUUID != null) {
                 if (parentUUID.equals(nodeId.getUUID())) {
                     return true;
                 }
-                state = getItemState(new NodeId(parentUUID), false);
-                parentUUID = state.getParentUUID();
+                return isAncestor(nodeId, new NodeId(parentUUID));
             }
             return false;
         } catch (NoSuchItemStateException nsise) {
@@ -522,7 +546,7 @@
             return attic.getItemState(id);
         }
         // fallback: get transient/persistent state
-        return provider.getItemState(id);
+        return getItemState(id);
     }
 
     /**

Modified: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/ItemImpl.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/ItemImpl.java?rev=190372&r1=190371&r2=190372&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/ItemImpl.java (original)
+++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/ItemImpl.java Mon Jun 13 01:45:49 2005
@@ -1047,6 +1047,8 @@
                      * if necessary
                      */
                     state.removeListener(this);
+                    persistentState.addListener(this);
+                    stateMgr.disconnectTransientItemState(state);
                     state = persistentState;
                     state.addListener(this);
 

Modified: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/NodeImpl.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/NodeImpl.java?rev=190372&r1=190371&r2=190372&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/NodeImpl.java (original)
+++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/NodeImpl.java Mon Jun 13 01:45:49 2005
@@ -551,8 +551,7 @@
             throws RepositoryException {
         // modify the state of 'this', i.e. the parent node
         NodeState thisState = (NodeState) getOrCreateTransientItemState();
-        thisState.removeChildNodeEntry(oldName, index);
-        thisState.addChildNodeEntry(newName, uuid);
+        thisState.renameChildNodeEntry(oldName, index, newName);
     }
 
     protected void removeChildProperty(String propName) throws RepositoryException {
@@ -902,8 +901,7 @@
         NodeState persistentState = (NodeState) transientState.getOverlayedState();
         if (persistentState == null) {
             // this node is 'new'
-            persistentState = stateMgr.createNew(transientState.getUUID(),
-                    transientState.getNodeTypeName(), transientState.getParentUUID());
+            persistentState = stateMgr.createNew(transientState);
         }
         // copy state from transient state:
         // parent uuid's
@@ -924,6 +922,8 @@
         transientState.removeListener(this);
         // add listener to persistent state
         persistentState.addListener(this);
+        // tell state manager to disconnect item state
+        stateMgr.disconnectTransientItemState(transientState);
         // swap transient state with persistent state
         state = persistentState;
         // reset status

Modified: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/PathMap.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/PathMap.java?rev=190372&r1=190371&r2=190372&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/PathMap.java (original)
+++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/PathMap.java Mon Jun 13 01:45:49 2005
@@ -30,7 +30,7 @@
     /**
      * Root element
      */
-    private final Child root = new Child(Path.ROOT.getNameElement());
+    private final Element root = new Element(Path.ROOT.getNameElement());
 
     /**
      * Map a path to a child. If <code>exact</code> is <code>false</code>,
@@ -40,12 +40,12 @@
      * @return child, maybe <code>null</code> if <code>exact</code> is
      *         <code>true</code>
      */
-    public Child map(Path path, boolean exact) {
+    public Element map(Path path, boolean exact) {
         Path.PathElement[] elements = path.getElements();
-        Child current = root;
+        Element current = root;
 
         for (int i = 1; i < elements.length; i++) {
-            Child next = current.getChild(elements[i], false);
+            Element next = current.getChild(elements[i]);
             if (next == null) {
                 if (exact) {
                     return null;
@@ -58,48 +58,53 @@
     }
 
     /**
-     * Create a child given by its path. The path map will create any necessary
-     * intermediate children.
+     * Create an element given by its path. The path map will create any necessary
+     * intermediate elements.
      * @param path path to child
      * @param obj object to store at destination
      */
-    public Child put(Path path, Object obj) {
-        Child child = put(path);
-        child.obj = obj;
-        return child;
+    public Element put(Path path, Object obj) {
+        Element element = put(path);
+        element.obj = obj;
+        return element;
     }
 
     /**
-     * Create an empty child given by its path.
+     * Put an element given by its path. The path map will create any necessary
+     * intermediate elements.
      * @param path path to child
+     * @param element element to store at destination
      */
-    public Child put(Path path) {
+    public void put(Path path, Element element) {
         Path.PathElement[] elements = path.getElements();
-        Child current = root;
+        Element current = root;
 
-        for (int i = 1; i < elements.length; i++) {
-            current = current.getChild(elements[i], true);
+        for (int i = 1; i < elements.length - 1; i++) {
+            Element next = current.getChild(elements[i]);
+            if (next == null) {
+                next = current.createChild(elements[i]);
+            }
+            current = next;
         }
-        return current;
+        current.put(path.getNameElement(), element);
     }
 
     /**
-     * Ressurrect a child previously removed, given by its path and the
-     * child structure. If an item at path already exists, nothing happens.
-     * @param path new path to child
-     * @param zombie previously removed child object to store at destination
+     * Create an empty child given by its path.
+     * @param path path to child
      */
-    public void resurrect(Path path, Child zombie) {
+    public Element put(Path path) {
         Path.PathElement[] elements = path.getElements();
-        Path.PathElement name = path.getNameElement();
-        Child parent = root;
+        Element current = root;
 
-        if (map(path, true) == null) {
-            for (int i = 1; i < elements.length - 1; i++) {
-                parent = parent.getChild(elements[i], true);
+        for (int i = 1; i < elements.length; i++) {
+            Element next = current.getChild(elements[i]);
+            if (next == null) {
+                next = current.createChild(elements[i]);
             }
-            parent.setChild(name, zombie);
+            current = next;
         }
+        return current;
     }
 
     /**
@@ -109,7 +114,7 @@
      *                     or not; otherwise call back on non-empty children
      *                     only
      */
-    public void traverse(ChildVisitor visitor, boolean includeEmpty) {
+    public void traverse(ElementVisitor visitor, boolean includeEmpty) {
         root.traverse(visitor, includeEmpty);
     }
 
@@ -117,67 +122,72 @@
      * Internal class holding the object associated with a certain
      * path element.
      */
-    public static class Child {
+    public static class Element {
 
         /**
-         * Parent child
+         * Parent element
          */
-        private Child parent;
+        private Element parent;
 
         /**
-         * Map of immediate children of this child.
+         * Map of immediate children
          */
         private Map children;
 
         /**
-         * Number of non-null children
+         * Number of non-empty children
          */
         private int childrenCount;
 
         /**
-         * Object associated with this child
+         * Object associated with this element
          */
         private Object obj;
 
         /**
-         * QName associated with this child
+         * QName associated with this element
          */
         private QName name;
 
         /**
-         * index associated with this child
+         * index associated with this element
          */
         private int index;
 
         /**
          * Create a new instance of this class with a path element.
-         * @param element path element of this child
+         * @param nameIndex path element of this child
          */
-        Child(Path.PathElement element) {
-            this.name = element.getName();
-            this.index = element.getIndex();
+        private Element(Path.PathElement nameIndex) {
+            this.name = nameIndex.getName();
+            this.index = nameIndex.getIndex();
         }
 
         /**
-         * Create a new instance of this class.
+         * Create a child of this node inside the path map.
+         * @param nameIndex position where child is created
+         * @return child
          */
-        Child() {
+        private Element createChild(Path.PathElement nameIndex) {
+            Element element = new Element(nameIndex);
+            put(nameIndex, element);
+            return element;
         }
 
         /**
          * Insert an empty child. Will shift all children having an index
          * greater than or equal to the child inserted to the right.
-         * @param element child's path element
+         * @param nameIndex position where child is inserted
          */
-        public void insertChild(Path.PathElement element) {
-            int index = getOneBasedIndex(element) - 1;
+        public void insert(Path.PathElement nameIndex) {
+            int index = getOneBasedIndex(nameIndex) - 1;
             if (children != null) {
-                ArrayList list = (ArrayList) children.get(element.getName());
+                ArrayList list = (ArrayList) children.get(nameIndex.getName());
                 if (list != null && list.size() > index) {
                     for (int i = index; i < list.size(); i++) {
-                        Child child = (Child) list.get(i);
-                        if (child != null) {
-                            child.index++;
+                        Element element = (Element) list.get(i);
+                        if (element != null) {
+                            element.index++;
                         }
                     }
                     list.add(index, null);
@@ -186,112 +196,109 @@
         }
 
         /**
-         * Remove a child. Will shift all children having an index greater than
-         * the child removed to the left.
-         * @param element child's path element
-         * @return removed child, may be <code>null</code>
-         */
-        public Child removeChild(Path.PathElement element) {
-            int index = getOneBasedIndex(element) - 1;
-            if (children != null) {
-                ArrayList list = (ArrayList) children.get(element.getName());
-                if (list != null && list.size() > index) {
-                    for (int i = index + 1; i < list.size(); i++) {
-                        Child child = (Child) list.get(i);
-                        if (child != null) {
-                            child.index--;
-                        }
-                    }
-                    Child child = (Child) list.remove(index);
-                    if (child != null) {
-                        child.parent = null;
-                        childrenCount--;
-                    }
-                    if (obj == null && childrenCount == 0) {
-                        remove();
-                    }
-                    return child;
-                }
-            }
-            return null;
-        }
-
-        /**
-         * Return a child matching a path element. If a child does not exist
-         * at that position and <code>create</code> is <code>true</code> a
-         * new child will be created.
-         * @param element child's path element
-         * @param create flag indicating whether this child should be
-         *        created if not available
-         */
-        private Child getChild(Path.PathElement element, boolean create) {
-            int index = getOneBasedIndex(element) - 1;
-            Child child = null;
+         * Return an element matching a name and index.
+         * @param nameIndex position where child is located
+         * @return element matching <code>nameIndex</code> or <code>null</code> if
+         *         none exists.
+         */
+        private Element getChild(Path.PathElement nameIndex) {
+            int index = getOneBasedIndex(nameIndex) - 1;
+            Element element = null;
 
             if (children != null) {
-                ArrayList list = (ArrayList) children.get(element.getName());
+                ArrayList list = (ArrayList) children.get(nameIndex.getName());
                 if (list != null && list.size() > index) {
-                    child = (Child) list.get(index);
+                    element = (Element) list.get(index);
                 }
             }
-            if (child == null && create) {
-                child = new Child();
-                setChild(element, child);
-            }
-            return child;
+            return element;
         }
 
         /**
-         * Add a child.
-         * @param element child's path element
-         * @param child child to add
+         * Link a child of this node. Position is given by <code>nameIndex</code>.
+         * @param nameIndex position where child should be located
+         * @param element element to add
          */
-        private void setChild(Path.PathElement element, Child child) {
-            int index = getOneBasedIndex(element) - 1;
+        public void put(Path.PathElement nameIndex, Element element) {
+            int index = getOneBasedIndex(nameIndex) - 1;
             if (children == null) {
                 children = new HashMap();
             }
-            ArrayList list = (ArrayList) children.get(element.getName());
+            ArrayList list = (ArrayList) children.get(nameIndex.getName());
             if (list == null) {
                 list = new ArrayList();
-                children.put(element.getName(), list);
+                children.put(nameIndex.getName(), list);
             }
             while (list.size() < index) {
                 list.add(null);
             }
             if (list.size() == index) {
-                list.add(child);
+                list.add(element);
             } else {
-                list.set(index, child);
+                list.set(index, element);
             }
 
-            child.parent = this;
-            child.name = element.getName();
-            child.index = element.getIndex();
+            element.parent = this;
+            element.name = nameIndex.getName();
+            element.index = nameIndex.getIndex();
 
             childrenCount++;
         }
 
         /**
-         * Remove this child. Delegates the call to the parent item.
+         * Remove a child. Will shift all children having an index greater than
+         * the child removed to the left. If there are no more children left in
+         * this child and no object is associated with this child, the child
+         * itself gets removed.
+         *
+         * @param nameIndex child's path element
+         * @return removed child, may be <code>null</code>
+         */
+        public Element remove(Path.PathElement nameIndex) {
+            int index = getOneBasedIndex(nameIndex) - 1;
+            if (children != null) {
+                ArrayList list = (ArrayList) children.get(nameIndex.getName());
+                if (list != null && list.size() > index) {
+                    for (int i = index + 1; i < list.size(); i++) {
+                        Element element = (Element) list.get(i);
+                        if (element != null) {
+                            element.index--;
+                        }
+                    }
+                    Element element = (Element) list.remove(index);
+                    if (element != null) {
+                        element.parent = null;
+                        childrenCount--;
+                    }
+                    if (childrenCount == 0 && obj == null) {
+                        remove();
+                    }
+                    return element;
+                }
+            }
+            return null;
+        }
+
+        /**
+         * Remove this element. Delegates the call to the parent item.
          */
         public void remove() {
             if (parent != null) {
-                parent.removeChild(getPathElement());
+                parent.remove(getPathElement());
             }
         }
 
         /**
-         * Return the object associated with this child
-         * @return object associated with this child
+         * Return the object associated with this element
+         * @return object associated with this element
          */
         public Object get() {
             return obj;
         }
 
         /**
-         * Set the object associated with this child
-         * @param obj object associated with this child
+         * Set the object associated with this element
+         * @param obj object associated with this element
          */
         public void set(Object obj) {
             this.obj = obj;
@@ -302,7 +309,7 @@
         }
 
         /**
-         * Return the name of this child
+         * Return the name of this element
          * @return name
          */
         public QName getName() {
@@ -310,7 +317,7 @@
         }
 
         /**
-         * Return the index of this child
+         * Return the index of this element
          * @return index
          */
         public int getIndex() {
@@ -318,7 +325,7 @@
         }
 
         /**
-         * Return a path element pointing to this child
+         * Return a path element pointing to this element
          * @return path element
          */
         public Path.PathElement getPathElement() {
@@ -359,7 +366,7 @@
         }
 
         /**
-         * Checks whether this child has the specified path. Introduced to
+         * Checks whether this element has the specified path. Introduced to
          * avoid catching a <code>MalformedPathException</code> for simple
          * path comparisons.
          * @param path path to compare to
@@ -371,11 +378,11 @@
         }
 
         /**
-         * Checks whether this child has the specified path, given by
+         * Checks whether this element has the specified path, given by
          * path elements.
          * @param elements path elements to compare to
          * @param len number of elements to compare to
-         * @return <code>true</code> if this child has the path given;
+         * @return <code>true</code> if this element has the path given;
          *         otherwise <code>false</code>
          */
         private boolean hasPath(Path.PathElement[] elements, int len) {
@@ -391,8 +398,8 @@
         /**
          * Return 1-based index of a path element.
          */
-        private static int getOneBasedIndex(Path.PathElement element) {
-            int index = element.getIndex();
+        private static int getOneBasedIndex(Path.PathElement nameIndex) {
+            int index = nameIndex.getIndex();
             if (index == 0) {
                 return 1;
             } else {
@@ -404,31 +411,31 @@
          * Recursively invoked traversal method.
          * @param visitor visitor to invoke
          * @param includeEmpty if <code>true</code> invoke call back on every
-         *        child regardless, whether the associated object is empty
+         *        element regardless, whether the associated object is empty
          *        or not; otherwise call back on non-empty children only
          */
-        public void traverse(ChildVisitor visitor, boolean includeEmpty) {
+        public void traverse(ElementVisitor visitor, boolean includeEmpty) {
             if (children != null) {
                 Iterator iter = children.values().iterator();
                 while (iter.hasNext()) {
                     ArrayList list = (ArrayList) iter.next();
                     for (int i = 0; i < list.size(); i++) {
-                        Child child = (Child) list.get(i);
-                        if (child != null) {
-                            child.traverse(visitor, includeEmpty);
+                        Element element = (Element) list.get(i);
+                        if (element != null) {
+                            element.traverse(visitor, includeEmpty);
                         }
                     }
                 }
             }
             if (includeEmpty || obj != null) {
-                visitor.childVisited(this);
+                visitor.elementVisited(this);
             }
         }
 
         /**
-         * Return the depth of this child. Defined to be <code>0</code> for the
-         * root node and <code>n + 1</code> for some child if the depth of its
-         * parent is <code>n</code>.
+         * Return the depth of this element. Defined to be <code>0</code> for the
+         * root element and <code>n + 1</code> for some element if the depth of
+         * its parent is <code>n</code>.
          */
         public int getDepth() {
             if (parent != null) {
@@ -442,8 +449,8 @@
          * child of this node.
          * @param other node to check
          */
-        public boolean isAncestorOf(Child other) {
-            Child parent = other.parent;
+        public boolean isAncestorOf(Element other) {
+            Element parent = other.parent;
             while (parent != null) {
                 if (parent == this) {
                     return true;
@@ -454,23 +461,31 @@
         }
 
         /**
-         * Return the parent of this child
-         * @return parent or <code>null</code> if this is the root node
+         * Return the parent of this element
+         * @return parent or <code>null</code> if this is the root element
          */
-        public Child getParent() {
+        public Element getParent() {
             return parent;
         }
+
+        /**
+         * Return the children count of this element
+         * @return children count
+         */
+        public int getChildrenCount() {
+            return childrenCount;
+        }
     }
 
     /**
-     * Child visitor used in {@link PathMap#traverse}
+     * Element visitor used in {@link PathMap#traverse}
      */
-    public interface ChildVisitor {
+    public interface ElementVisitor {
 
         /**
-         * Invoked for every child visited on a tree traversal
-         * @param child child visited
+         * Invoked for every element visited on a tree traversal
+         * @param element element visited
          */
-        void childVisited(Child child);
+        void elementVisited(Element element);
     }
 }

Modified: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/PropertyImpl.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/PropertyImpl.java?rev=190372&r1=190371&r2=190372&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/PropertyImpl.java (original)
+++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/PropertyImpl.java Mon Jun 13 01:45:49 2005
@@ -105,8 +105,7 @@
         PropertyState persistentState = (PropertyState) transientState.getOverlayedState();
         if (persistentState == null) {
             // this property is 'new'
-            persistentState = stateMgr.createNew(transientState.getName(),
-                    transientState.getParentUUID());
+            persistentState = stateMgr.createNew(transientState);
         }
         // copy state from transient state
         persistentState.setDefinitionId(transientState.getDefinitionId());
@@ -119,6 +118,8 @@
         transientState.removeListener(this);
         // add listener to persistent state
         persistentState.addListener(this);
+        // tell state manager to disconnect item state
+        stateMgr.disconnectTransientItemState(transientState);
         // swap transient state with persistent state
         state = persistentState;
         // reset status

Modified: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/WorkspaceImpl.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/WorkspaceImpl.java?rev=190372&r1=190371&r2=190372&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/WorkspaceImpl.java (original)
+++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/WorkspaceImpl.java Mon Jun 13 01:45:49 2005
@@ -128,7 +128,7 @@
         this.wspConfig = wspConfig;
         this.rep = rep;
         this.stateMgr = new TransactionalItemStateManager(stateMgr, this);
-        this.hierMgr = new HierarchyManagerImpl(rep.getRootNodeUUID(),
+        this.hierMgr = new CachingHierarchyManager(rep.getRootNodeUUID(),
                 this.stateMgr, session.getNamespaceResolver());
         this.session = session;
     }

Modified: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/lock/LockManagerImpl.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/lock/LockManagerImpl.java?rev=190372&r1=190371&r2=190372&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/lock/LockManagerImpl.java (original)
+++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/lock/LockManagerImpl.java Mon Jun 13 01:45:49 2005
@@ -28,6 +28,7 @@
 import org.apache.jackrabbit.core.observation.SynchronousEventListener;
 import org.apache.jackrabbit.core.observation.EventImpl;
 import org.apache.log4j.Logger;
+import org.apache.commons.collections.SequencedHashMap;
 
 import javax.jcr.lock.Lock;
 import javax.jcr.lock.LockException;
@@ -37,9 +38,8 @@
 import javax.jcr.Node;
 import javax.jcr.observation.Event;
 import javax.jcr.observation.EventIterator;
-import java.util.Map;
-import java.util.HashMap;
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.io.File;
 import java.io.IOException;
 import java.io.BufferedReader;
@@ -78,12 +78,6 @@
      */
     private final NamespaceResolver nsResolver;
 
-    /**`
-     * Map of nodes that been removed and may be re-added as result
-     * of a move operation
-     */
-    private final Map zombieNodes = new HashMap();
-
     /**
      * Create a new instance of this class.
      * @param session system session
@@ -178,9 +172,9 @@
     private void save() throws IOException {
         final ArrayList list = new ArrayList();
 
-        lockMap.traverse(new PathMap.ChildVisitor() {
-            public void childVisited(PathMap.Child child) {
-                LockInfo info = (LockInfo) child.get();
+        lockMap.traverse(new PathMap.ElementVisitor() {
+            public void elementVisited(PathMap.Element element) {
+                LockInfo info = (LockInfo) element.get();
                 if (!info.sessionScoped) {
                     list.add(info);
                 }
@@ -222,17 +216,17 @@
 
         // check whether node is already locked
         Path path = node.getPrimaryPath();
-        PathMap.Child child = lockMap.map(path, false);
+        PathMap.Element element = lockMap.map(path, false);
 
-        LockInfo other = (LockInfo) child.get();
+        LockInfo other = (LockInfo) element.get();
         if (other != null) {
-            if (child.hasPath(path)) {
+            if (element.hasPath(path)) {
                 throw new LockException("Node already locked: " + node.safeGetJCRPath());
             } else if (other.deep) {
                 throw new LockException("Parent node has deep lock.");
             }
         }
-        if (info.deep && child.hasPath(path)) {
+        if (info.deep && element.hasPath(path)) {
             throw new LockException("Some child node is locked.");
         }
 
@@ -273,9 +267,9 @@
                     new NodeId(info.getUUID()));
             Path path = node.getPrimaryPath();
 
-            PathMap.Child child = lockMap.map(path, true);
-            if (child != null) {
-                child.set(null);
+            PathMap.Element element = lockMap.map(path, true);
+            if (element != null) {
+                element.set(null);
             }
 
             // set live flag to false
@@ -317,12 +311,12 @@
 
         Path path = node.getPrimaryPath();
 
-        PathMap.Child child = lockMap.map(path, false);
-        LockInfo info = (LockInfo) child.get();
+        PathMap.Element element = lockMap.map(path, false);
+        LockInfo info = (LockInfo) element.get();
         if (info == null) {
             throw new LockException("Node not locked: " + node.safeGetJCRPath());
         }
-        if (child.hasPath(path) || info.deep) {
+        if (element.hasPath(path) || info.deep) {
             SessionImpl session = (SessionImpl) node.getSession();
             Node lockHolder = (Node) session.getItemManager().getItem(
                     new NodeId(info.getUUID()));
@@ -341,12 +335,12 @@
         // check whether node is locked by this session
         Path path = node.getPrimaryPath();
 
-        PathMap.Child child = lockMap.map(path, true);
-        if (child == null) {
+        PathMap.Element element = lockMap.map(path, true);
+        if (element == null) {
             throw new LockException("Node not locked: " + node.safeGetJCRPath());
         }
 
-        LockInfo info = (LockInfo) child.get();
+        LockInfo info = (LockInfo) element.get();
         if (info == null) {
             throw new LockException("Node not locked: " + node.safeGetJCRPath());
         }
@@ -355,7 +349,7 @@
         }
 
         // remove lock in path map
-        child.set(null);
+        element.set(null);
         info.setLive(false);
 
         // remove properties in content
@@ -368,11 +362,11 @@
      * {@inheritDoc}
      */
     public synchronized boolean holdsLock(NodeImpl node) throws RepositoryException {
-        PathMap.Child child = lockMap.map(node.getPrimaryPath(), true);
-        if (child == null) {
+        PathMap.Element element = lockMap.map(node.getPrimaryPath(), true);
+        if (element == null) {
             return false;
         }
-        return child.get() != null;
+        return element.get() != null;
     }
 
     /**
@@ -381,12 +375,12 @@
     public synchronized boolean isLocked(NodeImpl node) throws RepositoryException {
         Path path = node.getPrimaryPath();
 
-        PathMap.Child child = lockMap.map(path, false);
-        LockInfo info = (LockInfo) child.get();
+        PathMap.Element element = lockMap.map(path, false);
+        LockInfo info = (LockInfo) element.get();
         if (info == null) {
             return false;
         }
-        if (child.hasPath(path)) {
+        if (element.hasPath(path)) {
             return true;
         } else {
             return info.deep;
@@ -408,10 +402,10 @@
     public void checkLock(Path path, Session session)
             throws LockException, RepositoryException {
 
-        PathMap.Child child = lockMap.map(path, false);
-        LockInfo info = (LockInfo) child.get();
+        PathMap.Element element = lockMap.map(path, false);
+        LockInfo info = (LockInfo) element.get();
         if (info != null) {
-            if (child.hasPath(path) || info.deep) {
+            if (element.hasPath(path) || info.deep) {
                 if (!session.equals(info.getLockHolder())) {
                     throw new LockException("Node locked.");
                 }
@@ -428,9 +422,9 @@
 
             NodeImpl node = (NodeImpl) session.getItemManager().
                     getItem(new NodeId(lockToken.uuid));
-            PathMap.Child child = lockMap.map(node.getPrimaryPath(), true);
-            if (child != null) {
-                LockInfo info = (LockInfo) child.get();
+            PathMap.Element element = lockMap.map(node.getPrimaryPath(), true);
+            if (element != null) {
+                LockInfo info = (LockInfo) element.get();
                 if (info != null) {
                     if (info.getLockHolder() == null) {
                         info.setLockHolder(session);
@@ -457,9 +451,9 @@
 
             NodeImpl node = (NodeImpl) session.getItemManager().
                     getItem(new NodeId(lockToken.uuid));
-            PathMap.Child child = lockMap.map(node.getPrimaryPath(), true);
-            if (child != null) {
-                LockInfo info = (LockInfo) child.get();
+            PathMap.Element element = lockMap.map(node.getPrimaryPath(), true);
+            if (element != null) {
+                LockInfo info = (LockInfo) element.get();
                 if (info != null) {
                     if (session.equals(info.getLockHolder())) {
                         info.setLockHolder(null);
@@ -480,84 +474,210 @@
     //----------------------------------------------< SynchronousEventListener >
 
     /**
+     * Internal event class that holds old and new paths for moved nodes
+     */
+    private class HierarchyEvent {
+
+        /**
+         * UUID recorded in event
+         */
+        public final String uuid;
+
+        /**
+         * Path recorded in event
+         */
+        public final Path path;
+
+        /**
+         * Old path in move operation
+         */
+        private Path oldPath;
+
+        /**
+         * New path in move operation
+         */
+        private Path newPath;
+
+        /**
+         * Event type, may be {@link Event#NODE_ADDED},
+         * {@link Event#NODE_REMOVED} or a combination of both
+         */
+        private int type;
+
+        /**
+         * Create a new instance of this class.
+         * @param uuid uuid
+         * @param path path
+         * @param type event type
+         */
+        public HierarchyEvent(String uuid, Path path, int type) {
+            this.uuid = uuid;
+            this.path = path;
+            this.type = type;
+        }
+
+        /**
+         * Merge this event with another event. The result will be stored in
+         * this event
+         * @param event other event to merge with
+         */
+        public void merge(HierarchyEvent event) {
+            type |= event.type;
+            if (event.type == Event.NODE_ADDED) {
+                newPath = event.path;
+                oldPath = path;
+            } else {
+                oldPath = event.path;
+                newPath = path;
+            }
+        }
+
+        /**
+         * Return the event type. May be {@link Event#NODE_ADDED},
+         * {@link Event#NODE_REMOVED} or a combination of both.\
+         * @return event type
+         */
+        public int getType() {
+            return type;
+        }
+
+        /**
+         * Return the old path if this is a move operation
+         * @return old path
+         */
+        public Path getOldPath() {
+            return oldPath;
+        }
+
+        /**
+         * Return the new path if this is a move operation
+         * @return new path
+         */
+        public Path getNewPath() {
+            return newPath;
+        }
+    }
+
+    /**
      * {@inheritDoc}
      */
     public void onEvent(EventIterator events) {
-        while (events.hasNext()) {
-            EventImpl event = (EventImpl) events.nextEvent();
-            switch (event.getType()) {
+        Iterator iter = consolidateEvents(events);
+        while (iter.hasNext()) {
+            HierarchyEvent event = (HierarchyEvent) iter.next();
+            switch (event.type) {
                 case Event.NODE_ADDED:
-                    try {
-                        nodeAdded(event.getChildUUID(),
-                                Path.create(event.getPath(), nsResolver, true));
-                    } catch (MalformedPathException e) {
-                        log.info("Unable to get event's path: " + e.getMessage());
-                    } catch (RepositoryException e) {
-                        log.info("Unable to get event's path: " + e.getMessage());
-                    }
+                    nodeAdded(event.path);
                     break;
                 case Event.NODE_REMOVED:
-                    try {
-                        nodeRemoved(event.getChildUUID(),
-                                Path.create(event.getPath(), nsResolver, true));
-                    } catch (MalformedPathException e) {
-                        log.info("Unable to get event's path: " + e.getMessage());
-                    } catch (RepositoryException e) {
-                        log.info("Unable to get event's path: " + e.getMessage());
-                    }
+                    nodeRemoved(event.path);
+                    break;
+                case Event.NODE_ADDED | Event.NODE_REMOVED:
+                    nodeMoved(event.getOldPath(), event.getNewPath());
                     break;
             }
         }
     }
 
     /**
-     * Invoked when some node has been added. Relink the child inside our
-     * zombie map to the new parent. Revitalize all locks inside the
-     * zombie child hierarchy.
+     * Consolidate an event iterator obtained from observation, merging
+     * add and remove operations on nodes with the same UUID into a move
+     * operation.
+     */
+    private Iterator consolidateEvents(EventIterator events) {
+        SequencedHashMap eventMap = new SequencedHashMap();
+
+        while (events.hasNext()) {
+            EventImpl event = (EventImpl) events.nextEvent();
+            HierarchyEvent he;
+
+            try {
+                he = new HierarchyEvent(event.getChildUUID(),
+                        Path.create(event.getPath(), nsResolver, true),
+                        event.getType());
+            } catch (MalformedPathException e) {
+                log.info("Unable to get event's path: " + e.getMessage());
+                continue;
+            } catch (RepositoryException e) {
+                log.info("Unable to get event's path: " + e.getMessage());
+                continue;
+            }
+
+            HierarchyEvent heExisting = (HierarchyEvent) eventMap.get(he.uuid);
+            if (heExisting != null) {
+                heExisting.merge(he);
+            } else {
+                eventMap.put(he.uuid, he);
+            }
+        }
+        return eventMap.values().iterator();
+    }
+
+    /**
+     * Invoked when some node has been added. If the parent of that node
+     * exists, shift all name siblings of the new node having an index greater
+     * or equal.
+     * @param path path of added node
      */
-    private synchronized void nodeAdded(String uuid, Path path) {
+    private synchronized void nodeAdded(Path path) {
         try {
-            PathMap.Child parent = lockMap.map(path.getAncestor(1), true);
+            PathMap.Element parent = lockMap.map(path.getAncestor(1), true);
             if (parent != null) {
-                parent.insertChild(path.getNameElement());
+                parent.insert(path.getNameElement());
             }
-            PathMap.Child zombie = (PathMap.Child) zombieNodes.remove(uuid);
-            if (zombie != null) {
-                zombie.traverse(new PathMap.ChildVisitor() {
-                    public void childVisited(PathMap.Child child) {
-                        LockInfo info = (LockInfo) child.get();
-                        info.setLive(true);
-                    }
-                }, false);
-                lockMap.resurrect(path, zombie);
+        } catch (PathNotFoundException e) {
+            log.warn("Unable to determine path of added node's parent.", e);
+            return;
+        }
+    }
+
+    /**
+     * Invoked when some node has been moved. Relink the child inside our
+     * map to the new parent.
+     * @param oldPath old path
+     */
+    private synchronized void nodeMoved(Path oldPath, Path newPath) {
+        PathMap.Element element = lockMap.map(oldPath, true);
+        if (element != null) {
+            element.remove();
+        }
+
+        try {
+            PathMap.Element parent = lockMap.map(newPath.getAncestor(1), true);
+            if (parent != null) {
+                parent.insert(newPath.getNameElement());
+            }
+            if (element != null) {
+                lockMap.put(newPath, element);
             }
         } catch (PathNotFoundException e) {
-            log.warn("Added node does not have parent, ignoring event.");
+            log.warn("Unable to determine path of moved node's parent.", e);
+            return;
         }
     }
 
     /**
-     * Invoked when some node has been removed. Unlink the child inside
-     * our path map corresponding to that node. Disable all locks contained
-     * in that subtree.
+     * Invoked when some node has been removed. Remove the child from our
+     * path map. Disable all locks contained in that subtree.
+     * @param path path of removed node
      */
-    private synchronized void nodeRemoved(String uuid, Path path) {
+    private synchronized void nodeRemoved(Path path) {
         try {
-            PathMap.Child parent = lockMap.map(path.getAncestor(1), true);
+            PathMap.Element parent = lockMap.map(path.getAncestor(1), true);
             if (parent != null) {
-                PathMap.Child child = parent.removeChild(path.getNameElement());
-                if (child != null) {
-                    child.traverse(new PathMap.ChildVisitor() {
-                        public void childVisited(PathMap.Child child) {
-                            LockInfo info = (LockInfo) child.get();
+                PathMap.Element element = parent.remove(path.getNameElement());
+                if (element != null) {
+                    element.traverse(new PathMap.ElementVisitor() {
+                        public void elementVisited(PathMap.Element element) {
+                            LockInfo info = (LockInfo) element.get();
                             info.setLive(false);
                         }
                     }, false);
-                    zombieNodes.put(uuid, child);
                 }
             }
         } catch (PathNotFoundException e) {
-            log.warn("Removed node does not have parent, ignoring event.");
+            log.warn("Unable to determine path of moved node's parent.", e);
+            return;
         }
     }
 }

Modified: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/ItemState.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/ItemState.java?rev=190372&r1=190371&r2=190372&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/ItemState.java (original)
+++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/ItemState.java Mon Jun 13 01:45:49 2005
@@ -90,7 +90,7 @@
     /**
      * Listeners (weak references)
      */
-    protected final transient Map listeners =
+    private final transient Map listeners =
             Collections.synchronizedMap(new ReferenceMap(ReferenceMap.WEAK, ReferenceMap.WEAK));
 
     // the backing persistent item state (may be null)
@@ -127,12 +127,10 @@
     /**
      * Protected constructor
      *
-     * @param overlayedState backing persistent item state
      * @param initialStatus  the initial status of the new <code>ItemState</code> instance
      * @param isTransient   flag indicating whether this state is transient or not
      */
-    protected ItemState(ItemState overlayedState, int initialStatus,
-                        boolean isTransient) {
+    protected ItemState(int initialStatus, boolean isTransient) {
         switch (initialStatus) {
             case STATUS_EXISTING:
             case STATUS_EXISTING_MODIFIED:
@@ -144,8 +142,6 @@
                 log.debug(msg);
                 throw new IllegalArgumentException(msg);
         }
-        this.overlayedState = overlayedState;
-        this.overlayedState.addListener(this);
         this.isTransient = isTransient;
     }