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.NormalizedNodes;
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 org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNodeFactory;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.Version;
/**
* Node Modification Node and Tree
private NormalizedNode<?, ?> value;
private ModificationType modType;
+ // Alternative history introduced in WRITE nodes. Instantiated when we touch any child underneath such a node.
+ private TreeNode writtenOriginal;
+
private ModifiedNode(final PathArgument identifier, final Optional<TreeNode> original, final ChildTrackingPolicy childPolicy) {
this.identifier = identifier;
this.original = original;
return Optional.<ModifiedNode> fromNullable(children.get(child));
}
+ private Optional<TreeNode> metadataFromSnapshot(@Nonnull final PathArgument child) {
+ return original.isPresent() ? original.get().getChild(child) : Optional.<TreeNode>absent();
+ }
+
+ private Optional<TreeNode> metadataFromData(@Nonnull final PathArgument child, final Version modVersion) {
+ if (writtenOriginal == null) {
+ // Lazy instantiation, as we do not want do this for all writes. We are using the modification's version
+ // here, as that version is what the SchemaAwareApplyOperation will see when dealing with the resulting
+ // modifications.
+ writtenOriginal = TreeNodeFactory.createTreeNode(value, modVersion);
+ }
+
+ return writtenOriginal.getChild(child);
+ }
+
+ /**
+ * Determine the base tree node we are going to apply the operation to. This is not entirely trivial because
+ * both DELETE and WRITE operations unconditionally detach their descendants from the original snapshot, so we need
+ * to take the current node's operation into account.
+ *
+ * @param child Child we are looking to modify
+ * @param modVersion Version allocated by the calling {@link InMemoryDataTreeModification}
+ * @return Before-image tree node as observed by that child.
+ */
+ private Optional<TreeNode> findOriginalMetadata(@Nonnull final PathArgument child, final Version modVersion) {
+ switch (operation) {
+ case DELETE:
+ // DELETE implies non-presence
+ return Optional.absent();
+ case NONE:
+ case TOUCH:
+ return metadataFromSnapshot(child);
+ case MERGE:
+ // MERGE is half-way between TOUCH and WRITE. If the child exists in data, it behaves as a WRITE, otherwise
+ // it behaves as a TOUCH
+ if (NormalizedNodes.findNode(value, child).isPresent()) {
+ return metadataFromData(child, modVersion);
+ } else {
+ return metadataFromSnapshot(child);
+ }
+ case WRITE:
+ // WRITE implies presence based on written data
+ return metadataFromData(child, modVersion);
+ }
+
+ throw new IllegalStateException("Unhandled node operation " + operation);
+ }
+
/**
*
* Returns child modification if child was modified, creates {@link ModifiedNode}
*
* @param child child identifier, may not be null
* @param childPolicy child tracking policy for the node we are looking for
+ * @param modVersion Version allocated by the calling {@link InMemoryDataTreeModification}
* @return {@link ModifiedNode} for specified child, with {@link #getOriginal()}
* containing child metadata if child was present in original data.
*/
- ModifiedNode modifyChild(@Nonnull final PathArgument child, @Nonnull final ChildTrackingPolicy childPolicy) {
+ ModifiedNode modifyChild(@Nonnull final PathArgument child, @Nonnull final ChildTrackingPolicy childPolicy,
+ @Nonnull final Version modVersion) {
clearSnapshot();
if (operation == LogicalOperation.NONE) {
updateOperationType(LogicalOperation.TOUCH);
return potential;
}
- final Optional<TreeNode> currentMetadata;
- if (original.isPresent()) {
- final TreeNode orig = original.get();
- currentMetadata = orig.getChild(child);
- } else {
- currentMetadata = Optional.absent();
- }
+ final Optional<TreeNode> currentMetadata = findOriginalMetadata(child, modVersion);
+
final ModifiedNode newlyCreated = new ModifiedNode(child, currentMetadata, childPolicy);
children.put(child, newlyCreated);
*/
void seal(final ModificationApplyOperation schema) {
clearSnapshot();
+ writtenOriginal = null;
// A TOUCH node without any children is a no-op
switch (operation) {
private void updateOperationType(final LogicalOperation type) {
operation = type;
modType = null;
+
+ // Make sure we do not reuse previously-instantiated data-derived metadata
+ writtenOriginal = null;
clearSnapshot();
}
applyOperation.verifyStructure(value, false);
}
- private void recursiveMerge(final NormalizedNode<?,?> data) {
+ private void recursiveMerge(final NormalizedNode<?,?> data, final Version version) {
if (data instanceof NormalizedNodeContainer) {
@SuppressWarnings({ "rawtypes", "unchecked" })
final NormalizedNodeContainer<?,?, NormalizedNode<PathArgument, ?>> dataContainer =
}
} else {
// Not present, issue a write
- forChild(childId).write(c);
+ forChild(childId, version).write(c);
}
}
}
for (final NormalizedNode<PathArgument, ?> child : dataContainer.getValue()) {
final PathArgument childId = child.getIdentifier();
- forChild(childId).recursiveMerge(child);
+ forChild(childId, version).recursiveMerge(child, version);
}
}
modification.merge(data);
}
- void merge(final NormalizedNode<?, ?> data) {
+ void merge(final NormalizedNode<?, ?> data, final Version version) {
/*
* A merge operation will end up overwriting parts of the tree, retaining others. We want to
* make sure we do not validate the complete resulting structure, but rather just what was
* FIXME: Should be this moved to recursive merge and run for each node?
*/
applyOperation.verifyStructure(data, false);
- recursiveMerge(data);
+ recursiveMerge(data, version);
}
void delete() {
return new OperationWithModification(operation, modification);
}
- private OperationWithModification forChild(final PathArgument childId) {
+ private OperationWithModification forChild(final PathArgument childId, final Version version) {
final Optional<ModificationApplyOperation> maybeChildOp = applyOperation.getChild(childId);
- Preconditions.checkArgument(maybeChildOp.isPresent(), "Attempted to apply operation to non-existent child %s", childId);
+ Preconditions.checkArgument(maybeChildOp.isPresent(),
+ "Attempted to apply operation to non-existent child %s", childId);
final ModificationApplyOperation childOp = maybeChildOp.get();
- final ModifiedNode childMod = modification.modifyChild(childId, childOp.getChildPolicy());
+ final ModifiedNode childMod = modification.modifyChild(childId, childOp.getChildPolicy(), version);
return from(childOp, childMod);
}