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 2006/08/31 11:38:18 UTC

svn commit: r438851 [1/2] - in /jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core: ./ state/ version/ virtual/

Author: dpfister
Date: Thu Aug 31 02:38:17 2006
New Revision: 438851

URL: http://svn.apache.org/viewvc?rev=438851&view=rev
Log:
JCR-552 Move listeners from item state to item state managers

Added:
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/StateChangeDispatcher.java
Removed:
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/TransientItemStateManager.java
Modified:
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/CachingHierarchyManager.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/ItemImpl.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/ItemManager.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/NodeImpl.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/PropertyImpl.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/SessionImpl.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/WorkspaceImpl.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/ItemState.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/ItemStateListener.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/LocalItemStateManager.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/NodeState.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/NodeStateListener.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/SessionItemStateManager.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/version/InternalVersionItemImpl.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/version/VersionItemStateProvider.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/version/VersionManagerImpl.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/version/XAVersionManager.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/virtual/AbstractVISProvider.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/virtual/VirtualItemStateProvider.java
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/virtual/VirtualNodeState.java

Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/CachingHierarchyManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/CachingHierarchyManager.java?rev=438851&r1=438850&r2=438851&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/CachingHierarchyManager.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/CachingHierarchyManager.java Thu Aug 31 02:38:17 2006
@@ -282,7 +282,9 @@
      * {@inheritDoc}
      */
     public void stateModified(ItemState modified) {
-        stateModified((NodeState) modified);
+        if (modified.isNode()) {
+            stateModified((NodeState) modified);
+        }
     }
 
     /**
@@ -324,7 +326,6 @@
      * {@inheritDoc}
      */
     public void stateDestroyed(ItemState destroyed) {
-        destroyed.removeListener(this);
         remove(destroyed.getId());
     }
 
