Merge "Bug 2404: RPC and Notification support for Binding Data Codec"
[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.HashMap;
14 import java.util.Iterator;
15 import java.util.LinkedHashMap;
16 import java.util.Map;
17 import javax.annotation.Nonnull;
18 import javax.annotation.concurrent.NotThreadSafe;
19 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
20 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
21 import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
22 import org.opendaylight.yangtools.yang.data.api.schema.tree.StoreTreeNode;
23 import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode;
24
25 /**
26  * Node Modification Node and Tree
27  *
28  * Tree which structurally resembles data tree and captures client modifications
29  * to the data store tree.
30  *
31  * This tree is lazily created and populated via {@link #modifyChild(PathArgument)}
32  * and {@link TreeNode} which represents original state as tracked by {@link #getOriginal()}.
33  */
34 @NotThreadSafe
35 final class ModifiedNode extends NodeModification implements StoreTreeNode<ModifiedNode> {
36     static final Predicate<ModifiedNode> IS_TERMINAL_PREDICATE = new Predicate<ModifiedNode>() {
37         @Override
38         public boolean apply(final @Nonnull ModifiedNode input) {
39             Preconditions.checkNotNull(input);
40             switch (input.getType()) {
41             case DELETE:
42             case MERGE:
43             case WRITE:
44                 return true;
45             case SUBTREE_MODIFIED:
46             case UNMODIFIED:
47                 return false;
48             }
49
50             throw new IllegalArgumentException(String.format("Unhandled modification type %s", input.getType()));
51         }
52     };
53
54     private final Map<PathArgument, ModifiedNode> children;
55     private final Optional<TreeNode> original;
56     private final PathArgument identifier;
57     private ModificationType modificationType = ModificationType.UNMODIFIED;
58     private Optional<TreeNode> snapshotCache;
59     private NormalizedNode<?, ?> value;
60
61     private ModifiedNode(final PathArgument identifier, final Optional<TreeNode> original, final boolean isOrdered) {
62         this.identifier = identifier;
63         this.original = original;
64
65         if (isOrdered) {
66             children = new LinkedHashMap<>();
67         } else {
68             children = new HashMap<>();
69         }
70     }
71
72     /**
73      * Return the value which was written to this node.
74      *
75      * @return Currently-written value
76      */
77     public NormalizedNode<?, ?> getWrittenValue() {
78         return value;
79     }
80
81     @Override
82     public PathArgument getIdentifier() {
83         return identifier;
84     }
85
86     /**
87      *
88      * Returns original store metadata
89      * @return original store metadata
90      */
91     @Override
92     Optional<TreeNode> getOriginal() {
93         return original;
94     }
95
96     /**
97      * Returns modification type
98      *
99      * @return modification type
100      */
101     @Override
102     ModificationType getType() {
103         return modificationType;
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
128      * @return {@link ModifiedNode} for specified child, with {@link #getOriginal()}
129      *         containing child metadata if child was present in original data.
130      */
131     ModifiedNode modifyChild(final PathArgument child, final boolean isOrdered) {
132         clearSnapshot();
133         if (modificationType == ModificationType.UNMODIFIED) {
134             updateModificationType(ModificationType.SUBTREE_MODIFIED);
135         }
136         final ModifiedNode potential = children.get(child);
137         if (potential != null) {
138             return potential;
139         }
140
141         final Optional<TreeNode> currentMetadata;
142         if (original.isPresent()) {
143             final TreeNode orig = original.get();
144             currentMetadata = orig.getChild(child);
145         } else {
146             currentMetadata = Optional.absent();
147         }
148
149         final ModifiedNode newlyCreated = new ModifiedNode(child, currentMetadata, isOrdered);
150         children.put(child, newlyCreated);
151         return newlyCreated;
152     }
153
154     /**
155      * Returns all recorded direct child modification
156      *
157      * @return all recorded direct child modifications
158      */
159     @Override
160     Iterable<ModifiedNode> getChildren() {
161         return children.values();
162     }
163
164     /**
165      * Records a delete for associated node.
166      */
167     void delete() {
168         final ModificationType newType;
169
170         switch (modificationType) {
171         case DELETE:
172         case UNMODIFIED:
173             // We need to record this delete.
174             newType = ModificationType.DELETE;
175             break;
176         case MERGE:
177         case SUBTREE_MODIFIED:
178         case WRITE:
179             /*
180              * We are canceling a previous modification. This is a bit tricky,
181              * as the original write may have just introduced the data, or it
182              * may have modified it.
183              *
184              * As documented in BUG-2470, a delete of data introduced in this
185              * transaction needs to be turned into a no-op.
186              */
187             newType = original.isPresent() ? ModificationType.DELETE : ModificationType.UNMODIFIED;
188             break;
189         default:
190             throw new IllegalStateException("Unhandled deletion of node with " + modificationType);
191         }
192
193         clearSnapshot();
194         children.clear();
195         this.value = null;
196         updateModificationType(newType);
197     }
198
199     /**
200      * Records a write for associated node.
201      *
202      * @param value
203      */
204     void write(final NormalizedNode<?, ?> value) {
205         clearSnapshot();
206         updateModificationType(ModificationType.WRITE);
207         children.clear();
208         this.value = value;
209     }
210
211     void merge(final NormalizedNode<?, ?> value) {
212         clearSnapshot();
213         updateModificationType(ModificationType.MERGE);
214
215         /*
216          * Blind overwrite of any previous data is okay, no matter whether the node
217          * is simple or complex type.
218          *
219          * If this is a simple or complex type with unkeyed children, this merge will
220          * be turned into a write operation, overwriting whatever was there before.
221          *
222          * If this is a container with keyed children, there are two possibilities:
223          * - if it existed before, this value will never be consulted and the children
224          *   will get explicitly merged onto the original data.
225          * - if it did not exist before, this value will be used as a seed write and
226          *   children will be merged into it.
227          * In either case we rely on OperationWithModification to manipulate the children
228          * before calling this method, so unlike a write we do not want to clear them.
229          */
230         this.value = value;
231     }
232
233     /**
234      * Seal the modification node and prune any children which has not been
235      * modified.
236      */
237     void seal() {
238         clearSnapshot();
239
240         // Walk all child nodes and remove any children which have not
241         // been modified.
242         final Iterator<ModifiedNode> it = children.values().iterator();
243         while (it.hasNext()) {
244             final ModifiedNode child = it.next();
245             child.seal();
246
247             if (child.modificationType == ModificationType.UNMODIFIED) {
248                 it.remove();
249             }
250         }
251
252         // A SUBTREE_MODIFIED node without any children is a no-op
253         if (modificationType == ModificationType.SUBTREE_MODIFIED && children.isEmpty()) {
254             updateModificationType(ModificationType.UNMODIFIED);
255         }
256     }
257
258     private void clearSnapshot() {
259         snapshotCache = null;
260     }
261
262     Optional<TreeNode> getSnapshot() {
263         return snapshotCache;
264     }
265
266     Optional<TreeNode> setSnapshot(final Optional<TreeNode> snapshot) {
267         snapshotCache = Preconditions.checkNotNull(snapshot);
268         return snapshot;
269     }
270
271     private void updateModificationType(final ModificationType type) {
272         modificationType = type;
273         clearSnapshot();
274     }
275
276     @Override
277     public String toString() {
278         return "NodeModification [identifier=" + identifier + ", modificationType="
279                 + modificationType + ", childModification=" + children + "]";
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         final ModifiedNode ret = new ModifiedNode(getIdentifier(), Optional.<TreeNode>absent(), false);
291         ret.write(value);
292         return ret;
293     }
294
295     public static ModifiedNode createUnmodified(final TreeNode metadataTree, final boolean isOrdered) {
296         return new ModifiedNode(metadataTree.getIdentifier(), Optional.of(metadataTree), isOrdered);
297     }
298 }