Merge "Make NodeModification return a Collection"
[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.HashMap;
15 import java.util.Iterator;
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(final @Nonnull ModifiedNode input) {
40             Preconditions.checkNotNull(input);
41             switch (input.getType()) {
42             case DELETE:
43             case MERGE:
44             case WRITE:
45                 return true;
46             case SUBTREE_MODIFIED:
47             case UNMODIFIED:
48                 return false;
49             }
50
51             throw new IllegalArgumentException(String.format("Unhandled modification type %s", input.getType()));
52         }
53     };
54
55     private final Map<PathArgument, ModifiedNode> children;
56     private final Optional<TreeNode> original;
57     private final PathArgument identifier;
58     private ModificationType modificationType = ModificationType.UNMODIFIED;
59     private Optional<TreeNode> snapshotCache;
60     private NormalizedNode<?, ?> value;
61
62     private ModifiedNode(final PathArgument identifier, final Optional<TreeNode> original, final boolean isOrdered) {
63         this.identifier = identifier;
64         this.original = original;
65
66         if (isOrdered) {
67             children = new LinkedHashMap<>();
68         } else {
69             children = new HashMap<>();
70         }
71     }
72
73     /**
74      * Return the value which was written to this node.
75      *
76      * @return Currently-written value
77      */
78     public NormalizedNode<?, ?> getWrittenValue() {
79         return value;
80     }
81
82     @Override
83     public PathArgument getIdentifier() {
84         return identifier;
85     }
86
87     /**
88      *
89      * Returns original store metadata
90      * @return original store metadata
91      */
92     @Override
93     Optional<TreeNode> getOriginal() {
94         return original;
95     }
96
97     /**
98      * Returns modification type
99      *
100      * @return modification type
101      */
102     @Override
103     ModificationType getType() {
104         return modificationType;
105     }
106
107     /**
108      *
109      * Returns child modification if child was modified
110      *
111      * @return Child modification if direct child or it's subtree
112      *  was modified.
113      *
114      */
115     @Override
116     public Optional<ModifiedNode> getChild(final PathArgument child) {
117         return Optional.<ModifiedNode> fromNullable(children.get(child));
118     }
119
120     /**
121      *
122      * Returns child modification if child was modified, creates {@link ModifiedNode}
123      * for child otherwise.
124      *
125      * If this node's {@link ModificationType} is {@link ModificationType#UNMODIFIED}
126      * changes modification type to {@link ModificationType#SUBTREE_MODIFIED}
127      *
128      * @param child
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(final PathArgument child, final boolean isOrdered) {
133         clearSnapshot();
134         if (modificationType == ModificationType.UNMODIFIED) {
135             updateModificationType(ModificationType.SUBTREE_MODIFIED);
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, isOrdered);
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 ModificationType newType;
170
171         switch (modificationType) {
172         case DELETE:
173         case UNMODIFIED:
174             // We need to record this delete.
175             newType = ModificationType.DELETE;
176             break;
177         case MERGE:
178         case SUBTREE_MODIFIED:
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() ? ModificationType.DELETE : ModificationType.UNMODIFIED;
189             break;
190         default:
191             throw new IllegalStateException("Unhandled deletion of node with " + modificationType);
192         }
193
194         clearSnapshot();
195         children.clear();
196         this.value = null;
197         updateModificationType(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         updateModificationType(ModificationType.WRITE);
208         children.clear();
209         this.value = value;
210     }
211
212     void merge(final NormalizedNode<?, ?> value) {
213         clearSnapshot();
214         updateModificationType(ModificationType.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 and prune any children which has not been
236      * modified.
237      */
238     void seal() {
239         clearSnapshot();
240
241         // Walk all child nodes and remove any children which have not
242         // been modified.
243         final Iterator<ModifiedNode> it = children.values().iterator();
244         while (it.hasNext()) {
245             final ModifiedNode child = it.next();
246             child.seal();
247
248             if (child.modificationType == ModificationType.UNMODIFIED) {
249                 it.remove();
250             }
251         }
252
253         // A SUBTREE_MODIFIED node without any children is a no-op
254         if (modificationType == ModificationType.SUBTREE_MODIFIED && children.isEmpty()) {
255             updateModificationType(ModificationType.UNMODIFIED);
256         }
257     }
258
259     private void clearSnapshot() {
260         snapshotCache = null;
261     }
262
263     Optional<TreeNode> getSnapshot() {
264         return snapshotCache;
265     }
266
267     Optional<TreeNode> setSnapshot(final Optional<TreeNode> snapshot) {
268         snapshotCache = Preconditions.checkNotNull(snapshot);
269         return snapshot;
270     }
271
272     private void updateModificationType(final ModificationType type) {
273         modificationType = type;
274         clearSnapshot();
275     }
276
277     @Override
278     public String toString() {
279         return "NodeModification [identifier=" + identifier + ", modificationType="
280                 + modificationType + ", childModification=" + children + "]";
281     }
282
283     /**
284      * Create a node which will reflect the state of this node, except it will behave as newly-written
285      * value. This is useful only for merge validation.
286      *
287      * @param value Value associated with the node
288      * @return An isolated node. This node should never reach a datatree.
289      */
290     ModifiedNode asNewlyWritten(final NormalizedNode<?, ?> value) {
291         final ModifiedNode ret = new ModifiedNode(getIdentifier(), Optional.<TreeNode>absent(), false);
292         ret.write(value);
293         return ret;
294     }
295
296     public static ModifiedNode createUnmodified(final TreeNode metadataTree, final boolean isOrdered) {
297         return new ModifiedNode(metadataTree.getIdentifier(), Optional.of(metadataTree), isOrdered);
298     }
299 }