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 com.google.common.base.Preconditions.checkArgument;
11 import static java.util.Objects.requireNonNull;
13 import com.google.common.annotations.Beta;
14 import java.util.ArrayList;
15 import java.util.Collection;
16 import java.util.Iterator;
17 import java.util.List;
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.slf4j.Logger;
25 import org.slf4j.LoggerFactory;
28 * Utility class holding methods useful when dealing with {@link DataTreeCandidate} instances.
31 public final class DataTreeCandidates {
32 private static final Logger LOG = LoggerFactory.getLogger(DataTreeCandidates.class);
34 private DataTreeCandidates() {
38 public static @NonNull DataTreeCandidate newDataTreeCandidate(final YangInstanceIdentifier rootPath,
39 final DataTreeCandidateNode rootNode) {
40 return new DefaultDataTreeCandidate(rootPath, rootNode);
43 public static @NonNull DataTreeCandidate fromNormalizedNode(final YangInstanceIdentifier rootPath,
44 final NormalizedNode node) {
45 return new DefaultDataTreeCandidate(rootPath, new NormalizedNodeDataTreeCandidateNode(node));
48 public static void applyToCursor(final DataTreeModificationCursor cursor, final DataTreeCandidate candidate) {
49 DataTreeCandidateNodes.applyToCursor(cursor, candidate.getRootNode());
52 public static void applyToModification(final DataTreeModification modification, final DataTreeCandidate candidate) {
53 if (modification instanceof CursorAwareDataTreeModification) {
54 applyToCursorAwareModification((CursorAwareDataTreeModification) modification, candidate);
58 final DataTreeCandidateNode node = candidate.getRootNode();
59 final YangInstanceIdentifier path = candidate.getRootPath();
60 switch (node.getModificationType()) {
62 modification.delete(path);
63 LOG.debug("Modification {} deleted path {}", modification, path);
65 case SUBTREE_MODIFIED:
66 LOG.debug("Modification {} modified path {}", modification, path);
68 NodeIterator iterator = new NodeIterator(null, path, node.getChildNodes().iterator());
70 iterator = iterator.next(modification);
71 } while (iterator != null);
74 LOG.debug("Modification {} unmodified path {}", modification, path);
78 modification.write(path, node.getDataAfter().get());
79 LOG.debug("Modification {} written path {}", modification, path);
82 throw new IllegalArgumentException("Unsupported modification " + node.getModificationType());
87 * Compress a list of DataTreeCandidates into a single DataTreeCandidate. The resulting candidate is a summarization
88 * of changes recorded in the input candidates.
90 * @param candidates Input list, must be non-empty
91 * @return Summarized DataTreeCandidate
92 * @throws IllegalArgumentException if candidates is empty, or contains candidates with mismatched root path
93 * @throws NullPointerException if {@code candidates} is null or contains a null entry
95 public static @NonNull DataTreeCandidate aggregate(@NonNull final List<? extends DataTreeCandidate> candidates) {
96 final Iterator<? extends DataTreeCandidate> it = candidates.iterator();
97 checkArgument(it.hasNext(), "Input must not be empty");
98 final DataTreeCandidate first = requireNonNull(it.next(), "Input must not contain null entries");
103 final YangInstanceIdentifier rootPath = first.getRootPath();
104 final List<DataTreeCandidateNode> roots = new ArrayList<>(candidates.size());
105 roots.add(first.getRootNode());
106 it.forEachRemaining(candidate -> {
107 final YangInstanceIdentifier root = candidate.getRootPath();
108 checkArgument(rootPath.equals(root), "Expecting root path %s, encountered %s", rootPath, root);
109 roots.add(candidate.getRootNode());
111 return DataTreeCandidates.newDataTreeCandidate(rootPath, fastCompressNode(roots.get(0), roots));
114 private static DataTreeCandidateNode fastCompressNode(final DataTreeCandidateNode first,
115 final List<DataTreeCandidateNode> input) {
116 final DataTreeCandidateNode last = input.get(input.size() - 1);
117 ModificationType nodeModification = last.getModificationType();
118 Optional<NormalizedNode> dataBefore = first.getDataBefore();
119 Optional<NormalizedNode> dataAfter = last.getDataAfter();
120 switch (nodeModification) {
122 ModificationType previous = first.getModificationType();
123 // Check if node had data before
124 if (previous == ModificationType.DELETE
125 || previous == ModificationType.DISAPPEARED
126 || previous == ModificationType.UNMODIFIED && dataBefore.isEmpty()) {
127 illegalModification(ModificationType.DELETE, ModificationType.DELETE);
129 if (dataBefore.isEmpty()) {
130 return new TerminalDataTreeCandidateNode(null, ModificationType.UNMODIFIED, null, null);
132 return new TerminalDataTreeCandidateNode(null, nodeModification, dataBefore.get(), null);
134 return new TerminalDataTreeCandidateNode(null, nodeModification, dataBefore.orElse(null),
135 dataAfter.orElseThrow());
138 case SUBTREE_MODIFIED:
140 // No luck, we need to iterate
141 return slowCompressNodes(first, input);
143 throw new IllegalStateException("Unsupported modification type " + nodeModification);
147 private static DataTreeCandidateNode slowCompressNodes(final DataTreeCandidateNode first,
148 final List<DataTreeCandidateNode> input) {
149 // finalNode contains summarized changes
150 TerminalDataTreeCandidateNode finalNode = new TerminalDataTreeCandidateNode(
151 null, first.getDataBefore().orElse(null));
152 input.forEach(node -> compressNode(finalNode, node, null));
153 finalNode.setAfter(input.get(input.size() - 1).getDataAfter().orElse(null));
154 return cleanUpTree(finalNode);
157 private static void compressNode(final TerminalDataTreeCandidateNode finalNode, final DataTreeCandidateNode node,
158 final PathArgument parent) {
159 PathArgument identifier;
161 identifier = node.getIdentifier();
162 } catch (IllegalStateException e) {
165 // Check if finalNode has stored any changes for node
166 if (finalNode.getNode(identifier).isEmpty()) {
167 TerminalDataTreeCandidateNode parentNode = finalNode.getNode(parent)
168 .orElseThrow(() -> new IllegalArgumentException("No node found for " + parent + " identifier"));
169 TerminalDataTreeCandidateNode childNode = new TerminalDataTreeCandidateNode(
171 node.getDataBefore().orElse(null),
173 parentNode.addChildNode(childNode);
176 ModificationType nodeModification = node.getModificationType();
177 switch (nodeModification) {
179 // If node is unmodified there is no need iterate through its child nodes
185 case SUBTREE_MODIFIED:
186 finalNode.setModification(identifier,
187 compressModifications(finalNode.getModification(identifier), nodeModification,
188 finalNode.getDataAfter(identifier).isEmpty()));
189 finalNode.setData(identifier, node.getDataAfter().orElse(null));
191 for (DataTreeCandidateNode child : node.getChildNodes()) {
192 compressNode(finalNode, child, identifier);
196 throw new IllegalStateException("Unsupported modification type " + nodeModification);
200 // Removes redundant changes
201 private static DataTreeCandidateNode cleanUpTree(final TerminalDataTreeCandidateNode finalNode) {
202 return cleanUpTree(finalNode, finalNode);
205 // Compare data before and after in order to find modified nodes without actual changes
206 private static DataTreeCandidateNode cleanUpTree(final TerminalDataTreeCandidateNode finalNode,
207 final TerminalDataTreeCandidateNode node) {
208 PathArgument identifier = node.getIdentifier();
209 ModificationType nodeModification = node.getModificationType();
210 Collection<DataTreeCandidateNode> childNodes = node.getChildNodes();
211 for (Iterator<DataTreeCandidateNode> iterator = childNodes.iterator(); iterator.hasNext(); ) {
212 cleanUpTree(finalNode, (TerminalDataTreeCandidateNode) iterator.next());
214 Optional<NormalizedNode> dataBefore = finalNode.getDataBefore(identifier);
216 switch (nodeModification) {
218 finalNode.deleteNode(identifier);
223 if (dataBefore.isEmpty()) {
224 finalNode.deleteNode(identifier);
228 if (dataBefore.isPresent()) {
229 illegalModification(ModificationType.APPEARED, ModificationType.WRITE);
231 if (childNodes.isEmpty()) {
232 finalNode.deleteNode(identifier);
236 if (dataBefore.isEmpty() || childNodes.isEmpty()) {
237 finalNode.deleteNode(identifier);
240 case SUBTREE_MODIFIED:
241 if (dataBefore.isEmpty()) {
242 illegalModification(ModificationType.SUBTREE_MODIFIED, ModificationType.DELETE);
244 if (childNodes.isEmpty()) {
245 finalNode.deleteNode(identifier);
249 throw new IllegalStateException("Unsupported modification type " + nodeModification);
253 private static ModificationType compressModifications(final ModificationType firstModification,
254 final ModificationType secondModification,
255 final boolean hasNoDataBefore) {
256 switch (firstModification) {
258 if (hasNoDataBefore) {
259 switch (secondModification) {
263 return secondModification;
265 return illegalModification(ModificationType.DELETE, ModificationType.DELETE);
266 case SUBTREE_MODIFIED:
267 return illegalModification(ModificationType.SUBTREE_MODIFIED, ModificationType.DELETE);
269 return illegalModification(ModificationType.DISAPPEARED, ModificationType.DELETE);
271 throw new IllegalStateException("Unsupported modification type " + secondModification);
274 if (secondModification == ModificationType.APPEARED) {
275 return illegalModification(ModificationType.APPEARED, ModificationType.WRITE);
277 return secondModification;
279 switch (secondModification) {
282 case SUBTREE_MODIFIED:
283 return ModificationType.WRITE;
285 return ModificationType.DELETE;
287 return ModificationType.DISAPPEARED;
289 return illegalModification(ModificationType.APPEARED, firstModification);
291 throw new IllegalStateException("Unsupported modification type " + secondModification);
294 switch (secondModification) {
296 return ModificationType.DELETE;
299 return ModificationType.WRITE;
301 return illegalModification(ModificationType.DELETE, firstModification);
303 return illegalModification(ModificationType.DISAPPEARED, firstModification);
304 case SUBTREE_MODIFIED:
305 return illegalModification(ModificationType.SUBTREE_MODIFIED, firstModification);
307 throw new IllegalStateException("Unsupported modification type " + secondModification);
310 switch (secondModification) {
312 case SUBTREE_MODIFIED:
313 return ModificationType.APPEARED;
316 return ModificationType.UNMODIFIED;
318 return ModificationType.WRITE;
320 return illegalModification(ModificationType.APPEARED, firstModification);
322 throw new IllegalStateException("Unsupported modification type " + secondModification);
325 switch (secondModification) {
328 return secondModification;
330 return ModificationType.SUBTREE_MODIFIED;
332 return illegalModification(ModificationType.DELETE, firstModification);
334 return illegalModification(ModificationType.DISAPPEARED, firstModification);
336 case SUBTREE_MODIFIED:
337 return illegalModification(ModificationType.SUBTREE_MODIFIED, firstModification);
339 throw new IllegalStateException("Unsupported modification type " + secondModification);
341 case SUBTREE_MODIFIED:
342 switch (secondModification) {
344 case SUBTREE_MODIFIED:
345 return ModificationType.SUBTREE_MODIFIED;
349 return secondModification;
351 return illegalModification(ModificationType.APPEARED, firstModification);
353 throw new IllegalStateException("Unsupported modification type " + secondModification);
356 throw new IllegalStateException("Unsupported modification type " + secondModification);
360 private static ModificationType illegalModification(final ModificationType first, final ModificationType second) {
361 throw new IllegalArgumentException(first + " modification event on " + second + " node");
364 private static void applyToCursorAwareModification(final CursorAwareDataTreeModification modification,
365 final DataTreeCandidate candidate) {
366 final YangInstanceIdentifier candidatePath = candidate.getRootPath();
367 final YangInstanceIdentifier parent = candidatePath.getParent();
368 if (parent == null) {
369 try (DataTreeModificationCursor cursor = modification.openCursor()) {
370 DataTreeCandidateNodes.applyRootToCursor(cursor, candidate.getRootNode());
373 try (DataTreeModificationCursor cursor = modification.openCursor(parent).orElseThrow()) {
374 DataTreeCandidateNodes.applyRootedNodeToCursor(cursor, candidatePath, candidate.getRootNode());
379 private static final class NodeIterator {
380 private final Iterator<DataTreeCandidateNode> iterator;
381 private final YangInstanceIdentifier path;
382 private final NodeIterator parent;
384 NodeIterator(final @Nullable NodeIterator parent, final YangInstanceIdentifier path,
385 final Iterator<DataTreeCandidateNode> iterator) {
386 this.iterator = requireNonNull(iterator);
387 this.path = requireNonNull(path);
388 this.parent = parent;
391 NodeIterator next(final DataTreeModification modification) {
392 while (iterator.hasNext()) {
393 final DataTreeCandidateNode node = iterator.next();
394 final YangInstanceIdentifier child = path.node(node.getIdentifier());
396 switch (node.getModificationType()) {
398 modification.delete(child);
399 LOG.debug("Modification {} deleted path {}", modification, child);
403 case SUBTREE_MODIFIED:
404 LOG.debug("Modification {} modified path {}", modification, child);
405 return new NodeIterator(this, child, node.getChildNodes().iterator());
407 LOG.debug("Modification {} unmodified path {}", modification, child);
411 modification.write(child, node.getDataAfter().get());
412 LOG.debug("Modification {} written path {}", modification, child);
415 throw new IllegalArgumentException("Unsupported modification " + node.getModificationType());