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