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.tree.spi;
10 import static com.google.common.base.Verify.verifyNotNull;
11 import static java.util.Objects.requireNonNull;
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;
30 public final class DataTreeCandidateNodes {
31 private DataTreeCandidateNodes() {
36 * Return an empty {@link DataTreeCandidateNode} identified by specified {@link PathArgument}.
38 * @param identifier Node identifier
39 * @return An empty DataTreeCandidateNode
41 public static @NonNull DataTreeCandidateNode empty(final PathArgument identifier) {
42 return new EmptyDataTreeCandidateNode(identifier);
46 * Return an unmodified {@link DataTreeCandidateNode} identified by specified {@link NormalizedNode}.
48 * @param node Unchanged normalized node
49 * @return An empty DataTreeCandidateNode
51 public static @NonNull DataTreeCandidateNode unmodified(final NormalizedNode node) {
52 if (node instanceof DistinctNodeContainer) {
53 return new RecursiveUnmodifiedCandidateNode((DistinctNodeContainer<PathArgument, NormalizedNode>) node);
55 return new UnmodifiedLeafCandidateNode(node);
59 * Return a {@link DataTreeCandidateNode} pretending specified node was written without the data exsting beforehand.
61 * @param node Unchanged normalized node
62 * @return An empty DataTreeCandidateNode
63 * @throws NullPointerException if {@code node} is null
65 public static @NonNull DataTreeCandidateNode written(final NormalizedNode node) {
66 return new NormalizedNodeDataTreeCandidateNode(node);
70 * Return a collection of {@link DataTreeCandidateNode}s summarizing the changes between the contents of two
71 * {@link NormalizedNodeContainer}s.
73 * @param oldData Old data container, may be null
74 * @param newData New data container, may be null
75 * @return Collection of changes
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);
84 if (oldData == null) {
85 return Collections2.transform(newData.body(), DataTreeCandidateNodes::writeNode);
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().
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.
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);
108 node = writeNode(child);
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));
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.
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
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);
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.
154 * @param cursor cursor from the modification we want to apply the {@code node} to
155 * @param node candidate tree to apply
157 public static void applyToCursor(final DataTreeModificationCursor cursor, final DataTreeCandidateNode node) {
158 switch (node.modificationType()) {
160 cursor.delete(node.name());
162 case SUBTREE_MODIFIED:
163 cursor.enter(node.name());
164 AbstractNodeIterator iterator = new ExitingNodeIterator(null, node.childNodes().iterator());
166 iterator = iterator.next(cursor);
167 } while (iterator != null);
173 cursor.write(node.name(), verifyNotNull(node.dataAfter()));
176 throw new IllegalArgumentException("Unsupported modification " + node.modificationType());
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}.
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
188 public static void applyRootedNodeToCursor(final DataTreeModificationCursor cursor,
189 final YangInstanceIdentifier rootPath, final DataTreeCandidateNode node) {
190 switch (node.modificationType()) {
192 cursor.delete(rootPath.getLastPathArgument());
194 case SUBTREE_MODIFIED:
195 cursor.enter(rootPath.getLastPathArgument());
196 AbstractNodeIterator iterator = new ExitingNodeIterator(null, node.childNodes().iterator());
198 iterator = iterator.next(cursor);
199 } while (iterator != null);
205 cursor.write(rootPath.getLastPathArgument(), verifyNotNull(node.dataAfter()));
208 throw new IllegalArgumentException("Unsupported modification " + node.modificationType());
212 public static void applyRootToCursor(final DataTreeModificationCursor cursor, final DataTreeCandidateNode node) {
213 switch (node.modificationType()) {
215 throw new IllegalArgumentException("Can not delete root.");
217 case SUBTREE_MODIFIED:
218 AbstractNodeIterator iterator = new RootNonExitingIterator(node.childNodes().iterator());
220 iterator = iterator.next(cursor);
221 } while (iterator != null);
227 throw new IllegalArgumentException("Unsupported modification " + node.modificationType());
231 private static @Nullable NormalizedNode getChild(
232 final DistinctNodeContainer<PathArgument, ?> container, final PathArgument identifier) {
233 return container == null ? null : container.childByArg(identifier);
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);
242 return new DeleteLeafCandidateNode(data);
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);
253 return new ReplaceLeafCandidateNode(oldData, newData);
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);
261 return new WriteLeafCandidateNode(data);
264 private abstract static class AbstractNodeIterator {
265 private final Iterator<DataTreeCandidateNode> iterator;
267 AbstractNodeIterator(final Iterator<DataTreeCandidateNode> iterator) {
268 this.iterator = requireNonNull(iterator);
271 final AbstractNodeIterator next(final DataTreeModificationCursor cursor) {
272 while (iterator.hasNext()) {
273 final DataTreeCandidateNode node = iterator.next();
274 switch (node.modificationType()) {
276 cursor.delete(node.name());
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());
291 cursor.write(node.name(), verifyNotNull(node.dataAfter()));
294 throw new IllegalArgumentException("Unsupported modification " + node.modificationType());
301 abstract @Nullable AbstractNodeIterator getParent();
303 abstract void exitNode(DataTreeModificationCursor cursor);
306 private static final class RootNonExitingIterator extends AbstractNodeIterator {
307 RootNonExitingIterator(final Iterator<DataTreeCandidateNode> iterator) {
312 void exitNode(final DataTreeModificationCursor cursor) {
317 AbstractNodeIterator getParent() {
322 private static final class ExitingNodeIterator extends AbstractNodeIterator {
323 private final AbstractNodeIterator parent;
325 ExitingNodeIterator(final AbstractNodeIterator parent, final Iterator<DataTreeCandidateNode> iterator) {
327 this.parent = parent;
331 AbstractNodeIterator getParent() {
336 void exitNode(final DataTreeModificationCursor cursor) {