Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/memory/ModifiedNodeState.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/memory/ModifiedNodeState.java (revision 9f76b4feb099e1c90309fb8629e2dfee9fd6cf47) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/memory/ModifiedNodeState.java (revision ) @@ -47,34 +47,38 @@ * Immutable snapshot of a mutable node state. */ public class ModifiedNodeState extends AbstractNodeState { + // FIXME fix contract wrt exists + private final boolean exists; + static NodeState withProperties( - NodeState base, Map properties) { + NodeState base, Map properties, boolean exists) { if (properties.isEmpty()) { return base; } else { - return new ModifiedNodeState(base, properties, ImmutableMap.of()); + return new ModifiedNodeState(base, properties, ImmutableMap.of(), exists); } } static NodeState withNodes( - NodeState base, Map nodes) { + NodeState base, Map nodes, boolean exists) { if (nodes.isEmpty()) { return base; } else { - return new ModifiedNodeState(base, ImmutableMap.of(), nodes); + return new ModifiedNodeState(base, ImmutableMap.of(), nodes, exists); } } static NodeState with( NodeState base, Map properties, - Map nodes) { + Map nodes, + boolean exists) { if (properties.isEmpty() && nodes.isEmpty()) { return base; } else { // TODO: Do we need collapse() here? See OAK-778 - return collapse(new ModifiedNodeState(base, properties, nodes)); + return collapse(new ModifiedNodeState(base, properties, nodes, exists)); } } @@ -89,7 +93,7 @@ Map nodes = Maps.newHashMap(mbase.nodes); nodes.putAll(state.nodes); - return new ModifiedNodeState(mbase.getBaseState(), properties, nodes); + return new ModifiedNodeState(mbase.getBaseState(), properties, nodes, state.exists); } else { return state; } @@ -115,10 +119,12 @@ private ModifiedNodeState( @Nonnull NodeState base, @Nonnull Map properties, - @Nonnull Map nodes) { + @Nonnull Map nodes, + boolean exists) { this.base = checkNotNull(base); this.properties = checkNotNull(properties); this.nodes = checkNotNull(nodes); + this.exists = exists; } @Nonnull @@ -135,7 +141,7 @@ @Override public boolean exists() { - return true; + return exists; } @Override @@ -216,7 +222,7 @@ // checkArgument(!checkNotNull(name).isEmpty()); // TODO: should be caught earlier NodeState child = nodes.get(name); if (child != null) { - return true; + return child.exists(); } else if (nodes.containsKey(name)) { return false; } else { \ No newline at end of file Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/memory/MemoryNodeBuilder.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/memory/MemoryNodeBuilder.java (revision 9f76b4feb099e1c90309fb8629e2dfee9fd6cf47) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/memory/MemoryNodeBuilder.java (revision ) @@ -23,6 +23,7 @@ import static org.apache.jackrabbit.oak.api.Type.NAME; import static org.apache.jackrabbit.oak.api.Type.NAMES; import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE; +import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.MISSING_NODE; 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; @@ -141,7 +142,7 @@ this.revision = 0; this.baseState = checkNotNull(base); - this.writeState = new MutableNodeState(baseState); + this.writeState = new MutableNodeState(baseState, baseState.exists()); } private boolean classInvariants() { @@ -188,19 +189,25 @@ } /** - * Determine whether this child exists at its direct parent. - * @return {@code true} iff this child exists at its direct parent. + * @return {@code true} iff the shared parent state has an entry for the name + * of this builder or this is the root. */ - @Override // TODO: Check that the implementation matches NodeBuilder.exists() - public boolean exists() { + public boolean existsInParent() { if (isRoot()) { return true; } else if (parent.writeState == null) { return parent.baseState.hasChildNode(name); } else { - return parent.writeState.hasChildNode(name); + NodeState child = parent.writeState.nodes.get(name); + if (child != null) { + return true; + } else if (parent.writeState.nodes.containsKey(name)) { + return false; + } else { + return parent.writeState.base.hasChildNode(name); - } - } + } + } + } /** * Update the state of this builder for reading. @@ -210,7 +217,7 @@ if (revision != root.revision) { assert(!isRoot()); // root never gets here since revision == root.revision - if (parent.updateReadState() && exists()) { + if (parent.updateReadState() && existsInParent()) { // The builder could have been reset, need to re-get base state baseState = parent.getBaseState(name); @@ -243,7 +250,7 @@ // make sure that all revision numbers up to the root gets updated if (!isRoot()) { parent.write(newRevision, reconnect); - checkState(reconnect || exists(), "This node has been removed"); + checkState(reconnect || existsInParent(), "This node has been removed"); } if (writeState == null || revision != root.revision) { @@ -254,14 +261,11 @@ writeState = parent.getWriteState(name); if (writeState == null) { - if (exists()) { - NodeState writeBase = - parent.writeState.base.getChildNode(name); - writeState = new MutableNodeState(writeBase); - } - else { - writeState = new MutableNodeState(null); - } + NodeState writeBase = existsInParent() + ? parent.writeState.base.getChildNode(name) + : null; + boolean exists = baseState.exists() || parent.hasChildNode(name); + writeState = new MutableNodeState(writeBase, exists); assert parent.writeState != null; // guaranteed by called parent.write() parent.writeState.nodes.put(name, writeState); } @@ -361,7 +365,7 @@ public void reset(NodeState newBase) { checkState(isRoot(), "Cannot reset a non-root builder"); baseState = checkNotNull(newBase); - writeState = new MutableNodeState(baseState); + writeState = new MutableNodeState(baseState, baseState.exists()); revision++; } @@ -492,7 +496,24 @@ } @Override + public boolean exists() { + read(); + return baseState.exists() || (parent != null && parent.hasChildNode(name)); + } + + @Override public NodeBuilder child(String name) { + boolean exists = read().getChildNode(name).exists(); + if (exists) { + return getChild(name); + } else { + return addChild(name); + } + } + + @Override @Nonnull + public NodeBuilder getChild(@Nonnull String name) { + // TODO better implementation read(); MemoryNodeBuilder builder = createChildBuilder(name); @@ -504,16 +525,18 @@ } @Override @Nonnull - public NodeBuilder getChild(@Nonnull String name) { - throw new UnsupportedOperationException(); // TODO - } - - @Override @Nonnull public NodeBuilder addChild(@Nonnull String name) { - // TODO: better implementation? + // TODO better implementation setNode(name, EMPTY_NODE); - return child(name); + read(); + MemoryNodeBuilder builder = createChildBuilder(name); + + boolean modified = writeState != null && (writeState.base != baseState || writeState.nodes.containsKey(name)); + if (!hasBaseState(name) || modified) { + builder.write(root.revision + 1, true); - } + } + return builder; + } /** * The mutable state being built. Instances of this class @@ -522,7 +545,10 @@ * assumption of the {@link NodeState} interface. */ private static class MutableNodeState extends AbstractNodeState { + // FIXME fix contract wrt exists + private boolean exists; + /** * The immutable base state. */ @@ -540,7 +566,7 @@ */ private final Map nodes = newHashMap(); - public MutableNodeState(NodeState base) { + public MutableNodeState(NodeState base, boolean exists) { if (base instanceof ModifiedNodeState) { ModifiedNodeState modified = (ModifiedNodeState) base; this.base = modified.getBaseState(); @@ -560,12 +586,12 @@ } @Override public void childNodeAdded(String name, NodeState after) { - nodes.put(name, new MutableNodeState(after)); + nodes.put(name, new MutableNodeState(after, after.exists())); } @Override public void childNodeChanged( String name, NodeState before, NodeState after) { - nodes.put(name, new MutableNodeState(after)); + nodes.put(name, new MutableNodeState(after, after.exists())); } @Override public void childNodeDeleted(String name, NodeState before) { @@ -575,8 +601,9 @@ } else if (base != null) { this.base = base; } else { - this.base = EMPTY_NODE; + this.base = exists ? EMPTY_NODE : MISSING_NODE; } + this.exists = exists; } public NodeState snapshot() { @@ -596,7 +623,7 @@ } } } - return with(base, newHashMap(this.properties), nodes); + return with(base, newHashMap(this.properties), nodes, exists); } void reset(NodeState newBase) { @@ -638,7 +665,7 @@ if (cstate != null) { cstate.reset(after); } else { - nodes.put(name, new MutableNodeState(after)); + nodes.put(name, new MutableNodeState(after, after.exists())); } } @Override @@ -648,7 +675,7 @@ if (cstate != null) { cstate.reset(after); } else { - nodes.put(name, new MutableNodeState(after)); + nodes.put(name, new MutableNodeState(after, after.exists())); } } @Override @@ -673,6 +700,7 @@ } } } + exists = base.exists(); } @Override @@ -698,51 +726,51 @@ @Override public boolean exists() { - return true; + return exists; } @Override public long getPropertyCount() { - return withProperties(base, properties).getPropertyCount(); + return withProperties(base, properties, exists).getPropertyCount(); } @Override public boolean hasProperty(String name) { - return withProperties(base, properties).hasProperty(name); + return withProperties(base, properties, exists).hasProperty(name); } @Override public PropertyState getProperty(String name) { - return withProperties(base, properties).getProperty(name); + return withProperties(base, properties, exists).getProperty(name); } @Override @Nonnull public Iterable getProperties() { Map copy = newHashMap(properties); - return withProperties(base, copy).getProperties(); + return withProperties(base, copy, exists).getProperties(); } @Override public long getChildNodeCount() { - return withNodes(base, nodes).getChildNodeCount(); + return withNodes(base, nodes, exists).getChildNodeCount(); } @Override public boolean hasChildNode(String name) { checkNotNull(name); // checkArgument(!name.isEmpty()); TODO: should be caught earlier - return withNodes(base, nodes).hasChildNode(name); + return withNodes(base, nodes, exists).hasChildNode(name); } @Override public NodeState getChildNode(String name) { - throw new UnsupportedOperationException(); + return withNodes(base, nodes, exists).getChildNode(name); } @Override @Nonnull public Iterable getChildNodeNames() { Map copy = newHashMap(nodes); - return withNodes(base, copy).getChildNodeNames(); + return withNodes(base, copy, exists).getChildNodeNames(); } @Override @Nonnull Index: oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/memory/MemoryNodeBuilderTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/memory/MemoryNodeBuilderTest.java (revision 9f76b4feb099e1c90309fb8629e2dfee9fd6cf47) +++ oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/memory/MemoryNodeBuilderTest.java (revision ) @@ -32,7 +32,6 @@ import org.apache.jackrabbit.oak.spi.state.NodeBuilder; import org.apache.jackrabbit.oak.spi.state.NodeState; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; public class MemoryNodeBuilderTest { @@ -198,7 +197,6 @@ } @Test - @Ignore("OAK-781") public void getExistingChildTest() { NodeBuilder rootBuilder = base.builder(); NodeBuilder x = rootBuilder.getChild("x"); @@ -207,7 +205,6 @@ } @Test - @Ignore("OAK-781") public void getNonExistingChildTest() { NodeBuilder rootBuilder = base.builder(); NodeBuilder any = rootBuilder.getChild("any"); @@ -288,7 +285,6 @@ } @Test - @Ignore("OAK-781") public void modifyChildNodeOfNonExistingNode() { NodeBuilder rootBuilder = EMPTY_NODE.builder(); @@ -355,7 +351,6 @@ } @Test - @Ignore("OAK-781") public void shadowNonExistingNode2() { NodeBuilder rootBuilder = EMPTY_NODE.builder();