2 * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.yangtools.yang.data.api.schema.tree;
10 import static java.util.Objects.requireNonNull;
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;
28 public final class DataTreeCandidateNodes {
29 private DataTreeCandidateNodes() {
34 * Return an empty {@link DataTreeCandidateNode} identified by specified {@link PathArgument}.
36 * @param identifier Node identifier
37 * @return An empty DataTreeCandidateNode
39 public static @NonNull DataTreeCandidateNode empty(final PathArgument identifier) {
40 return new EmptyDataTreeCandidateNode(identifier);
44 * Return an unmodified {@link DataTreeCandidateNode} identified by specified {@link NormalizedNode}.
46 * @param node Unchanged normalized node
47 * @return An empty DataTreeCandidateNode
49 public static @NonNull DataTreeCandidateNode unmodified(final NormalizedNode node) {
50 if (node instanceof DistinctNodeContainer) {
51 return new RecursiveUnmodifiedCandidateNode((DistinctNodeContainer<PathArgument, NormalizedNode>) node);
53 return new UnmodifiedLeafCandidateNode(node);
57 * Return a {@link DataTreeCandidateNode} pretending specified node was written without the data exsting beforehand.
59 * @param node Unchanged normalized node
60 * @return An empty DataTreeCandidateNode
61 * @throws NullPointerException if {@code node} is null
63 public static @NonNull DataTreeCandidateNode written(final NormalizedNode node) {
64 return new NormalizedNodeDataTreeCandidateNode(node);
68 * Return a collection of {@link DataTreeCandidateNode}s summarizing the changes between the contents of two
69 * {@link NormalizedNodeContainer}s.
71 * @param oldData Old data container, may be null
72 * @param newData New data container, may be null
73 * @return Collection of changes
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);
82 if (oldData == null) {
83 return Collections2.transform(newData.body(), DataTreeCandidateNodes::writeNode);
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().
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.
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);
103 node = writeNode(child);
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));
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.
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
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));
138 return Optional.empty();
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.
147 * @param cursor cursor from the modification we want to apply the {@code node} to
148 * @param node candidate tree to apply
150 public static void applyToCursor(final DataTreeModificationCursor cursor, final DataTreeCandidateNode node) {
151 switch (node.getModificationType()) {
153 cursor.delete(node.getIdentifier());
155 case SUBTREE_MODIFIED:
156 cursor.enter(node.getIdentifier());
157 AbstractNodeIterator iterator = new ExitingNodeIterator(null, node.getChildNodes().iterator());
159 iterator = iterator.next(cursor);
160 } while (iterator != null);
166 cursor.write(node.getIdentifier(), node.getDataAfter().get());
169 throw new IllegalArgumentException("Unsupported modification " + node.getModificationType());
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}.
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
181 public static void applyRootedNodeToCursor(final DataTreeModificationCursor cursor,
182 final YangInstanceIdentifier rootPath, final DataTreeCandidateNode node) {
183 switch (node.getModificationType()) {
185 cursor.delete(rootPath.getLastPathArgument());
187 case SUBTREE_MODIFIED:
188 cursor.enter(rootPath.getLastPathArgument());
189 AbstractNodeIterator iterator = new ExitingNodeIterator(null, node.getChildNodes().iterator());
191 iterator = iterator.next(cursor);
192 } while (iterator != null);
198 cursor.write(rootPath.getLastPathArgument(), node.getDataAfter().get());
201 throw new IllegalArgumentException("Unsupported modification " + node.getModificationType());
205 public static void applyRootToCursor(final DataTreeModificationCursor cursor, final DataTreeCandidateNode node) {
206 switch (node.getModificationType()) {
208 throw new IllegalArgumentException("Can not delete root.");
210 case SUBTREE_MODIFIED:
211 AbstractNodeIterator iterator = new RootNonExitingIterator(node.getChildNodes().iterator());
213 iterator = iterator.next(cursor);
214 } while (iterator != null);
220 throw new IllegalArgumentException("Unsupported modification " + node.getModificationType());
224 private static @Nullable NormalizedNode getChild(
225 final DistinctNodeContainer<PathArgument, ?> container, final PathArgument identifier) {
226 return container == null ? null : container.childByArg(identifier);
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);
235 return new DeleteLeafCandidateNode(data);
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);
247 return new ReplaceLeafCandidateNode(oldData, newData);
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);
255 return new WriteLeafCandidateNode(data);
258 private abstract static class AbstractNodeIterator {
259 private final Iterator<DataTreeCandidateNode> iterator;
261 AbstractNodeIterator(final Iterator<DataTreeCandidateNode> iterator) {
262 this.iterator = requireNonNull(iterator);
265 final AbstractNodeIterator next(final DataTreeModificationCursor cursor) {
266 while (iterator.hasNext()) {
267 final DataTreeCandidateNode node = iterator.next();
268 switch (node.getModificationType()) {
270 cursor.delete(node.getIdentifier());
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());
285 cursor.write(node.getIdentifier(), node.getDataAfter().get());
288 throw new IllegalArgumentException("Unsupported modification " + node.getModificationType());
295 abstract @Nullable AbstractNodeIterator getParent();
297 abstract void exitNode(DataTreeModificationCursor cursor);
300 private static final class RootNonExitingIterator extends AbstractNodeIterator {
301 RootNonExitingIterator(final Iterator<DataTreeCandidateNode> iterator) {
306 void exitNode(final DataTreeModificationCursor cursor) {
311 AbstractNodeIterator getParent() {
316 private static final class ExitingNodeIterator extends AbstractNodeIterator {
317 private final AbstractNodeIterator parent;
319 ExitingNodeIterator(final AbstractNodeIterator parent, final Iterator<DataTreeCandidateNode> iterator) {
321 this.parent = parent;
325 AbstractNodeIterator getParent() {
330 void exitNode(final DataTreeModificationCursor cursor) {