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 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;
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<PathArgument, NormalizedNode> oldData,
79 final @Nullable DistinctNodeContainer<PathArgument, NormalizedNode> 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.
96 final Collection<DataTreeCandidateNode> result = new ArrayList<>();
97 for (NormalizedNode child : newData.body()) {
98 final DataTreeCandidateNode node;
99 final NormalizedNode oldChild = oldData.childByArg(child.getIdentifier());
100 if (oldChild != null) {
101 // This does not find children which have not in fact been modified, as doing that
102 // reliably would require us running a full equals() on the two nodes.
103 node = replaceNode(oldChild, child);
105 node = writeNode(child);
111 // Process removals next, looking into new data to see if we processed it
112 for (NormalizedNode child : oldData.body()) {
113 if (newData.childByArg(child.getIdentifier()) == null) {
114 result.add(deleteNode(child));
122 * Return a collection of {@link DataTreeCandidateNode}s summarizing the change in a child, identified by a
123 * {@link PathArgument}, between two {@link NormalizedNodeContainer}s.
125 * @param oldData Old data container, may be null
126 * @param newData New data container, may be null
127 * @return A {@link DataTreeCandidateNode} describing the change, or empty if the node is not present
129 public static @NonNull Optional<DataTreeCandidateNode> containerDelta(
130 final @Nullable DistinctNodeContainer<PathArgument, NormalizedNode> oldData,
131 final @Nullable DistinctNodeContainer<PathArgument, NormalizedNode> newData,
132 final @NonNull PathArgument child) {
133 final NormalizedNode newChild = getChild(newData, child);
134 final NormalizedNode oldChild = getChild(oldData, child);
135 if (oldChild != null) {
136 return Optional.of(newChild != null ? replaceNode(oldChild, newChild) : deleteNode(oldChild));
137 } else if (newChild != null) {
138 return Optional.of(DataTreeCandidateNodes.writeNode(newChild));
140 return Optional.empty();
145 * Applies the {@code node} to the {@code cursor}, note that if the top node of (@code node} is RootNode
146 * you need to use {@link #applyRootedNodeToCursor(DataTreeModificationCursor, YangInstanceIdentifier,
147 * DataTreeCandidateNode) applyRootedNodeToCursor} method that works with rooted node candidates.
149 * @param cursor cursor from the modification we want to apply the {@code node} to
150 * @param node candidate tree to apply
152 public static void applyToCursor(final DataTreeModificationCursor cursor, final DataTreeCandidateNode node) {
153 switch (node.getModificationType()) {
155 cursor.delete(node.getIdentifier());
157 case SUBTREE_MODIFIED:
158 cursor.enter(node.getIdentifier());
159 AbstractNodeIterator iterator = new ExitingNodeIterator(null, node.getChildNodes().iterator());
161 iterator = iterator.next(cursor);
162 } while (iterator != null);
168 cursor.write(node.getIdentifier(), node.getDataAfter().get());
171 throw new IllegalArgumentException("Unsupported modification " + node.getModificationType());
176 * Applies the {@code node} that is rooted(doesn't have an identifier) in tree A to tree B's {@code cursor}
177 * at location specified by {@code rootPath}.
179 * @param cursor cursor from the modification we want to apply the {@code node} to
180 * @param rootPath path in the {@code cursor}'s tree we want to apply to candidate to
181 * @param node candidate tree to apply
183 public static void applyRootedNodeToCursor(final DataTreeModificationCursor cursor,
184 final YangInstanceIdentifier rootPath, final DataTreeCandidateNode node) {
185 switch (node.getModificationType()) {
187 cursor.delete(rootPath.getLastPathArgument());
189 case SUBTREE_MODIFIED:
190 cursor.enter(rootPath.getLastPathArgument());
191 AbstractNodeIterator iterator = new ExitingNodeIterator(null, node.getChildNodes().iterator());
193 iterator = iterator.next(cursor);
194 } while (iterator != null);
200 cursor.write(rootPath.getLastPathArgument(), node.getDataAfter().get());
203 throw new IllegalArgumentException("Unsupported modification " + node.getModificationType());
207 public static void applyRootToCursor(final DataTreeModificationCursor cursor, final DataTreeCandidateNode node) {
208 switch (node.getModificationType()) {
210 throw new IllegalArgumentException("Can not delete root.");
212 case SUBTREE_MODIFIED:
213 AbstractNodeIterator iterator = new RootNonExitingIterator(node.getChildNodes().iterator());
215 iterator = iterator.next(cursor);
216 } while (iterator != null);
222 throw new IllegalArgumentException("Unsupported modification " + node.getModificationType());
226 private static @Nullable NormalizedNode getChild(
227 final DistinctNodeContainer<PathArgument, ?> container, final PathArgument identifier) {
228 return container == null ? null : container.childByArg(identifier);
231 @SuppressWarnings("unchecked")
232 private static @NonNull DataTreeCandidateNode deleteNode(final NormalizedNode data) {
233 if (data instanceof DistinctNodeContainer) {
234 return new RecursiveDeleteCandidateNode(
235 (DistinctNodeContainer<PathArgument, NormalizedNode>) data);
237 return new DeleteLeafCandidateNode(data);
240 @SuppressWarnings("unchecked")
241 private static @NonNull DataTreeCandidateNode replaceNode(final NormalizedNode oldData,
242 final NormalizedNode newData) {
243 if (oldData instanceof DistinctNodeContainer) {
244 return new RecursiveReplaceCandidateNode(
245 (DistinctNodeContainer<PathArgument, NormalizedNode>) oldData,
246 (DistinctNodeContainer<PathArgument, NormalizedNode>) newData);
248 return new ReplaceLeafCandidateNode(oldData, newData);
251 @SuppressWarnings("unchecked")
252 private static @NonNull DataTreeCandidateNode writeNode(final NormalizedNode data) {
253 if (data instanceof DistinctNodeContainer) {
254 return new RecursiveWriteCandidateNode((DistinctNodeContainer<PathArgument, NormalizedNode>) data);
256 return new WriteLeafCandidateNode(data);
259 private abstract static class AbstractNodeIterator {
260 private final Iterator<DataTreeCandidateNode> iterator;
262 AbstractNodeIterator(final Iterator<DataTreeCandidateNode> iterator) {
263 this.iterator = requireNonNull(iterator);
266 final AbstractNodeIterator next(final DataTreeModificationCursor cursor) {
267 while (iterator.hasNext()) {
268 final DataTreeCandidateNode node = iterator.next();
269 switch (node.getModificationType()) {
271 cursor.delete(node.getIdentifier());
275 case SUBTREE_MODIFIED:
276 final Collection<DataTreeCandidateNode> children = node.getChildNodes();
277 if (!children.isEmpty()) {
278 cursor.enter(node.getIdentifier());
279 return new ExitingNodeIterator(this, children.iterator());
286 cursor.write(node.getIdentifier(), node.getDataAfter().get());
289 throw new IllegalArgumentException("Unsupported modification " + node.getModificationType());
296 abstract @Nullable AbstractNodeIterator getParent();
298 abstract void exitNode(DataTreeModificationCursor cursor);
301 private static final class RootNonExitingIterator extends AbstractNodeIterator {
302 RootNonExitingIterator(final Iterator<DataTreeCandidateNode> iterator) {
307 void exitNode(final DataTreeModificationCursor cursor) {
312 AbstractNodeIterator getParent() {
317 private static final class ExitingNodeIterator extends AbstractNodeIterator {
318 private final AbstractNodeIterator parent;
320 ExitingNodeIterator(final AbstractNodeIterator parent, final Iterator<DataTreeCandidateNode> iterator) {
322 this.parent = parent;
326 AbstractNodeIterator getParent() {
331 void exitNode(final DataTreeModificationCursor cursor) {