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