You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by st...@apache.org on 2006/01/30 18:01:03 UTC

svn commit: r373549 - in /incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state: NodeState.java util/Serializer.java

Author: stefan
Date: Mon Jan 30 09:00:59 2006
New Revision: 373549

URL: http://svn.apache.org/viewcvs?rev=373549&view=rev
Log:
JCR-307: improved performance when handling nodes with large number of child node entries
- reimplemented NodeState.ChildNodeEntries
- made ChildNodeEntry immutable again
- avoided unnecessary object creation when de-/serializing child node entries

Modified:
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/NodeState.java
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/util/Serializer.java

Modified: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/NodeState.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/NodeState.java?rev=373549&r1=373548&r2=373549&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/NodeState.java (original)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/NodeState.java Mon Jan 30 09:00:59 2006
@@ -53,7 +53,7 @@
     protected QName nodeTypeName;
 
     /** the names of this node's mixin types */
-    protected Set mixinTypeNames = new HashSet();
+    protected HashSet mixinTypeNames = new HashSet();
 
     /** id of this node's definition */
     protected NodeDefId defId;
@@ -62,7 +62,7 @@
     protected ChildNodeEntries childNodeEntries = new ChildNodeEntries();
 
     /** set of property names (QName objects) */
-    protected Set propertyNames = new HashSet();
+    protected HashSet propertyNames = new HashSet();
 
     /**
      * Listeners (weak references)
@@ -111,12 +111,11 @@
 
             NodeState nodeState = (NodeState) state;
             nodeTypeName = nodeState.getNodeTypeName();
-            mixinTypeNames = new HashSet(nodeState.getMixinTypeNames());
+            mixinTypeNames = (HashSet) nodeState.mixinTypeNames.clone();
             defId = nodeState.getDefinitionId();
             uuid = nodeState.getUUID();
-            propertyNames = new HashSet(nodeState.getPropertyNames());
-            childNodeEntries = new ChildNodeEntries();
-            childNodeEntries.addAll(nodeState.getChildNodeEntries());
+            propertyNames = (HashSet) nodeState.propertyNames.clone();
+            childNodeEntries = (ChildNodeEntries) nodeState.childNodeEntries.clone();
         }
     }
 
@@ -711,31 +710,86 @@
      * <code>ChildNodeEntries</code> also provides an unmodifiable
      * <code>List</code> view.
      */
