You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by an...@apache.org on 2007/05/29 17:51:31 UTC

svn commit: r542571 [1/2] - in /jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi: ./ hierarchy/ nodetype/ operation/ state/ version/ xml/

Author: angela
Date: Tue May 29 08:51:30 2007
New Revision: 542571

URL: http://svn.apache.org/viewvc?view=rev&rev=542571
Log:
improve memory consumption of the current hierarchy implementation

Added:
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/ChildNodeEntriesImpl.java   (with props)
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/ChildPropertyEntries.java   (with props)
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/ChildPropertyEntriesImpl.java   (with props)
Modified:
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/NodeImpl.java
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/ChildNodeEntries.java
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/EntryFactory.java
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/HierarchyEntryImpl.java
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/NodeEntry.java
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/NodeEntryImpl.java
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/nodetype/NodeTypeRegistryImpl.java
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/AddLabel.java
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/Move.java
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/RemoveLabel.java
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/SetMixin.java
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ItemState.java
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/NodeState.java
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/PropertyState.java
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/WorkspaceItemStateFactory.java
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/version/VersionHistoryImpl.java
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/version/VersionManagerImpl.java
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/xml/SessionImporter.java

Modified: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/NodeImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/NodeImpl.java?view=diff&rev=542571&r1=542570&r2=542571
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/NodeImpl.java (original)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/NodeImpl.java Tue May 29 08:51:30 2007
@@ -31,7 +31,6 @@
 import org.apache.jackrabbit.jcr2spi.state.ItemStateValidator;
 import org.apache.jackrabbit.jcr2spi.state.NodeReferences;
 import org.apache.jackrabbit.jcr2spi.state.Status;
-import org.apache.jackrabbit.jcr2spi.state.PropertyState;
 import org.apache.jackrabbit.jcr2spi.nodetype.NodeTypeManagerImpl;
 import org.apache.jackrabbit.jcr2spi.nodetype.EffectiveNodeType;
 import org.apache.jackrabbit.jcr2spi.nodetype.NodeTypeConflictException;
