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