blob: b986671348deadb360804deea5ba40d1fccf0bbb [file] [log] [blame]
/*
* 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.collect.Maps.newHashMap;
import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.MISSING_NODE;
import java.util.Map;
import java.util.Map.Entry;
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.EqualsDiff;
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 org.jetbrains.annotations.NotNull;
/**
* A <em>mutable</em> state being built.
*
* Instances of this class are never passed beyond the containing
* {@link MemoryNodeBuilder}, so it's not a problem that we intentionally
* break the immutability assumption of the
* {@link org.apache.jackrabbit.oak.spi.state.NodeState} interface.
*/
class MutableNodeState extends AbstractNodeState {
/**
* The immutable base state.
*/
private NodeState base;
/**
* Set of added, modified or removed ({@code null} value)
* property states.
*/
private final Map<String, PropertyState> properties = newHashMap();
/**
* Set of added, modified or removed (non-existent value)
* child nodes.
*/
private final Map<String, MutableNodeState> nodes = newHashMap();
/**
* Flag to indicate that this child has been replace in its parent.
* @see org.apache.jackrabbit.oak.spi.state.NodeBuilder#isReplaced()
*/
private boolean replaced;
MutableNodeState(@NotNull NodeState base) {
checkNotNull(base);
this.base = ModifiedNodeState.unwrap(base, properties, nodes);
}
NodeState snapshot() {
assert base != null;
if (properties.isEmpty() && nodes.isEmpty()) {
return base;
} else {
return new ModifiedNodeState(base, properties, nodes);
}
}
void reset(NodeState newBase) {
assert base != null;
checkNotNull(newBase);
base = ModifiedNodeState.unwrap(newBase, properties, nodes);
}
/**
* Equivalent to
* <pre>
* MutableNodeState child = getChildNode(name, true);
* child.reset(state);
* return child;
* </pre>
*
* @throws IllegalArgumentException if the given name string is empty
* or contains the forward slash character
*/
@NotNull
MutableNodeState setChildNode(String name, NodeState state)
throws IllegalArgumentException {
assert base != null;
MutableNodeState child = nodes.get(name);
if (child == null) {
checkValidName(name);
child = new MutableNodeState(state);
if (base.hasChildNode(name)) {
child.replaced = true;
}
nodes.put(name, child);
} else {
child.replaced = true;
child.reset(state);
}
return child;
}
/**
* Determine whether this node state is modified wrt. the passed
* {@code before} state.
* <p>
* A node state is modified if it either has not the same properties
* or has not the same child nodes as a {@code before} state. A node
* state has the same properties as a {@code before} state iff its
* set of properties is equal to the set of properties of
* {@code before}. A node state has the same child nodes as a
* {@code before} state iff its set of child node names is equal to
* the set of child node names of {@code before}.
*/
boolean isModified(NodeState before) {
if (!exists()) {
return false;
} else if (nodes.isEmpty() && properties.isEmpty()) {
return EqualsDiff.modified(before, base);
}
// was a child node added or removed?
for (Entry<String, MutableNodeState> n : nodes.entrySet()) {
if (n.getValue().exists() != before.hasChildNode(n.getKey())) {
return true;
}
}
// was a property added, removed or modified
for (Entry<String, PropertyState> p : properties.entrySet()) {
PropertyState pState = p.getValue();
if (pState == null
|| !pState.equals(before.getProperty(p.getKey()))) {
return true;
}
}
return false;
}
boolean isReplaced(NodeState before) {
return replaced;
}
boolean isReplaced(NodeState before, String name) {
return before.hasProperty(name)
&& (!base.equals(before) || properties.containsKey(name));
}
/**
* Remove the child node with the given {@code name}.
* @param name name of the child node to remove
* @return {@code true} if a child node {@code name} existed, {@code false} otherwise.
*/
boolean removeChildNode(String name) {
assert base != null;
MutableNodeState child = nodes.get(name);
if (child != null) {
boolean existed = child.exists();
child.reset(MISSING_NODE);
return existed;
} else {
nodes.put(name, new MutableNodeState(MISSING_NODE));
return base.hasChildNode(name);
}
}
/**
* Remove the property of the given {@code name}.
* @param name name of the property to remove
* @return {@code true} if a property {@code name} existed, {@code false} otherwise.
*/
boolean removeProperty(String name) {
assert base != null;
if (base.hasProperty(name)) {
properties.put(name, null);
return true;
} else {
return properties.remove(name) != null;
}
}
/**
* Set the value of a property
*/
void setProperty(PropertyState property) {
String name = property.getName();
checkValidName(name);
properties.put(name, property);
}
@Override
public String toString() {
assert base != null;
StringBuilder builder = new StringBuilder();
builder.append(base).append(" + {");
String separator = " ";
for (PropertyState property : properties.values()) {
builder.append(separator);
separator = ", ";
builder.append(property);
}
for (Entry<String, MutableNodeState> entry : nodes.entrySet()) {
builder.append(separator);
separator = ", ";
builder.append(entry.getKey()).append(" : ").append(entry.getValue());
}
builder.append(" }");
return builder.toString();
}
//-----------------------------------------------------< NodeState >--
@Override
public boolean exists() {
assert base != null;
return base.exists();
}
@Override
public long getPropertyCount() {
return ModifiedNodeState.getPropertyCount(base, properties);
}
@Override
public boolean hasProperty(@NotNull String name) {
return ModifiedNodeState.hasProperty(base, properties, name);
}
@Override
public PropertyState getProperty(@NotNull String name) {
return ModifiedNodeState.getProperty(base, properties, name);
}
@Override @NotNull
public Iterable<? extends PropertyState> getProperties() {
return ModifiedNodeState.getProperties(base, properties, true);
}
@Override
public long getChildNodeCount(long max) {
assert base != null;
return ModifiedNodeState.getChildNodeCount(base, nodes, max);
}
@Override
public boolean hasChildNode(@NotNull String name) {
assert base != null;
NodeState child = nodes.get(name);
if (child != null) {
return child.exists();
} else {
return base.hasChildNode(name);
}
}
/**
* Returns a mutable child node with the given name. If the named
* child node has already been modified, i.e. there's an entry for
* it in the {@link #nodes} map, then that child instance is returned
* directly. Otherwise a new mutable child instance is created based
* on the (possibly non-existent) respective child node of the base
* state, added to the {@link #nodes} map and returned.
*/
MutableNodeState getMutableChildNode(String name) {
assert base != null;
MutableNodeState child = nodes.get(name);
if (child == null) {
child = new MutableNodeState(base.getChildNode(name));
nodes.put(name, child);
}
return child;
}
@NotNull
@Override
public NodeState getChildNode(@NotNull String name) {
NodeState child = nodes.get(name);
if (child == null) {
child = base.getChildNode(name);
}
return child;
}
@Override @NotNull
public Iterable<String> getChildNodeNames() {
assert base != null;
return ModifiedNodeState.getChildNodeNames(base, nodes, true);
}
@Override @NotNull
public Iterable<? extends ChildNodeEntry> getChildNodeEntries() {
throw new UnsupportedOperationException();
}
@Override
public boolean compareAgainstBaseState(NodeState base, NodeStateDiff diff) {
throw new UnsupportedOperationException();
}
@Override @NotNull
public NodeBuilder builder() {
throw new UnsupportedOperationException();
}
}