X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=yang%2Fyang-data-impl%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fyangtools%2Fyang%2Fdata%2Fimpl%2Fschema%2Ftree%2FModifiedNode.java;h=a2739c5f8ff110703fc2f96f7001480b830d976a;hb=1f89c6655dc4769ee59343c356fc8ca131918c6b;hp=56353350af803bcf1553bc35476d93e63e7f38a4;hpb=44fcb7d28655165df53709a55b098d3dae39883f;p=yangtools.git diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ModifiedNode.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ModifiedNode.java index 56353350af..a2739c5f8f 100644 --- a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ModifiedNode.java +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ModifiedNode.java @@ -8,18 +8,22 @@ package org.opendaylight.yangtools.yang.data.impl.schema.tree; import com.google.common.base.Optional; +import com.google.common.base.Preconditions; import com.google.common.base.Predicate; -import org.opendaylight.yangtools.concepts.Identifiable; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.concurrent.NotThreadSafe; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType; import org.opendaylight.yangtools.yang.data.api.schema.tree.StoreTreeNode; import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode; -import javax.annotation.concurrent.GuardedBy; -import java.util.LinkedHashMap; -import java.util.Map; - /** * Node Modification Node and Tree * @@ -27,43 +31,59 @@ import java.util.Map; * to the data store tree. * * This tree is lazily created and populated via {@link #modifyChild(PathArgument)} - * and {@link StoreMetadataNode} which represents original state {@link #getOriginal()}. + * and {@link TreeNode} which represents original state as tracked by {@link #getOriginal()}. */ -final class ModifiedNode implements StoreTreeNode, Identifiable, NodeModification { - - public static final Predicate IS_TERMINAL_PREDICATE = new Predicate() { +@NotThreadSafe +final class ModifiedNode extends NodeModification implements StoreTreeNode { + static final Predicate IS_TERMINAL_PREDICATE = new Predicate() { @Override - public boolean apply(final ModifiedNode input) { - switch (input.getType()) { + public boolean apply(final @Nonnull ModifiedNode input) { + Preconditions.checkNotNull(input); + switch (input.getOperation()) { case DELETE: case MERGE: case WRITE: return true; - case SUBTREE_MODIFIED: - case UNMODIFIED: + case TOUCH: + case NONE: return false; } - throw new IllegalArgumentException(String.format("Unhandled modification type %s", input.getType())); + throw new IllegalArgumentException(String.format("Unhandled modification type %s", input.getOperation())); } }; - private final Map children = new LinkedHashMap<>(); + private final Map children; private final Optional original; private final PathArgument identifier; - private ModificationType modificationType = ModificationType.UNMODIFIED; + private LogicalOperation operation = LogicalOperation.NONE; private Optional snapshotCache; private NormalizedNode value; + private ModificationType modType; - private ModifiedNode(final PathArgument identifier, final Optional original) { + private ModifiedNode(final PathArgument identifier, final Optional original, final ChildTrackingPolicy childPolicy) { this.identifier = identifier; this.original = original; + + switch (childPolicy) { + case NONE: + children = Collections.emptyMap(); + break; + case ORDERED: + children = new LinkedHashMap<>(); + break; + case UNORDERED: + children = new HashMap<>(); + break; + default: + throw new IllegalArgumentException("Unsupported child tracking policy " + childPolicy); + } } /** + * Return the value which was written to this node. * - * - * @return + * @return Currently-written value */ public NormalizedNode getWrittenValue() { return value; @@ -74,24 +94,14 @@ final class ModifiedNode implements StoreTreeNode, Identifiable getOriginal() { + Optional getOriginal() { return original; } - /** - * Returns modification type - * - * @return modification type - */ @Override - public ModificationType getType() { - return modificationType; + LogicalOperation getOperation() { + return operation; } /** @@ -109,20 +119,21 @@ final class ModifiedNode implements StoreTreeNode, Identifiable, Identifiable getChildren() { + Collection getChildren() { return children.values(); } /** - * * Records a delete for associated node. - * */ - public void delete() { + void delete() { + final LogicalOperation newType; + + switch (operation) { + case DELETE: + case NONE: + // We need to record this delete. + newType = LogicalOperation.DELETE; + break; + case MERGE: + case TOUCH: + case WRITE: + /* + * We are canceling a previous modification. This is a bit tricky, + * as the original write may have just introduced the data, or it + * may have modified it. + * + * As documented in BUG-2470, a delete of data introduced in this + * transaction needs to be turned into a no-op. + */ + newType = original.isPresent() ? LogicalOperation.DELETE : LogicalOperation.NONE; + break; + default: + throw new IllegalStateException("Unhandled deletion of node with " + operation); + } + clearSnapshot(); - updateModificationType(ModificationType.DELETE); children.clear(); this.value = null; + updateOperationType(newType); } /** - * * Records a write for associated node. * * @param value */ - public void write(final NormalizedNode value) { + void write(final NormalizedNode value) { clearSnapshot(); - updateModificationType(ModificationType.WRITE); + updateOperationType(LogicalOperation.WRITE); children.clear(); this.value = value; } - public void merge(final NormalizedNode data) { + void merge(final NormalizedNode value) { clearSnapshot(); - updateModificationType(ModificationType.MERGE); - // FIXME: Probably merge with previous value. - this.value = data; + updateOperationType(LogicalOperation.MERGE); + + /* + * Blind overwrite of any previous data is okay, no matter whether the node + * is simple or complex type. + * + * If this is a simple or complex type with unkeyed children, this merge will + * be turned into a write operation, overwriting whatever was there before. + * + * If this is a container with keyed children, there are two possibilities: + * - if it existed before, this value will never be consulted and the children + * will get explicitly merged onto the original data. + * - if it did not exist before, this value will be used as a seed write and + * children will be merged into it. + * In either case we rely on OperationWithModification to manipulate the children + * before calling this method, so unlike a write we do not want to clear them. + */ + this.value = value; } + /** + * Seal the modification node and prune any children which has not been + * modified. + */ void seal() { clearSnapshot(); - for (ModifiedNode child : children.values()) { + + // Walk all child nodes and remove any children which have not + // been modified. + final Iterator it = children.values().iterator(); + while (it.hasNext()) { + final ModifiedNode child = it.next(); child.seal(); + + if (child.operation == LogicalOperation.NONE) { + it.remove(); + } + } + + // A TOUCH node without any children is a no-op + if (operation == LogicalOperation.TOUCH && children.isEmpty()) { + updateOperationType(LogicalOperation.NONE); } } @@ -196,28 +261,68 @@ final class ModifiedNode implements StoreTreeNode, Identifiable storeSnapshot(final Optional snapshot) { - snapshotCache = snapshot; - return snapshot; + Optional getSnapshot() { + return snapshotCache; } - public Optional> getSnapshotCache() { - return Optional.fromNullable(snapshotCache); + Optional setSnapshot(final Optional snapshot) { + snapshotCache = Preconditions.checkNotNull(snapshot); + return snapshot; } - @GuardedBy("this") - private void updateModificationType(final ModificationType type) { - modificationType = type; + private void updateOperationType(final LogicalOperation type) { + operation = type; + modType = null; clearSnapshot(); } @Override public String toString() { return "NodeModification [identifier=" + identifier + ", modificationType=" - + modificationType + ", childModification=" + children + "]"; + + operation + ", childModification=" + children + "]"; + } + + void resolveModificationType(@Nonnull final ModificationType type) { + modType = type; + } + + /** + * Return the physical modification done to data. May return null if the + * operation has not been applied to the underlying tree. This is different + * from the logical operation in that it can actually be a no-op if the + * operation has no side-effects (like an empty merge on a container). + * + * @return Modification type. + */ + ModificationType getModificationType() { + return modType; + } + + /** + * Create a node which will reflect the state of this node, except it will behave as newly-written + * value. This is useful only for merge validation. + * + * @param value Value associated with the node + * @return An isolated node. This node should never reach a datatree. + */ + ModifiedNode asNewlyWritten(final NormalizedNode value) { + /* + * We are instantiating an "equivalent" of this node. Currently the only callsite does not care + * about the actual iteration order, so we do not have to specify the same tracking policy as + * we were instantiated with. Since this is the only time we need to know that policy (it affects + * only things in constructor), we do not want to retain it (saves some memory on per-instance + * basis). + * + * We could reconstruct it using two instanceof checks (to undo what the constructor has done), + * which would give perfect results. The memory saving would be at most 32 bytes of a short-lived + * object, so let's not bother with that. + */ + final ModifiedNode ret = new ModifiedNode(getIdentifier(), Optional.absent(), ChildTrackingPolicy.UNORDERED); + ret.write(value); + return ret; } - public static ModifiedNode createUnmodified(final TreeNode metadataTree) { - return new ModifiedNode(metadataTree.getIdentifier(), Optional.of(metadataTree)); + public static ModifiedNode createUnmodified(final TreeNode metadataTree, final ChildTrackingPolicy childPolicy) { + return new ModifiedNode(metadataTree.getIdentifier(), Optional.of(metadataTree), childPolicy); } }