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