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.NormalizedNode;
24 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
27 public final class DataTreeCandidateNodes {
28 private DataTreeCandidateNodes() {
33 * Return an empty {@link DataTreeCandidateNode} identified by specified {@link PathArgument}.
35 * @param identifier Node identifier
36 * @return An empty DataTreeCandidateNode
38 public static @NonNull DataTreeCandidateNode empty(final PathArgument identifier) {
39 return new EmptyDataTreeCandidateNode(identifier);
43 * Return an unmodified {@link DataTreeCandidateNode} identified by specified {@link NormalizedNode}.
45 * @param node Unchanged normalized node
46 * @return An empty DataTreeCandidateNode
48 public static @NonNull DataTreeCandidateNode unmodified(final NormalizedNode<?, ?> node) {
49 if (node instanceof NormalizedNodeContainer) {
50 return new RecursiveUnmodifiedCandidateNode(
51 (NormalizedNodeContainer<?, 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 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);
82 if (oldData == null) {
83 return Collections2.transform(newData.getValue(), 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.getValue()) {
96 final DataTreeCandidateNode node;
97 final Optional<NormalizedNode<?, ?>> maybeOldChild = oldData.getChild(child.getIdentifier());
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);
104 node = writeNode(child);
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));
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.
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
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));
140 return maybeNewChild.map(DataTreeCandidateNodes::writeNode);
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.
148 * @param cursor cursor from the modification we want to apply the {@code node} to
149 * @param node candidate tree to apply
151 public static void applyToCursor(final DataTreeModificationCursor cursor, final DataTreeCandidateNode node) {
152 switch (node.getModificationType()) {
154 cursor.delete(node.getIdentifier());
156 case SUBTREE_MODIFIED:
157 cursor.enter(node.getIdentifier());
158 AbstractNodeIterator iterator = new ExitingNodeIterator(null, node.getChildNodes().iterator());
160 iterator = iterator.next(cursor);
161 } while (iterator != null);
167 cursor.write(node.getIdentifier(), node.getDataAfter().get());
170 throw new IllegalArgumentException("Unsupported modification " + node.getModificationType());
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}.
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
182 public static void applyRootedNodeToCursor(final DataTreeModificationCursor cursor,
183 final YangInstanceIdentifier rootPath, final DataTreeCandidateNode node) {
184 switch (node.getModificationType()) {
186 cursor.delete(rootPath.getLastPathArgument());
188 case SUBTREE_MODIFIED:
189 cursor.enter(rootPath.getLastPathArgument());
190 AbstractNodeIterator iterator = new ExitingNodeIterator(null, node.getChildNodes().iterator());
192 iterator = iterator.next(cursor);
193 } while (iterator != null);
199 cursor.write(rootPath.getLastPathArgument(), node.getDataAfter().get());
202 throw new IllegalArgumentException("Unsupported modification " + node.getModificationType());
206 public static void applyRootToCursor(final DataTreeModificationCursor cursor, final DataTreeCandidateNode node) {
207 switch (node.getModificationType()) {
209 throw new IllegalArgumentException("Can not delete root.");
211 case SUBTREE_MODIFIED:
212 AbstractNodeIterator iterator = new RootNonExitingIterator(node.getChildNodes().iterator());
214 iterator = iterator.next(cursor);
215 } while (iterator != null);
221 throw new IllegalArgumentException("Unsupported modification " + node.getModificationType());
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);
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);
237 return new DeleteLeafCandidateNode(data);
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);
249 return new ReplaceLeafCandidateNode(oldData, newData);
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);
258 return new WriteLeafCandidateNode(data);
261 private abstract static class AbstractNodeIterator {
262 private final Iterator<DataTreeCandidateNode> iterator;
264 AbstractNodeIterator(final Iterator<DataTreeCandidateNode> iterator) {
265 this.iterator = requireNonNull(iterator);
268 final AbstractNodeIterator next(final DataTreeModificationCursor cursor) {
269 while (iterator.hasNext()) {
270 final DataTreeCandidateNode node = iterator.next();
271 switch (node.getModificationType()) {
273 cursor.delete(node.getIdentifier());
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());
288 cursor.write(node.getIdentifier(), node.getDataAfter().get());
291 throw new IllegalArgumentException("Unsupported modification " + node.getModificationType());
298 abstract @Nullable AbstractNodeIterator getParent();
300 abstract void exitNode(DataTreeModificationCursor cursor);
303 private static final class RootNonExitingIterator extends AbstractNodeIterator {
304 RootNonExitingIterator(final Iterator<DataTreeCandidateNode> iterator) {
309 void exitNode(final DataTreeModificationCursor cursor) {
314 AbstractNodeIterator getParent() {
319 private static final class ExitingNodeIterator extends AbstractNodeIterator {
320 private final AbstractNodeIterator parent;
322 ExitingNodeIterator(final AbstractNodeIterator parent, final Iterator<DataTreeCandidateNode> iterator) {
324 this.parent = parent;
328 AbstractNodeIterator getParent() {
333 void exitNode(final DataTreeModificationCursor cursor) {