Fix child ordering assumptions
[yangtools.git] / yang / yang-data-impl / src / main / java / org / opendaylight / yangtools / yang / data / impl / schema / tree / 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.impl.schema.tree;
9
10 import com.google.common.base.Optional;
11 import com.google.common.base.Preconditions;
12 import com.google.common.base.Predicate;
13 import com.google.common.base.Verify;
14 import java.util.Collection;
15 import java.util.Collections;
16 import java.util.HashMap;
17 import java.util.Iterator;
18 import java.util.LinkedHashMap;
19 import java.util.Map;
20 import javax.annotation.Nonnull;
21 import javax.annotation.concurrent.NotThreadSafe;
22 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
23 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
24 import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
25 import org.opendaylight.yangtools.yang.data.api.schema.tree.StoreTreeNode;
26 import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode;
27
28 /**
29  * Node Modification Node and Tree
30  *
31  * Tree which structurally resembles data tree and captures client modifications
32  * to the data store tree.
33  *
34  * This tree is lazily created and populated via {@link #modifyChild(PathArgument)}
35  * and {@link TreeNode} which represents original state as tracked by {@link #getOriginal()}.
36  */
37 @NotThreadSafe
38 final class ModifiedNode extends NodeModification implements StoreTreeNode<ModifiedNode> {
39     static final Predicate<ModifiedNode> IS_TERMINAL_PREDICATE = new Predicate<ModifiedNode>() {
40         @Override
41         public boolean apply(final @Nonnull ModifiedNode input) {
42             Preconditions.checkNotNull(input);
43             switch (input.getOperation()) {
44             case DELETE:
45             case MERGE:
46             case WRITE:
47                 return true;
48             case TOUCH:
49             case NONE:
50                 return false;
51             }
52
53             throw new IllegalArgumentException(String.format("Unhandled modification type %s", input.getOperation()));
54         }
55     };
56
57     private final Map<PathArgument, ModifiedNode> children;
58     private final Optional<TreeNode> original;
59     private final PathArgument identifier;
60     private LogicalOperation operation = LogicalOperation.NONE;
61     private Optional<TreeNode> snapshotCache;
62     private NormalizedNode<?, ?> value;
63     private ModificationType modType;
64
65     private ModifiedNode(final PathArgument identifier, final Optional<TreeNode> original, final ChildTrackingPolicy childPolicy) {
66         this.identifier = identifier;
67         this.original = original;
68
69         switch (childPolicy) {
70         case NONE:
71             children = Collections.emptyMap();
72             break;
73         case ORDERED:
74             children = new LinkedHashMap<>();
75             break;
76         case UNORDERED:
77             children = new HashMap<>();
78             break;
79         default:
80             throw new IllegalArgumentException("Unsupported child tracking policy " + childPolicy);
81         }
82     }
83
84     /**
85      * Return the value which was written to this node.
86      *
87      * @return Currently-written value
88      */
89     public NormalizedNode<?, ?> getWrittenValue() {
90         return value;
91     }
92
93     @Override
94     public PathArgument getIdentifier() {
95         return identifier;
96     }
97
98     @Override
99     Optional<TreeNode> getOriginal() {
100         return original;
101     }
102
103     @Override
104     LogicalOperation getOperation() {
105         return operation;
106     }
107
108     /**
109      *
110      * Returns child modification if child was modified
111      *
112      * @return Child modification if direct child or it's subtree
113      *  was modified.
114      *
115      */
116     @Override
117     public Optional<ModifiedNode> getChild(final PathArgument child) {
118         return Optional.<ModifiedNode> fromNullable(children.get(child));
119     }
120
121     /**
122      *
123      * Returns child modification if child was modified, creates {@link ModifiedNode}
124      * for child otherwise.
125      *
126      * If this node's {@link ModificationType} is {@link ModificationType#UNMODIFIED}
127      * changes modification type to {@link ModificationType#SUBTREE_MODIFIED}
128      *
129      * @param child child identifier, may not be null
130      * @param childPolicy child tracking policy for the node we are looking for
131      * @return {@link ModifiedNode} for specified child, with {@link #getOriginal()}
132      *         containing child metadata if child was present in original data.
133      */
134     ModifiedNode modifyChild(@Nonnull final PathArgument child, @Nonnull final ChildTrackingPolicy childPolicy) {
135         clearSnapshot();
136         if (operation == LogicalOperation.NONE) {
137             updateOperationType(LogicalOperation.TOUCH);
138         }
139         final ModifiedNode potential = children.get(child);
140         if (potential != null) {
141             return potential;
142         }
143
144         final Optional<TreeNode> currentMetadata;
145         if (original.isPresent()) {
146             final TreeNode orig = original.get();
147             currentMetadata = orig.getChild(child);
148         } else {
149             currentMetadata = Optional.absent();
150         }
151
152         final ModifiedNode newlyCreated = new ModifiedNode(child, currentMetadata, childPolicy);
153         children.put(child, newlyCreated);
154         return newlyCreated;
155     }
156
157     /**
158      * Returns all recorded direct child modification
159      *
160      * @return all recorded direct child modifications
161      */
162     @Override
163     Collection<ModifiedNode> getChildren() {
164         return children.values();
165     }
166
167     /**
168      * Records a delete for associated node.
169      */
170     void delete() {
171         final LogicalOperation newType;
172
173         switch (operation) {
174         case DELETE:
175         case NONE:
176             // We need to record this delete.
177             newType = LogicalOperation.DELETE;
178             break;
179         case MERGE:
180         case TOUCH:
181         case WRITE:
182             /*
183              * We are canceling a previous modification. This is a bit tricky,
184              * as the original write may have just introduced the data, or it
185              * may have modified it.
186              *
187              * As documented in BUG-2470, a delete of data introduced in this
188              * transaction needs to be turned into a no-op.
189              */
190             newType = original.isPresent() ? LogicalOperation.DELETE : LogicalOperation.NONE;
191             break;
192         default:
193             throw new IllegalStateException("Unhandled deletion of node with " + operation);
194         }
195
196         clearSnapshot();
197         children.clear();
198         this.value = null;
199         updateOperationType(newType);
200     }
201
202     /**
203      * Records a write for associated node.
204      *
205      * @param value
206      */
207     void write(final NormalizedNode<?, ?> value) {
208         clearSnapshot();
209         updateOperationType(LogicalOperation.WRITE);
210         children.clear();
211         this.value = value;
212     }
213
214     void merge(final NormalizedNode<?, ?> value) {
215         clearSnapshot();
216         updateOperationType(LogicalOperation.MERGE);
217
218         /*
219          * Blind overwrite of any previous data is okay, no matter whether the node
220          * is simple or complex type.
221          *
222          * If this is a simple or complex type with unkeyed children, this merge will
223          * be turned into a write operation, overwriting whatever was there before.
224          *
225          * If this is a container with keyed children, there are two possibilities:
226          * - if it existed before, this value will never be consulted and the children
227          *   will get explicitly merged onto the original data.
228          * - if it did not exist before, this value will be used as a seed write and
229          *   children will be merged into it.
230          * In either case we rely on OperationWithModification to manipulate the children
231          * before calling this method, so unlike a write we do not want to clear them.
232          */
233         this.value = value;
234     }
235
236     /**
237      * Seal the modification node and prune any children which has not been
238      * modified.
239      */
240     void seal() {
241         clearSnapshot();
242
243         // Walk all child nodes and remove any children which have not
244         // been modified.
245         final Iterator<ModifiedNode> it = children.values().iterator();
246         while (it.hasNext()) {
247             final ModifiedNode child = it.next();
248             child.seal();
249
250             if (child.operation == LogicalOperation.NONE) {
251                 it.remove();
252             }
253         }
254
255         // A TOUCH node without any children is a no-op
256         if (operation == LogicalOperation.TOUCH && children.isEmpty()) {
257             updateOperationType(LogicalOperation.NONE);
258         }
259     }
260
261     private void clearSnapshot() {
262         snapshotCache = null;
263     }
264
265     Optional<TreeNode> getSnapshot() {
266         return snapshotCache;
267     }
268
269     Optional<TreeNode> setSnapshot(final Optional<TreeNode> snapshot) {
270         snapshotCache = Preconditions.checkNotNull(snapshot);
271         return snapshot;
272     }
273
274     private void updateOperationType(final LogicalOperation type) {
275         operation = type;
276         modType = null;
277         clearSnapshot();
278     }
279
280     @Override
281     public String toString() {
282         return "NodeModification [identifier=" + identifier + ", modificationType="
283                 + operation + ", childModification=" + children + "]";
284     }
285
286     void resolveModificationType(@Nonnull final ModificationType type) {
287         modType = type;
288     }
289
290     @Nonnull ModificationType modificationType() {
291         return Verify.verifyNotNull(modType, "Node %s does not have resolved modification type", this);
292     }
293
294     /**
295      * Create a node which will reflect the state of this node, except it will behave as newly-written
296      * value. This is useful only for merge validation.
297      *
298      * @param value Value associated with the node
299      * @return An isolated node. This node should never reach a datatree.
300      */
301     ModifiedNode asNewlyWritten(final NormalizedNode<?, ?> value) {
302         /*
303          * We are instantiating an "equivalent" of this node. Currently the only callsite does not care
304          * about the actual iteration order, so we do not have to specify the same tracking policy as
305          * we were instantiated with. Since this is the only time we need to know that policy (it affects
306          * only things in constructor), we do not want to retain it (saves some memory on per-instance
307          * basis).
308          *
309          * We could reconstruct it using two instanceof checks (to undo what the constructor has done),
310          * which would give perfect results. The memory saving would be at most 32 bytes of a short-lived
311          * object, so let's not bother with that.
312          */
313         final ModifiedNode ret = new ModifiedNode(getIdentifier(), Optional.<TreeNode>absent(), ChildTrackingPolicy.UNORDERED);
314         ret.write(value);
315         return ret;
316     }
317
318     public static ModifiedNode createUnmodified(final TreeNode metadataTree, final ChildTrackingPolicy childPolicy) {
319         return new ModifiedNode(metadataTree.getIdentifier(), Optional.of(metadataTree), childPolicy);
320     }
321 }