Fix DataTreeCandidateNodes handling of deleted nodes
[yangtools.git] / data / yang-data-tree-spi / src / main / java / org / opendaylight / yangtools / yang / data / tree / spi / DataTreeCandidateNodes.java
1 /*
2  * Copyright (c) 2015 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.tree.spi;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.annotations.Beta;
13 import com.google.common.collect.Collections2;
14 import com.google.common.collect.ImmutableList;
15 import java.util.ArrayList;
16 import java.util.Collection;
17 import java.util.Iterator;
18 import java.util.Optional;
19 import org.eclipse.jdt.annotation.NonNull;
20 import org.eclipse.jdt.annotation.Nullable;
21 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
22 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
23 import org.opendaylight.yangtools.yang.data.api.schema.DistinctNodeContainer;
24 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
25 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
26 import org.opendaylight.yangtools.yang.data.tree.api.DataTreeCandidateNode;
27 import org.opendaylight.yangtools.yang.data.tree.api.DataTreeModificationCursor;
28
29 @Beta
30 public final class DataTreeCandidateNodes {
31     private DataTreeCandidateNodes() {
32
33     }
34
35     /**
36      * Return an empty {@link DataTreeCandidateNode} identified by specified {@link PathArgument}.
37      *
38      * @param identifier Node identifier
39      * @return An empty DataTreeCandidateNode
40      */
41     public static @NonNull DataTreeCandidateNode empty(final PathArgument identifier) {
42         return new EmptyDataTreeCandidateNode(identifier);
43     }
44
45     /**
46      * Return an unmodified {@link DataTreeCandidateNode} identified by specified {@link NormalizedNode}.
47      *
48      * @param node Unchanged normalized node
49      * @return An empty DataTreeCandidateNode
50      */
51     public static @NonNull DataTreeCandidateNode unmodified(final NormalizedNode node) {
52         if (node instanceof DistinctNodeContainer) {
53             return new RecursiveUnmodifiedCandidateNode((DistinctNodeContainer<PathArgument, NormalizedNode>) node);
54         }
55         return new UnmodifiedLeafCandidateNode(node);
56     }
57
58     /**
59      * Return a {@link DataTreeCandidateNode} pretending specified node was written without the data exsting beforehand.
60      *
61      * @param node Unchanged normalized node
62      * @return An empty DataTreeCandidateNode
63      * @throws NullPointerException if {@code node} is null
64      */
65     public static @NonNull DataTreeCandidateNode written(final NormalizedNode node) {
66         return new NormalizedNodeDataTreeCandidateNode(node);
67     }
68
69     /**
70      * Return a collection of {@link DataTreeCandidateNode}s summarizing the changes between the contents of two
71      * {@link NormalizedNodeContainer}s.
72      *
73      * @param oldData Old data container, may be null
74      * @param newData New data container, may be null
75      * @return Collection of changes
76      */
77     public static @NonNull Collection<DataTreeCandidateNode> containerDelta(
78             final @Nullable DistinctNodeContainer<PathArgument, NormalizedNode> oldData,
79             final @Nullable DistinctNodeContainer<PathArgument, NormalizedNode> newData) {
80         if (newData == null) {
81             return oldData == null ? ImmutableList.of()
82                     : Collections2.transform(oldData.body(), DataTreeCandidateNodes::deleteNode);
83         }
84         if (oldData == null) {
85             return Collections2.transform(newData.body(), DataTreeCandidateNodes::writeNode);
86         }
87
88         /*
89          * This is slightly inefficient, as it requires N*F(M)+M*F(N) lookup operations, where
90          * F is dependent on the implementation of NormalizedNodeContainer.getChild().
91          *
92          * We build the return collection by iterating over new data and looking each child up
93          * in old data. Based on that we construct replaced/written nodes. We then proceed to
94          * iterate over old data and looking up each child in new data.
95          */
96         final Collection<DataTreeCandidateNode> result = new ArrayList<>();
97         for (NormalizedNode child : newData.body()) {
98             final DataTreeCandidateNode node;
99             final NormalizedNode oldChild = oldData.childByArg(child.getIdentifier());
100             if (oldChild != null) {
101                 // This does not find children which have not in fact been modified, as doing that
102                 // reliably would require us running a full equals() on the two nodes.
103                 node = replaceNode(oldChild, child);
104             } else {
105                 node = writeNode(child);
106             }
107
108             result.add(node);
109         }
110
111         // Process removals next, looking into new data to see if we processed it
112         for (NormalizedNode child : oldData.body()) {
113             if (newData.childByArg(child.getIdentifier()) == null) {
114                 result.add(deleteNode(child));
115             }
116         }
117
118         return result;
119     }
120
121     /**
122      * Return a collection of {@link DataTreeCandidateNode}s summarizing the change in a child, identified by a
123      * {@link PathArgument}, between two {@link NormalizedNodeContainer}s.
124      *
125      * @param oldData Old data container, may be null
126      * @param newData New data container, may be null
127      * @return A {@link DataTreeCandidateNode} describing the change, or empty if the node is not present
128      */
129     public static @NonNull Optional<DataTreeCandidateNode> containerDelta(
130             final @Nullable DistinctNodeContainer<PathArgument, NormalizedNode> oldData,
131             final @Nullable DistinctNodeContainer<PathArgument, NormalizedNode> newData,
132             final @NonNull PathArgument child) {
133         final NormalizedNode newChild = getChild(newData, child);
134         final NormalizedNode oldChild = getChild(oldData, child);
135         if (oldChild != null) {
136             return Optional.of(newChild != null ? replaceNode(oldChild, newChild) : deleteNode(oldChild));
137         } else if (newChild != null) {
138             return Optional.of(DataTreeCandidateNodes.writeNode(newChild));
139         } else {
140             return Optional.empty();
141         }
142     }
143
144     /**
145      * Applies the {@code node} to the {@code cursor}, note that if the top node of (@code node} is RootNode
146      * you need to use {@link #applyRootedNodeToCursor(DataTreeModificationCursor, YangInstanceIdentifier,
147      * DataTreeCandidateNode) applyRootedNodeToCursor} method that works with rooted node candidates.
148      *
149      * @param cursor cursor from the modification we want to apply the {@code node} to
150      * @param node candidate tree to apply
151      */
152     public static void applyToCursor(final DataTreeModificationCursor cursor, final DataTreeCandidateNode node) {
153         switch (node.getModificationType()) {
154             case DELETE:
155                 cursor.delete(node.getIdentifier());
156                 break;
157             case SUBTREE_MODIFIED:
158                 cursor.enter(node.getIdentifier());
159                 AbstractNodeIterator iterator = new ExitingNodeIterator(null, node.getChildNodes().iterator());
160                 do {
161                     iterator = iterator.next(cursor);
162                 } while (iterator != null);
163                 break;
164             case UNMODIFIED:
165                 // No-op
166                 break;
167             case WRITE:
168                 cursor.write(node.getIdentifier(), node.getDataAfter().get());
169                 break;
170             default:
171                 throw new IllegalArgumentException("Unsupported modification " + node.getModificationType());
172         }
173     }
174
175     /**
176      * Applies the {@code node} that is rooted(doesn't have an identifier) in tree A to tree B's {@code cursor}
177      * at location specified by {@code rootPath}.
178      *
179      * @param cursor cursor from the modification we want to apply the {@code node} to
180      * @param rootPath path in the {@code cursor}'s tree we want to apply to candidate to
181      * @param node candidate tree to apply
182      */
183     public static void applyRootedNodeToCursor(final DataTreeModificationCursor cursor,
184             final YangInstanceIdentifier rootPath, final DataTreeCandidateNode node) {
185         switch (node.getModificationType()) {
186             case DELETE:
187                 cursor.delete(rootPath.getLastPathArgument());
188                 break;
189             case SUBTREE_MODIFIED:
190                 cursor.enter(rootPath.getLastPathArgument());
191                 AbstractNodeIterator iterator = new ExitingNodeIterator(null, node.getChildNodes().iterator());
192                 do {
193                     iterator = iterator.next(cursor);
194                 } while (iterator != null);
195                 break;
196             case UNMODIFIED:
197                 // No-op
198                 break;
199             case WRITE:
200                 cursor.write(rootPath.getLastPathArgument(), node.getDataAfter().get());
201                 break;
202             default:
203                 throw new IllegalArgumentException("Unsupported modification " + node.getModificationType());
204         }
205     }
206
207     public static void applyRootToCursor(final DataTreeModificationCursor cursor, final DataTreeCandidateNode node) {
208         switch (node.getModificationType()) {
209             case DELETE:
210                 throw new IllegalArgumentException("Can not delete root.");
211             case WRITE:
212             case SUBTREE_MODIFIED:
213                 AbstractNodeIterator iterator = new RootNonExitingIterator(node.getChildNodes().iterator());
214                 do {
215                     iterator = iterator.next(cursor);
216                 } while (iterator != null);
217                 break;
218             case UNMODIFIED:
219                 // No-op
220                 break;
221             default:
222                 throw new IllegalArgumentException("Unsupported modification " + node.getModificationType());
223         }
224     }
225
226     private static @Nullable NormalizedNode getChild(
227             final DistinctNodeContainer<PathArgument, ?> container, final PathArgument identifier) {
228         return container == null ? null : container.childByArg(identifier);
229     }
230
231     @SuppressWarnings("unchecked")
232     private static @NonNull DataTreeCandidateNode deleteNode(final NormalizedNode data) {
233         if (data instanceof DistinctNodeContainer) {
234             return new RecursiveDeleteCandidateNode(
235                 (DistinctNodeContainer<PathArgument, NormalizedNode>) data);
236         }
237         return new DeleteLeafCandidateNode(data);
238     }
239
240     @SuppressWarnings("unchecked")
241     private static @NonNull DataTreeCandidateNode replaceNode(final NormalizedNode oldData,
242             final NormalizedNode newData) {
243         if (oldData instanceof DistinctNodeContainer) {
244             return new RecursiveReplaceCandidateNode(
245                 (DistinctNodeContainer<PathArgument, NormalizedNode>) oldData,
246                 (DistinctNodeContainer<PathArgument, NormalizedNode>) newData);
247         }
248         return new ReplaceLeafCandidateNode(oldData, newData);
249     }
250
251     @SuppressWarnings("unchecked")
252     private static @NonNull DataTreeCandidateNode writeNode(final NormalizedNode data) {
253         if (data instanceof DistinctNodeContainer) {
254             return new RecursiveWriteCandidateNode((DistinctNodeContainer<PathArgument, NormalizedNode>) data);
255         }
256         return new WriteLeafCandidateNode(data);
257     }
258
259     private abstract static class AbstractNodeIterator {
260         private final Iterator<DataTreeCandidateNode> iterator;
261
262         AbstractNodeIterator(final Iterator<DataTreeCandidateNode> iterator) {
263             this.iterator = requireNonNull(iterator);
264         }
265
266         final AbstractNodeIterator next(final DataTreeModificationCursor cursor) {
267             while (iterator.hasNext()) {
268                 final DataTreeCandidateNode node = iterator.next();
269                 switch (node.getModificationType()) {
270                     case DELETE:
271                         cursor.delete(node.getIdentifier());
272                         break;
273                     case APPEARED:
274                     case DISAPPEARED:
275                     case SUBTREE_MODIFIED:
276                         final Collection<DataTreeCandidateNode> children = node.getChildNodes();
277                         if (!children.isEmpty()) {
278                             cursor.enter(node.getIdentifier());
279                             return new ExitingNodeIterator(this, children.iterator());
280                         }
281                         break;
282                     case UNMODIFIED:
283                         // No-op
284                         break;
285                     case WRITE:
286                         cursor.write(node.getIdentifier(), node.getDataAfter().get());
287                         break;
288                     default:
289                         throw new IllegalArgumentException("Unsupported modification " + node.getModificationType());
290                 }
291             }
292             exitNode(cursor);
293             return getParent();
294         }
295
296         abstract @Nullable AbstractNodeIterator getParent();
297
298         abstract void exitNode(DataTreeModificationCursor cursor);
299     }
300
301     private static final class RootNonExitingIterator extends AbstractNodeIterator {
302         RootNonExitingIterator(final Iterator<DataTreeCandidateNode> iterator) {
303             super(iterator);
304         }
305
306         @Override
307         void exitNode(final DataTreeModificationCursor cursor) {
308             // Intentional noop.
309         }
310
311         @Override
312         AbstractNodeIterator getParent() {
313             return null;
314         }
315     }
316
317     private static final class ExitingNodeIterator extends AbstractNodeIterator {
318         private final AbstractNodeIterator parent;
319
320         ExitingNodeIterator(final AbstractNodeIterator parent, final Iterator<DataTreeCandidateNode> iterator) {
321             super(iterator);
322             this.parent = parent;
323         }
324
325         @Override
326         AbstractNodeIterator getParent() {
327             return parent;
328         }
329
330         @Override
331         void exitNode(final DataTreeModificationCursor cursor) {
332             cursor.exit();
333         }
334     }
335 }