Do not store Optional in ModifiedNode
[yangtools.git] / data / yang-data-tree-ri / src / main / java / org / opendaylight / yangtools / yang / data / tree / impl / ModifiedNode.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.yangtools.yang.data.tree.impl;
9
10 import static com.google.common.base.Verify.verifyNotNull;
11 import static java.util.Objects.requireNonNull;
12
13 import com.google.common.base.MoreObjects;
14 import com.google.common.base.MoreObjects.ToStringHelper;
15 import java.util.Collection;
16 import java.util.Map;
17 import java.util.Optional;
18 import org.eclipse.jdt.annotation.NonNull;
19 import org.eclipse.jdt.annotation.Nullable;
20 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
21 import org.opendaylight.yangtools.yang.data.api.schema.DistinctNodeContainer;
22 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
23 import org.opendaylight.yangtools.yang.data.api.schema.tree.StoreTreeNode;
24 import org.opendaylight.yangtools.yang.data.tree.api.ModificationType;
25 import org.opendaylight.yangtools.yang.data.tree.impl.node.TreeNode;
26 import org.opendaylight.yangtools.yang.data.tree.impl.node.Version;
27
28 /**
29  * Node Modification Node and Tree.
30  *
31  * <p>
32  * Tree which structurally resembles data tree and captures client modifications to the data store tree. This tree is
33  * lazily created and populated via {@link #modifyChild(PathArgument, ModificationApplyOperation, Version)} and
34  * {@link TreeNode} which represents original state as tracked by {@link #getOriginal()}.
35  *
36  * <p>
37  * The contract is that the state information exposed here preserves the temporal ordering of whatever modifications
38  * were executed. A child's effects pertain to data node as modified by its ancestors. This means that in order to
39  * reconstruct the effective data node presentation, it is sufficient to perform a depth-first pre-order traversal of
40  * the tree.
41  */
42 final class ModifiedNode extends NodeModification implements StoreTreeNode<ModifiedNode> {
43     private final Map<PathArgument, ModifiedNode> children;
44     private final @Nullable TreeNode original;
45     private final @NonNull PathArgument identifier;
46
47     private LogicalOperation operation = LogicalOperation.NONE;
48     private Optional<TreeNode> snapshotCache;
49     private NormalizedNode value;
50     private ModificationType modType;
51
52     // Alternative history introduced in WRITE nodes. Instantiated when we touch any child underneath such a node.
53     private TreeNode writtenOriginal;
54
55     // Internal cache for TreeNodes created as part of validation
56     private ModificationApplyOperation validatedOp;
57     private Optional<? extends TreeNode> validatedCurrent;
58     private ValidatedTreeNode validatedNode;
59
60     private ModifiedNode(final PathArgument identifier, final @Nullable TreeNode original,
61             final ChildTrackingPolicy childPolicy) {
62         this.identifier = requireNonNull(identifier);
63         this.original = original;
64         children = childPolicy.createMap();
65     }
66
67     @Override
68     public PathArgument getIdentifier() {
69         return identifier;
70     }
71
72     @Override
73     LogicalOperation getOperation() {
74         return operation;
75     }
76
77     @Override
78     TreeNode original() {
79         return original;
80     }
81
82     /**
83      * Return the value which was written to this node. The returned object is only valid for
84      * {@link LogicalOperation#MERGE} and {@link LogicalOperation#WRITE}.
85      * operations. It should only be consulted when this modification is going to end up being
86      * {@link ModificationType#WRITE}.
87      *
88      * @return Currently-written value
89      */
90     @NonNull NormalizedNode getWrittenValue() {
91         return verifyNotNull(value);
92     }
93
94     /**
95      * Returns child modification if child was modified.
96      *
97      * @return Child modification if direct child or it's subtree was modified.
98      */
99     @Override
100     public ModifiedNode childByArg(final PathArgument arg) {
101         return children.get(arg);
102     }
103
104     private @Nullable TreeNode metadataFromSnapshot(final @NonNull PathArgument child) {
105         final var local = original;
106         return local != null ? local.childByArg(child) : null;
107     }
108
109     private @Nullable TreeNode metadataFromData(final @NonNull PathArgument child, final Version modVersion) {
110         if (writtenOriginal == null) {
111             // Lazy instantiation, as we do not want do this for all writes. We are using the modification's version
112             // here, as that version is what the SchemaAwareApplyOperation will see when dealing with the resulting
113             // modifications.
114             writtenOriginal = TreeNode.of(value, modVersion);
115         }
116
117         return writtenOriginal.childByArg(child);
118     }
119
120     /**
121      * Determine the base tree node we are going to apply the operation to. This is not entirely trivial because
122      * both DELETE and WRITE operations unconditionally detach their descendants from the original snapshot, so we need
123      * to take the current node's operation into account.
124      *
125      * @param child Child we are looking to modify
126      * @param modVersion Version allocated by the calling {@link InMemoryDataTreeModification}
127      * @return Before-image tree node as observed by that child.
128      */
129     private @Nullable TreeNode originalMetadata(final @NonNull PathArgument child, final Version modVersion) {
130         return switch (operation) {
131             case DELETE ->
132                 // DELETE implies non-presence
133                 null;
134             case NONE, TOUCH, MERGE -> metadataFromSnapshot(child);
135             case WRITE ->
136                 // WRITE implies presence based on written data
137                 metadataFromData(child, modVersion);
138         };
139     }
140
141     /**
142      * Returns child modification if child was modified, creates {@link ModifiedNode}
143      * for child otherwise. If this node's {@link ModificationType} is {@link ModificationType#UNMODIFIED}
144      * changes modification type to {@link ModificationType#SUBTREE_MODIFIED}.
145      *
146      * @param child child identifier, may not be null
147      * @param childOper Child operation
148      * @param modVersion Version allocated by the calling {@link InMemoryDataTreeModification}
149      * @return {@link ModifiedNode} for specified child, with {@link #getOriginal()}
150      *         containing child metadata if child was present in original data.
151      */
152     ModifiedNode modifyChild(final @NonNull PathArgument child, final @NonNull ModificationApplyOperation childOper,
153             final @NonNull Version modVersion) {
154         clearSnapshot();
155         if (operation == LogicalOperation.NONE) {
156             updateOperationType(LogicalOperation.TOUCH);
157         }
158         final var potential = children.get(child);
159         if (potential != null) {
160             return potential;
161         }
162
163         final var newlyCreated = new ModifiedNode(child, originalMetadata(child, modVersion),
164             childOper.getChildPolicy());
165         if (operation == LogicalOperation.MERGE && value != null) {
166             /*
167              * We are attempting to modify a previously-unmodified part of a MERGE node. If the
168              * value contains this component, we need to materialize it as a MERGE modification.
169              */
170             @SuppressWarnings({ "rawtypes", "unchecked" })
171             final var childData = ((DistinctNodeContainer) value).childByArg(child);
172             if (childData != null) {
173                 childOper.mergeIntoModifiedNode(newlyCreated, childData, modVersion);
174             }
175         }
176
177         children.put(child, newlyCreated);
178         return newlyCreated;
179     }
180
181     /**
182      * Returns all recorded direct child modifications.
183      *
184      * @return all recorded direct child modifications
185      */
186     @Override
187     Collection<ModifiedNode> getChildren() {
188         return children.values();
189     }
190
191     @Override
192     boolean isEmpty() {
193         return children.isEmpty();
194     }
195
196     /**
197      * Records a delete for associated node.
198      */
199     void delete() {
200         final LogicalOperation newType = switch (operation) {
201             case DELETE, NONE ->
202                 // We need to record this delete.
203                 LogicalOperation.DELETE;
204             case MERGE ->
205                 // In case of merge - delete needs to be recored and must not to be changed into NONE, because lazy
206                 // expansion of parent MERGE node would reintroduce it again.
207                 LogicalOperation.DELETE;
208             case TOUCH, WRITE ->
209                 // We are canceling a previous modification. This is a bit tricky, as the original write may have just
210                 // introduced the data, or it may have modified it.
211                 //
212                 // As documented in BUG-2470, a delete of data introduced in this transaction needs to be turned into
213                 // a no-op.
214                 original != null ? LogicalOperation.DELETE : LogicalOperation.NONE;
215         };
216
217         clearSnapshot();
218         children.clear();
219         value = null;
220         updateOperationType(newType);
221     }
222
223     /**
224      * Records a write for associated node.
225      *
226      * @param newValue new value
227      */
228     void write(final NormalizedNode newValue) {
229         updateValue(LogicalOperation.WRITE, newValue);
230         children.clear();
231     }
232
233     /**
234      * Seal the modification node and prune any children which has not been modified.
235      *
236      * @param schema associated apply operation
237      * @param version target version
238      */
239     void seal(final ModificationApplyOperation schema, final Version version) {
240         clearSnapshot();
241         writtenOriginal = null;
242
243         switch (operation) {
244             case TOUCH -> {
245                 // A TOUCH node without any children is a no-op
246                 if (children.isEmpty()) {
247                     updateOperationType(LogicalOperation.NONE);
248                 }
249             }
250             case WRITE -> {
251                 // A WRITE can collapse all of its children
252                 if (!children.isEmpty()) {
253                     value = schema.apply(this, getOriginal(), version).map(TreeNode::getData).orElse(null);
254                     children.clear();
255                 }
256
257                 if (value == null) {
258                     // The write has ended up being empty, such as a write of an empty list.
259                     updateOperationType(LogicalOperation.DELETE);
260                 } else {
261                     schema.fullVerifyStructure(value);
262                 }
263             }
264             default -> {
265                 // No-op
266             }
267         }
268     }
269
270     private void clearSnapshot() {
271         snapshotCache = null;
272     }
273
274     Optional<TreeNode> getSnapshot() {
275         return snapshotCache;
276     }
277
278     Optional<TreeNode> setSnapshot(final Optional<TreeNode> snapshot) {
279         snapshotCache = requireNonNull(snapshot);
280         return snapshot;
281     }
282
283     void updateOperationType(final LogicalOperation type) {
284         operation = type;
285         modType = null;
286
287         // Make sure we do not reuse previously-instantiated data-derived metadata
288         writtenOriginal = null;
289         clearSnapshot();
290     }
291
292     @Override
293     public String toString() {
294         final ToStringHelper helper = MoreObjects.toStringHelper(this).omitNullValues()
295                 .add("identifier", identifier).add("operation", operation).add("modificationType", modType);
296         if (!children.isEmpty()) {
297             helper.add("childModification", children);
298         }
299         return helper.toString();
300     }
301
302     void resolveModificationType(final @NonNull ModificationType type) {
303         modType = type;
304     }
305
306     /**
307      * Update this node's value and operation type without disturbing any of its child modifications.
308      *
309      * @param type New operation type
310      * @param newValue New node value
311      */
312     void updateValue(final LogicalOperation type, final NormalizedNode newValue) {
313         value = requireNonNull(newValue);
314         updateOperationType(type);
315     }
316
317     /**
318      * Return the physical modification done to data. May return null if the
319      * operation has not been applied to the underlying tree. This is different
320      * from the logical operation in that it can actually be a no-op if the
321      * operation has no side-effects (like an empty merge on a container).
322      *
323      * @return Modification type.
324      */
325     ModificationType getModificationType() {
326         return modType;
327     }
328
329     public static ModifiedNode createUnmodified(final TreeNode metadataTree, final ChildTrackingPolicy childPolicy) {
330         return new ModifiedNode(metadataTree.getIdentifier(), requireNonNull(metadataTree), childPolicy);
331     }
332
333     void setValidatedNode(final ModificationApplyOperation op, final Optional<? extends TreeNode> current,
334             final Optional<? extends TreeNode> node) {
335         validatedOp = requireNonNull(op);
336         validatedCurrent = requireNonNull(current);
337         validatedNode = new ValidatedTreeNode(node);
338     }
339
340     /**
341      * Acquire pre-validated node assuming a previous operation and node. This is a counterpart to
342      * {@link #setValidatedNode(ModificationApplyOperation, Optional, Optional)}.
343      *
344      * @param op Currently-executing operation
345      * @param current Currently-used tree node
346      * @return {@code null} if there is a mismatch with previously-validated node (if present) or the result of previous
347      *         validation.
348      */
349     @Nullable ValidatedTreeNode validatedNode(final ModificationApplyOperation op,
350             final Optional<? extends TreeNode> current) {
351         return op.equals(validatedOp) && current.equals(validatedCurrent) ? validatedNode : null;
352     }
353 }