You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by ju...@apache.org on 2012/10/25 10:56:13 UTC

svn commit: r1402028 - in /jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak: kernel/KernelRootBuilder.java plugins/memory/MemoryNodeBuilder.java plugins/memory/ModifiedNodeState.java spi/state/AbstractNodeState.java

Author: jukka
Date: Thu Oct 25 08:56:12 2012
New Revision: 1402028

URL: http://svn.apache.org/viewvc?rev=1402028&view=rev
Log:
OAK-170: Child node state builder

Extract ModifiedNodeState to a standalone class so we can leverage it also elsewhere.

Added:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/memory/ModifiedNodeState.java
Modified:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelRootBuilder.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/memory/MemoryNodeBuilder.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/state/AbstractNodeState.java

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelRootBuilder.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelRootBuilder.java?rev=1402028&r1=1402027&r2=1402028&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelRootBuilder.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelRootBuilder.java Thu Oct 25 08:56:12 2012
@@ -17,6 +17,7 @@
 package org.apache.jackrabbit.oak.kernel;
 
 import static com.google.common.base.Preconditions.checkNotNull;
+import static org.apache.jackrabbit.oak.plugins.memory.ModifiedNodeState.collapse;
 
 import java.util.Map;
 import java.util.Set;
@@ -24,6 +25,7 @@ import java.util.Set;
 import org.apache.jackrabbit.mk.api.MicroKernel;
 import org.apache.jackrabbit.mk.json.JsopBuilder;
 import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeBuilder;
+import org.apache.jackrabbit.oak.plugins.memory.ModifiedNodeState;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
 
 import com.google.common.collect.Maps;
