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() {
29 throw new UnsupportedOperationException();
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 public static @NonNull DataTreeCandidateNode fromNormalizedNode(final NormalizedNode<?, ?> node) {
48 * Return an unmodified {@link DataTreeCandidateNode} identified by specified {@link NormalizedNode}.
50 * @param node Unchanged normalized node
51 * @return An empty DataTreeCandidateNode
53 public static @NonNull DataTreeCandidateNode unmodified(final NormalizedNode<?, ?> node) {
54 if (node instanceof NormalizedNodeContainer) {
55 return new RecursiveUnmodifiedCandidateNode(
56 (NormalizedNodeContainer<?, PathArgument, NormalizedNode<?, ?>>) node);
58 return new UnmodifiedLeafCandidateNode(node);
62 * Return a {@link DataTreeCandidateNode} pretending specified node was written without the data exsting beforehand.
64 * @param node Unchanged normalized node
65 * @return An empty DataTreeCandidateNode
66 * @throws NullPointerException if {@code node} is null
68 public static @NonNull DataTreeCandidateNode written(final NormalizedNode<?, ?> node) {
69 return new NormalizedNodeDataTreeCandidateNode(node);
73 * Return a collection of {@link DataTreeCandidateNode}s summarizing the changes between the contents of two
74 * {@link NormalizedNodeContainer}s.
76 * @param oldData Old data container, may be null
77 * @param newData New data container, may be null
78 * @return Collection of changes
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);
87 if (oldData == null) {
88 return Collections2.transform(newData.getValue(), DataTreeCandidateNodes::writeNode);
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().
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.
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());
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);
109 node = writeNode(child);
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));
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.
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
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));
145 return maybeNewChild.map(DataTreeCandidateNodes::writeNode);
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.
153 * @param cursor cursor from the modification we want to apply the {@code node} to
154 * @param node candidate tree to apply
156 public static void applyToCursor(final DataTreeModificationCursor cursor, final DataTreeCandidateNode node) {
157 switch (node.getModificationType()) {
159 cursor.delete(node.getIdentifier());
161 case SUBTREE_MODIFIED:
162 cursor.enter(node.getIdentifier());
163 AbstractNodeIterator iterator = new ExitingNodeIterator(null, node.getChildNodes().iterator());
165 iterator = iterator.next(cursor);
166 } while (iterator != null);
172 cursor.write(node.getIdentifier(), node.getDataAfter().get());
175 throw new IllegalArgumentException("Unsupported modification " + node.getModificationType());
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}.
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
187 public static void applyRootedNodeToCursor(final DataTreeModificationCursor cursor,
188 final YangInstanceIdentifier rootPath, final DataTreeCandidateNode node) {
189 switch (node.getModificationType()) {
191 cursor.delete(rootPath.getLastPathArgument());
193 case SUBTREE_MODIFIED:
194 cursor.enter(rootPath.getLastPathArgument());
195 AbstractNodeIterator iterator = new ExitingNodeIterator(null, node.getChildNodes().iterator());
197 iterator = iterator.next(cursor);
198 } while (iterator != null);
204 cursor.write(rootPath.getLastPathArgument(), node.getDataAfter().get());
207 throw new IllegalArgumentException("Unsupported modification " + node.getModificationType());
211 public static void applyRootToCursor(final DataTreeModificationCursor cursor, final DataTreeCandidateNode node) {
212 switch (node.getModificationType()) {
214 throw new IllegalArgumentException("Can not delete root.");
216 case SUBTREE_MODIFIED:
217 AbstractNodeIterator iterator = new RootNonExitingIterator(node.getChildNodes().iterator());
219 iterator = iterator.next(cursor);
220 } while (iterator != null);
226 throw new IllegalArgumentException("Unsupported modification " + node.getModificationType());
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);
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);
242 return new DeleteLeafCandidateNode(data);
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);
254 return new ReplaceLeafCandidateNode(oldData, newData);
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);
263 return new WriteLeafCandidateNode(data);
266 private abstract static class AbstractNodeIterator {
267 private final Iterator<DataTreeCandidateNode> iterator;
269 AbstractNodeIterator(final Iterator<DataTreeCandidateNode> iterator) {
270 this.iterator = requireNonNull(iterator);
273 AbstractNodeIterator next(final DataTreeModificationCursor cursor) {
274 while (iterator.hasNext()) {
275 final DataTreeCandidateNode node = iterator.next();
276 switch (node.getModificationType()) {
278 cursor.delete(node.getIdentifier());
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());
293 cursor.write(node.getIdentifier(), node.getDataAfter().get());
296 throw new IllegalArgumentException("Unsupported modification " + node.getModificationType());
303 abstract @Nullable AbstractNodeIterator getParent();
305 abstract void exitNode(DataTreeModificationCursor cursor);
308 private static final class RootNonExitingIterator extends AbstractNodeIterator {
310 RootNonExitingIterator(final Iterator<DataTreeCandidateNode> iterator) {
315 protected void exitNode(final DataTreeModificationCursor cursor) {
320 protected AbstractNodeIterator getParent() {
325 private static final class ExitingNodeIterator extends AbstractNodeIterator {
327 private final AbstractNodeIterator parent;
329 ExitingNodeIterator(final AbstractNodeIterator parent, final Iterator<DataTreeCandidateNode> iterator) {
331 this.parent = parent;
335 AbstractNodeIterator getParent() {
340 void exitNode(final DataTreeModificationCursor cursor) {