@@ -709,30 +708,35 @@
     }
 
     /**
-     * Retrieves all mixins currently present on this node including those,
-     * that have been transiently added and excluding those, that have been
-     * transiently removed.<br>
+     * Retrieves the value of the jcr:mixinTypes property present with this
+     * Node including those that have been transiently added and excluding
+     * those, that have been transiently removed.<br>
      * NOTE, that the result of this method, does NOT represent the list of
-     * mixin-types that currently affect this node. Instead if represents the
-     * current value of the jcr:mixinTypes property.
-     *
+     * mixin-types that currently affect this node.
+     * 
      * @return
      */
     private List getMixinTypes() {
-        QName[] mixinValue = new QName[0];
-        if (hasProperty(QName.JCR_MIXINTYPES)) {
-            if (getNodeState().getStatus() == Status.EXISTING) {
-                mixinValue = getNodeState().getMixinTypeNames();
-            } else {
-                // possibility that a mixin has been transient added
-                try {
-                    PropertyState ps = getNodeState().getPropertyState(QName.JCR_MIXINTYPES);
-                    mixinValue = StateUtility.getMixinNames(ps);
-                } catch (RepositoryException e) {
-                    // should never occur
-                    log.error("Internal error", e);
+        QName[] mixinValue;
+        if (getNodeState().getStatus() == Status.EXISTING) {
+            // jcr:mixinTypes must correspond to the mixins present on the nodestate.
+            mixinValue = getNodeState().getMixinTypeNames();
+        } else {
+            try {
+                PropertyEntry pe = getNodeEntry().getPropertyEntry(QName.JCR_MIXINTYPES);
+                if (pe != null) {
+                    // prop entry exists (and ev. has been transiently mod.)
+                    // -> retrieve mixin types from prop
+                    mixinValue = StateUtility.getMixinNames(pe.getPropertyState());
+                } else {
+                    // prop entry has not been loaded yet -> not modified
+                    mixinValue = getNodeState().getMixinTypeNames();
                 }
-            } // else: no mixins present
+            } catch (RepositoryException e) {
+                // should never occur
+                log.warn("Internal error", e);
+                mixinValue = new QName[0];
+            }
         }
         List l = new ArrayList();
         l.addAll(Arrays.asList(mixinValue));
@@ -1343,7 +1347,7 @@
     protected Property getProperty(QName qName) throws PathNotFoundException, RepositoryException {
         checkStatus();
         try {
-            PropertyEntry pEntry = getNodeEntry().getPropertyEntry(qName);
+            PropertyEntry pEntry = getNodeEntry().getPropertyEntry(qName, true);
             if (pEntry == null) {
                 throw new PathNotFoundException(qName.toString());
             }
@@ -1589,11 +1593,11 @@
                 } else if (pe == Path.PARENT_ELEMENT) {
                     targetEntry = getNodeEntry().getParent();
                 } else {
-                    targetEntry = getNodeEntry().getNodeEntry(pe.getName(), pe.getNormalizedIndex());
+                    // try to get child entry + force loading of not known yet
+                    targetEntry = getNodeEntry().getNodeEntry(pe.getName(), pe.getNormalizedIndex(), true);
                 }
-            }
-            if (targetEntry == null) {
-                // rp length > 1 OR child entry has not yet been loaded.
+            } else {
+                // rp length > 1
                 Path p = getQPath(rp);
                 HierarchyEntry entry = session.getHierarchyManager().getHierarchyEntry(p.getCanonicalPath());
                 if (entry.denotesNode()) {
@@ -1626,17 +1630,14 @@
         PropertyEntry targetEntry = null;
         try {
             Path rp = PathFormat.parse(relPath, session.getNamespaceResolver());
-            if (rp.getLength() == 1) {
+            if (rp.getLength() == 1 && rp.getNameElement().denotesName()) {
                 // a single path element must always denote a name. '.' and '..'
-                // will never point to a property.
-                if (rp.getNameElement().denotesName()) {
-                    QName propName = rp.getNameElement().getName();
-                    // check if property entry exists
-                    targetEntry = getNodeEntry().getPropertyEntry(propName);
-                } // else: entry may not have been loaded yet -> try via H-Mgr
-            }
-
-            if (targetEntry == null) {
+                // will never point to a property. If the NodeEntry does not
+                // contain such a pe, the targetEntry is 'null;
+                QName propName = rp.getNameElement().getName();
+                // check if property entry exists
+                targetEntry = getNodeEntry().getPropertyEntry(propName, true);
+            } else {
                 // build and resolve absolute path
                 Path p = getQPath(rp).getCanonicalPath();
                 try {

Modified: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/ChildNodeEntries.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/ChildNodeEntries.java?view=diff&rev=542571&r1=542570&r2=542571
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/ChildNodeEntries.java (original)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/ChildNodeEntries.java Tue May 29 08:51:30 2007
@@ -16,258 +16,88 @@
  */
 package org.apache.jackrabbit.jcr2spi.hierarchy;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.apache.jackrabbit.name.QName;
 import org.apache.jackrabbit.name.Path;
 import org.apache.jackrabbit.spi.ChildInfo;
-import org.apache.commons.collections.list.AbstractLinkedList;
-import org.apache.commons.collections.iterators.UnmodifiableIterator;
 
+import javax.jcr.ItemNotFoundException;
 import javax.jcr.RepositoryException;
-import java.util.Collection;
-import java.util.Map;
-import java.util.HashMap;
-import java.util.Iterator;
 import java.util.List;
-import java.util.Collections;
-import java.util.AbstractList;
-import java.util.ArrayList;
-import java.util.ConcurrentModificationException;
 import java.util.NoSuchElementException;
+import java.util.Iterator;
 
 /**
- * <code>ChildNodeEntries</code> represents an insertion-ordered collection of
- * <code>NodeEntry</code>s that also maintains the index values of same-name
- * siblings on insertion and removal.
- * <p/>
- * <code>ChildNodeEntries</code> also provides an unmodifiable
- * <code>Collection</code> view.
+ * <code>ChildNodeEntries</code> represents a collection of <code>NodeEntry</code>s that
+ * also maintains the index values of same-name siblings on insertion and removal.
  */
-final class ChildNodeEntries implements Collection {
-
-    private static Logger log = LoggerFactory.getLogger(ChildNodeEntries.class);
+public interface ChildNodeEntries {
 
     static final int STATUS_OK = 0;
     static final int STATUS_INVALIDATED = 1;
 
-    private final NodeEntryImpl parent;
-    private int status = STATUS_OK;
-
-    /**
-     * Linked list of {@link NodeEntry} instances.
-     */
-    private final ChildNodeEntries.LinkedEntries entries = new LinkedEntries();
-
     /**
-     * map used for lookup by name
-     * (key=name, value=either a single {@link AbstractLinkedList.Node} or a
-     * list of {@link AbstractLinkedList.Node}s which are sns entries)
-     */
-    private final Map nameMap = new HashMap();
-
-    /**
-     * Create <code>ChildNodeEntries</code> for the given node state.
+     * Returns the status of this ChildNodeEntries object.
      *
-     * @param parent
+     * @return {@link #STATUS_OK} or {@link #STATUS_INVALIDATED}
      */
-    ChildNodeEntries(NodeEntryImpl parent) {
-        this.parent = parent;
-    }
+    int getStatus();
 
     /**
-     * Mark <code>ChildNodeEntries</code> in order to force
-     */
-    void setStatus(int status) {
-        if (status == STATUS_INVALIDATED || status == STATUS_OK) {
-            this.status = status;
-        } else {
-            throw new IllegalArgumentException();
-        }
-    }
-
-    int getStatus() {
-        return status;
-    }
-
-    /**
-     * Returns true, if this ChildNodeEntries contains a entry that matches
-     * the given name and either index or uniqueID:<br>
-     * If <code>uniqueID</code> is not <code>null</code> the given index is
-     * ignored since it is not required to identify a child node entry.
-     * Otherwise the given index is used.
+     * Mark <code>ChildNodeEntries</code> in order to force reloading the
+     * entries.
      *
-     * @param name
-     * @param index
-     * @param uniqueID
-     * @return
+     * @param status
      */
-    boolean contains(QName name, int index, String uniqueID) {
-        if (uniqueID == null) {
-            return contains(name, index);
-        } else {
-            return contains(name, uniqueID);
-        }
-    }
+    void setStatus(int status);
 
     /**
+     * Reloads this <code>ChildNodeEntries</code> object.
      *
-     * @param name
-     * @param index
-     * @return
+     * @throws ItemNotFoundException
+     * @throws RepositoryException
      */
-    private boolean contains(QName name, int index) {
-        if (!nameMap.containsKey(name) || index < Path.INDEX_DEFAULT) {
-            // no matching child node entry
-            return false;
-        }
-        Object o = nameMap.get(name);
-        if (o instanceof List) {
-            // SNS
-            int listIndex = index - 1;
-            return listIndex < ((List) o).size();
-        } else {
-            // single child node with this name -> matches only if request
-            // index equals the default-index
-            return index == Path.INDEX_DEFAULT;
-        }
-    }
-
+    void reload() throws ItemNotFoundException, RepositoryException;
+    
     /**
+     * Returns an unmodifiable iterator over all NodeEntry objects present in
+     * this ChildNodeEntries collection irrespective of their status.
      *
-     * @param name
-     * @param uniqueID
-     * @return
+     * @return Iterator over all NodeEntry object
      */
-    private boolean contains(QName name, String uniqueID) {
-        if (uniqueID == null) {
-            throw new IllegalArgumentException();
-        }
-        if (!nameMap.containsKey(name)) {
-            // no matching child node entry
-            return false;
-        }
-        Object o = nameMap.get(name);
-        if (o instanceof List) {
-            // SNS
-            for (Iterator it = ((List) o).iterator(); it.hasNext(); ) {
-                ChildNodeEntries.LinkedEntries.LinkNode n = (LinkedEntries.LinkNode) it.next();
-                NodeEntry cne = n.getNodeEntry();
-                if (uniqueID.equals(cne.getUniqueID())) {
-                    return true;
-                }
-            }
-        } else {
-            // single child node with this name
-            NodeEntry cne = ((ChildNodeEntries.LinkedEntries.LinkNode) o).getNodeEntry();
-            return uniqueID.equals(cne.getUniqueID());
-        }
-        // no matching entry found
-        return false;
-    }
+    Iterator iterator();
 
     /**
      * Returns a <code>List</code> of <code>NodeEntry</code>s for the
      * given <code>nodeName</code>. This method does <b>not</b> filter out
-     * removed <code>NodeEntry</code>s!
+     * removed <code>NodeEntry</code>s.
      *
      * @param nodeName the child node name.
      * @return same name sibling nodes with the given <code>nodeName</code>.
      */
-    List get(QName nodeName) {
-        Object obj = nameMap.get(nodeName);
-        if (obj == null) {
-            return Collections.EMPTY_LIST;
-        }
-        if (obj instanceof List) {
-            final List sns = (List) obj;
-            // map entry is a list of siblings
-            return Collections.unmodifiableList(new AbstractList() {
-
-                public Object get(int index) {
-                    return ((LinkedEntries.LinkNode) sns.get(index)).getNodeEntry();
-                }
-
-                public int size() {
-                    return sns.size();
-                }
-
-                public Iterator iterator() {
-                    return new Iterator() {
-
-                        private Iterator iter = sns.iterator();
-
-                        public void remove() {
-                            throw new UnsupportedOperationException("remove");
-                        }
-
-                        public boolean hasNext() {
-                            return iter.hasNext();
-                        }
-
-                        public Object next() {
-                            return ((LinkedEntries.LinkNode) iter.next()).getNodeEntry();
-                        }
-                    };
-                }
-            });
-        } else {
-            // map entry is a single child node entry
-            return Collections.singletonList(((LinkedEntries.LinkNode) obj).getNodeEntry());
-        }
-    }
+    List get(QName nodeName);
 
     /**
      * Returns the <code>NodeEntry</code> with the given
-     * <code>nodeName</code> and <code>index</code>. This method ignores
-     * <code>NodeEntry</code>s which are marked removed!
+     * <code>nodeName</code> and <code>index</code>. Note, that this method
+     * does <b>not</b> filter out removed <code>NodeEntry</code>s.
      *
      * @param nodeName name of the child node entry.
      * @param index    the index of the child node entry.
      * @return the <code>NodeEntry</code> or <code>null</code> if there
-     *         is no such <code>NodeEntry</code>.
+     * is no such <code>NodeEntry</code>.
      */
-    NodeEntry get(QName nodeName, int index) {
-        if (index < Path.INDEX_DEFAULT) {
-            throw new IllegalArgumentException("index is 1-based");
-        }
-        Object obj = nameMap.get(nodeName);
-        if (obj == null) {
-            return null;
-        }
-        if (obj instanceof List) {
-            // map entry is a list of siblings
-            List siblings = (List) obj;
-            return findMatchingEntry(siblings, index, true);
-        } else {
-            // map entry is a single child node entry
-            if (index == Path.INDEX_DEFAULT) {
-                return ((LinkedEntries.LinkNode) obj).getNodeEntry();
-            }
-        }
-        return null;
-    }
+    NodeEntry get(QName nodeName, int index);
 
     /**
+     * Return the <code>NodeEntry</code> that matches the given nodeName and
+     * uniqueID or <code>null</code> if no matching entry can be found.
      *
      * @param nodeName
      * @param uniqueID
      * @return
      * @throws IllegalArgumentException if the given uniqueID is null.
      */
-    NodeEntry get(QName nodeName, String uniqueID) {
-        if (uniqueID == null) {
-            throw new IllegalArgumentException();
-        }
-        Iterator cneIter = (nodeName != null) ? get(nodeName).iterator() : entries.iterator();
-        while (cneIter.hasNext()) {
-            NodeEntry cne = (NodeEntry) cneIter.next();
-            if (uniqueID.equals(cne.getUniqueID())) {
-                return cne;
-            }
-        }
-        return null;
-    }
+    NodeEntry get(QName nodeName, String uniqueID);
 
     /**
      * Find the matching NodeEntry for the given <code>ChildInfo</code>. Returns
@@ -277,61 +107,7 @@
      * @param childInfo
      * @return
      */
-    NodeEntry get(ChildInfo childInfo) {
-        String uniqueID = childInfo.getUniqueID();
-        if (uniqueID != null) {
-            return get(childInfo.getName(), uniqueID);
-        } else {
-            int index = childInfo.getIndex();
-            Object obj = nameMap.get(childInfo.getName());
-            if (obj == null) {
-                return null;
-            } else if (obj instanceof List) {
-                // map entry is a list of siblings
-                List siblings = (List) obj;
-                return findMatchingEntry(siblings, index, false);
-            } else if (index == Path.INDEX_DEFAULT) {
-                // map entry is a single child node entry
-                return ((LinkedEntries.LinkNode) obj).getNodeEntry();
-            } // else return 'null'
-        }
-        return null;
-    }
-
-    private static NodeEntry findMatchingEntry(List siblings, int index, boolean checkValidity) {
-        // shortcut if index can never match
-        if (index > siblings.size()) {
-            return null;
-        }
-        if (!checkValidity) {
-            return ((LinkedEntries.LinkNode) siblings.get(index - 1)).getNodeEntry();
-        } else {
-            // filter out removed states
-            for (Iterator it = siblings.iterator(); it.hasNext(); ) {
-                NodeEntry cne = ((LinkedEntries.LinkNode) it.next()).getNodeEntry();
-                if (cne.isAvailable()) {
-                    try {
-                        if (cne.getNodeState().isValid()) {
-                            index--;
-                        } else {
-                            // child node removed
-                        }
-                    } catch (RepositoryException e) {
-                        // ignore for index detection. entry does not exist or is
-                        // not accessible
-                    }
-                } else {
-                    // then this child node entry has never been accessed
-                    // before and is assumed valid // TODO: check if correct.
-                    index--;
-                }
-                if (index == 0) {
-                    return cne;
-                }
-            }
-        }
-        return null;
-    }
+    NodeEntry get(ChildInfo childInfo);
 
     /**
      * Adds a <code>NodeEntry</code> to the end of the list. Same as
@@ -339,9 +115,7 @@
      *
      * @param cne the <code>NodeEntry</code> to add.
      */
-     void add(NodeEntry cne) {
-        add(cne, Path.INDEX_UNDEFINED);
-    }
+    void add(NodeEntry cne);
 
     /**
      * Adds a <code>NodeEntry</code>.<br>
@@ -355,134 +129,15 @@
      *
      * @param cne the <code>NodeEntry</code> to add.
      */
-    void add(NodeEntry cne, int index) {
-        QName nodeName = cne.getQName();
+    void add(NodeEntry cne, int index);
 
-        // retrieve ev. sibling node with same index if index is 'undefined'
-        // the existing entry is always null and no reordering occur.
-        LinkedEntries.LinkNode existing = (index < Path.INDEX_DEFAULT) ? null : getLinkNode(nodeName, index);
-
-        // in case index greater than default -> make sure all intermediate
-        // entries exist.
-        if (index > Path.INDEX_DEFAULT) {
-            int previousIndex = index - 1;
-            LinkedEntries.LinkNode previous = getLinkNode(nodeName, previousIndex);
-            if (previous == null) {
-                // add missing entry (or entries)
-                try {
-                    parent.addNodeEntry(nodeName, null, previousIndex);
-                } catch (RepositoryException e) {
-                    // should never occur
-                    log.debug("Internal error", e.getMessage());
-                }
-
-            } // else: all intermediate entries exist
-        } // else: undefined or default index are not affected
-
-        // add new entry (same as #add(NodeEntry)
-        List siblings = null;
-        Object obj = nameMap.get(nodeName);
-        if (obj != null) {
-            if (obj instanceof List) {
-                // map entry is a list of siblings
-                siblings = (ArrayList) obj;
-            } else {
-                // map entry is a single child node entry,
-                // convert to siblings list
-                siblings = new ArrayList();
-                siblings.add(obj);
-                nameMap.put(nodeName, siblings);
-            }
-        }
-
-        LinkedEntries.LinkNode ln = entries.add(cne);
-        if (siblings != null) {
-            siblings.add(ln);
-        } else {
-            nameMap.put(nodeName, ln);
-        }
-
-        // reorder the child entries if, the new entry must be inserted rather
-        // than appended at the end of the list.
-        if (existing != null) {
-            reorder(obj, ln, existing);
-        }
-    }
-
-    void add(NodeEntry entry, NodeEntry beforeEntry) {
-        if (beforeEntry != null) {
-            // the link node where the new entry is ordered before
-            LinkedEntries.LinkNode beforeLN = getLinkNode(beforeEntry);
-            if (beforeLN == null) {
-                throw new NoSuchElementException();
-            }
-            add(entry);
-            Object insertObj = nameMap.get(entry.getQName());
-            LinkedEntries.LinkNode insertLN = getLinkNode(entry);
-            reorder(insertObj, insertLN, beforeLN);
-        } else {
-            // 'before' is null -> simply append new entry at the end
-            add(entry);
-        }
-    }
-
-    /**
-     * Removes the child node entry with the given <code>nodeName</code> and
-     * <code>index</code>.
-     *
-     * @param nodeName the name of the child node entry to remove.
-     * @param index    the index of the child node entry to remove.
-     * @return the removed <code>NodeEntry</code> or <code>null</code>
-     *         if there is no matching <code>NodeEntry</code>.
-     */
-    NodeEntry remove(QName nodeName, int index) {
-        if (index < Path.INDEX_DEFAULT) {
-            throw new IllegalArgumentException("index is 1-based");
-        }
-
-        Object obj = nameMap.get(nodeName);
-        if (obj == null) {
-            return null;
-        }
-
-        if (obj instanceof LinkedEntries.LinkNode) {
-            // map entry is a single child node entry
-            if (index != Path.INDEX_DEFAULT) {
-                return null;
-            }
-            LinkedEntries.LinkNode ln = (LinkedEntries.LinkNode) obj;
-            nameMap.remove(nodeName);
-            // remove LinkNode from entries
-            ln.remove();
-            return ln.getNodeEntry();
-        }
-
-        // map entry is a list of siblings
-        List siblings = (List) obj;
-        if (index > siblings.size()) {
-            return null;
-        }
-
-        // remove from siblings list
-        LinkedEntries.LinkNode ln = (LinkedEntries.LinkNode) siblings.remove(index - 1);
-        NodeEntry removedEntry = ln.getNodeEntry();
-        // remove from ordered entries
-        ln.remove();
-
-        // clean up name lookup map if necessary
-        if (siblings.size() == 0) {
-            // no more entries with that name left:
-            // remove from name lookup map as well
-            nameMap.remove(nodeName);
-        } else if (siblings.size() == 1) {
-            // just one entry with that name left:
-            // discard siblings list and update name lookup map accordingly
-            nameMap.put(nodeName, siblings.get(0));
-        }
-
-        // we're done
-        return removedEntry;
-    }
+    /**
+     * Adds a the new  <code>NodeEntry</code> before <code>beforeEntry</code>.
+     *
+     * @param entry
+     * @param beforeEntry
+     */
+    void add(NodeEntry entry, NodeEntry beforeEntry);
 
     /**
      * Removes the child node entry refering to the node state.
@@ -490,432 +145,21 @@
      * @param childEntry the entry to be removed.
      * @return the removed entry or <code>null</code> if there is no such entry.
      */
-    NodeEntry remove(NodeEntry childEntry) {
-        List l = get(childEntry.getQName());
-        for (int i = 0; i < l.size(); i++) {
-            NodeEntry tmp = (NodeEntry) l.get(i);
-            if (tmp == childEntry) {
-                int index = i+1; // index is 1-based
-                return remove(childEntry.getQName(), index);
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Reorders an existing <code>NodeState</code> before another
-     * <code>NodeState</code>. If <code>beforeNode</code> is
-     * <code>null</code> <code>insertNode</code> is moved to the end of the
+    NodeEntry remove(NodeEntry childEntry);
+
+    /**
+     * Reorders an existing <code>NodeEntry</code> before another
+     * <code>NodeEntry</code>. If <code>beforeEntry</code> is
+     * <code>null</code> <code>insertEntry</code> is moved to the end of the
      * child node entries.
      *
-     * @param insertNode the NodeEntry to move.
-     * @param beforeNode the NodeEntry where <code>insertNode</code> is
+     * @param insertEntry the NodeEntry to move.
+     * @param beforeEntry the NodeEntry where <code>insertEntry</code> is
      * reordered to.
-     * @return the NodeEntry that followed the 'insertNode' before the reordering.
-     * @throws NoSuchElementException if <code>insertNode</code> or
-     * <code>beforeNode</code> does not have a <code>NodeEntry</code>
+     * @return the NodeEntry that followed the 'insertEntry' before the reordering.
+     * @throws NoSuchElementException if <code>insertEntry</code> or
+     * <code>beforeEntry</code> does not have a <code>NodeEntry</code>
      * in this <code>ChildNodeEntries</code>.
      */
-    NodeEntry reorder(NodeEntry insertNode, NodeEntry beforeNode) {
-        Object insertObj = nameMap.get(insertNode.getQName());
-        // the link node to move
-        LinkedEntries.LinkNode insertLN = getLinkNode(insertNode);
-        if (insertLN == null) {
-            throw new NoSuchElementException();
-        }
-        // the link node where insertLN is ordered before
-        LinkedEntries.LinkNode beforeLN = (beforeNode != null) ? getLinkNode(beforeNode) : null;
-        if (beforeNode != null && beforeLN == null) {
-            throw new NoSuchElementException();
-        }
-
-        NodeEntry previousBefore = insertLN.getNextLinkNode().getNodeEntry();
-        if (previousBefore != beforeNode) {
-            reorder(insertObj, insertLN, beforeLN);
-        }
-        return previousBefore;
-    }
-
-    /**
-     *
-     * @param insertObj
-     * @param insertLN
-     * @param beforeLN
-     */
-    private void reorder(Object insertObj, LinkedEntries.LinkNode insertLN, LinkedEntries.LinkNode beforeLN) {
-        if (insertObj instanceof List) {
-            // adapt name lookup lists
-            List insertList = (List) insertObj;
-            if (beforeLN == null) {
-                // simply move to end of list
-                insertList.remove(insertLN);
-                insertList.add(insertLN);
-            } else {
-                // move based on position of beforeLN
-                // count our same name siblings until we reach beforeLN
-                int snsCount = 0;
-                QName insertName = insertLN.getNodeEntry().getQName();
-                for (Iterator it = entries.linkNodeIterator(); it.hasNext(); ) {
-                    LinkedEntries.LinkNode ln = (LinkedEntries.LinkNode) it.next();
-                    if (ln == beforeLN) {
-                        insertList.remove(insertLN);
-                        insertList.add(snsCount, insertLN);
-                        break;
-                    } else if (ln == insertLN) {
-                        // do not increment snsCount for node to reorder
-                    } else if (ln.getNodeEntry().getQName().equals(insertName)) {
-                        snsCount++;
-                    }
-                }
-            }
-        } // else: no same name siblings -> no special handling required
-
-        // reorder in linked list
-        entries.reorderNode(insertLN, beforeLN);
-    }
-
-    /**
-     * Returns the matching <code>LinkNode</code> from a list or a single
-     * <code>LinkNode</code>. This method will return <code>null</code>
-     * if none of the entries matches either due to missing entry for given
-     * state name or due to missing availability of the <code>NodeEntry</code>.
-     *
-     * @param nodeEntry the <code>NodeEntry</code> that is compared to the
-     * resolution of any <code>NodeEntry</code> that matches by name.
-     * @return the matching <code>LinkNode</code> or <code>null</code>
-     */
-    private LinkedEntries.LinkNode getLinkNode(NodeEntry nodeEntry) {
-        Object listOrLinkNode = nameMap.get(nodeEntry.getQName());
-        if (listOrLinkNode == null) {
-            // no matching child node entry
-            return null;
-        }
-
-        if (listOrLinkNode instanceof List) {
-            // has same name sibling
-            for (Iterator it = ((List) listOrLinkNode).iterator(); it.hasNext();) {
-                LinkedEntries.LinkNode n = (LinkedEntries.LinkNode) it.next();
-                NodeEntry cne = n.getNodeEntry();
-                if (cne == nodeEntry) {
-                    return n;
-                }
-            }
-        } else {
-            // single child node with this name
-            NodeEntry cne = ((LinkedEntries.LinkNode) listOrLinkNode).getNodeEntry();
-            if (cne == nodeEntry) {
-                return (LinkedEntries.LinkNode) listOrLinkNode;
-            }
-        }
-        // not found
-        return null;
-    }
-
-    /**
-     * Returns the matching <code>LinkNode</code> from a list or a single
-     * <code>LinkNode</code>. This method will return <code>null</code>
-     * if none of the entries matches.
-     *
-     * @param name
-     * @param index
-     * @return the matching <code>LinkNode</code> or <code>null</code>.
-     */
-    private LinkedEntries.LinkNode getLinkNode(QName name, int index) {
-        Object listOrLinkNode = nameMap.get(name);
-        if (listOrLinkNode == null) {
-            // no matching child node entry
-            return null;
-        }
-
-        if (listOrLinkNode instanceof List) {
-            // has same name sibling -> check if list size matches
-            int listIndex = index - 1;
-            List lnList = (List) listOrLinkNode;
-            if (listIndex < lnList.size()) {
-                return (LinkedEntries.LinkNode) lnList.get(listIndex);
-            }
-        } else if (index == Path.INDEX_DEFAULT) {
-            // single child node with this name -> matches is requested index
-            // equals to the default index.
-            return (LinkedEntries.LinkNode) listOrLinkNode;
-        }
-
-        // no matching entry
-        return null;
-    }
-    //--------------------------------------------< unmodifiable Collection >---
-    /**
-     * @see Collection#contains(Object)
-     */
-    public boolean contains(Object o) {
-        if (o instanceof NodeEntry) {
-            // narrow down to same name sibling nodes and check list
-            return get(((NodeEntry) o).getQName()).contains(o);
-        } else {
-            return false;
-        }
-    }
-
-    /**
-     * @see Collection#containsAll(Collection)
-     */
-    public boolean containsAll(Collection c) {
-        Iterator iter = c.iterator();
-        while (iter.hasNext()) {
-            if (!contains(iter.next())) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    /**
-     * @see Collection#isEmpty()
-     */
-    public boolean isEmpty() {
-        return entries.isEmpty();
-    }
-
-    /**
-     * @see Collection#iterator()
-     */
-    public Iterator iterator() {
-        return UnmodifiableIterator.decorate(entries.iterator());
-    }
-
-    /**
-     * @see Collection#size()
-     */
-    public int size() {
-        return entries.size();
-    }
-
-    /**
-     * @see Collection#toArray()
-     */
-    public Object[] toArray() {
-        NodeEntry[] array = new NodeEntry[size()];
-        return toArray(array);
-    }
-
-    /**
-     * @see Collection#toArray(Object[])
-     */
-    public Object[] toArray(Object[] a) {
-        if (!a.getClass().getComponentType().isAssignableFrom(NodeEntry.class)) {
-            throw new ArrayStoreException();
-        }
-        if (a.length < size()) {
-            a = new NodeEntry[size()];
-        }
-        Iterator iter = entries.iterator();
-        int i = 0;
-        while (iter.hasNext()) {
-            a[i++] = iter.next();
-        }
-        while (i < a.length) {
-            a[i++] = null;
-        }
-        return a;
-    }
-
-    /**
-     * Throws <code>UnsupportedOperationException</code>.
-     *
-     * @see Collection#add(Object)
-     */
-    public boolean add(Object o) {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * Throws <code>UnsupportedOperationException</code>.
-     *
-     * @see Collection#addAll(Collection)
-     */
-    public boolean addAll(Collection c) {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * Throws <code>UnsupportedOperationException</code>.
-     *
-     * @see Collection#clear()
-     */
-    public void clear() {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * Throws <code>UnsupportedOperationException</code>.
-     *
-     * @see Collection#remove(Object)
-     */
-    public boolean remove(Object o) {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * Throws <code>UnsupportedOperationException</code>.
-     *
-     * @see Collection#removeAll(Collection)
-     */
-    public boolean removeAll(Collection c) {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * Throws <code>UnsupportedOperationException</code>.
-     *
-     * @see Collection#retainAll(Collection)
-     */
-    public boolean retainAll(Collection c) {
-        throw new UnsupportedOperationException();
-    }
-
-    //-------------------------------------------------< AbstractLinkedList >---
-    /**
-     * An implementation of a linked list which provides access to the internal
-     * LinkNode which links the entries of the list.
-     */
-    private static final class LinkedEntries extends AbstractLinkedList {
-
-        LinkedEntries() {
-            super();
-            init();
-        }
-
-        /**
-         * Adds a child node entry to this list.
-         *
-         * @param cne the child node entry to add.
-         * @return the LinkNode which refers to the added <code>NodeEntry</code>.
-         */
-        LinkedEntries.LinkNode add(NodeEntry cne) {
-            LinkedEntries.LinkNode ln = (LinkedEntries.LinkNode) createNode(cne);
-            addNode(ln, header);
-            return ln;
-        }
-
-        /**
-         * Reorders an existing <code>LinkNode</code> before another existing
-         * <code>LinkNode</code>. If <code>before</code> is <code>null</code>
-         * the <code>insert</code> node is moved to the end of the list.
-         *
-         * @param insert the node to reorder.
-         * @param before the node where to reorder node <code>insert</code>.
-         */
-        void reorderNode(LinkedEntries.LinkNode insert, LinkedEntries.LinkNode before) {
-            removeNode(insert);
-            if (before == null) {
-                addNode(insert, header);
-            } else {
-                addNode(insert, before);
-            }
-        }
-
-        /**
-         * Replace the value of the given LinkNode with a new NodeEntry
-         * value.
-         *
-         * @param node
-         * @param value
-         */
-        void replaceNode(LinkedEntries.LinkNode node, NodeEntry value) {
-            updateNode(node, value);
-        }
-
-        /**
-         * Create a new <code>LinkNode</code> for a given {@link NodeEntry}
-         * <code>value</code>.
-         *
-         * @param value a child node entry.
-         * @return a wrapping {@link org.apache.jackrabbit.jcr2spi.hierarchy.ChildNodeEntries.LinkedEntries.LinkNode}.
-         * @see AbstractLinkedList#createNode(Object)
-         */
-        protected Node createNode(Object value) {
-            return new LinkedEntries.LinkNode(value);
-        }
-
-        /**
-         * @return a new <code>LinkNode</code>.
-         * @see AbstractLinkedList#createHeaderNode()
-         */
-        protected Node createHeaderNode() {
-            return new LinkedEntries.LinkNode();
-        }
-
-        /**
-         * Returns an iterator over all
-         * @return
-         */
-        Iterator linkNodeIterator() {
-            return new Iterator() {
-
-                private LinkedEntries.LinkNode next = ((LinkedEntries.LinkNode) header).getNextLinkNode();
-
-                private int expectedModCount = modCount;
-
-                public void remove() {
-                    throw new UnsupportedOperationException("remove");
-                }
-
-                public boolean hasNext() {
-                    if (expectedModCount != modCount) {
-                        throw new ConcurrentModificationException();
-                    }
-                    return next != header;
-                }
-
-                public Object next() {
-                    if (expectedModCount != modCount) {
-                        throw new ConcurrentModificationException();
-                    }
-                    if (!hasNext()) {
-                        throw new NoSuchElementException();
-                    }
-                    LinkedEntries.LinkNode n = next;
-                    next = next.getNextLinkNode();
-                    return n;
-                }
-            };
-        }
-
-        //----------------------------------------------------------------------
-
-        /**
-         * Extends the <code>AbstractLinkedList.Node</code>.
-         */
-        private final class LinkNode extends Node {
-
-            protected LinkNode() {
-                super();
-            }
-
-            protected LinkNode(Object value) {
-                super(value);
-            }
-
-            /**
-             * @return the wrapped <code>NodeEntry</code>.
-             */
-            public NodeEntry getNodeEntry() {
-                return (NodeEntry) super.getValue();
-            }
-
-            /**
-             * Removes this <code>LinkNode</code> from the linked list.
-             */
-            public void remove() {
-                removeNode(this);
-            }
-
-            /**
-             * @return the next LinkNode.
-             */
-            public LinkedEntries.LinkNode getNextLinkNode() {
-                return (LinkedEntries.LinkNode) super.getNextNode();
-            }
-        }
-    }
+    NodeEntry reorder(NodeEntry insertEntry, NodeEntry beforeEntry);
 }

Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/ChildNodeEntriesImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/ChildNodeEntriesImpl.java?view=auto&rev=542571
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/ChildNodeEntriesImpl.java (added)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/ChildNodeEntriesImpl.java Tue May 29 08:51:30 2007
@@ -0,0 +1,745 @@
+/*
+ * 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.jcr2spi.hierarchy;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.jackrabbit.name.QName;
+import org.apache.jackrabbit.name.Path;
+import org.apache.jackrabbit.spi.ChildInfo;
+import org.apache.jackrabbit.spi.NodeId;
+import org.apache.jackrabbit.jcr2spi.state.Status;
+import org.apache.commons.collections.list.AbstractLinkedList;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.ItemNotFoundException;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.ArrayList;
+import java.util.ConcurrentModificationException;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Collections;
+import java.lang.ref.WeakReference;
+
+/**
+ * <code>ChildNodeEntriesImpl</code> implements a memory sensitive implementation
+ * of the <code>ChildNodeEntries</code> interface. 
+ */
+final class ChildNodeEntriesImpl implements ChildNodeEntries {
+
+    private static Logger log = LoggerFactory.getLogger(ChildNodeEntriesImpl.class);
+
+    private int status = STATUS_OK;
+
+    /**
+     * Linked list of {@link NodeEntry} instances.
+     */
+    private final LinkedEntries entries;
+
+    /**
+     * Map used for lookup by name.
+     */
+    private final NameMap entriesByName;
+
+    private final NodeEntry parent;
+    private final EntryFactory factory;
+
+    /**
+     * Create a new <code>ChildNodeEntries</code> collection
+     */
+    ChildNodeEntriesImpl(NodeEntry parent, EntryFactory factory) throws ItemNotFoundException, RepositoryException {
+        entriesByName = new NameMap();
+        entries = new LinkedEntries();
+
+        this.parent = parent;
+        this.factory = factory;
+
+        if (parent.getStatus() == Status.NEW || Status.isTerminal(parent.getStatus())) {
+            return; // cannot retrieve child-entries from persistent layer
+        }
+
+        NodeId id = parent.getWorkspaceId();
+        Iterator it = factory.getItemStateFactory().getChildNodeInfos(id);
+        // simply add all child entries to the empty collection
+        while (it.hasNext()) {
+            ChildInfo ci = (ChildInfo) it.next();
+            NodeEntry entry = factory.createNodeEntry(parent, ci.getName(), ci.getUniqueID());
+            add(entry, ci.getIndex());
+        }
+    }
+
+    /**
+     * @see ChildNodeEntries#getStatus()
+     */
+    public int getStatus() {
+        return status;
+    }
+
+    /**
+     * Mark <code>ChildNodeEntries</code> in order to force reloading the
+     * entries.
+     *
+     * @see ChildNodeEntries#setStatus(int)
+     */
+    public void setStatus(int status) {
+        if (status == STATUS_INVALIDATED || status == STATUS_OK) {
+            this.status = status;
+        } else {
+            throw new IllegalArgumentException();
+        }
+    }
+
+    /**
+     * @see ChildNodeEntries#reload()
+     */
+    public synchronized void reload() throws ItemNotFoundException, RepositoryException {
+        if (status == STATUS_OK ||
+            parent.getStatus() == Status.NEW || Status.isTerminal(parent.getStatus())) {
+            // nothing to do
+            return;
+        }
+
+        NodeId id = parent.getWorkspaceId();
+        Iterator it = factory.getItemStateFactory().getChildNodeInfos(id);
+        // create list from all ChildInfos (for multiple loop)
+        List cInfos = new ArrayList();
+        while (it.hasNext()) {
+            cInfos.add(it.next());
+        }
+        // first make sure the ordering of all existing entries is ok
+        NodeEntry entry = null;
+        for (it = cInfos.iterator(); it.hasNext();) {
+            ChildInfo ci = (ChildInfo) it.next();
+            NodeEntry nextEntry = get(ci);
+            if (nextEntry != null) {
+                if (entry != null) {
+                    reorder(entry, nextEntry);
+                }
+                entry = nextEntry;
+            }
+        }
+        // then insert the 'new' entries
+        List newEntries = new ArrayList();
+        for (it = cInfos.iterator(); it.hasNext();) {
+            ChildInfo ci = (ChildInfo) it.next();
+            NodeEntry beforeEntry = get(ci);
+            if (beforeEntry == null) {
+                NodeEntry ne = factory.createNodeEntry(parent, ci.getName(), ci.getUniqueID());
+                newEntries.add(ne);
+            } else {
+                // insert all new entries from the list BEFORE the existing
+                // 'nextEntry'. Then clear the list.
+                for (int i = 0; i < newEntries.size(); i++) {
+                    add((NodeEntry) newEntries.get(i), beforeEntry);
+                }
+                newEntries.clear();
+            }
+        }
+        // deal with new entries at the end
+        for (int i = 0; i < newEntries.size(); i++) {
+            add((NodeEntry) newEntries.get(i));
+        }
+        // finally reset the status
+        setStatus(ChildNodeEntries.STATUS_OK);
+    }
+
+    /**
+     * @see ChildNodeEntries#iterator()
+     */
+    public Iterator iterator() {
+        List l = new ArrayList(entries.size());
+        for (Iterator it = entries.linkNodeIterator(); it.hasNext();) {
+            l.add(((LinkedEntries.LinkNode)it.next()).getNodeEntry());
+        }
+        return Collections.unmodifiableList(l).iterator();
+    }
+
+    /**
+     * @see ChildNodeEntries#get(QName)
+     */
+    public List get(QName nodeName) {
+        return entriesByName.getList(nodeName);
+    }
+
+    /**
+     * @see ChildNodeEntries#get(QName, int)
+     */
+    public NodeEntry get(QName nodeName, int index) {
+        if (index < Path.INDEX_DEFAULT) {
+            throw new IllegalArgumentException("index is 1-based");
+        }
+        return entriesByName.getNodeEntry(nodeName, index);
+    }
+
+    /**
+     * @see ChildNodeEntries#get(QName, String)
+     */
+    public NodeEntry get(QName nodeName, String uniqueID) {
+        if (uniqueID == null || nodeName == null) {
+            throw new IllegalArgumentException();
+        }
+        Iterator cneIter = get(nodeName).iterator();
+        while (cneIter.hasNext()) {
+            NodeEntry cne = (NodeEntry) cneIter.next();
+            if (uniqueID.equals(cne.getUniqueID())) {
+                return cne;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * @see ChildNodeEntries#get(ChildInfo)
+     */
+    public NodeEntry get(ChildInfo childInfo) {
+        String uniqueID = childInfo.getUniqueID();
+        NodeEntry child = null;
+        if (uniqueID != null) {
+            child = get(childInfo.getName(), uniqueID);
+        }
+        // try to load the child entry by name and index.
+        // this is required in case of a null uniqueID OR if the child entry has
+        // been created but never been resolved and therefore the uniqueID might
+        // be unknown.
+        if (child == null) {
+            int index = childInfo.getIndex();
+            child = entriesByName.getNodeEntry(childInfo.getName(), index);
+        }
+        return child;
+    }
+
+    /**
+     * Adds a <code>NodeEntry</code> to the end of the list. Same as
+     * {@link #add(NodeEntry, int)}, where the index is {@link Path#INDEX_UNDEFINED}.
+     *
+     * @param cne the <code>NodeEntry</code> to add.
+     * @see ChildNodeEntries#add(NodeEntry)
+     */
+     public void add(NodeEntry cne) {
+        internalAdd(cne, Path.INDEX_UNDEFINED);
+    }
+
+    /**
+     * @see ChildNodeEntries#add(NodeEntry, int)
+     */
+    public void add(NodeEntry cne, int index) {
+        if (index < Path.INDEX_UNDEFINED) {
+            throw new IllegalArgumentException("Invalid index" + index);
+        }
+        internalAdd(cne, index);
+    }
+
+    /**
+     *
+     * @param entry
+     * @param index
+     * @return
+     */
+    private LinkedEntries.LinkNode internalAdd(NodeEntry entry, int index) {
+        QName nodeName = entry.getQName();
+
+        // retrieve ev. sibling node with same index. if index is 'undefined'
+        // the existing entry is always null and no reordering occurs.
+        LinkedEntries.LinkNode existing = null;
+        if (index >= Path.INDEX_DEFAULT) {
+            existing = entriesByName.getLinkNode(nodeName, index);
+        }
+
+        // in case index greater than default -> create intermediate entries.
+        // TODO: TOBEFIXED in case of orderable node the order in the 'linked-entries' must be respected.
+        for (int i = Path.INDEX_DEFAULT; i < index; i++) {
+            LinkedEntries.LinkNode previous = entriesByName.getLinkNode(nodeName, i);
+            if (previous == null) {
+                NodeEntry sibling = factory.createNodeEntry(parent, nodeName, null);
+                internalAdd(sibling, i);
+            }
+        }
+
+        // add new entry
+        LinkedEntries.LinkNode ln = entries.add(entry);
+        entriesByName.put(nodeName, ln);
+
+        // reorder the child entries if, the new entry must be inserted rather
+        // than appended at the end of the list.
+        if (existing != null) {
+            reorder(nodeName, ln, existing);
+        }
+        return ln;
+    }
+
+    /**
+     * @see ChildNodeEntries#add(NodeEntry, NodeEntry)
+     */
+    public void add(NodeEntry entry, NodeEntry beforeEntry) {
+        if (beforeEntry != null) {
+            // the link node where the new entry is ordered before
+            LinkedEntries.LinkNode beforeLN = entries.getLinkNode(beforeEntry);
+            if (beforeLN == null) {
+                throw new NoSuchElementException();
+            }
+            LinkedEntries.LinkNode insertLN = internalAdd(entry, Path.INDEX_UNDEFINED);
+            reorder(entry.getQName(), insertLN, beforeLN);
+        } else {
+            // 'before' is null -> simply append new entry at the end
+            add(entry);
+        }
+    }
+
+    /**
+     * Removes the child node entry refering to the node state.
+     *
+     * @param childEntry the entry to be removed.
+     * @return the removed entry or <code>null</code> if there is no such entry.
+     * @see ChildNodeEntries#remove(NodeEntry)
+     */
+    public synchronized NodeEntry remove(NodeEntry childEntry) {
+        LinkedEntries.LinkNode ln = entries.removeNodeEntry(childEntry);
+        if (ln != null) {
+            entriesByName.remove(childEntry.getQName(), ln);
+            return childEntry;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Reorders an existing <code>NodeState</code> before another
+     * <code>NodeState</code>. If <code>beforeNode</code> is
+     * <code>null</code> <code>insertNode</code> is moved to the end of the
+     * child node entries.
+     *
+     * @param insertNode the NodeEntry to move.
+     * @param beforeNode the NodeEntry where <code>insertNode</code> is
+     * reordered to.
+     * @return the NodeEntry that followed the 'insertNode' before the reordering.
+     * @throws NoSuchElementException if <code>insertNode</code> or
+     * <code>beforeNode</code> does not have a <code>NodeEntry</code>
+     * in this <code>ChildNodeEntries</code>.
+     * @see ChildNodeEntries#reorder(NodeEntry, NodeEntry)
+     */
+    public NodeEntry reorder(NodeEntry insertEntry, NodeEntry beforeEntry) {
+        // the link node to move
+        LinkedEntries.LinkNode insertLN = entries.getLinkNode(insertEntry);
+        if (insertLN == null) {
+            throw new NoSuchElementException();
+        }
+        // the link node where insertLN is ordered before
+        LinkedEntries.LinkNode beforeLN = (beforeEntry != null) ? entries.getLinkNode(beforeEntry) : null;
+        if (beforeEntry != null && beforeLN == null) {
+            throw new NoSuchElementException();
+        }
+
+        NodeEntry previousBefore = insertLN.getNextLinkNode().getNodeEntry();
+        if (previousBefore != beforeEntry) {
+            reorder(insertEntry.getQName(), insertLN, beforeLN);
+        }
+        return previousBefore;
+    }
+
+    /**
+     *
+     * @param insertObj
+     * @param insertLN
+     * @param beforeLN
+     */
+    private void reorder(QName insertName, LinkedEntries.LinkNode insertLN, LinkedEntries.LinkNode beforeLN) {
+        // reorder named map
+        if (entriesByName.containsSiblings(insertName)) {
+            int position;
+            if (beforeLN == null) {
+                // reorder to the end -> use illegal position as marker
+                position = - 1;
+            } else {
+                // count all SNS-entries that are before 'beforeLN' in order to
+                // determine the new position of the reordered node regarding
+                // his siblings.
+                position = 0;
+                for (Iterator it = entries.linkNodeIterator(); it.hasNext(); ) {
+                    LinkedEntries.LinkNode ln = (LinkedEntries.LinkNode) it.next();
+                    if (ln == beforeLN) {
+                        break;
+                    } else if (ln != insertLN && ln.getNodeEntry().getQName().equals(insertName)) {
+                        position++;
+                    } // else: ln == inserLN OR no SNS -> not relevant for position count
+                }
+            }
+            entriesByName.reorder(insertName, insertLN, position);
+        }
+        // reorder in linked list
+        entries.reorderNode(insertLN, beforeLN);
+    }
+
+    //-------------------------------------------------< AbstractLinkedList >---
+    /**
+     * An implementation of a linked list which provides access to the internal
+     * LinkNode which links the entries of the list.
+     */
+    private final class LinkedEntries extends AbstractLinkedList {
+
+        LinkedEntries() {
+            super();
+            init();
+        }
+
+        /**
+         * Returns the matching <code>LinkNode</code> from a list or a single
+         * <code>LinkNode</code>. This method will return <code>null</code>
+         * if none of the entries matches either due to missing entry for given
+         * state name or due to missing availability of the <code>NodeEntry</code>.
+         *
+         * @param nodeEntry the <code>NodeEntry</code> that is compared to the
+         * resolution of any <code>NodeEntry</code> that matches by name.
+         * @return the matching <code>LinkNode</code> or <code>null</code>
+         */
+        private LinkedEntries.LinkNode getLinkNode(NodeEntry nodeEntry) {
+            for (Iterator it = linkNodeIterator(); it.hasNext();) {
+                LinkedEntries.LinkNode ln = (LinkedEntries.LinkNode) it.next();
+                if (ln.getNodeEntry() == nodeEntry) {
+                    return ln;
+                }
+            }
+            // not found
+            return null;
+        }
+
+        /**
+         * Adds a child node entry to this list.
+         *
+         * @param cne the child node entry to add.
+         * @return the LinkNode which refers to the added <code>NodeEntry</code>.
+         */
+        LinkedEntries.LinkNode add(NodeEntry cne) {
+            LinkedEntries.LinkNode ln = new LinkedEntries.LinkNode(cne);
+            addNode(ln, header);
+            return ln;
+        }
+
+        /**
+         * Remove the LinkEntry the contains the given NodeEntry as value.
+         *
+         * @param cne NodeEntry to be removed.
+         * @return LinkedEntries.LinkNode that has been removed.
+         */
+        LinkedEntries.LinkNode removeNodeEntry(NodeEntry cne) {
+            LinkedEntries.LinkNode ln = getLinkNode(cne);
+            if (ln != null) {
+                ln.remove();
+            }
+            return ln;
+        }
+
+        /**
+         * Reorders an existing <code>LinkNode</code> before another existing
+         * <code>LinkNode</code>. If <code>before</code> is <code>null</code>
+         * the <code>insert</code> node is moved to the end of the list.
+         *
+         * @param insert the node to reorder.
+         * @param before the node where to reorder node <code>insert</code>.
+         */
+        void reorderNode(LinkedEntries.LinkNode insert, LinkedEntries.LinkNode before) {
+            removeNode(insert);
+            if (before == null) {
+                addNode(insert, header);
+            } else {
+                addNode(insert, before);
+            }
+        }
+
+        /**
+         * Create a new <code>LinkNode</code> for a given {@link NodeEntry}
+         * <code>value</code>.
+         *
+         * @param value a child node entry.
+         * @return a wrapping {@link LinkedEntries.LinkNode}.
+         * @see AbstractLinkedList#createNode(Object)
+         */
+        protected Node createNode(Object value) {
+            return new LinkedEntries.LinkNode(value);
+        }
+
+        /**
+         * @return a new <code>LinkNode</code>.
+         * @see AbstractLinkedList#createHeaderNode()
+         */
+        protected Node createHeaderNode() {
+            return new LinkedEntries.LinkNode();
+        }
+
+        /**
+         * @return iterator over all LinkNode entries in this list.
+         */
+        private Iterator linkNodeIterator() {
+            return new LinkNodeIterator();
+        }
+
+        //----------------------------------------------------------------------
+        /**
+         * Extends the <code>AbstractLinkedList.Node</code>.
+         */
+        private final class LinkNode extends Node {
+
+            private final QName qName;
+
+            protected LinkNode() {
+                super();
+                qName = null;
+            }
+
+            protected LinkNode(Object value) {
+                super(new WeakReference(value));
+                qName = ((NodeEntry) value).getQName();
+            }
+
+            protected void setValue(Object value) {
+                throw new UnsupportedOperationException("Not implemented");
+            }
+
+            protected Object getValue() {
+                WeakReference val = (WeakReference) super.getValue();
+                // if the nodeEntry has been g-collected in the mean time
+                // create a new NodeEntry in order to avoid returning null.
+                NodeEntry ne = (val == null) ?  null : (NodeEntry) val.get();
+                if (ne == null && this != header) {
+                    ne = factory.createNodeEntry(parent, qName, null);
+                    super.setValue(new WeakReference(ne));
+                }
+                return ne;
+            }
+
+            /**
+             * @return the wrapped <code>NodeEntry</code>.
+             */
+            public NodeEntry getNodeEntry() {
+                return (NodeEntry) getValue();
+            }
+
+            /**
+             * Removes this <code>LinkNode</code> from the linked list.
+             */
+            public void remove() {
+                removeNode(this);
+            }
+
+            /**
+             * @return the next LinkNode.
+             */
+            public LinkedEntries.LinkNode getNextLinkNode() {
+                return (LinkedEntries.LinkNode) super.getNextNode();
+            }
+        }
+
+        //----------------------------------------------------------------------
+        private class LinkNodeIterator implements Iterator {
+
+            private LinkedEntries.LinkNode next = ((LinkedEntries.LinkNode) header).getNextLinkNode();
+            private int expectedModCount = modCount;
+
+            public boolean hasNext() {
+                checkModCount();
+                return next != header;
+            }
+
+            public Object next() {
+                checkModCount();
+                if (!hasNext()) {
+                    throw new NoSuchElementException();
+                }
+                LinkedEntries.LinkNode n = next;
+                next = next.getNextLinkNode();
+                return n;
+            }
+
+            public void remove() {
+                throw new UnsupportedOperationException("remove");
+            }
+
+            private void checkModCount() {
+                if (expectedModCount != modCount) {
+                    throw new ConcurrentModificationException();
+                }
+            }
+        }
+    }
+
+
+
+    //--------------------------------------------------------------------------
+    /**
+     * Mapping of QName to LinkNode OR List of LinkNode(s) in case of SNSiblings.
+     */
+    private static class NameMap {
+
+        private Map snsMap = new HashMap();
+        private Map nameMap = new HashMap();
+
+        /**
+         * Return true if more than one NodeEnty with the given name exists.
+         * 
+         * @param qName
+         * @return
+         */
+        public boolean containsSiblings(QName qName) {
+            return snsMap.containsKey(qName);
+        }
+
+        /**
+         * Returns a single <code>NodeEntry</code> or an unmodifiable
+         * <code>List</code> of NodeEntry objects.
+         *
+         * @param qName
+         * @return a single <code>NodeEntry</code> or a <code>List</code> of
+         * NodeEntry objects.
+         */
+        private Object get(QName qName) {
+            Object val = nameMap.get(qName);
+            if (val != null) {
+                return ((LinkedEntries.LinkNode) val).getNodeEntry();
+            } else {
+                List l = (List) snsMap.get(qName);
+                if (l != null) {
+                    List nodeEntries = new ArrayList(l.size());
+                    for (Iterator it = l.iterator(); it.hasNext();) {
+                        LinkedEntries.LinkNode ln = (LinkedEntries.LinkNode) it.next();
+                        nodeEntries.add(ln.getNodeEntry());
+                    }
+                    return nodeEntries;
+                }
+            }
+            return null;
+        }
+
+        /**
+         * Returns a unmodifiable List of NodeEntry objects even if the name map
+         * only contains a single entry for the given name. If no matching entry
+         * exists for the given qualified name an empty list is returned.
+         *
+         * @param qName
+         * @return
+         */
+        public List getList(QName qName) {
+            Object obj = get(qName);
+            if (obj == null) {
+                return Collections.EMPTY_LIST;
+            } else if (obj instanceof List) {
+                List l = new ArrayList((List)obj);
+                return Collections.unmodifiableList(l);
+            } else {
+                // NodeEntry
+                return Collections.singletonList(obj);
+            }
+        }
+
+        public NodeEntry getNodeEntry(QName qName, int index) {
+            Object obj = get(qName);
+            if (obj == null) {
+                return null;
+            }
+            if (obj instanceof List) {
+                // map entry is a list of siblings
+                return findMatchingEntry((List) obj, index);
+            } else {
+                // map entry is a single child node entry
+                if (index == Path.INDEX_DEFAULT) {
+                    return (NodeEntry) obj;
+                }
+            }
+            return null;
+        }
+
+        public LinkedEntries.LinkNode getLinkNode(QName qName, int index) {
+            if (index < Path.INDEX_DEFAULT) {
+                throw new IllegalArgumentException("Illegal index " + index);
+            }
+
+            LinkedEntries.LinkNode val = (LinkedEntries.LinkNode) nameMap.get(qName);
+            if (val != null) {
+                return (index == Path.INDEX_DEFAULT) ? val : null;
+            } else {
+                // look in snsMap
+                List l = (List) snsMap.get(qName);
+                int pos = index - 1; // Index of NodeEntry is 1-based
+                return (l != null && pos < l.size()) ? (LinkedEntries.LinkNode) l.get(pos) : null;
+            }
+        }
+
+        public void put(QName qName, LinkedEntries.LinkNode value) {
+            // if 'nameMap' already contains a single entry -> move it to snsMap
+            LinkedEntries.LinkNode single = (LinkedEntries.LinkNode) nameMap.remove(qName);
+            List l;
+            if (single != null) {
+                l = new ArrayList();
+                l.add(single);
+                snsMap.put(qName, l);
+            } else {
+                // if 'snsMap' already contains list
+                l = (List) snsMap.get(qName);
+            }
+
+            if (l == null) {
+                nameMap.put(qName, value);
+            } else {
+                l.add(value);
+            }
+        }
+
+        public LinkedEntries.LinkNode remove(QName qName, LinkedEntries.LinkNode value) {
+            Object rm = nameMap.remove(qName);
+            if (rm == null) {
+                List l = (List) snsMap.get(qName);
+                if (l != null && l.remove(value)) {
+                    rm = value;
+                }
+            }
+            return ((LinkedEntries.LinkNode) rm);
+        }
+
+        public void reorder(QName qName, LinkedEntries.LinkNode insertValue, int position) {
+            List sns = (List) snsMap.get(qName);
+            if (sns == null) {
+                // no same name siblings -> no special handling required
+                return;
+            }
+            // reorder sns in the name-list
+            if (position < 0) {
+                // simply move to end of list
+                sns.remove(insertValue);
+                sns.add(insertValue);
+            } else {
+                sns.remove(insertValue);
+                sns.add(position, insertValue);
+            }
+        }
+
+        /**
+         *
+         * @param siblings
+         * @param index
+         * @param checkValidity
+         * @return
+         */
+        private static NodeEntry findMatchingEntry(List siblings, int index) {
+            // shortcut if index can never match
+            if (index > siblings.size()) {
+                return null;
+            } else {
+                return (NodeEntry) siblings.get(index - 1);
+            }
+        }
+    }
+}

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/ChildNodeEntriesImpl.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/ChildNodeEntriesImpl.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/ChildPropertyEntries.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/ChildPropertyEntries.java?view=auto&rev=542571
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/ChildPropertyEntries.java (added)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/ChildPropertyEntries.java Tue May 29 08:51:30 2007
@@ -0,0 +1,83 @@
+/*
+ * 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.jcr2spi.hierarchy;
+
+import org.apache.jackrabbit.name.QName;
+
+import java.util.Collection;
+
+/**
+ * <code>ChildPropertyEntries</code>...
+ */
+public interface ChildPropertyEntries {
+
+    /**
+     * Returns true if a property entry with the given name exists.
+     *
+     * @param propertyName
+     * @return true if a property entry with the given name exists.
+     */
+    public boolean contains(QName propertyName);
+
+    /**
+     * Return the PropertyEntry with the given <code>QName</code> or
+     * <code>null</code>.
+     *
+     * @param propertyName
+     * @return
+     */
+    public PropertyEntry get(QName propertyName);
+
+    /**
+     * Returns an unmodifiable collection containing all <code>PropertyEntry</code>
+     * objects present.
+     * 
+     * @return Collection of all <code>PropertyEntry</code> objects present.
+     */
+    public Collection getPropertyEntries();
+
+    /**
+     * Returns an unmodifiable collection containing all existing property names.
+     *
+     * @return Collection of <code>QName</code>
+     */
+    public Collection getPropertyNames();
+
+    /**
+     * Adds the new <code>PropertyEntry</code> to this <code>ChildPropertyEntries</code>.
+     *
+     * @param propertyEntry
+     */
+    public void add(PropertyEntry propertyEntry);
+
+    /**
+     * Adds all <code>PropertyEntry</code>s from the given collection to this
+     * <code>ChildPropertyEntries</code>.
+     *
+     * @param propertyEntries
+     */
+    public void addAll(Collection propertyEntries);
+
+    /**
+     * Remove the collection entry with the given <code>QName</code>.
+     *
+     * @param propertyName
+     * @return true If this <code>ChildPropertyEntries</code> contained any
+     * entry with the given <code>QName</code>. False otherwise.
+     */
+    public boolean remove(QName propertyName);
+}
\ No newline at end of file

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/ChildPropertyEntries.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/ChildPropertyEntries.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/ChildPropertyEntriesImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/ChildPropertyEntriesImpl.java?view=auto&rev=542571
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/ChildPropertyEntriesImpl.java (added)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/ChildPropertyEntriesImpl.java Tue May 29 08:51:30 2007
@@ -0,0 +1,124 @@
+/*
+ * 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.jcr2spi.hierarchy;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.jackrabbit.name.QName;
+
+import java.util.Map;
+import java.util.Iterator;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.Collections;
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+
+/**
+ * <code>ChildPropertyEntriesImpl</code>...
+ */
+public class ChildPropertyEntriesImpl implements ChildPropertyEntries {
+
+    private static Logger log = LoggerFactory.getLogger(ChildPropertyEntriesImpl.class);
+
+    private final Map properties;
+    private final NodeEntry parent;
+    private final EntryFactory factory;
+
+    ChildPropertyEntriesImpl(NodeEntry parent, EntryFactory factory) {
+        properties = new HashMap();
+        this.parent = parent;
+        this.factory = factory;
+    }
+
+    /**
+     * @see ChildPropertyEntries#contains(QName)
+     */
+    public boolean contains(QName propertyName) {
+        return properties.containsKey(propertyName);
+    }
+
+    /**
+     * @see ChildPropertyEntries#get(QName)
+     */
+    public PropertyEntry get(QName propertyName) {
+        Object ref = properties.get(propertyName);
+        if (ref == null) {
+            // no entry exists with the given name
+            return null;
+        }
+
+        PropertyEntry entry = (PropertyEntry) ((Reference) ref).get();
+        if (entry == null) {
+            // entry has been g-collected -> create new entry and return it.
+            entry = factory.createPropertyEntry(parent, propertyName);
+            add(entry);
+        }
+        return entry;
+    }
+
+    /**
+     * @see ChildPropertyEntries#getPropertyEntries()
+     */
+    public Collection getPropertyEntries() {
+        synchronized (properties) {
+            Set entries = new HashSet(properties.size());
+            for (Iterator it = getPropertyNames().iterator(); it.hasNext();) {
+                QName propName = (QName) it.next();
+                entries.add(get(propName));
+            }
+            return Collections.unmodifiableCollection(entries);
+        }
+    }
+
+    /**
+     * @see ChildPropertyEntries#getPropertyNames()
+     */
+    public Collection getPropertyNames() {
+        return properties.keySet();
+    }
+
+    /**
+     * @see ChildPropertyEntries#add(PropertyEntry)
+     */
+    public void add(PropertyEntry propertyEntry) {
+        WeakReference ref = new WeakReference(propertyEntry);
+        properties.put(propertyEntry.getQName(), ref);
+    }
+
+    /**
+     * @see ChildPropertyEntries#addAll(Collection)
+     */
+    public void addAll(Collection propertyEntries) {
+        for (Iterator it = propertyEntries.iterator(); it.hasNext();) {
+            Object pe = it.next();
+            if (pe instanceof PropertyEntry) {
+                add((PropertyEntry) pe);
+            }
+        }
+    }
+
+    /**
+     * @see ChildPropertyEntries#remove(QName) 
+     */
+    public boolean remove(QName propertyName) {
+        WeakReference ref = (WeakReference) properties.remove(propertyName);
+        return ref != null;
+    }
+}
\ No newline at end of file

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/ChildPropertyEntriesImpl.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/ChildPropertyEntriesImpl.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Modified: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/EntryFactory.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/EntryFactory.java?view=diff&rev=542571&r1=542570&r2=542571
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/EntryFactory.java (original)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/EntryFactory.java Tue May 29 08:51:30 2007
@@ -20,6 +20,7 @@
 import org.slf4j.LoggerFactory;
 import org.apache.jackrabbit.jcr2spi.state.TransientItemStateFactory;
 import org.apache.jackrabbit.spi.IdFactory;
+import org.apache.jackrabbit.name.QName;
 
 /**
  * <code>EntryFactory</code>...
@@ -58,6 +59,20 @@
      */
     public NodeEntry createRootEntry() {
         return rootEntry;
+    }
+
+    public NodeEntry createNodeEntry(NodeEntry parent, QName qName, String uniqueId) {
+        if (!(parent instanceof NodeEntryImpl)) {
+            throw new IllegalArgumentException();
+        }
+        return NodeEntryImpl.createNodeEntry((NodeEntryImpl) parent, qName, uniqueId, this);
+    }
+
+    public PropertyEntry createPropertyEntry(NodeEntry parent, QName qName) {
+        if (!(parent instanceof NodeEntryImpl)) {
+            throw new IllegalArgumentException();
+        }
+        return PropertyEntryImpl.create((NodeEntryImpl) parent, qName, this);
     }
 
     public IdFactory getIdFactory() {

Modified: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/HierarchyEntryImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/HierarchyEntryImpl.java?view=diff&rev=542571&r1=542570&r2=542571
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/HierarchyEntryImpl.java (original)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/HierarchyEntryImpl.java Tue May 29 08:51:30 2007
@@ -39,7 +39,6 @@
 
     /**
      * Cached weak reference to the target ItemState.
-     * // TODO: check correct?
      */
     private WeakReference target;
 
@@ -49,7 +48,7 @@
     protected QName name;
 
     /**
-     * The parent <code>HierarchyEntry</code>.
+     * Hard reference to the parent <code>NodeEntry</code>.
      */
     protected NodeEntryImpl parent;
 
@@ -319,7 +318,7 @@
                 remove();
             } catch (RepositoryException e) {
                 // TODO: rather throw? remove from parent?
-                log.warn("Exception while reloading property state: " + e);
+                log.warn("Exception while reloading item state: " + e);
                 log.debug("Stacktrace: ", e);
             }
         }

Modified: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/NodeEntry.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/NodeEntry.java?view=diff&rev=542571&r1=542570&r2=542571
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/NodeEntry.java (original)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/NodeEntry.java Tue May 29 08:51:30 2007
@@ -142,16 +142,20 @@
     public NodeEntry getNodeEntry(QName nodeName, int index) throws RepositoryException;
 
     /**
-     * Returns the <code>NodeEntry</code> with the specified
-     * <code>NodeId</code> or <code>null</code> if there's no matching
-     * entry.
-     *
-     * @param childId the id of the child entry.
-     * @return the <code>NodeEntry</code> with the specified
-     * <code>NodeId</code> or <code>null</code> if there's no matching entry.
+     * Returns the valid <code>NodeEntry</code> with the specified name
+     * and index or <code>null</code> if there's no matching entry. If
+     * <code>loadIfNotFound</code> is true, the implementation must make
+     * sure, that it's list of child entries is up to date and eventually
+     * try to load the node entry.
+     *
+     * @param nodeName <code>QName</code> object specifying a node name.
+     * @param index 1-based index if there are same-name child node entries.
+     * @param loadIfNotFound
+     * @return The <code>NodeEntry</code> with the specified name and index
+     * or <code>null</code> if there's no matching entry.
      * @throws RepositoryException If an unexpected error occurs.
      */
-    public NodeEntry getNodeEntry(NodeId childId) throws RepositoryException;
+    public NodeEntry getNodeEntry(QName nodeName, int index, boolean loadIfNotFound) throws RepositoryException;
 
     /**
      * Returns a unmodifiable iterator of <code>NodeEntry</code> objects
@@ -210,8 +214,24 @@
      * @param propName <code>QName</code> object specifying a property name.
      * @return The <code>PropertyEntry</code> with the specified name or
      * <code>null</code> if no matching entry exists.
+     * @throws RepositoryException If an unexpected error occurs.
      */
-    public PropertyEntry getPropertyEntry(QName propName);
+    public PropertyEntry getPropertyEntry(QName propName) throws RepositoryException;
+
+    /**
+     * Returns the valid <code>PropertyEntry</code> with the specified name
+     * or <code>null</code> if no matching entry exists.  If
+     * <code>loadIfNotFound</code> is true, the implementation must make
+     * sure, that it's list of property entries is up to date and eventually
+     * try to load the property entry with the given name.
+     *
+     * @param propName <code>QName</code> object specifying a property name.
+     * @param loadIfNotFound
+     * @return The <code>PropertyEntry</code> with the specified name or
+     * <code>null</code> if no matching entry exists.
+     * @throws RepositoryException If an unexpected error occurs.
+     */
+    public PropertyEntry getPropertyEntry(QName propName,  boolean loadIfNotFound) throws RepositoryException;
 
     /**
      * Returns an unmodifiable Iterator over those children that represent valid
@@ -222,9 +242,12 @@
     public Iterator getPropertyEntries();
 
     /**
+     * Add an existing <code>PropertyEntry</code> with the given name.
+     * Please note the difference to {@link #addNewPropertyEntry(QName, QPropertyDefinition)
+     * which adds a new, transient entry.
      *
      * @param propName
-     * @return
+     * @return the <code>PropertyEntry</code>
      * @throws ItemExistsException if a child item exists with the given name
      * @throws RepositoryException if an unexpected error occurs.
      */
@@ -243,6 +266,8 @@
     public void addPropertyEntries(Collection propNames) throws ItemExistsException, RepositoryException;
 
     /**
+     * Add a new, transient <code>PropertyEntry</code> to this <code>NodeEntry</code>
+     * and return the <code>PropertyState</code> associated with the new entry.
      *
      * @param propName
      * @return The PropertyState associated with the new property entry.
@@ -262,6 +287,13 @@
     public void orderBefore(NodeEntry beforeEntry) throws RepositoryException;
 
     /**
+     * Moves this <code>NodeEntry</code> as new child entry of the
+     * <code>NodeEntry</code> identified by <code>newParent</code> and/or renames
+     * it to <code>newName</code>. If <code>transientMove</code> is true, an
+     * implementation must make sure, that reverting this modification by calling
+     * {@link HierarchyEntry#revert()} on the common ancestor of both parents
+     * moves this NodeEntry back and resets the name to its original value.
+     *
      * @param newName
      * @param newParent
      * @return the moved entry