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