-    private static class ChildNodeEntries implements List, Serializable {
+    private static class ChildNodeEntries implements List, Cloneable, Serializable {
 
         // insertion-ordered map of entries (key=uuid, value=entry)
         LinkedMap entries;
-        // map used for lookup by name (key=name, value=1st same-name sibling entry)
-        Map nameMap;
+        // map used for lookup by name
+        // (key=name, value=either a single entry or a list of sns entries)
+        HashMap nameMap;
 
         ChildNodeEntries() {
             entries = new LinkedMap();
             nameMap = new HashMap();
         }
 
+        ChildNodeEntry get(String uuid) {
+            return (ChildNodeEntry) entries.get(uuid);
+        }
+
+        List get(QName nodeName) {
+            Object obj = nameMap.get(nodeName);
+            if (obj == null) {
+                return Collections.EMPTY_LIST;
+            }
+            if (obj instanceof ArrayList) {
+                // map entry is a list of siblings
+                return Collections.unmodifiableList((ArrayList) obj);
+            } else {
+                // map entry is a single child node entry
+                return Collections.singletonList(obj);
+            }
+        }
+
+        ChildNodeEntry get(QName nodeName, int index) {
+            if (index < 1) {
+                throw new IllegalArgumentException("index is 1-based");
+            }
+
+            Object obj = nameMap.get(nodeName);
+            if (obj == null) {
+                return null;
+            }
+            if (obj instanceof ArrayList) {
+                // map entry is a list of siblings
+                ArrayList siblings = (ArrayList) obj;
+                if (index <= siblings.size()) {
+                    return (ChildNodeEntry) siblings.get(index - 1);
+                }
+            } else {
+                // map entry is a single child node entry
+                if (index == 1) {
+                    return (ChildNodeEntry) obj;
+                }
+            }
+            return null;
+        }
+
         ChildNodeEntry add(QName nodeName, String uuid) {
-            ChildNodeEntry sibling = (ChildNodeEntry) nameMap.get(nodeName);
-            while (sibling != null && sibling.getNextSibling() != null) {
-                sibling = sibling.getNextSibling();
+            List siblings = null;
+            int index = 0;
+            Object obj = nameMap.get(nodeName);
+            if (obj != null) {
+                if (obj instanceof ArrayList) {
+                    // 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);
+                }
+                index = siblings.size();
             }
 
-            int index = (sibling == null) ? 1 : sibling.getIndex() + 1;
+            index++;
 
             ChildNodeEntry entry = new ChildNodeEntry(nodeName, uuid, index);
-            if (sibling == null) {
-                nameMap.put(nodeName, entry);
+            if (siblings != null) {
+                siblings.add(entry);
             } else {
-                sibling.setNextSibling(entry);
+                nameMap.put(nodeName, entry);
             }
             entries.put(uuid, entry);
 
@@ -751,96 +805,78 @@
             }
         }
 
-        public void removeAll() {
-            entries.clear();
-            nameMap.clear();
-        }
-
-        ChildNodeEntry remove(String uuid) {
-            ChildNodeEntry entry = (ChildNodeEntry) entries.get(uuid);
-            if (entry != null) {
-                return remove(entry.getName(), entry.getIndex());
-           }
-            return entry;
-        }
-
-        public ChildNodeEntry remove(ChildNodeEntry entry) {
-            return remove(entry.getName(), entry.getIndex());
-        }
-
         public ChildNodeEntry remove(QName nodeName, int index) {
             if (index < 1) {
                 throw new IllegalArgumentException("index is 1-based");
             }
 
-            ChildNodeEntry sibling = (ChildNodeEntry) nameMap.get(nodeName);
-            ChildNodeEntry prevSibling = null;
-            while (sibling != null) {
-                if (sibling.getIndex() == index) {
-                    break;
+            Object obj = nameMap.get(nodeName);
+            if (obj == null) {
+                return null;
+            }
+
+            if (obj instanceof ChildNodeEntry) {
+                // map entry is a single child node entry
+                if (index != 1) {
+                    return null;
                 }
-                prevSibling = sibling;
-                sibling = sibling.getNextSibling();
+                ChildNodeEntry removedEntry = (ChildNodeEntry) obj;
+                nameMap.remove(nodeName);
+                entries.remove(removedEntry.getUUID());
+                return removedEntry;
             }
-            if (sibling == null) {
+
+            // map entry is a list of siblings
+            List siblings = (ArrayList) obj;
+            if (index > siblings.size()) {
                 return null;
             }
 
-            // remove from entries list
-            entries.remove(sibling.getUUID());
+            // remove from siblings list
+            ChildNodeEntry removedEntry = (ChildNodeEntry) siblings.remove(index - 1);
+            // remove from ordered entries map
+            entries.remove(removedEntry.getUUID());
 
-            // update linked list of siblings & name map entry
-            if (prevSibling != null) {
-                prevSibling.setNextSibling(sibling.getNextSibling());
-            } else {
-                // the head is removed from the linked siblings list,
-                // update name map
-                if (sibling.getNextSibling() == null) {
-                    nameMap.remove(nodeName);
-                } else {
-                    nameMap.put(nodeName, sibling.getNextSibling());
-                }
-            }
             // update indices of subsequent same-name siblings
-            ChildNodeEntry nextSibling = sibling.getNextSibling();
-            while (nextSibling != null) {
-                nextSibling.decIndex();
-                nextSibling = nextSibling.getNextSibling();
+            for (int i = index - 1; i < siblings.size(); i++) {
+                ChildNodeEntry oldEntry = (ChildNodeEntry) siblings.get(i);
+                ChildNodeEntry newEntry = new ChildNodeEntry(nodeName, oldEntry.getUUID(), oldEntry.getIndex() - 1);
+                // overwrite old entry with updated entry in siblings list
+                siblings.set(i, newEntry);
+                // overwrite old entry with updated entry in ordered entries map
+                entries.put(newEntry.getUUID(), newEntry);
+            }
+
+            // 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));
             }
 
-            return sibling;
+            // we're done
+            return removedEntry;
         }
 
-        List get(QName nodeName) {
-            ChildNodeEntry sibling = (ChildNodeEntry) nameMap.get(nodeName);
-            if (sibling == null) {
-                return Collections.EMPTY_LIST;
-            }
-            List siblings = new ArrayList();
-            while (sibling != null) {
-                siblings.add(sibling);
-                sibling = sibling.getNextSibling();
-            }
-            return siblings;
+        ChildNodeEntry remove(String uuid) {
+            ChildNodeEntry entry = (ChildNodeEntry) entries.get(uuid);
+            if (entry != null) {
+                return remove(entry.getName(), entry.getIndex());
+           }
+           return entry;
         }
 
-        ChildNodeEntry get(String uuid) {
-            return (ChildNodeEntry) entries.get(uuid);
+        public ChildNodeEntry remove(ChildNodeEntry entry) {
+            return remove(entry.getName(), entry.getIndex());
         }
 
-        ChildNodeEntry get(QName nodeName, int index) {
-            if (index < 1) {
-                throw new IllegalArgumentException("index is 1-based");
-            }
-
-            ChildNodeEntry sibling = (ChildNodeEntry) nameMap.get(nodeName);
-            while (sibling != null) {
-                if (sibling.getIndex() == index) {
-                    return sibling;
-                }
-                sibling = sibling.getNextSibling();
-            }
-            return null;
+        public void removeAll() {
+            nameMap.clear();
+            entries.clear();
         }
 
         /**
@@ -1030,6 +1066,28 @@
             }
         }
 
+        //------------------------------------------------< Cloneable support >
+        /**
+         * Returns a shallow copy of this <code>ChildNodeEntries</code> instance;
+         * the entries themselves are not cloned.
+         * @return a shallow copy of this instance.
+         */
+        protected Object clone() {
+            ChildNodeEntries clone = new ChildNodeEntries();
+            clone.entries = (LinkedMap) entries.clone();
+            clone.nameMap = new HashMap(nameMap.size());
+            for (Iterator it = nameMap.keySet().iterator(); it.hasNext(); ) {
+                Object key = it.next();
+                Object obj = nameMap.get(key);
+                if (obj instanceof ArrayList) {
+                    // clone List
+                    obj = ((ArrayList) obj).clone();
+                }
+                clone.nameMap.put(key, obj);
+            }
+            return clone;
+        }
+
         //----------------------------------------------------< inner classes >
         class OrderedMapIterator implements ListIterator {
 
@@ -1082,13 +1140,16 @@
     /**
      * <code>ChildNodeEntry</code> specifies the name, index (in the case of
      * same-name siblings) and the UUID of a child node entry.
+     * <p/>
+     * <code>ChildNodeEntry</code> instances are immutable.
      */
     public static class ChildNodeEntry {
 
-        private QName name;
-        private int index; // 1-based index for same-name siblings
-        private String uuid;
-        private ChildNodeEntry nextSibling;
+        private int hash = 0;
+
+        private final QName name;
+        private final int index; // 1-based index for same-name siblings
+        private final String uuid;
 
         private ChildNodeEntry(QName name, String uuid, int index) {
             if (name == null) {
@@ -1105,8 +1166,6 @@
                 throw new IllegalArgumentException("index is 1-based");
             }
             this.index = index;
-
-            nextSibling = null;
         }
 
         public String getUUID() {
@@ -1121,29 +1180,6 @@
             return index;
         }
 
-        public ChildNodeEntry getNextSibling() {
-            return nextSibling;
-        }
-
-        void setNextSibling(ChildNodeEntry nextSibling) {
-            if (nextSibling != null && !nextSibling.getName().equals(name)) {
-                throw new IllegalArgumentException("not a same-name sibling entry");
-            }
-
-            this.nextSibling = nextSibling;
-        }
-
-        int incIndex() {
-            return ++index;
-        }
-
-        int decIndex() {
-            if (index == 1) {
-                throw new IndexOutOfBoundsException();
-            }
-            return --index;
-        }
-
         //---------------------------------------< java.lang.Object overrides >
         public boolean equals(Object obj) {
             if (this == obj) {
@@ -1161,15 +1197,17 @@
             return name.toString() + "[" + index + "] -> " + uuid;
         }
 
-        /**
-         * Returns zero to satisfy the Object equals/hashCode contract.
-         * This class is mutable and not meant to be used as a hash key.
-         *
-         * @return always zero
-         * @see Object#hashCode()
-         */
         public int hashCode() {
-            return 0;
+            // ChildNodeEntry is immutable, we can store the computed hash code value
+            int h = hash;
+            if (h == 0) {
+                h = 17;
+                h = 37 * h + name.hashCode();
+                h = 37 * h + uuid.hashCode();
+                h = 37 * h + index;
+                hash = h;
+            }
+            return h;
         }
     }
 }

Modified: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/util/Serializer.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/util/Serializer.java?rev=373549&r1=373548&r2=373549&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/util/Serializer.java (original)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/util/Serializer.java Mon Jan 30 09:00:59 2006
@@ -38,6 +38,7 @@
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Set;
+import java.util.Arrays;
 
 /**
  * <code>Serializer</code> is a utility class that provides static methods
@@ -47,8 +48,9 @@
  */
 public final class Serializer {
 
-    private static final UUID NULL_UUID_PLACEHOLDER =
-            UUID.fromString("00000000-0000-0000-0000-000000000000");
+    private static final byte[] NULL_UUID_PLACEHOLDER_BYTES = new byte[] {
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+    };
 
     /**
      * encoding used for serializing String values
@@ -73,9 +75,9 @@
         out.writeUTF(state.getNodeTypeName().toString());
         // parentUUID
         if (state.getParentUUID() == null) {
-            out.write(NULL_UUID_PLACEHOLDER.getRawBytes());
+            out.write(NULL_UUID_PLACEHOLDER_BYTES);
         } else {
-            out.write(UUID.fromString(state.getParentUUID()).getRawBytes());
+            out.write(UUID.stringToBytes(state.getParentUUID()));
         }
         // definitionId
         out.writeUTF(state.getDefinitionId().toString());
@@ -100,7 +102,7 @@
         for (Iterator iter = c.iterator(); iter.hasNext();) {
             NodeState.ChildNodeEntry entry = (NodeState.ChildNodeEntry) iter.next();
             out.writeUTF(entry.getName().toString());   // name
-            out.write(UUID.fromString(entry.getUUID()).getRawBytes());  // uuid
+            out.write(UUID.stringToBytes(entry.getUUID()));    // uuid
         }
     }
 
@@ -123,9 +125,8 @@
         // parentUUID (may be null)
         byte[] uuidBytes = new byte[UUID.UUID_BYTE_LENGTH];
         in.readFully(uuidBytes);
-        UUID uuid = new UUID(uuidBytes);
-        if (!uuid.equals(NULL_UUID_PLACEHOLDER)) {
-            state.setParentUUID(uuid.toString());
+        if (!Arrays.equals(uuidBytes, NULL_UUID_PLACEHOLDER_BYTES)) {
+            state.setParentUUID(UUID.bytesToString(uuidBytes));
         }
         // definitionId
         s = in.readUTF();
@@ -153,7 +154,7 @@
             QName name = QName.valueOf(in.readUTF());    // name
             // uuid
             in.readFully(uuidBytes);
-            state.addChildNodeEntry(name, new UUID(uuidBytes).toString());
+            state.addChildNodeEntry(name, UUID.bytesToString(uuidBytes));
         }
     }