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