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