Seal NormalizedNode hierarchy
[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 com.google.common.base.Verify.verifyNotNull;
11 import static java.util.Objects.requireNonNull;
12
13 import com.google.common.annotations.Beta;
14 import com.google.common.collect.Collections2;
15 import com.google.common.collect.ImmutableList;
16 import java.util.ArrayList;
17 import java.util.Collection;
18 import java.util.Iterator;
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<?, ?> oldData,
79             final @Nullable DistinctNodeContainer<?, ?> 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
97         final var result = new ArrayList<DataTreeCandidateNode>();
98         @SuppressWarnings("unchecked")
99         final var oldCast = (DistinctNodeContainer<PathArgument, ?>) oldData;
100         for (var child : newData.body()) {
101             final DataTreeCandidateNode node;
102             final NormalizedNode oldChild = oldCast.childByArg(child.name());
103             if (oldChild != null) {
104                 // This does not find children which have not in fact been modified, as doing that
105                 // reliably would require us running a full equals() on the two nodes.
106                 node = replaceNode(oldChild, child);
107             } else {
108                 node = writeNode(child);
109             }
110
111             result.add(node);
112         }
113
114         // Process removals next, looking into new data to see if we processed it
115         @SuppressWarnings("unchecked")
116         final var newCast = (DistinctNodeContainer<PathArgument, ?>) newData;
117         for (var child : oldData.body()) {
118             if (newCast.childByArg(child.name()) == null) {
119                 result.add(deleteNode(child));
120             }
121         }
122
123         return result;
124     }
125
126     /**
127      * Return a collection of {@link DataTreeCandidateNode}s summarizing the change in a child, identified by a
128      * {@link PathArgument}, between two {@link NormalizedNodeContainer}s.
129      *
130      * @param oldData Old data container, may be null
131      * @param newData New data container, may be null
132      * @return A {@link DataTreeCandidateNode} describing the change, or empty if the node is not present
133      */
134     public static @Nullable DataTreeCandidateNode containerDelta(
135             final @Nullable DistinctNodeContainer<PathArgument, NormalizedNode> oldData,
136             final @Nullable DistinctNodeContainer<PathArgument, NormalizedNode> newData,
137             final @NonNull PathArgument child) {
138         final NormalizedNode newChild = getChild(newData, child);
139         final NormalizedNode oldChild = getChild(oldData, child);
140         if (oldChild != null) {
141             return newChild != null ? replaceNode(oldChild, newChild) : deleteNode(oldChild);
142         } else if (newChild != null) {
143             return DataTreeCandidateNodes.writeNode(newChild);
144         } else {
145             return null;
146         }
147     }
148
149     /**
150      * Applies the {@code node} to the {@code cursor}, note that if the top node of (@code node} is RootNode
151      * you need to use {@link #applyRootedNodeToCursor(DataTreeModificationCursor, YangInstanceIdentifier,
152      * DataTreeCandidateNode) applyRootedNodeToCursor} method that works with rooted node candidates.
153      *
154      * @param cursor cursor from the modification we want to apply the {@code node} to
155      * @param node candidate tree to apply
156      */
157     public static void applyToCursor(final DataTreeModificationCursor cursor, final DataTreeCandidateNode node) {
158         switch (node.modificationType()) {
159             case DELETE:
160                 cursor.delete(node.name());
161                 break;
162             case SUBTREE_MODIFIED:
163                 cursor.enter(node.name());
164                 AbstractNodeIterator iterator = new ExitingNodeIterator(null, node.childNodes().iterator());
165                 do {
166                     iterator = iterator.next(cursor);
167                 } while (iterator != null);
168                 break;
169             case UNMODIFIED:
170                 // No-op
171                 break;
172             case WRITE:
173                 cursor.write(node.name(), verifyNotNull(node.dataAfter()));
174                 break;
175             default:
176                 throw new IllegalArgumentException("Unsupported modification " + node.modificationType());
177         }
178     }
179
180     /**
181      * Applies the {@code node} that is rooted(doesn't have an identifier) in tree A to tree B's {@code cursor}
182      * at location specified by {@code rootPath}.
183      *
184      * @param cursor cursor from the modification we want to apply the {@code node} to
185      * @param rootPath path in the {@code cursor}'s tree we want to apply to candidate to
186      * @param node candidate tree to apply
187      */
188     public static void applyRootedNodeToCursor(final DataTreeModificationCursor cursor,
189             final YangInstanceIdentifier rootPath, final DataTreeCandidateNode node) {
190         switch (node.modificationType()) {
191             case DELETE:
192                 cursor.delete(rootPath.getLastPathArgument());
193                 break;
194             case SUBTREE_MODIFIED:
195                 cursor.enter(rootPath.getLastPathArgument());
196                 AbstractNodeIterator iterator = new ExitingNodeIterator(null, node.childNodes().iterator());
197                 do {
198                     iterator = iterator.next(cursor);
199                 } while (iterator != null);
200                 break;
201             case UNMODIFIED:
202                 // No-op
203                 break;
204             case WRITE:
205                 cursor.write(rootPath.getLastPathArgument(), verifyNotNull(node.dataAfter()));
206                 break;
207             default:
208                 throw new IllegalArgumentException("Unsupported modification " + node.modificationType());
209         }
210     }
211
212     public static void applyRootToCursor(final DataTreeModificationCursor cursor, final DataTreeCandidateNode node) {
213         switch (node.modificationType()) {
214             case DELETE:
215                 throw new IllegalArgumentException("Can not delete root.");
216             case WRITE:
217             case SUBTREE_MODIFIED:
218                 AbstractNodeIterator iterator = new RootNonExitingIterator(node.childNodes().iterator());
219                 do {
220                     iterator = iterator.next(cursor);
221                 } while (iterator != null);
222                 break;
223             case UNMODIFIED:
224                 // No-op
225                 break;
226             default:
227                 throw new IllegalArgumentException("Unsupported modification " + node.modificationType());
228         }
229     }
230
231     private static @Nullable NormalizedNode getChild(
232             final DistinctNodeContainer<PathArgument, ?> container, final PathArgument identifier) {
233         return container == null ? null : container.childByArg(identifier);
234     }
235
236     @SuppressWarnings("unchecked")
237     private static @NonNull DataTreeCandidateNode deleteNode(final NormalizedNode data) {
238         if (data instanceof DistinctNodeContainer) {
239             return new RecursiveDeleteCandidateNode(
240                 (DistinctNodeContainer<PathArgument, NormalizedNode>) data);
241         }
242         return new DeleteLeafCandidateNode(data);
243     }
244
245     @SuppressWarnings("unchecked")
246     private static @NonNull DataTreeCandidateNode replaceNode(final NormalizedNode oldData,
247             final NormalizedNode newData) {
248         if (oldData instanceof DistinctNodeContainer) {
249             return new RecursiveReplaceCandidateNode(
250                 (DistinctNodeContainer<PathArgument, NormalizedNode>) oldData,
251                 (DistinctNodeContainer<PathArgument, NormalizedNode>) newData);
252         }
253         return new ReplaceLeafCandidateNode(oldData, newData);
254     }
255
256     @SuppressWarnings("unchecked")
257     private static @NonNull DataTreeCandidateNode writeNode(final NormalizedNode data) {
258         if (data instanceof DistinctNodeContainer) {
259             return new RecursiveWriteCandidateNode((DistinctNodeContainer<PathArgument, NormalizedNode>) data);
260         }
261         return new WriteLeafCandidateNode(data);
262     }
263
264     private abstract static class AbstractNodeIterator {
265         private final Iterator<DataTreeCandidateNode> iterator;
266
267         AbstractNodeIterator(final Iterator<DataTreeCandidateNode> iterator) {
268             this.iterator = requireNonNull(iterator);
269         }
270
271         final AbstractNodeIterator next(final DataTreeModificationCursor cursor) {
272             while (iterator.hasNext()) {
273                 final DataTreeCandidateNode node = iterator.next();
274                 switch (node.modificationType()) {
275                     case DELETE:
276                         cursor.delete(node.name());
277                         break;
278                     case APPEARED:
279                     case DISAPPEARED:
280                     case SUBTREE_MODIFIED:
281                         final var children = node.childNodes();
282                         if (!children.isEmpty()) {
283                             cursor.enter(node.name());
284                             return new ExitingNodeIterator(this, children.iterator());
285                         }
286                         break;
287                     case UNMODIFIED:
288                         // No-op
289                         break;
290                     case WRITE:
291                         cursor.write(node.name(), verifyNotNull(node.dataAfter()));
292                         break;
293                     default:
294                         throw new IllegalArgumentException("Unsupported modification " + node.modificationType());
295                 }
296             }
297             exitNode(cursor);
298             return getParent();
299         }
300
301         abstract @Nullable AbstractNodeIterator getParent();
302
303         abstract void exitNode(DataTreeModificationCursor cursor);
304     }
305
306     private static final class RootNonExitingIterator extends AbstractNodeIterator {
307         RootNonExitingIterator(final Iterator<DataTreeCandidateNode> iterator) {
308             super(iterator);
309         }
310
311         @Override
312         void exitNode(final DataTreeModificationCursor cursor) {
313             // Intentional noop.
314         }
315
316         @Override
317         AbstractNodeIterator getParent() {
318             return null;
319         }
320     }
321
322     private static final class ExitingNodeIterator extends AbstractNodeIterator {
323         private final AbstractNodeIterator parent;
324
325         ExitingNodeIterator(final AbstractNodeIterator parent, final Iterator<DataTreeCandidateNode> iterator) {
326             super(iterator);
327             this.parent = parent;
328         }
329
330         @Override
331         AbstractNodeIterator getParent() {
332             return parent;
333         }
334
335         @Override
336         void exitNode(final DataTreeModificationCursor cursor) {
337             cursor.exit();
338         }
339     }
340 }