@@ -332,7 +333,6 @@
      * {@inheritDoc}
      */
     public void stateDiscarded(ItemState discarded) {
-        discarded.removeListener(this);
         if (discarded.isTransient() && !discarded.hasOverlayedState()) {
             // a new node has been discarded -> remove from cache
             remove(discarded.getId());
@@ -344,36 +344,6 @@
     }
 
     /**
-     * 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, NodeId id) {
@@ -507,8 +477,6 @@
             LRUEntry entry = new LRUEntry(id, element);
             element.set(entry);
             idCache.put(id, entry);
-
-            state.addListener(this);
         }
     }
 

Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/ItemImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/ItemImpl.java?rev=438851&r1=438850&r2=438851&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/ItemImpl.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/ItemImpl.java Thu Aug 31 02:38:17 2006
@@ -141,9 +141,6 @@
             }
         }
         notifyCreated();
-
-        // add this item as listener to events of the underlying state object
-        this.state.addListener(this);
     }
 
     /**
@@ -893,110 +890,108 @@
      * {@inheritDoc}
      */
     public void stateDestroyed(ItemState destroyed) {
-        // underlying state has been permanently destroyed
-
-        // set state of this instance to 'destroyed'
-        status = STATUS_DESTROYED;
-        // dispose state
         if (state == destroyed) {
-            state.removeListener(this);
-            state = null;
+            // set state of this instance to 'destroyed'
+            status = STATUS_DESTROYED;
+            // dispose state
+            if (state == destroyed) {
+                state = null;
+            }
+            /**
+             * notify the listeners that this instance has been
+             * permanently invalidated
+             */
+            notifyDestroyed();
         }
-        /**
-         * notify the listeners that this instance has been
-         * permanently invalidated
-         */
-        notifyDestroyed();
     }
 
     /**
      * {@inheritDoc}
      */
     public void stateModified(ItemState modified) {
-        status = STATUS_MODIFIED;
+        if (state == modified) {
+            status = STATUS_MODIFIED;
+        }
     }
 
     /**
      * {@inheritDoc}
      */
     public void stateDiscarded(ItemState discarded) {
-        /**
-         * the state of this item has been discarded, probably as a result
-         * of calling Item.refresh(false) or ItemImpl.setRemoved()
-         */
-        if (isTransient()) {
-            switch (state.getStatus()) {
-                /**
-                 * persistent item that has been transiently removed
-                 */
-                case ItemState.STATUS_EXISTING_REMOVED:
-                    /**
-                     * persistent item that has been transiently modified
-                     */
-                case ItemState.STATUS_EXISTING_MODIFIED:
-                    /**
-                     * persistent item that has been transiently modified or removed
-                     * and the underlying persistent state has been externally
-                     * modified since the transient modification/removal.
-                     */
-                case ItemState.STATUS_STALE_MODIFIED:
-                    ItemState persistentState = state.getOverlayedState();
+        if (state == discarded) {
+            /**
+             * the state of this item has been discarded, probably as a result
+             * of calling Item.refresh(false) or ItemImpl.setRemoved()
+             */
+            if (isTransient()) {
+                switch (state.getStatus()) {
                     /**
-                     * the state is a transient wrapper for the underlying
-                     * persistent state, therefore restore the
-                     * persistent state and resurrect this item instance
-                     * if necessary
+                     * persistent item that has been transiently removed
                      */
-                    state.removeListener(this);
-                    persistentState.addListener(this);
-                    stateMgr.disconnectTransientItemState(state);
-                    state = persistentState;
+                    case ItemState.STATUS_EXISTING_REMOVED:
+                        /**
+                         * persistent item that has been transiently modified
+                         */
+                    case ItemState.STATUS_EXISTING_MODIFIED:
+                        /**
+                         * persistent item that has been transiently modified or removed
+                         * and the underlying persistent state has been externally
+                         * modified since the transient modification/removal.
+                         */
+                    case ItemState.STATUS_STALE_MODIFIED:
+                        ItemState persistentState = state.getOverlayedState();
+                        /**
+                         * the state is a transient wrapper for the underlying
+                         * persistent state, therefore restore the
+                         * persistent state and resurrect this item instance
+                         * if necessary
+                         */
+                        stateMgr.disconnectTransientItemState(state);
+                        state = persistentState;
 
-                    return;
+                        return;
 
-                    /**
-                     * persistent item that has been transiently modified or removed
-                     * and the underlying persistent state has been externally
-                     * destroyed since the transient modification/removal.
-                     */
-                case ItemState.STATUS_STALE_DESTROYED:
-                    /**
-                     * first notify the listeners that this instance has been
-                     * permanently invalidated
-                     */
-                    notifyDestroyed();
-                    // now set state of this instance to 'destroyed'
-                    status = STATUS_DESTROYED;
-                    // finally dispose state
-                    state.removeListener(this);
-                    state = null;
-                    return;
+                        /**
+                         * persistent item that has been transiently modified or removed
+                         * and the underlying persistent state has been externally
+                         * destroyed since the transient modification/removal.
+                         */
+                    case ItemState.STATUS_STALE_DESTROYED:
+                        /**
+                         * first notify the listeners that this instance has been
+                         * permanently invalidated
+                         */
+                        notifyDestroyed();
+                        // now set state of this instance to 'destroyed'
+                        status = STATUS_DESTROYED;
+                        state = null;
+                        return;
 
-                    /**
-                     * new item that has been transiently added
-                     */
-                case ItemState.STATUS_NEW:
-                    /**
-                     * first notify the listeners that this instance has been
-                     * permanently invalidated
-                     */
-                    notifyDestroyed();
-                    // now set state of this instance to 'destroyed'
-                    status = STATUS_DESTROYED;
-                    // finally dispose state
-                    state.removeListener(this);
-                    state = null;
-                    return;
+                        /**
+                         * new item that has been transiently added
+                         */
+                    case ItemState.STATUS_NEW:
+                        /**
+                         * first notify the listeners that this instance has been
+                         * permanently invalidated
+                         */
+                        notifyDestroyed();
+                        // now set state of this instance to 'destroyed'
+                        status = STATUS_DESTROYED;
+                        // finally dispose state
+                        state = null;
+                        return;
+                }
             }
-        }
 
-        /**
-         * first notify the listeners that this instance has been
-         * invalidated
-         */
-        notifyInvalidated();
-        // now render this instance 'invalid'
-        status = STATUS_INVALIDATED;
+            /**
+             * first notify the listeners that this instance has been
+             * invalidated
+             */
+            notifyInvalidated();
+            // now render this instance 'invalid'
+            status = STATUS_INVALIDATED;
+        }
     }
 
     //-----------------------------------------------------------------< Item >

Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/ItemManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/ItemManager.java?rev=438851&r1=438850&r2=438851&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/ItemManager.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/ItemManager.java Thu Aug 31 02:38:17 2006
@@ -28,6 +28,8 @@
 import org.apache.jackrabbit.core.state.NoSuchItemStateException;
 import org.apache.jackrabbit.core.state.NodeState;
 import org.apache.jackrabbit.core.state.PropertyState;
+import org.apache.jackrabbit.core.state.SessionItemStateManager;
+import org.apache.jackrabbit.core.state.ItemStateListener;
 import org.apache.jackrabbit.core.util.Dumpable;
 import org.apache.jackrabbit.core.version.VersionHistoryImpl;
 import org.apache.jackrabbit.core.version.VersionImpl;
@@ -74,7 +76,7 @@
  * If the parent <code>Session</code> is an <code>XASession</code>, there is
  * one <code>ItemManager</code> instance per started global transaction.
  */
-public class ItemManager implements ItemLifeCycleListener, Dumpable {
+public class ItemManager implements ItemLifeCycleListener, Dumpable, ItemStateListener {
 
     private static Logger log = LoggerFactory.getLogger(ItemManager.class);
 
@@ -100,7 +102,7 @@
      * @param rootNodeDef       the definition of the root node
      * @param rootNodeId        the id of the root node
      */
-    protected ItemManager(ItemStateManager itemStateProvider, HierarchyManager hierMgr,
+    protected ItemManager(SessionItemStateManager itemStateProvider, HierarchyManager hierMgr,
                           SessionImpl session, NodeDefinition rootNodeDef,
                           NodeId rootNodeId) {
         this.itemStateProvider = itemStateProvider;
@@ -111,6 +113,7 @@
 
         // setup item cache with weak references to items
         itemCache = new ReferenceMap(ReferenceMap.HARD, ReferenceMap.WEAK);
+        itemStateProvider.addListener(this);
     }
 
     /**
@@ -689,6 +692,48 @@
                 ps.print("          ");
             }
             ps.println(id + "\t" + item.safeGetJCRPath() + " (" + item + ")");
+        }
+    }
+
+    //----------------------------------------------------< ItemStateListener >
+
+    /**
+     * {@inheritDoc}
+     */
+    public void stateCreated(ItemState created) {
+        ItemImpl item = retrieveItem(created.getId());
+        if (item != null) {
+            item.stateCreated(created);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void stateModified(ItemState modified) {
+        ItemImpl item = retrieveItem(modified.getId());
+        if (item != null) {
+            item.stateModified(modified);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void stateDestroyed(ItemState destroyed) {
+        ItemImpl item = retrieveItem(destroyed.getId());
+        if (item != null) {
+            item.stateDestroyed(destroyed);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void stateDiscarded(ItemState discarded) {
+        ItemImpl item = retrieveItem(discarded.getId());
+        if (item != null) {
+            item.stateDiscarded(discarded);
         }
     }
 }

Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/NodeImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/NodeImpl.java?rev=438851&r1=438850&r2=438851&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/NodeImpl.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/NodeImpl.java Thu Aug 31 02:38:17 2006
@@ -281,10 +281,6 @@
                 // make transient (copy-on-write)
                 NodeState transientState =
                         stateMgr.createTransientNodeState((NodeState) state, ItemState.STATUS_EXISTING_MODIFIED);
-                // remove listener on persistent state
-                state.removeListener(this);
-                // add listener on transient state
-                transientState.addListener(this);
                 // replace persistent with transient state
                 state = transientState;
             } catch (ItemStateException ise) {
@@ -972,10 +968,6 @@
             stateMgr.store(persistentState);
         }
 
-        // remove listener from transient state
-        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

Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/PropertyImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/PropertyImpl.java?rev=438851&r1=438850&r2=438851&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/PropertyImpl.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/PropertyImpl.java Thu Aug 31 02:38:17 2006
@@ -89,10 +89,6 @@
             try {
                 PropertyState transientState =
                         stateMgr.createTransientPropertyState((PropertyState) state, ItemState.STATUS_EXISTING_MODIFIED);
-                // remove listener on persistent state
-                state.removeListener(this);
-                // add listener on transient state
-                transientState.addListener(this);
                 // swap persistent with transient state
                 state = transientState;
             } catch (ItemStateException ise) {
@@ -134,10 +130,6 @@
             stateMgr.store(persistentState);
         }
 
-        // remove listener from transient state
-        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

Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/SessionImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/SessionImpl.java?rev=438851&r1=438850&r2=438851&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/SessionImpl.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/SessionImpl.java Thu Aug 31 02:38:17 2006
@@ -30,6 +30,7 @@
 import org.apache.jackrabbit.core.state.SessionItemStateManager;
 import org.apache.jackrabbit.core.state.UpdatableItemStateManager;
 import org.apache.jackrabbit.core.state.SharedItemStateManager;
+import org.apache.jackrabbit.core.state.LocalItemStateManager;
 import org.apache.jackrabbit.value.ValueFactoryImpl;
 import org.apache.jackrabbit.core.version.VersionManager;
 import org.apache.jackrabbit.core.xml.DocViewSAXEventGenerator;
@@ -258,7 +259,7 @@
      *
      * @return session item state manager
      */
-    protected SessionItemStateManager createSessionItemStateManager(UpdatableItemStateManager manager) {
+    protected SessionItemStateManager createSessionItemStateManager(LocalItemStateManager manager) {
         return new SessionItemStateManager(rep.getRootNodeId(),
                 manager, getNamespaceResolver());
     }

Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/WorkspaceImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/WorkspaceImpl.java?rev=438851&r1=438850&r2=438851&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/WorkspaceImpl.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/WorkspaceImpl.java Thu Aug 31 02:38:17 2006
@@ -95,7 +95,7 @@
      * (i.e. that is isolated from transient changes made through
      * the session).
      */
-    protected final HierarchyManagerImpl hierMgr;
+    protected final CachingHierarchyManager hierMgr;
 
     /**
      * The <code>ObservationManager</code> instance for this session.
@@ -133,6 +133,7 @@
         this.stateMgr = createItemStateManager(stateMgr);
         this.hierMgr = new CachingHierarchyManager(rep.getRootNodeId(),
                 this.stateMgr, session.getNamespaceResolver());
+        this.stateMgr.addListener(hierMgr);
         this.session = session;
     }
 

Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/ItemState.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/ItemState.java?rev=438851&r1=438850&r2=438851&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/ItemState.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/ItemState.java Thu Aug 31 02:38:17 2006
@@ -18,7 +18,6 @@
 
 import org.apache.jackrabbit.core.ItemId;
 import org.apache.jackrabbit.core.NodeId;
-import org.apache.jackrabbit.util.WeakIdentityCollection;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -26,12 +25,11 @@
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 import java.io.Serializable;
-import java.util.Collection;
 
 /**
  * <code>ItemState</code> represents the state of an <code>Item</code>.
  */
-public abstract class ItemState implements ItemStateListener, Serializable {
+public abstract class ItemState implements Serializable {
 
     /** Serialization UID of this class. */
     static final long serialVersionUID = -1473610775880779769L;
@@ -87,9 +85,9 @@
     private final boolean isTransient;
 
     /**
-     * Listeners (weak references)
+     * Parent container.
      */
-    private final transient Collection listeners = new WeakIdentityCollection(5);
+    private transient ItemStateListener container;
 
     /**
      * the backing persistent item state (may be null)
@@ -173,10 +171,6 @@
      * <code>LocalItemStateManager</code> when this item state has been disposed.
      */
     void onDisposed() {
-        // prepare this instance so it can be gc'ed
-        synchronized (listeners) {
-            listeners.clear();
-        }
         disconnect();
         overlayedState = null;
         status = STATUS_UNDEFINED;
@@ -192,7 +186,6 @@
             }
         }
         this.overlayedState = overlayedState;
-        this.overlayedState.addListener(this);
     }
 
     /**
@@ -203,7 +196,6 @@
         if (this.overlayedState == null) {
             throw new IllegalStateException("Item state cannot be reconnected because there's no underlying state to reconnect to: " + this);
         }
-        this.overlayedState.addListener(this);
     }
 
     /**
@@ -211,77 +203,51 @@
      */
     protected void disconnect() {
         if (overlayedState != null) {
-            // de-register listener on overlayed state...
-            overlayedState.removeListener(this);
             overlayedState = null;
         }
     }
 
     /**
-     * Notify the listeners that the persistent state this object is
-     * representing has been discarded.
+     * Return a flag indicating whether this state is connected to some other state.
+     * @return <code>true</code> if this state is connected, <code>false</code> otherwise.
+     */
+    protected boolean isConnected() {
+        return overlayedState != null;
+    }
+
+    /**
+     * Notify the parent container about changes to this state.
      */
     protected void notifyStateDiscarded() {
-        // copy listeners to array to avoid ConcurrentModificationException
-        ItemStateListener[] la;
-        synchronized (listeners) {
-            la = (ItemStateListener[]) listeners.toArray(new ItemStateListener[listeners.size()]);
-        }
-        for (int i = 0; i < la.length; i++) {
-            if (la[i] != null) {
-                la[i].stateDiscarded(this);
-            }
+        if (container != null) {
+            container.stateDiscarded(this);
         }
     }
 
     /**
-     * Notify the listeners that the persistent state this object is
-     * representing has been created.
+     * Notify the parent container about changes to this state.
      */
     protected void notifyStateCreated() {
-        // copy listeners to array to avoid ConcurrentModificationException
-        ItemStateListener[] la;
-        synchronized (listeners) {
-            la = (ItemStateListener[]) listeners.toArray(new ItemStateListener[listeners.size()]);
-        }
-        for (int i = 0; i < la.length; i++) {
-            if (la[i] != null) {
-                la[i].stateCreated(this);
-            }
+        if (container != null) {
+            container.stateCreated(this);
         }
     }
 
     /**
-     * Notify the listeners that the persistent state this object is
-     * representing has been updated.
+     * Notify the parent container about changes to this state.
      */
     public void notifyStateUpdated() {
-        // copy listeners to array to avoid ConcurrentModificationException
-        ItemStateListener[] la;
-        synchronized (listeners) {
-            la = (ItemStateListener[]) listeners.toArray(new ItemStateListener[listeners.size()]);
-        }
-        for (int i = 0; i < la.length; i++) {
-            if (la[i] != null) {
-                la[i].stateModified(this);
-            }
+        if (container != null) {
+            container.stateModified(this);
         }
     }
 
     /**
-     * Notify the listeners that the persistent state this object is
-     * representing has been destroyed.
+     * Notify the parent container about changes to this state.
      */
     protected void notifyStateDestroyed() {
-        // copy listeners to array to avoid ConcurrentModificationException
-        ItemStateListener[] la;
-        synchronized (listeners) {
-            la = (ItemStateListener[]) listeners.toArray(new ItemStateListener[listeners.size()]);
-        }
-        for (int i = 0; i < la.length; i++) {
-            if (la[i] != null) {
-                la[i].stateDestroyed(this);
-            }
+        if (container != null) {
+            container.stateDestroyed(this);
         }
     }
 
@@ -427,26 +393,23 @@
     }
 
     /**
-     * Add an <code>ItemStateListener</code>
-     *
-     * @param listener the new listener to be informed on modifications
+     * Set the parent container that will receive notifications about changes to this state.
+     * @param container container to be informed on modifications
      */
-    public void addListener(ItemStateListener listener) {
-        synchronized (listeners) {
-            assert (!listeners.contains(listener));
-            listeners.add(listener);
+    public void setContainer(ItemStateListener container) {
+        if (this.container != null) {
+            throw new IllegalStateException("State already connected to a container: " + this.container);
         }
+        this.container = container;
     }
 
     /**
-     * Remove an <code>ItemStateListener</code>
-     *
-     * @param listener an existing listener
+     * Return the parent container that will receive notifications about changes to this state. Returns
+     * <code>null</code> if none has been yet assigned.
+     * @return container or <code>null</code>
      */
-    public void removeListener(ItemStateListener listener) {
-        synchronized (listeners) {
-            listeners.remove(listener);
-        }
+    public ItemStateListener getContainer() {
+        return container;
     }
 
     /**
@@ -455,53 +418,6 @@
      * @return the approximate memory consumption of this state.
      */
     public abstract long calculateMemoryFootprint();
-
-    //----------------------------------------------------< ItemStateListener >
-    /**
-     * {@inheritDoc}
-     */
-    public void stateCreated(ItemState created) {
-        // underlying state has been permanently created
-        status = STATUS_EXISTING;
-        pull();
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public void stateDestroyed(ItemState destroyed) {
-        // underlying state has been permanently destroyed
-        if (isTransient) {
-            status = STATUS_STALE_DESTROYED;
-        } else {
-            status = STATUS_EXISTING_REMOVED;
-            notifyStateDestroyed();
-        }
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public void stateModified(ItemState modified) {
-        // underlying state has been modified
-        if (isTransient) {
-            status = STATUS_STALE_MODIFIED;
-        } else {
-            synchronized (this) {
-                // this instance represents existing state, update it
-                pull();
-                notifyStateUpdated();
-            }
-        }
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public void stateDiscarded(ItemState discarded) {
-        // underlying persistent state has been discarded, discard this instance too
-        discard();
-    }
 
     //-------------------------------------------------< Serializable support >
     private void writeObject(ObjectOutputStream out) throws IOException {

Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/ItemStateListener.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/ItemStateListener.java?rev=438851&r1=438850&r2=438851&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/ItemStateListener.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/ItemStateListener.java Thu Aug 31 02:38:17 2006
@@ -19,8 +19,6 @@
 /**
  * The <code>ItemStateListener</code> interface allows an implementing object
  * to be informed about changes on an <code>ItemState</code>.
- *
- * @see ItemState#addListener
  */
 public interface ItemStateListener {
 

Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/LocalItemStateManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/LocalItemStateManager.java?rev=438851&r1=438850&r2=438851&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/LocalItemStateManager.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/LocalItemStateManager.java Thu Aug 31 02:38:17 2006
@@ -30,7 +30,7 @@
  * persistent states from other clients.
  */
 public class LocalItemStateManager
-        implements UpdatableItemStateManager, ItemStateListener {
+        implements UpdatableItemStateManager, NodeStateListener {
 
     /**
      * cache of weak references to ItemState objects issued by this
@@ -59,6 +59,11 @@
     private final ChangeLog changeLog = new ChangeLog();
 
     /**
+     * State change dispatcher.
+     */
+    private final transient StateChangeDispatcher dispatcher = new StateChangeDispatcher();
+
+    /**
      * Creates a new <code>LocalItemStateManager</code> instance.
      * @param sharedStateMgr shared state manager
      * @param factory event state collection factory
@@ -68,6 +73,8 @@
         cache = new ItemStateReferenceCache();
         this.sharedStateMgr = sharedStateMgr;
         this.factory = factory;
+
+        sharedStateMgr.addListener(this);
     }
 
     /**
@@ -90,8 +97,8 @@
         // put it in cache
         cache.cache(state);
 
-        // register as listener
-        state.addListener(this);
+        // set parent container
+        state.setContainer(this);
         return state;
     }
 
@@ -115,8 +122,8 @@
         // put it in cache
         cache.cache(state);
 
-        // register as listener
-        state.addListener(this);
+        // set parent container
+        state.setContainer(this);
         return state;
     }
 
@@ -231,6 +238,7 @@
         NodeState state = new NodeState(id, nodeTypeName, parentId,
                 ItemState.STATUS_NEW, false);
         changeLog.added(state);
+        state.setContainer(this);
         return state;
     }
 
@@ -245,6 +253,7 @@
         PropertyState state = new PropertyState(
                 new PropertyId(parentId, propName), ItemState.STATUS_NEW, false);
         changeLog.added(state);
+        state.setContainer(this);
         return state;
     }
 
@@ -321,16 +330,14 @@
      * {@inheritDoc}
      */
     public void dispose() {
+        sharedStateMgr.removeListener(this);
+
         // this LocalItemStateManager instance is no longer needed;
         // cached item states can now be safely discarded
         Iterator iter = cache.values().iterator();
         while (iter.hasNext()) {
             ItemState state = (ItemState) iter.next();
-            // we're no longer interested in status changes of this item state
-            state.removeListener(this);
-            // discard item state; any remaining listeners will be informed
-            // about this status change
-            state.discard();
+            dispatcher.notifyStateDiscarded(state);
             // let the item state know that it has been disposed
             state.onDisposed();
         }
@@ -338,34 +345,157 @@
         cache.evictAll();
     }
 
+    /**
+     * Add an <code>ItemStateListener</code>
+     * @param listener the new listener to be informed on modifications
+     */
+    public void addListener(ItemStateListener listener) {
+        dispatcher.addListener(listener);
+    }
+
+    /**
+     * Remove an <code>ItemStateListener</code>
+     * @param listener an existing listener
+     */
+    public void removeListener(ItemStateListener listener) {
+        dispatcher.removeListener(listener);
+    }
+
     //----------------------------------------------------< ItemStateListener >
+
     /**
      * {@inheritDoc}
+     * <p/>
+     * Notification handler gets called for both local states that this state manager
+     * has created, as well as states that were created by the shared state manager
+     * we're listening to.
      */
     public void stateCreated(ItemState created) {
+        ItemState local = null;
+        if (created.getContainer() != this) {
+            // shared state was created
+            try {
+                local = changeLog.get(created.getId());
+                if (local != null) {
+                    // underlying state has been permanently created
+                    local.pull();
+                    local.setStatus(ItemState.STATUS_EXISTING);
+                    cache.cache(local);
+                }
+            } catch (NoSuchItemStateException e) {
+                /* ignore */
+            }
+        } else {
+            // local state was created
+            local = created;
+        }
+        if (local != null) {
+            dispatcher.notifyStateCreated(created);
+        }
     }
 
     /**
      * {@inheritDoc}
+     * <p/>
+     * Notification handler gets called for both local states that this state manager
+     * has created, as well as states that were created by the shared state manager
+     * we're listening to.
      */
     public void stateModified(ItemState modified) {
+        ItemState local = null;
+        if (modified.getContainer() != this) {
+            // shared state was modified
+            local = cache.retrieve(modified.getId());
+            if (local != null && local.isConnected()) {
+                // this instance represents existing state, update it
+                local.pull();
+            }
+        } else {
+            // local state was modified
+            local = modified;
+        }
+        if (local != null) {
+            dispatcher.notifyStateModified(local);
+        }
     }
 
     /**
      * {@inheritDoc}
+     * <p/>
+     * Notification handler gets called for both local states that this state manager
+     * has created, as well as states that were created by the shared state manager
+     * we're listening to.
      */
     public void stateDestroyed(ItemState destroyed) {
-        destroyed.removeListener(this);
-
+        ItemState local = null;
+        if (destroyed.getContainer() != this) {
+            // shared state was destroyed
+            local = cache.retrieve(destroyed.getId());
+            if (local != null && local.isConnected()) {
+                local.setStatus(ItemState.STATUS_EXISTING_REMOVED);
+            }
+        } else {
+            // local state was destroyed
+            local = destroyed;
+        }
         cache.evict(destroyed.getId());
+        if (local != null) {
+            dispatcher.notifyStateDestroyed(local);
+        }
     }
 
     /**
      * {@inheritDoc}
+     * <p/>
+     * Notification handler gets called for both local states that this state manager
+     * has created, as well as states that were created by the shared state manager
+     * we're listening to.
      */
     public void stateDiscarded(ItemState discarded) {
-        discarded.removeListener(this);
-
+        ItemState local = null;
+        if (discarded.getContainer() != this) {
+            // shared state was discarded
+            local = cache.retrieve(discarded.getId());
+            if (local != null && local.isConnected()) {
+                local.setStatus(ItemState.STATUS_UNDEFINED);
+            }
+        } else {
+            // local state was discarded
+            local = discarded;
+        }
         cache.evict(discarded.getId());
+        if (local != null) {
+            dispatcher.notifyStateDiscarded(local);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p/>
+     * Optimization: shared state manager we're listening to does not deliver node state changes, therefore the state
+     * concerned must be a local state.
+     */
+    public void nodeAdded(NodeState state, QName name, int index, NodeId id) {
+        dispatcher.notifyNodeAdded(state, name, index, id);
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p/>
+     * Optimization: shared state manager we're listening to does not deliver node state changes, therefore the state
+     * concerned must be a local state.
+     */
+    public void nodesReplaced(NodeState state) {
+        dispatcher.notifyNodesReplaced(state);
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p/>
+     * Optimization: shared state manager we're listening to does not deliver node state changes, therefore the state
+     * concerned must be a local state.
+     */
+    public void nodeRemoved(NodeState state, QName name, int index, NodeId id) {
+        dispatcher.notifyNodeRemoved(state, name, index, id);
     }
 }

Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/NodeState.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/NodeState.java?rev=438851&r1=438850&r2=438851&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/NodeState.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/NodeState.java Thu Aug 31 02:38:17 2006
@@ -23,7 +23,6 @@
 import org.apache.jackrabbit.core.ItemId;
 import org.apache.jackrabbit.core.nodetype.NodeDefId;
 import org.apache.jackrabbit.name.QName;
-import org.apache.jackrabbit.util.WeakIdentityCollection;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -102,9 +101,9 @@
     private boolean sharedPropertyNames = false;
 
     /**
-     * Listeners (weak references)
+     * Listener.
      */
-    private final transient Collection listeners = new WeakIdentityCollection(3);
+    private transient NodeStateListener listener;
 
     /**
      * Constructs a new node state that is initially connected to an overlayed
@@ -736,40 +735,21 @@
     }
 
     //--------------------------------------------------< ItemState overrides >
-    /**
-     * {@inheritDoc}
-     * <p/>
-     * If the listener passed is at the same time a <code>NodeStateListener</code>
-     * we add it to our list of specialized listeners.
-     */
-    public void addListener(ItemStateListener listener) {
-        if (listener instanceof NodeStateListener) {
-            synchronized (listeners) {
-                if (listeners.contains(listener)) {
-                    log.debug("listener already registered: " + listener);
-                    // no need to add to call ItemState.addListener()
-                    return;
-                } else {
-                    listeners.add(listener);
-                }
-            }
-        }
-        super.addListener(listener);
-    }
 
     /**
      * {@inheritDoc}
      * <p/>
      * If the listener passed is at the same time a <code>NodeStateListener</code>
-     * we remove it from our list of specialized listeners.
+     * we remember it as well.
      */
-    public void removeListener(ItemStateListener listener) {
+    public void setContainer(ItemStateListener listener) {
         if (listener instanceof NodeStateListener) {
-            synchronized (listeners) {
-                listeners.remove(listener);
+            if (this.listener != null) {
+                throw new IllegalStateException("State already connected to a listener: " + this.listener);
             }
+            this.listener = (NodeStateListener) listener;
         }
-        super.removeListener(listener);
+        super.setContainer(listener);
     }
 
     //-------------------------------------------------< misc. helper methods >
@@ -804,15 +784,8 @@
      * Notify the listeners that a child node entry has been added
      */
     protected void notifyNodeAdded(ChildNodeEntry added) {
-        synchronized (listeners) {
-            Iterator iter = listeners.iterator();
-            while (iter.hasNext()) {
-                NodeStateListener l = (NodeStateListener) iter.next();
-                if (l != null) {
-                    l.nodeAdded(this, added.getName(),
-                            added.getIndex(), added.getId());
-                }
-            }
+        if (listener != null) {
+            listener.nodeAdded(this, added.getName(), added.getIndex(), added.getId());
         }
     }
 
@@ -820,14 +793,8 @@
      * Notify the listeners that the child node entries have been replaced
      */
     protected void notifyNodesReplaced() {
-        synchronized (listeners) {
-            Iterator iter = listeners.iterator();
-            while (iter.hasNext()) {
-                NodeStateListener l = (NodeStateListener) iter.next();
-                if (l != null) {
-                    l.nodesReplaced(this);
-                }
-            }
+        if (listener != null) {
+            listener.nodesReplaced(this);
         }
     }
 
@@ -835,15 +802,8 @@
      * Notify the listeners that a child node entry has been removed
      */
     protected void notifyNodeRemoved(ChildNodeEntry removed) {
-        synchronized (listeners) {
-            Iterator iter = listeners.iterator();
-            while (iter.hasNext()) {
-                NodeStateListener l = (NodeStateListener) iter.next();
-                if (l != null) {
-                    l.nodeRemoved(this, removed.getName(),
-                            removed.getIndex(), removed.getId());
-                }
-            }
+        if (listener != null) {
+            listener.nodeRemoved(this, removed.getName(), removed.getIndex(), removed.getId());
         }
     }
 

Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/NodeStateListener.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/NodeStateListener.java?rev=438851&r1=438850&r2=438851&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/NodeStateListener.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/NodeStateListener.java Thu Aug 31 02:38:17 2006
@@ -22,8 +22,6 @@
 /**
  * Extends the <code>ItemStateListener</code> allowing a client to be
  * additionally informed about changes on a <code>NodeState</code>.
- *
- * @see NodeState#addListener
  */
 public interface NodeStateListener extends ItemStateListener {
 

Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/SessionItemStateManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/SessionItemStateManager.java?rev=438851&r1=438850&r2=438851&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/SessionItemStateManager.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/SessionItemStateManager.java Thu Aug 31 02:38:17 2006
@@ -22,6 +22,7 @@
 import org.apache.jackrabbit.core.ItemId;
 import org.apache.jackrabbit.core.NodeId;
 import org.apache.jackrabbit.core.ZombieHierarchyManager;
+import org.apache.jackrabbit.core.PropertyId;
 import org.apache.jackrabbit.core.util.Dumpable;
 import org.apache.jackrabbit.name.NamespaceResolver;
 import org.apache.jackrabbit.name.QName;
@@ -37,46 +38,67 @@
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Collection;
 
 /**
- * <code>SessionItemStateManager</code> ...
+ * Item state manager that handles both transient and persistent items.
  */
 public class SessionItemStateManager
-        implements UpdatableItemStateManager, Dumpable {
+        implements UpdatableItemStateManager, Dumpable, NodeStateListener {
 
     private static Logger log = LoggerFactory.getLogger(SessionItemStateManager.class);
 
     /**
      * State manager that allows updates
      */
-    private final UpdatableItemStateManager persistentStateMgr;
+    private final UpdatableItemStateManager stateMgr;
 
     /**
-     * State manager for the transient items
+     * Hierarchy manager
      */
-    private final TransientItemStateManager transientStateMgr;
+    private CachingHierarchyManager hierMgr;
 
     /**
-     * Hierarchy manager
+     * map of those states that have been removed transiently
      */
-    private CachingHierarchyManager hierMgr;
+    private final ItemStateStore atticStore;
+
+    /**
+     * map of new or modified transient states
+     */
+    private final ItemStateStore transientStore;
+
+    /**
+     * ItemStateManager view of the states in the attic; lazily instantiated
+     * in {@link #getAttic()}
+     */
+    private AtticItemStateManager attic;
+
+    /**
+     * State change dispatcher.
+     */
+    private final transient StateChangeDispatcher dispatcher = new StateChangeDispatcher();
 
     /**
      * Creates a new <code>SessionItemStateManager</code> instance.
      *
      * @param rootNodeId
-     * @param persistentStateMgr
+     * @param stateMgr
      * @param nsResolver
      */
     public SessionItemStateManager(NodeId rootNodeId,
-                                   UpdatableItemStateManager persistentStateMgr,
+                                   LocalItemStateManager stateMgr,
                                    NamespaceResolver nsResolver) {
 
-        this.persistentStateMgr = persistentStateMgr;
-        // create transient item state manager
-        transientStateMgr = new TransientItemStateManager();
+        this.stateMgr = stateMgr;
+        stateMgr.addListener(this);
+
         // create hierarchy manager that uses both transient and persistent state
         hierMgr = new CachingHierarchyManager(rootNodeId, this, nsResolver);
+        addListener(hierMgr);
+
+        transientStore = new ItemStateMap();
+        atticStore = new ItemStateMap();
     }
 
     /**
@@ -95,7 +117,19 @@
     public void dump(PrintStream ps) {
         ps.println("SessionItemStateManager (" + this + ")");
         ps.println();
-        transientStateMgr.dump(ps);
+        ps.print("[transient] ");
+        if (transientStore instanceof Dumpable) {
+            ((Dumpable) transientStore).dump(ps);
+        } else {
+            ps.println(transientStore.toString());
+        }
+        ps.println();
+        ps.print("[attic]     ");
+        if (atticStore instanceof Dumpable) {
+            ((Dumpable) atticStore).dump(ps);
+        } else {
+            ps.println(atticStore.toString());
+        }
         ps.println();
     }
 
@@ -107,7 +141,7 @@
             throws NoSuchItemStateException, ItemStateException {
 
         // first check if the specified item has been transiently removed
-        if (transientStateMgr.getAttic().hasItemState(id)) {
+        if (atticStore.contains(id)) {
             /**
              * check if there's new transient state for the specified item
              * (e.g. if a property with name 'x' has been removed and a new
@@ -115,17 +149,17 @@
              * this will throw a NoSuchItemStateException if there's no new
              * transient state
              */
-            return transientStateMgr.getItemState(id);
+            return getTransientItemState(id);
         }
 
         // check if there's transient state for the specified item
-        if (transientStateMgr.hasItemState(id)) {
-            return transientStateMgr.getItemState(id);
+        if (transientStore.contains(id)) {
+            return getTransientItemState(id);
         }
 
         // check if there's persistent state for the specified item
-        if (persistentStateMgr.hasItemState(id)) {
-            return persistentStateMgr.getItemState(id);
+        if (stateMgr.hasItemState(id)) {
+            return stateMgr.getItemState(id);
         }
 
         throw new NoSuchItemStateException(id.toString());
@@ -136,20 +170,20 @@
      */
     public boolean hasItemState(ItemId id) {
         // first check if the specified item has been transiently removed
-        if (transientStateMgr.getAttic().hasItemState(id)) {
+        if (atticStore.contains(id)) {
             /**
              * check if there's new transient state for the specified item
              * (e.g. if a property with name 'x' has been removed and a new
              * property with same name has been created);
              */
-            return transientStateMgr.hasItemState(id);
+            return transientStore.contains(id);
         }
         // check if there's transient state for the specified item
-        if (transientStateMgr.hasItemState(id)) {
+        if (transientStore.contains(id)) {
             return true;
         }
         // check if there's persistent state for the specified item
-        return persistentStateMgr.hasItemState(id);
+        return stateMgr.hasItemState(id);
     }
 
     /**
@@ -158,14 +192,14 @@
     public NodeReferences getNodeReferences(NodeReferencesId id)
             throws NoSuchItemStateException, ItemStateException {
 
-        return persistentStateMgr.getNodeReferences(id);
+        return stateMgr.getNodeReferences(id);
     }
 
     /**
      * {@inheritDoc}
      */
     public boolean hasNodeReferences(NodeReferencesId id) {
-        return persistentStateMgr.hasNodeReferences(id);
+        return stateMgr.hasNodeReferences(id);
     }
 
     //--------------------------------------------< UpdatableItemStateManager >
@@ -173,14 +207,14 @@
      * {@inheritDoc}
      */
     public void edit() throws IllegalStateException {
-        persistentStateMgr.edit();
+        stateMgr.edit();
     }
 
     /**
      * {@inheritDoc}
      */
     public boolean inEditMode() {
-        return persistentStateMgr.inEditMode();
+        return stateMgr.inEditMode();
     }
 
     /**
@@ -189,7 +223,7 @@
     public NodeState createNew(NodeId id, QName nodeTypeName,
                                NodeId parentId)
             throws IllegalStateException {
-        return persistentStateMgr.createNew(id, nodeTypeName, parentId);
+        return stateMgr.createNew(id, nodeTypeName, parentId);
     }
 
     /**
@@ -211,7 +245,7 @@
      */
     public PropertyState createNew(QName propName, NodeId parentId)
             throws IllegalStateException {
-        return persistentStateMgr.createNew(propName, parentId);
+        return stateMgr.createNew(propName, parentId);
     }
 
     /**
@@ -232,21 +266,21 @@
      * {@inheritDoc}
      */
     public void store(ItemState state) throws IllegalStateException {
-        persistentStateMgr.store(state);
+        stateMgr.store(state);
     }
 
     /**
      * {@inheritDoc}
      */
     public void destroy(ItemState state) throws IllegalStateException {
-        persistentStateMgr.destroy(state);
+        stateMgr.destroy(state);
     }
 
     /**
      * {@inheritDoc}
      */
     public void cancel() throws IllegalStateException {
-        persistentStateMgr.cancel();
+        stateMgr.cancel();
     }
 
     /**
@@ -255,7 +289,7 @@
     public void update()
             throws ReferentialIntegrityException, StaleItemStateException,
             ItemStateException, IllegalStateException {
-        persistentStateMgr.update();
+        stateMgr.update();
     }
 
     /**
@@ -263,9 +297,9 @@
      */
     public void dispose() {
         // discard all transient changes
-        transientStateMgr.disposeAllItemStates();
+        disposeAllTransientItemStates();
         // dispose our (i.e. 'local') state manager
-        persistentStateMgr.dispose();
+        stateMgr.dispose();
     }
 
     //< more methods for listing and retrieving transient ItemState instances >
@@ -278,7 +312,13 @@
      */
     public ItemState getTransientItemState(ItemId id)
             throws NoSuchItemStateException, ItemStateException {
-        return transientStateMgr.getItemState(id);
+
+        ItemState state = transientStore.get(id);
+        if (state != null) {
+            return state;
+        } else {
+            throw new NoSuchItemStateException(id.toString());
+        }
     }
 
     /**
@@ -286,7 +326,7 @@
      *         <code>false</code> otherwise.
      */
     public boolean hasAnyTransientItemStates() {
-        return transientStateMgr.hasAnyItemStates();
+        return !transientStore.isEmpty();
     }
 
     /**
@@ -306,7 +346,7 @@
      */
     public Iterator getDescendantTransientItemStates(NodeId parentId)
             throws InvalidItemStateException, RepositoryException {
-        if (!transientStateMgr.hasAnyItemStates()) {
+        if (transientStore.isEmpty()) {
             return Collections.EMPTY_LIST.iterator();
         }
 
@@ -317,7 +357,7 @@
         // the depth is used as array index
         List[] la = new List[10];
         try {
-            Iterator iter = transientStateMgr.getEntries();
+            Iterator iter = transientStore.values().iterator();
             while (iter.hasNext()) {
                 ItemState state = (ItemState) iter.next();
                 // determine relative depth: > 0 means it's a descendant
@@ -395,7 +435,7 @@
      * @return an iterator over descendant transient item state instances in the attic
      */
     public Iterator getDescendantTransientItemStatesInAttic(NodeId parentId) {
-        if (!transientStateMgr.hasAnyItemStatesInAttic()) {
+        if (atticStore.isEmpty()) {
             return Collections.EMPTY_LIST.iterator();
         }
 
@@ -406,14 +446,14 @@
         ZombieHierarchyManager zombieHierMgr =
                 new ZombieHierarchyManager(hierMgr.getRootNodeId(),
                         this,
-                        transientStateMgr.getAttic(),
+                        getAttic(),
                         hierMgr.getNamespaceResolver());
 
         // use an array of lists to group the descendants by relative depth;
         // the depth is used as array index
         List[] la = new List[10];
         try {
-            Iterator iter = transientStateMgr.getEntriesInAttic();
+            Iterator iter = atticStore.values().iterator();
             while (iter.hasNext()) {
                 ItemState state = (ItemState) iter.next();
                 // determine relative depth: > 0 means it's a descendant
@@ -471,7 +511,7 @@
      *         <code>false</code> otherwise
      */
     public boolean isItemStateInAttic(ItemId id) {
-        return transientStateMgr.getAttic().hasItemState(id);
+        return atticStore.contains(id);
     }
 
     //------< methods for creating & discarding transient ItemState instances >
@@ -485,7 +525,22 @@
      */
     public NodeState createTransientNodeState(NodeId id, QName nodeTypeName, NodeId parentId, int initialStatus)
             throws ItemStateException {
-        return transientStateMgr.createNodeState(id, nodeTypeName, parentId, initialStatus);
+
+        // check map; synchronized to ensure an entry is not created twice.
+        synchronized (transientStore) {
+            if (transientStore.contains(id)) {
+                String msg = "there's already a node state instance with id " + id;
+                log.debug(msg);
+                throw new ItemStateException(msg);
+            }
+
+            NodeState state = new NodeState(id, nodeTypeName, parentId,
+                    initialStatus, true);
+            // put transient state in the map
+            transientStore.put(state);
+            state.setContainer(this);
+            return state;
+        }
     }
 
     /**
@@ -497,9 +552,22 @@
     public NodeState createTransientNodeState(NodeState overlayedState, int initialStatus)
             throws ItemStateException {
 
-        NodeState state = transientStateMgr.createNodeState(overlayedState, initialStatus);
-        hierMgr.stateOverlaid(state);
-        return state;
+        ItemId id = overlayedState.getNodeId();
+
+        // check map; synchronized to ensure an entry is not created twice.
+        synchronized (transientStore) {
+            if (transientStore.contains(id)) {
+                String msg = "there's already a node state instance with id " + id;
+                log.debug(msg);
+                throw new ItemStateException(msg);
+            }
+
+            NodeState state = new NodeState(overlayedState, initialStatus, true);
+            // put transient state in the map
+            transientStore.put(state);
+            state.setContainer(this);
+            return state;
+        }
     }
 
     /**
@@ -511,7 +579,23 @@
      */
     public PropertyState createTransientPropertyState(NodeId parentId, QName propName, int initialStatus)
             throws ItemStateException {
-        return transientStateMgr.createPropertyState(parentId, propName, initialStatus);
+
+        PropertyId id = new PropertyId(parentId, propName);
+
+        // check map; synchronized to ensure an entry is not created twice.
+        synchronized (transientStore) {
+            if (transientStore.contains(id)) {
+                String msg = "there's already a property state instance with id " + id;
+                log.debug(msg);
+                throw new ItemStateException(msg);
+            }
+
+            PropertyState state = new PropertyState(id, initialStatus, true);
+            // put transient state in the map
+            transientStore.put(state);
+            state.setContainer(this);
+            return state;
+        }
     }
 
     /**
@@ -523,9 +607,22 @@
     public PropertyState createTransientPropertyState(PropertyState overlayedState, int initialStatus)
             throws ItemStateException {
 
-        PropertyState state = transientStateMgr.createPropertyState(overlayedState, initialStatus);
-        hierMgr.stateOverlaid(state);
-        return state;
+        PropertyId id = overlayedState.getPropertyId();
+
+        // check map; synchronized to ensure an entry is not created twice.
+        synchronized (transientStore) {
+            if (transientStore.contains(id)) {
+                String msg = "there's already a property state instance with id " + id;
+                log.debug(msg);
+                throw new ItemStateException(msg);
+            }
+
+            PropertyState state = new PropertyState(overlayedState, initialStatus, true);
+            // put transient state in the map
+            transientStore.put(state);
+            state.setContainer(this);
+            return state;
+        }
     }
 
     /**
@@ -536,7 +633,6 @@
      *              be disconnected
      */
     public void disconnectTransientItemState(ItemState state) {
-        hierMgr.stateUncovered(state);
         state.disconnect();
     }
 
@@ -549,7 +645,13 @@
      * @see ItemState#discard()
      */
     public void disposeTransientItemState(ItemState state) {
-        transientStateMgr.disposeItemState(state);
+        // discard item state, this will invalidate the wrapping Item
+        // instance of the transient state
+        state.discard();
+        // remove from map
+        transientStore.remove(state.getId());
+        // give the instance a chance to prepare to get gc'ed
+        state.onDisposed();
     }
 
     /**
@@ -560,7 +662,10 @@
      *              be moved to the attic
      */
     public void moveTransientItemStateToAttic(ItemState state) {
-        transientStateMgr.moveItemStateToAttic(state);
+        // remove from map
+        transientStore.remove(state.getId());
+        // add to attic
+        atticStore.put(state);
     }
 
     /**
@@ -571,13 +676,229 @@
      *              be disposed @see ItemState#discard()
      */
     public void disposeTransientItemStateInAttic(ItemState state) {
-        transientStateMgr.disposeItemStateInAttic(state);
+        // discard item state, this will invalidate the wrapping Item
+        // instance of the transient state
+        state.discard();
+        // remove from attic
+        atticStore.remove(state.getId());
+        // give the instance a chance to prepare to get gc'ed
+        state.onDisposed();
     }
 
     /**
      * Disposes all transient item states in the cache and in the attic.
      */
     public void disposeAllTransientItemStates() {
-        transientStateMgr.disposeAllItemStates();
+        // dispose item states in transient map & attic
+        // (use temp collection to avoid ConcurrentModificationException)
+        Collection tmp = new ArrayList(transientStore.values());
+        Iterator iter = tmp.iterator();
+        while (iter.hasNext()) {
+            ItemState state = (ItemState) iter.next();
+            disposeTransientItemState(state);
+        }
+        tmp = new ArrayList(atticStore.values());
+        iter = tmp.iterator();
+        while (iter.hasNext()) {
+            ItemState state = (ItemState) iter.next();
+            disposeTransientItemStateInAttic(state);
+        }
+    }
+
+    /**
+     * Add an <code>ItemStateListener</code>
+     * @param listener the new listener to be informed on modifications
+     */
+    public void addListener(ItemStateListener listener) {
+        dispatcher.addListener(listener);
+    }
+
+    /**
+     * Remove an <code>ItemStateListener</code>
+     * @param listener an existing listener
+     */
+    public void removeListener(ItemStateListener listener) {
+        dispatcher.removeListener(listener);
+    }
+
+    /**
+     * Return the attic item state provider that holds all items
+     * moved into the attic.
+     *
+     * @return attic
+     */
+    ItemStateManager getAttic() {
+        if (attic == null) {
+            attic = new AtticItemStateManager();
+        }
+        return attic;
+    }
+
+    //----------------------------------------------------< ItemStateListener >
+
+    /**
+     * {@inheritDoc}
+     * <p/>
+     * Notification handler gets called for both transient states that this state manager
+     * has created, as well as states that were created by the local state manager
+     * we're listening to.
+     */
+    public void stateCreated(ItemState created) {
+        ItemState visibleState = created;
+        if (created.getContainer() != this) {
+            // local state was created
+            ItemState transientState = transientStore.get(created.getId());
+            if (transientState != null) {
+                // underlying state has been permanently created
+                transientState.pull();
+                transientState.setStatus(ItemState.STATUS_EXISTING);
+                visibleState = transientState;
+            }
+        }
+        dispatcher.notifyStateCreated(visibleState);
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p/>
+     * Notification handler gets called for both transient states that this state manager
+     * has created, as well as states that were created by the local state manager
+     * we're listening to.
+     */
+    public void stateModified(ItemState modified) {
+        ItemState visibleState = modified;
+        if (modified.getContainer() != this) {
+            // local state was modified
+            ItemState transientState = transientStore.get(modified.getId());
+            if (transientState != null) {
+                transientState.setStatus(ItemState.STATUS_STALE_MODIFIED);
+                visibleState = transientState;
+            }
+        }
+        dispatcher.notifyStateModified(visibleState);
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p/>
+     * Notification handler gets called for both transient states that this state manager
+     * has created, as well as states that were created by the local state manager
+     * we're listening to.
+     */
+    public void stateDestroyed(ItemState destroyed) {
+        ItemState visibleState = destroyed;
+        if (destroyed.getContainer() != this) {
+            // local state was destroyed
+            ItemState transientState = transientStore.get(destroyed.getId());
+            if (transientState != null) {
+                transientState.setStatus(ItemState.STATUS_STALE_DESTROYED);
+                visibleState = transientState;
+            }
+        }
+        dispatcher.notifyStateDestroyed(visibleState);
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p/>
+     * Notification handler gets called for both transient states that this state manager
+     * has created, as well as states that were created by the local state manager
+     * we're listening to.
+     */
+    public void stateDiscarded(ItemState discarded) {
+        ItemState visibleState = discarded;
+        if (discarded.getContainer() != this) {
+            // local state was discarded
+            ItemState transientState = transientStore.get(discarded.getId());
+            if (transientState != null) {
+                transientState.setStatus(ItemState.STATUS_UNDEFINED);
+                visibleState = transientState;
+            }
+        }
+        dispatcher.notifyStateDiscarded(visibleState);
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p/>
+     * Pass notification to listeners if a transient state was modified
+     * or if the local state is not overlayed.
+     */
+    public void nodeAdded(NodeState state, QName name, int index, NodeId id) {
+        if (state.getContainer() == this || !transientStore.contains(state.getId())) {
+            dispatcher.notifyNodeAdded(state, name, index, id);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p/>
+     * Pass notification to listeners if a transient state was modified
+     * or if the local state is not overlayed.
+     */
+    public void nodesReplaced(NodeState state) {
+        if (state.getContainer() == this || !transientStore.contains(state.getId())) {
+            dispatcher.notifyNodesReplaced(state);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p/>
+     * Pass notification to listeners if a transient state was modified
+     * or if the local state is not overlayed.
+     */
+    public void nodeRemoved(NodeState state, QName name, int index, NodeId id) {
+        if (state.getContainer() == this || !transientStore.contains(state.getId())) {
+            dispatcher.notifyNodeRemoved(state, name, index, id);
+        }
+    }
+
+    //--------------------------------------------------------< inner classes >
+
+    /**
+     * ItemStateManager view of the states in the attic
+     *
+     * @see SessionItemStateManager#getAttic
+     */
+    private class AtticItemStateManager implements ItemStateManager {
+
+        /**
+         * {@inheritDoc}
+         */
+        public ItemState getItemState(ItemId id)
+                throws NoSuchItemStateException, ItemStateException {
+
+            ItemState state = atticStore.get(id);
+            if (state != null) {
+                return state;
+            } else {
+                throw new NoSuchItemStateException(id.toString());
+            }
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public boolean hasItemState(ItemId id) {
+            return atticStore.contains(id);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public NodeReferences getNodeReferences(NodeReferencesId id)
+                throws NoSuchItemStateException, ItemStateException {
+            // n/a
+            throw new ItemStateException("getNodeReferences() not implemented");
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public boolean hasNodeReferences(NodeReferencesId id) {
+            // n/a
+            return false;
+        }
     }
 }

Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java?rev=438851&r1=438850&r2=438851&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java Thu Aug 31 02:38:17 2006
@@ -156,6 +156,11 @@
     private boolean noLockHack = false;
 
     /**
+     * State change dispatcher.
+     */
+    private final transient StateChangeDispatcher dispatcher = new StateChangeDispatcher();
+
+    /**
      * Read-/Write-Lock to synchronize access on this item state manager.
      */
     private final ReadWriteLock rwLock =
@@ -340,34 +345,57 @@
     }
 
     //----------------------------------------------------< ItemStateListener >
+
     /**
      * {@inheritDoc}
+     * <p/>
+     * Notifications are received for items that this manager created itself or items that are
+     * managed by one of the virtual providers.
      */
     public void stateCreated(ItemState created) {
-        cache.cache(created);
+        if (created.getContainer() == this) {
+            // shared state was created
+            cache.cache(created);
+        }
+        dispatcher.notifyStateCreated(created);
     }
 
     /**
      * {@inheritDoc}
+     * <p/>
+     * Notifications are received for items that this manager created itself or items that are
+     * managed by one of the virtual providers.
      */
     public void stateModified(ItemState modified) {
-        // not interested
+        dispatcher.notifyStateModified(modified);
     }
 
     /**
      * {@inheritDoc}
+     * <p/>
+     * Notifications are received for items that this manager created itself or items that are
+     * managed by one of the virtual providers.
      */
     public void stateDestroyed(ItemState destroyed) {
-        destroyed.removeListener(this);
-        cache.evict(destroyed.getId());
+        if (destroyed.getContainer() == this) {
+            // shared state was destroyed
+            cache.evict(destroyed.getId());
+        }
+        dispatcher.notifyStateDestroyed(destroyed);
     }
 
     /**
      * {@inheritDoc}
+     * <p/>
+     * Notifications are received for items that this manager created itself or items that are
+     * managed by one of the virtual providers.
      */
     public void stateDiscarded(ItemState discarded) {
-        discarded.removeListener(this);
-        cache.evict(discarded.getId());
+        if (discarded.getContainer() == this) {
+            // shared state was discarded
+            cache.evict(discarded.getId());
+        }
+        dispatcher.notifyStateDiscarded(discarded);
     }
 
     //-------------------------------------------------------------< Dumpable >
@@ -405,6 +433,8 @@
         System.arraycopy(virtualProviders, 0, provs, 0, virtualProviders.length);
         provs[virtualProviders.length] = prov;
         virtualProviders = provs;
+
+        prov.addListener(this);
     }
 
     /**
@@ -717,7 +747,24 @@
         beginUpdate(local, factory, null).end();
     }
 
+    /**
+     * Add an <code>ItemStateListener</code>
+     * @param listener the new listener to be informed on modifications
+     */
+    public void addListener(ItemStateListener listener) {
+        dispatcher.addListener(listener);
+    }
+
+    /**
+     * Remove an <code>ItemStateListener</code>
+     * @param listener an existing listener
+     */
+    public void removeListener(ItemStateListener listener) {
+        dispatcher.removeListener(listener);
+    }
+
     //-------------------------------------------------------< implementation >
+
     /**
      * Create a new node state instance
      *
@@ -733,7 +780,7 @@
         state.setNodeTypeName(nodeTypeName);
         state.setParentId(parentId);
         state.setStatus(ItemState.STATUS_NEW);
-        state.addListener(this);
+        state.setContainer(this);
 
         return state;
     }
@@ -810,8 +857,8 @@
                 state.setStatus(ItemState.STATUS_EXISTING);
                 // put it in cache
                 cache.cache(state);
-                // register as listener
-                state.addListener(this);
+                // set parent container
+                state.setContainer(this);
             }
             return state;
         }
@@ -863,7 +910,7 @@
     private PropertyState createInstance(QName propName, NodeId parentId) {
         PropertyState state = persistMgr.createNew(new PropertyId(parentId, propName));
         state.setStatus(ItemState.STATUS_NEW);
-        state.addListener(this);
+        state.setContainer(this);
 
         return state;
     }

Added: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/StateChangeDispatcher.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/StateChangeDispatcher.java?rev=438851&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/StateChangeDispatcher.java (added)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/StateChangeDispatcher.java Thu Aug 31 02:38:17 2006
@@ -0,0 +1,198 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.state;
+
+import org.apache.jackrabbit.util.WeakIdentityCollection;
+import org.apache.jackrabbit.name.QName;
+import org.apache.jackrabbit.core.NodeId;
+
+import java.util.Collection;
+
+/**
+ * Component that holds weak references to listeners interested in changes to item states and dispatches notifications.
+ */
+public class StateChangeDispatcher {
+
+    /**
+     * Simple item state listeners (weak references)
+     */
+    private final transient Collection listeners = new WeakIdentityCollection(5);
+
+    /**
+     * Node state listeners (weak references)
+     */
+    private final transient Collection nsListeners = new WeakIdentityCollection(5);
+
+    /**
+     * Add an <code>ItemStateListener</code>.
+     * @param listener the new listener to be informed on modifications
+     */
+    public void addListener(ItemStateListener listener) {
+        synchronized (listeners) {
+            assert (!listeners.contains(listener));
+            listeners.add(listener);
+        }
+        if (listener instanceof NodeStateListener) {
+            synchronized (nsListeners) {
+                assert (!nsListeners.contains(listener));
+                nsListeners.add(listener);
+            }
+        }
+    }
+
+    /**
+     * Remove an <code>ItemStateListener</code>
+     * @param listener an existing listener
+     */
+    public void removeListener(ItemStateListener listener) {
+        if (listener instanceof NodeStateListener) {
+            synchronized (nsListeners) {
+                nsListeners.remove(listener);
+            }
+        }
+        synchronized (listeners) {
+            listeners.remove(listener);
+        }
+    }
+
+    /**
+     * Notify listeners about changes to some state.
+     * @param created created state.
+     */
+    public void notifyStateCreated(ItemState created) {
+        ItemStateListener[] la;
+        synchronized (listeners) {
+            la = (ItemStateListener[]) listeners.toArray(new ItemStateListener[listeners.size()]);
+        }
+        for (int i = 0; i < la.length; i++) {
+            if (la[i] != null) {
+                la[i].stateCreated(created);
+            }
+        }
+    }
+
+    /**
+     * Notify listeners about changes to some state.
+     * @param modified modified state.
+     */
+    public void notifyStateModified(ItemState modified) {
+        ItemStateListener[] la;
+        synchronized (listeners) {
+            la = (ItemStateListener[]) listeners.toArray(new ItemStateListener[listeners.size()]);
+        }
+        for (int i = 0; i < la.length; i++) {
+            if (la[i] != null) {
+                la[i].stateModified(modified);
+            }
+        }
+    }
+
+    /**
+     * Notify listeners about changes to some state.
+     * @param destroyed destroyed state.
+     */
+    public void notifyStateDestroyed(ItemState destroyed) {
+        ItemStateListener[] la;
+        synchronized (listeners) {
+            la = (ItemStateListener[]) listeners.toArray(new ItemStateListener[listeners.size()]);
+        }
+        for (int i = 0; i < la.length; i++) {
+            if (la[i] != null) {
+                la[i].stateDestroyed(destroyed);
+            }
+        }
+    }
+
+    /**
+     * Notify listeners about changes to some state.
+     * @param discarded discarded state.
+     */
+    public void notifyStateDiscarded(ItemState discarded) {
+        ItemStateListener[] la;
+        synchronized (listeners) {
+            la = (ItemStateListener[]) listeners.toArray(new ItemStateListener[listeners.size()]);
+        }
+        for (int i = 0; i < la.length; i++) {
+            if (la[i] != null) {
+                la[i].stateDiscarded(discarded);
+            }
+        }
+    }
+
+    /**
+     * Notify listeners about changes to some state.
+     * @param state node state that changed
+     * @param name  name of node that was added
+     * @param index index of new node
+     * @param id    id of new node
+     */
+    public void notifyNodeAdded(NodeState state, QName name, int index, NodeId id) {
+        // small optimization as there are only a few clients interested in node state modifications
+        if (!nsListeners.isEmpty()) {
+            NodeStateListener[] la;
+            synchronized (nsListeners) {
+                la = (NodeStateListener[]) nsListeners.toArray(new NodeStateListener[nsListeners.size()]);
+            }
+            for (int i = 0; i < la.length; i++) {
+                if (la[i] != null) {
+                    la[i].nodeAdded(state, name, index, id);
+                }
+            }
+        }
+    }
+
+    /**
+     * Notify listeners about changes to some state.
+     * @param state node state that changed
+     */
+    public void notifyNodesReplaced(NodeState state) {
+        // small optimization as there are only a few clients interested in node state modifications
+        if (!nsListeners.isEmpty()) {
+            NodeStateListener[] la;
+            synchronized (nsListeners) {
+                la = (NodeStateListener[]) nsListeners.toArray(new NodeStateListener[nsListeners.size()]);
+            }
+            for (int i = 0; i < la.length; i++) {
+                if (la[i] != null) {
+                    la[i].nodesReplaced(state);
+                }
+            }
+        }
+    }
+
+    /**
+     * Notify listeners about changes to some state.
+     * @param state node state that changed
+     * @param name  name of node that was added
+     * @param index index of new node
+     * @param id    id of new node
+     */
+    public void notifyNodeRemoved(NodeState state, QName name, int index, NodeId id) {
+        // small optimization as there are only a few clients interested in node state modifications
+        if (!nsListeners.isEmpty()) {
+            NodeStateListener[] la;
+            synchronized (nsListeners) {
+                la = (NodeStateListener[]) nsListeners.toArray(new NodeStateListener[nsListeners.size()]);
+            }
+            for (int i = 0; i < la.length; i++) {
+                if (la[i] != null) {
+                    la[i].nodeRemoved(state, name, index, id);
+                }
+            }
+        }
+    }
+}
\ No newline at end of file

Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/version/InternalVersionItemImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/version/InternalVersionItemImpl.java?rev=438851&r1=438850&r2=438851&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/version/InternalVersionItemImpl.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/version/InternalVersionItemImpl.java Thu Aug 31 02:38:17 2006
@@ -23,7 +23,7 @@
 /**
  * Implements a <code>InternalVersionItem</code>.
  */
-abstract class InternalVersionItemImpl implements InternalVersionItem, ItemStateListener {
+abstract class InternalVersionItemImpl implements InternalVersionItem {
 
     /**
      * the underlying persistance node
@@ -43,10 +43,6 @@
     protected InternalVersionItemImpl(AbstractVersionManager vMgr, NodeStateEx node) {
         this.vMgr = vMgr;
         this.node = node;
-        // register as listener. this is not the best solution since this item
-        // could be discarded by the GC and then later be recreated. this will
-        // unnecessarily increase the number of listeners.
-        node.getState().addListener(this);
     }
 
     /**
@@ -71,41 +67,4 @@
      * @return the parent version item or <code>null</code>.
      */
     public abstract InternalVersionItem getParent();
-
-    //-----------------------------------------------------< ItemStateListener >
-    // handle notifications from underlying item states. currently, we only need
-    // to care about removals, since the versioning items do not cache their
-    // values
-
-    /**
-     * {@inheritDoc}
-     */
-    public void stateCreated(ItemState item) {
-        // ignore
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public void stateModified(ItemState item) {
-        // ignore
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public void stateDestroyed(ItemState item) {
-        vMgr.itemDiscarded(this);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public void stateDiscarded(ItemState item) {
-        // ignore
-    }
-
-
-
-
 }

Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/version/VersionItemStateProvider.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/version/VersionItemStateProvider.java?rev=438851&r1=438850&r2=438851&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/version/VersionItemStateProvider.java (original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/version/VersionItemStateProvider.java Thu Aug 31 02:38:17 2006
@@ -73,8 +73,12 @@
         this.vMgr = vMgr;
         this.stateMgr = stateMgr;
         this.historyRootId = vMgr.getHistoryRootId();
+
+        stateMgr.addListener(this);
     }
 
+    //------------------------------------------< VirtualItemStateProvider >---
+
     /**
      * @inheritDoc
      */
@@ -117,9 +121,6 @@
         if (item == null) {
             item = stateMgr.getItemState(id);
             items.put(id, item);
-
-            // attach us as listener
-            item.addListener(this);
         }
         return item;
     }
@@ -152,6 +153,22 @@
     public boolean hasNodeReferences(NodeReferencesId id) {
         return stateMgr.hasNodeReferences(id);
     }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void addListener(ItemStateListener listener) {
+        stateMgr.addListener(listener);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void removeListener(ItemStateListener listener) {
+        stateMgr.removeListener(listener);
+    }
+
+    //-------------------------------------------------< ItemStateListener >---
 
     /**
      * @inheritDoc