@@ -164,8 +166,8 @@ class KernelRootBuilder extends MemoryNo
         //-------------------------------------------------------< private >--
 
         private KernelNodeState getKernelBaseState(NodeState state) {
-            if (state instanceof MutableNodeState) {
-                state = ((MutableNodeState) state).getBaseState();
+            if (state instanceof ModifiedNodeState) {
+                state = collapse((ModifiedNodeState) state).getBaseState();
             }
 
             if (state instanceof KernelNodeState) {

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/memory/MemoryNodeBuilder.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/memory/MemoryNodeBuilder.java?rev=1402028&r1=1402027&r2=1402028&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/memory/MemoryNodeBuilder.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/memory/MemoryNodeBuilder.java Thu Oct 25 08:56:12 2012
@@ -17,33 +17,31 @@
 package org.apache.jackrabbit.oak.plugins.memory;
 
 import java.util.Iterator;
-import java.util.List;
 import java.util.Map;
-import java.util.Set;
 
 import javax.annotation.Nonnull;
 
-import com.google.common.base.Predicate;
-import com.google.common.base.Predicates;
-import com.google.common.collect.Collections2;
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
+
 import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.jackrabbit.oak.api.Type;
 import org.apache.jackrabbit.oak.spi.state.AbstractNodeState;
-import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry;
 import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
 import org.apache.jackrabbit.oak.spi.state.NodeStateDiff;
 
 import static com.google.common.base.Preconditions.checkNotNull;
+import static org.apache.jackrabbit.oak.plugins.memory.ModifiedNodeState.with;
+import static org.apache.jackrabbit.oak.plugins.memory.ModifiedNodeState.withNodes;
+import static org.apache.jackrabbit.oak.plugins.memory.ModifiedNodeState.withProperties;
 
 /**
- * In-memory node state builder. The following two builder states are used
+ * In-memory node state builder.
+ * <p>
+ * TODO: The following description is somewhat out of date
+ * <p>
+ * The following two builder states are used
  * to properly track uncommitted chances without relying on weak references
  * or requiring hard references on the entire accessed subtree:
  * <dl>
@@ -71,7 +69,7 @@ import static com.google.common.base.Pre
  */
 public class MemoryNodeBuilder implements NodeBuilder {
 
-    private static final NodeState NULL_STATE = new MemoryNodeState(
+    static final NodeState NULL_STATE = new MemoryNodeState(
             ImmutableMap.<String, PropertyState>of(),
             ImmutableMap.<String, NodeState>of());
 
@@ -248,11 +246,12 @@ public class MemoryNodeBuilder implement
 
     @Override
     public NodeState getNodeState() {
-        NodeState state = read();
+        read();
         if (writeState != null) {
-            return new ModifiedNodeState(writeState);
+            return writeState.snapshot();
         } else {
-            return state;
+            assert baseState != null; // guaranteed by read()
+            return baseState;
         }
     }
 
@@ -337,9 +336,9 @@ public class MemoryNodeBuilder implement
         MutableNodeState mstate = write();
 
         if (mstate.base.getProperty(name) != null) {
-            mstate.props.put(name, null);
+            mstate.properties.put(name, null);
         } else {
-            mstate.props.remove(name);
+            mstate.properties.remove(name);
         }
 
         updated();
@@ -349,7 +348,7 @@ public class MemoryNodeBuilder implement
     @Override
     public NodeBuilder setProperty(PropertyState property) {
         MutableNodeState mstate = write();
-        mstate.props.put(property.getName(), property);
+        mstate.properties.put(property.getName(), property);
         updated();
         return this;
     }
@@ -398,79 +397,63 @@ public class MemoryNodeBuilder implement
     }
 
     /**
-     * Filter for skipping property states with given names.
-     */
-    private static class SkipNamedProps implements Predicate<PropertyState> {
-
-        private final Set<String> names;
-
-        private SkipNamedProps(Set<String> names) {
-            this.names = names;
-        }
-
-        @Override
-        public boolean apply(PropertyState input) {
-            return !names.contains(input.getName());
-        }
-
-    }
-
-    /**
-     * Filter for skipping child node states with given names.
-     */
-    private static class SkipNamedNodes implements Predicate<ChildNodeEntry> {
-
-        private final Set<String> names;
-
-        private SkipNamedNodes(Set<String> names) {
-            this.names = names;
-        }
-
-        @Override
-        public boolean apply(ChildNodeEntry input) {
-            return !names.contains(input.getName());
-        }
-
-    }
-
-    /**
      * The <em>mutable</em> state being built. Instances of this class
      * are never passed beyond the containing {@code MemoryNodeBuilder},
      * so it's not a problem that we intentionally break the immutability
      * assumption of the {@link NodeState} interface.
      */
-    protected static class MutableNodeState extends AbstractNodeState {
+    private class MutableNodeState extends AbstractNodeState {
 
         /**
          * The immutable base state.
          */
-        protected NodeState base;
+        private NodeState base;
 
         /**
          * Set of added, modified or removed ({@code null} value)
          * property states.
          */
-        protected final Map<String, PropertyState> props =
+        private final Map<String, PropertyState> properties =
                 Maps.newHashMap();
 
         /**
          * Set of added, modified or removed ({@code null} value)
          * child nodes.
          */
-        protected final Map<String, MutableNodeState> nodes =
+        private final Map<String, MutableNodeState> nodes =
                 Maps.newHashMap();
 
         public MutableNodeState(NodeState base) {
             if (base != null) {
                 this.base = base;
             } else {
-                this.base = NULL_STATE;
+                this.base = MemoryNodeBuilder.NULL_STATE;
+            }
+        }
+
+        public NodeState snapshot() {
+            Map<String, NodeState> nodes = Maps.newHashMap();
+            for (Map.Entry<String, MutableNodeState> entry : this.nodes.entrySet()) {
+                String name = entry.getKey();
+                MutableNodeState node = entry.getValue();
+                NodeState before = base.getChildNode(name);
+                if (node == null) {
+                    if (before != null) {
+                        nodes.put(name, null);
+                    }
+                } else {
+                    NodeState after = node.snapshot();
+                    if (after != before) {
+                        nodes.put(name, after);
+                    }
+                }
             }
+            return with(base, Maps.newHashMap(this.properties), nodes);
         }
 
-        private void reset(NodeState newBase) {
+        void reset(NodeState newBase) {
             base = newBase;
-            props.clear();
+            properties.clear();
 
             Iterator<Map.Entry<String, MutableNodeState>> iterator =
                     nodes.entrySet().iterator();
@@ -486,293 +469,53 @@ public class MemoryNodeBuilder implement
             }
         }
 
-        public NodeState getBaseState() {
-            return base;
-        }
-
         //-----------------------------------------------------< NodeState >--
 
         @Override
         public long getPropertyCount() {
-            long count = base.getPropertyCount();
-
-            for (Map.Entry<String, PropertyState> entry : props.entrySet()) {
-                if (base.getProperty(entry.getKey()) != null) {
-                    count--;
-                }
-                if (entry.getValue() != null) {
-                    count++;
-                }
-            }
-
-            return count;
+            return withProperties(base, properties).getPropertyCount();
         }
 
         @Override
         public PropertyState getProperty(String name) {
-            PropertyState property = props.get(name);
-            if (property != null || props.containsKey(name)) {
-                return property;
-            }
-
-            return base.getProperty(name);
+            return withProperties(base, properties).getProperty(name);
         }
 
-        @Override
+        @Override @Nonnull
         public Iterable<? extends PropertyState> getProperties() {
-            if (props.isEmpty()) {
-                return base.getProperties(); // shortcut
-            } else {
-                return internalGetProperties();
-            }
-        }
-
-        protected Iterable<? extends PropertyState> internalGetProperties() {
-            Predicate<PropertyState> unmodifiedFilter =
-                    new SkipNamedProps(ImmutableSet.copyOf(props.keySet()));
-            Predicate<PropertyState> modifiedFilter = Predicates.notNull();
-            return Iterables.concat(
-                    Iterables.filter(base.getProperties(), unmodifiedFilter),
-                    ImmutableList.copyOf(Collections2.filter(
-                            props.values(), modifiedFilter)));
+            Map<String, PropertyState> copy = Maps.newHashMap(properties);
+            return withProperties(base, copy).getProperties();
         }
 
         @Override
         public long getChildNodeCount() {
-            long count = base.getChildNodeCount();
-
-            for (Map.Entry<String, MutableNodeState> entry : nodes.entrySet()) {
-                if (base.getChildNode(entry.getKey()) != null) {
-                    count--;
-                }
-                if (entry.getValue() != null) {
-                    count++;
-                }
-            }
-
-            return count;
+            return withNodes(base, nodes).getChildNodeCount();
         }
 
         @Override
         public boolean hasChildNode(String name) {
-            MutableNodeState node = nodes.get(name);
-            if (node != null) {
-                return true;
-            } else if (nodes.containsKey(name)) {
-                return false;
-            }
-
-            return base.hasChildNode(name);
+            return withNodes(base, nodes).hasChildNode(name);
         }
 
         @Override
         public NodeState getChildNode(String name) {
-            MutableNodeState node = nodes.get(name);
-            if (node != null) {
-                return node;
-            } else if (nodes.containsKey(name)) {
-                return null;
-            }
-
-            return base.getChildNode(name);
+            return withNodes(base, nodes).getChildNode(name); // mutable
         }
 
-        @Override
+        @Override @Nonnull
         public Iterable<String> getChildNodeNames() {
-            if (nodes.isEmpty()) {
-                return base.getChildNodeNames(); // shortcut
-            } else {
-                return internalGetChildNodeNames();
-            }
-        }
-
-        protected Iterable<String> internalGetChildNodeNames() {
-            Iterable<String> unmodified = base.getChildNodeNames();
-            Predicate<String> unmodifiedFilter = Predicates.not(Predicates.in(
-                    ImmutableSet.copyOf(nodes.keySet())));
-            Set<String> modified = ImmutableSet.copyOf(
-                    Maps.filterValues(nodes, Predicates.notNull()).keySet());
-            return Iterables.concat(
-                    Iterables.filter(unmodified, unmodifiedFilter),
-                    modified);
-        }
-
-        @Override
-        public Iterable<? extends ChildNodeEntry> getChildNodeEntries() {
-            if (nodes.isEmpty()) {
-                return base.getChildNodeEntries(); // shortcut
-            } else {
-                return internalGetChildNodeEntries();
-            }
-        }
-
-        protected Iterable<? extends ChildNodeEntry> internalGetChildNodeEntries() {
-            Iterable<? extends ChildNodeEntry> unmodified =
-                    base.getChildNodeEntries();
-            Predicate<ChildNodeEntry> unmodifiedFilter =
-                    new SkipNamedNodes(ImmutableSet.copyOf(nodes.keySet()));
-
-            List<ChildNodeEntry> modified = Lists.newArrayList();
-            for (Map.Entry<String, MutableNodeState> entry : nodes.entrySet()) {
-                MutableNodeState cstate = entry.getValue();
-                if (cstate != null) {
-                    modified.add(new MemoryChildNodeEntry(
-                            entry.getKey(),
-                            new ModifiedNodeState(cstate)));
-                }
-            }
-
-            return Iterables.concat(
-                    Iterables.filter(unmodified, unmodifiedFilter),
-                    modified);
+            Map<String, MutableNodeState> copy = Maps.newHashMap(nodes);
+            return withNodes(base, copy).getChildNodeNames();
         }
 
         @Override
-        public NodeBuilder builder() {
-            return new ModifiedNodeState(this).builder();
-        }
-
-        /**
-         * Since we keep track of an explicit base node state for a
-         * {@link ModifiedNodeState} instance, we can do this in two steps:
-         * first compare the base states to each other (often a fast operation),
-         * ignoring all changed properties and child nodes for which we have
-         * further modifications, and then compare all the modified properties
-         * and child nodes to those in the given base state.
-         */
-        @Override
-        public void compareAgainstBaseState(
-                NodeState base, final NodeStateDiff diff) {
-            this.base.compareAgainstBaseState(base, new NodeStateDiff() {
-                @Override
-                public void propertyAdded(PropertyState after) {
-                    if (!props.containsKey(after.getName())) {
-                        diff.propertyAdded(after);
-                    }
-                }
-                @Override
-                public void propertyChanged(
-                        PropertyState before, PropertyState after) {
-                    if (!props.containsKey(before.getName())) {
-                        diff.propertyChanged(before, after);
-                    }
-                }
-                @Override
-                public void propertyDeleted(PropertyState before) {
-                    if (!props.containsKey(before.getName())) {
-                        diff.propertyDeleted(before);
-                    }
-                }
-                @Override
-                public void childNodeAdded(String name, NodeState after) {
-                    if (!nodes.containsKey(name)) {
-                        diff.childNodeAdded(name, after);
-                    }
-                }
-                @Override
-                public void childNodeChanged(String name, NodeState before, NodeState after) {
-                    if (!nodes.containsKey(name)) {
-                        diff.childNodeChanged(name, before, after);
-                    }
-                }
-                @Override
-                public void childNodeDeleted(String name, NodeState before) {
-                    if (!nodes.containsKey(name)) {
-                        diff.childNodeDeleted(name, before);
-                    }
-                }
-            });
-
-            for (Map.Entry<String, PropertyState> entry : props.entrySet()) {
-                PropertyState before = base.getProperty(entry.getKey());
-                PropertyState after = entry.getValue();
-                if (before == null && after == null) {
-                    // do nothing
-                } else if (after == null) {
-                    diff.propertyDeleted(before);
-                } else if (before == null) {
-                    diff.propertyAdded(after);
-                } else if (!before.equals(after)) {
-                    diff.propertyChanged(before, after);
-                }
-            }
-
-            for (Map.Entry<String, MutableNodeState> entry : nodes.entrySet()) {
-                String name = entry.getKey();
-                NodeState before = base.getChildNode(name);
-                NodeState after = entry.getValue();
-                if (before == null && after == null) {
-                    // do nothing
-                } else if (after == null) {
-                    diff.childNodeDeleted(name, before);
-                } else if (before == null) {
-                    diff.childNodeAdded(name, after);
-                } else if (!before.equals(after)) {
-                    diff.childNodeChanged(name, before, after);
-                }
-            }
+        public void compareAgainstBaseState(NodeState base, NodeStateDiff diff) {
+            with(this.base, properties, nodes).compareAgainstBaseState(base, diff);
         }
 
-    }
-
-    /**
-     * Immutable snapshot of a mutable node state.
-     */
-    protected static class ModifiedNodeState extends MutableNodeState {
-
-        public ModifiedNodeState(MutableNodeState mstate) {
-            super(mstate.base);
-            props.putAll(mstate.props);
-            for (Map.Entry<String, MutableNodeState> entry
-                    : mstate.nodes.entrySet()) {
-                String name = entry.getKey();
-                MutableNodeState node = entry.getValue();
-                if (node != null) {
-                    nodes.put(name, new ModifiedNodeState(node));
-                } else {
-                    nodes.put(name, null);
-                }
-            }
-        }
-
-        @Override
+        @Override @Nonnull
         public NodeBuilder builder() {
-            return new MemoryNodeBuilder(this);
-        }
-
-        //----------------------------------------------< MutableNodeState >--
-
-        @Override
-        protected Iterable<? extends PropertyState> internalGetProperties() {
-            Predicate<PropertyState> unmodifiedFilter =
-                    new SkipNamedProps(props.keySet());
-            Predicate<PropertyState> modifiedFilter = Predicates.notNull();
-            return Iterables.concat(
-                    Iterables.filter(base.getProperties(), unmodifiedFilter),
-                    Collections2.filter(props.values(), modifiedFilter));
-        }
-
-        @Override
-        protected Iterable<String> internalGetChildNodeNames() {
-            Iterable<String> unmodified = base.getChildNodeNames();
-            Predicate<String> unmodifiedFilter =
-                    Predicates.not(Predicates.in(nodes.keySet()));
-            return Iterables.concat(
-                    Iterables.filter(unmodified, unmodifiedFilter),
-                    Maps.filterValues(nodes, Predicates.notNull()).keySet());
-        }
-
-        @Override
-        protected Iterable<? extends ChildNodeEntry> internalGetChildNodeEntries() {
-            Iterable<? extends ChildNodeEntry> unmodified =
-                    base.getChildNodeEntries();
-            Predicate<ChildNodeEntry> unmodifiedFilter =
-                    new SkipNamedNodes(nodes.keySet());
-            Map<String, MutableNodeState> modified =
-                    Maps.filterValues(nodes, Predicates.notNull());
-            return Iterables.concat(
-                    Iterables.filter(unmodified, unmodifiedFilter),
-                    MemoryChildNodeEntry.iterable(modified.entrySet()));
+            throw new UnsupportedOperationException();
         }
 
     }

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/memory/ModifiedNodeState.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/memory/ModifiedNodeState.java?rev=1402028&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/memory/ModifiedNodeState.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/memory/ModifiedNodeState.java Thu Oct 25 08:56:12 2012
@@ -0,0 +1,340 @@
+/*
+ * 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.oak.plugins.memory;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Predicates.in;
+import static com.google.common.base.Predicates.not;
+import static com.google.common.base.Predicates.notNull;
+import static com.google.common.collect.Collections2.filter;
+import static com.google.common.collect.Iterables.concat;
+import static com.google.common.collect.Iterables.filter;
+import static com.google.common.collect.Maps.filterValues;
+import static org.apache.jackrabbit.oak.plugins.memory.MemoryChildNodeEntry.iterable;
+
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.spi.state.AbstractNodeState;
+import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.apache.jackrabbit.oak.spi.state.NodeStateDiff;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+
+/**
+ * Immutable snapshot of a mutable node state.
+ */
+public class ModifiedNodeState extends AbstractNodeState {
+
+    public static NodeState withProperties(
+            NodeState base, Map<String, ? extends PropertyState> properties) {
+        if (properties.isEmpty()) {
+            return base;
+        } else {
+            return new ModifiedNodeState(
+                    base, properties, ImmutableMap.<String, NodeState>of());
+        }
+    }
+
+    public static NodeState withNodes(
+            NodeState base, Map<String, ? extends NodeState> nodes) {
+        if (nodes.isEmpty()) {
+            return base;
+        } else {
+            return new ModifiedNodeState(
+                    base, ImmutableMap.<String, PropertyState>of(), nodes);
+        }
+    }
+
+    public static NodeState with(
+            NodeState base,
+            Map<String, ? extends PropertyState> properties,
+            Map<String, ? extends NodeState> nodes) {
+        if (properties.isEmpty() && nodes.isEmpty()) {
+            return base;
+        } else {
+            return new ModifiedNodeState(base, properties, nodes);
+        }
+    }
+
+    public static ModifiedNodeState collapse(ModifiedNodeState state) {
+        NodeState base = state.getBaseState();
+        if (base instanceof ModifiedNodeState) {
+            ModifiedNodeState mbase = collapse((ModifiedNodeState) base);
+
+            Map<String, PropertyState> properties =
+                    Maps.newHashMap(mbase.properties);
+            properties.putAll(state.properties);
+
+            Map<String, NodeState> nodes =
+                    Maps.newHashMap(mbase.nodes);
+            nodes.putAll(state.nodes);
+
+            return new ModifiedNodeState(
+                    mbase.getBaseState(), properties, nodes);
+        } else {
+            return state;
+        }
+    }
+
+    /**
+     * The base state.
+     */
+    private final NodeState base;
+
+    /**
+     * Set of added, modified or removed ({@code null} value)
+     * property states.
+     */
+    private final Map<String, ? extends PropertyState> properties;
+
+    /**
+     * Set of added, modified or removed ({@code null} value)
+     * child nodes.
+     */
+    private final Map<String, ? extends NodeState> nodes;
+
+    public ModifiedNodeState(
+            @Nonnull NodeState base,
+            @Nonnull Map<String, ? extends PropertyState> properties,
+            @Nonnull Map<String, ? extends NodeState> nodes) {
+        this.base = checkNotNull(base);
+        this.properties = checkNotNull(properties);
+        this.nodes = checkNotNull(nodes);
+    }
+
+    public ModifiedNodeState(
+            @Nonnull NodeState base,
+            @Nonnull Map<String, ? extends PropertyState> properties) {
+        this(base, properties, ImmutableMap.<String, NodeState>of());
+    }
+
+    @Nonnull
+    public NodeState getBaseState() {
+        return base;
+    }
+
+    //---------------------------------------------------------< NodeState >--
+
+    @Override
+    public NodeBuilder builder() {
+        return new MemoryNodeBuilder(this);
+    }
+
+    @Override
+    public long getPropertyCount() {
+        long count = base.getPropertyCount();
+
+        for (Map.Entry<String, ? extends PropertyState> entry : properties.entrySet()) {
+            if (base.getProperty(entry.getKey()) != null) {
+                count--;
+            }
+            if (entry.getValue() != null) {
+                count++;
+            }
+        }
+
+        return count;
+    }
+
+    @Override
+    public PropertyState getProperty(String name) {
+        PropertyState property = properties.get(name);
+        if (property != null) {
+            return property;
+        } else if (properties.containsKey(name)) {
+            return null; // removed
+        } else {
+            return base.getProperty(name);
+        }
+    }
+
+    @Override
+    public Iterable<? extends PropertyState> getProperties() {
+        if (properties.isEmpty()) {
+            return base.getProperties(); // shortcut
+        } else {
+            Predicate<PropertyState> filter = new Predicate<PropertyState>() {
+                @Override
+                public boolean apply(PropertyState input) {
+                    return !properties.containsKey(input.getName());
+                }
+            };
+            return concat(
+                    filter(base.getProperties(), filter),
+                    filter(properties.values(), notNull()));
+        }
+    }
+
+    @Override
+    public long getChildNodeCount() {
+        long count = base.getChildNodeCount();
+
+        for (Map.Entry<String, ? extends NodeState> entry : nodes.entrySet()) {
+            if (base.getChildNode(entry.getKey()) != null) {
+                count--;
+            }
+            if (entry.getValue() != null) {
+                count++;
+            }
+        }
+
+        return count;
+    }
+
+    @Override
+    public boolean hasChildNode(String name) {
+        NodeState child = nodes.get(name);
+        if (child != null) {
+            return true;
+        } else if (nodes.containsKey(name)) {
+            return false; // removed
+        } else {
+            return base.hasChildNode(name);
+        }
+    }
+
+    @Override
+    public NodeState getChildNode(String name) {
+        NodeState child = nodes.get(name);
+        if (child != null) {
+            return child;
+        } else if (nodes.containsKey(name)) {
+            return null; // removed
+        } else {
+            return base.getChildNode(name);
+        }
+    }
+
+    @Override
+    public Iterable<String> getChildNodeNames() {
+        if (nodes.isEmpty()) {
+            return base.getChildNodeNames(); // shortcut
+        } else {
+            return concat(
+                    filter(base.getChildNodeNames(), not(in(nodes.keySet()))),
+                    filterValues(nodes, notNull()).keySet());
+        }
+    }
+
+    @Override
+    public Iterable<? extends ChildNodeEntry> getChildNodeEntries() {
+        if (nodes.isEmpty()) {
+            return base.getChildNodeEntries(); // shortcut
+        } else {
+            Predicate<ChildNodeEntry> filter = new Predicate<ChildNodeEntry>() {
+                @Override
+                public boolean apply(ChildNodeEntry input) {
+                    return !nodes.containsKey(input.getName());
+                }
+            };
+            return concat(
+                    filter(base.getChildNodeEntries(), filter),
+                    iterable(filterValues(nodes, notNull()).entrySet()));
+        }
+    }
+
+    /**
+     * Since we keep track of an explicit base node state for a
+     * {@link ModifiedNodeState} instance, we can do this in two steps:
+     * first compare the base states to each other (often a fast operation),
+     * ignoring all changed properties and child nodes for which we have
+     * further modifications, and then compare all the modified properties
+     * and child nodes to those in the given base state.
+     */
+    @Override
+    public void compareAgainstBaseState(
+            NodeState base, final NodeStateDiff diff) {
+        if (this.base != base) {
+            this.base.compareAgainstBaseState(base, new NodeStateDiff() {
+                @Override
+                public void propertyAdded(PropertyState after) {
+                    if (!properties.containsKey(after.getName())) {
+                        diff.propertyAdded(after);
+                    }
+                }
+                @Override
+                public void propertyChanged(
+                        PropertyState before, PropertyState after) {
+                    if (!properties.containsKey(before.getName())) {
+                        diff.propertyChanged(before, after);
+                    }
+                }
+                @Override
+                public void propertyDeleted(PropertyState before) {
+                    if (!properties.containsKey(before.getName())) {
+                        diff.propertyDeleted(before);
+                    }
+                }
+                @Override
+                public void childNodeAdded(String name, NodeState after) {
+                    if (!nodes.containsKey(name)) {
+                        diff.childNodeAdded(name, after);
+                    }
+                }
+                @Override
+                public void childNodeChanged(String name, NodeState before, NodeState after) {
+                    if (!nodes.containsKey(name)) {
+                        diff.childNodeChanged(name, before, after);
+                    }
+                }
+                @Override
+                public void childNodeDeleted(String name, NodeState before) {
+                    if (!nodes.containsKey(name)) {
+                        diff.childNodeDeleted(name, before);
+                    }
+                }
+            });
+        }
+
+        for (Map.Entry<String, ? extends PropertyState> entry : properties.entrySet()) {
+            PropertyState before = base.getProperty(entry.getKey());
+            PropertyState after = entry.getValue();
+            if (before == null && after == null) {
+                // do nothing
+            } else if (after == null) {
+                diff.propertyDeleted(before);
+            } else if (before == null) {
+                diff.propertyAdded(after);
+            } else if (!before.equals(after)) {
+                diff.propertyChanged(before, after);
+            }
+        }
+
+        for (Map.Entry<String, ? extends NodeState> entry : nodes.entrySet()) {
+            String name = entry.getKey();
+            NodeState before = base.getChildNode(name);
+            NodeState after = entry.getValue();
+            if (before == null && after == null) {
+                // do nothing
+            } else if (after == null) {
+                diff.childNodeDeleted(name, before);
+            } else if (before == null) {
+                diff.childNodeAdded(name, after);
+            } else if (!before.equals(after)) {
+                diff.childNodeChanged(name, before, after);
+            }
+        }
+    }
+
+}
\ No newline at end of file

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/state/AbstractNodeState.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/state/AbstractNodeState.java?rev=1402028&r1=1402027&r2=1402028&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/state/AbstractNodeState.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/state/AbstractNodeState.java Thu Oct 25 08:56:12 2012
@@ -26,6 +26,8 @@ import java.util.Iterator;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
 
+import javax.annotation.Nonnull;
+
 /**
  * Abstract base class for {@link NodeState} implementations.
  * This base class contains default implementations of the
@@ -88,6 +90,27 @@ public abstract class AbstractNodeState 
                 });
     }
 
+    @Override
+    public Iterable<? extends ChildNodeEntry> getChildNodeEntries() {
+        return Iterables.transform(
+                getChildNodeNames(),
+                new Function<String, ChildNodeEntry>() {
+                    @Override
+                    public ChildNodeEntry apply(final String input) {
+                        return new AbstractChildNodeEntry() {
+                            @Override @Nonnull
+                            public String getName() {
+                                return input;
+                            }
+                            @Override @Nonnull
+                            public NodeState getNodeState() {
+                                return getChildNode(input);
+                            }
+                        };
+                    }
+                });
+    }
+
     /**
      * Generic default comparison algorithm that simply walks through the
      * property and child node lists of the given base state and compares