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.Preconditions.checkArgument;
11 import static com.google.common.base.Verify.verifyNotNull;
12 import static java.util.Objects.requireNonNull;
14 import com.google.common.annotations.Beta;
15 import java.util.ArrayList;
16 import java.util.Iterator;
17 import java.util.List;
18 import org.eclipse.jdt.annotation.NonNull;
19 import org.eclipse.jdt.annotation.Nullable;
20 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
21 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
22 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
23 import org.opendaylight.yangtools.yang.data.tree.api.CursorAwareDataTreeModification;
24 import org.opendaylight.yangtools.yang.data.tree.api.DataTreeCandidate;
25 import org.opendaylight.yangtools.yang.data.tree.api.DataTreeCandidateNode;
26 import org.opendaylight.yangtools.yang.data.tree.api.DataTreeModification;
27 import org.opendaylight.yangtools.yang.data.tree.api.DataTreeModificationCursor;
28 import org.opendaylight.yangtools.yang.data.tree.api.ModificationType;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
33 * Utility class holding methods useful when dealing with {@link DataTreeCandidate} instances.
36 public final class DataTreeCandidates {
37 private static final Logger LOG = LoggerFactory.getLogger(DataTreeCandidates.class);
39 private DataTreeCandidates() {
43 public static @NonNull DataTreeCandidate newDataTreeCandidate(final YangInstanceIdentifier rootPath,
44 final DataTreeCandidateNode rootNode) {
45 return new DefaultDataTreeCandidate(rootPath, rootNode);
48 public static @NonNull DataTreeCandidate fromNormalizedNode(final YangInstanceIdentifier rootPath,
49 final NormalizedNode node) {
50 return new DefaultDataTreeCandidate(rootPath, new NormalizedNodeDataTreeCandidateNode(node));
53 public static void applyToCursor(final DataTreeModificationCursor cursor, final DataTreeCandidate candidate) {
54 DataTreeCandidateNodes.applyToCursor(cursor, candidate.getRootNode());
57 public static void applyToModification(final DataTreeModification modification, final DataTreeCandidate candidate) {
58 if (modification instanceof CursorAwareDataTreeModification) {
59 applyToCursorAwareModification((CursorAwareDataTreeModification) modification, candidate);
63 final DataTreeCandidateNode node = candidate.getRootNode();
64 final YangInstanceIdentifier path = candidate.getRootPath();
65 switch (node.modificationType()) {
67 modification.delete(path);
68 LOG.debug("Modification {} deleted path {}", modification, path);
70 case SUBTREE_MODIFIED:
71 LOG.debug("Modification {} modified path {}", modification, path);
73 NodeIterator iterator = new NodeIterator(null, path, node.childNodes().iterator());
75 iterator = iterator.next(modification);
76 } while (iterator != null);
79 LOG.debug("Modification {} unmodified path {}", modification, path);
83 modification.write(path, verifyNotNull(node.dataAfter()));
84 LOG.debug("Modification {} written path {}", modification, path);
87 throw new IllegalArgumentException("Unsupported modification " + node.modificationType());
92 * Compress a list of DataTreeCandidates into a single DataTreeCandidate. The resulting candidate is a summarization
93 * of changes recorded in the input candidates.
95 * @param candidates Input list, must be non-empty
96 * @return Summarized DataTreeCandidate
97 * @throws IllegalArgumentException if candidates is empty, or contains candidates with mismatched root path
98 * @throws NullPointerException if {@code candidates} is null or contains a null entry
100 public static @NonNull DataTreeCandidate aggregate(@NonNull final List<? extends DataTreeCandidate> candidates) {
101 final Iterator<? extends DataTreeCandidate> it = candidates.iterator();
102 checkArgument(it.hasNext(), "Input must not be empty");
103 final DataTreeCandidate first = requireNonNull(it.next(), "Input must not contain null entries");
108 final YangInstanceIdentifier rootPath = first.getRootPath();
109 final List<DataTreeCandidateNode> roots = new ArrayList<>(candidates.size());
110 roots.add(first.getRootNode());
111 it.forEachRemaining(candidate -> {
112 final YangInstanceIdentifier root = candidate.getRootPath();
113 checkArgument(rootPath.equals(root), "Expecting root path %s, encountered %s", rootPath, root);
114 roots.add(candidate.getRootNode());
116 return DataTreeCandidates.newDataTreeCandidate(rootPath, fastCompressNode(roots.get(0), roots));
119 private static DataTreeCandidateNode fastCompressNode(final DataTreeCandidateNode first,
120 final List<DataTreeCandidateNode> input) {
121 final var last = input.get(input.size() - 1);
122 final var nodeModification = last.modificationType();
123 final var dataBefore = first.dataBefore();
124 final var dataAfter = last.dataAfter();
125 switch (nodeModification) {
127 ModificationType previous = first.modificationType();
128 // Check if node had data before
129 if (previous == ModificationType.DELETE || previous == ModificationType.DISAPPEARED
130 || previous == ModificationType.UNMODIFIED && dataBefore == null) {
131 illegalModification(ModificationType.DELETE, ModificationType.DELETE);
133 if (dataBefore == null) {
134 return new TerminalDataTreeCandidateNode(null, ModificationType.UNMODIFIED, null, null);
136 return new TerminalDataTreeCandidateNode(null, nodeModification, verifyNotNull(dataBefore), null);
138 return new TerminalDataTreeCandidateNode(null, nodeModification, dataBefore, verifyNotNull(dataAfter));
141 case SUBTREE_MODIFIED:
143 // No luck, we need to iterate
144 return slowCompressNodes(first, input);
146 throw new IllegalStateException("Unsupported modification type " + nodeModification);
150 private static DataTreeCandidateNode slowCompressNodes(final DataTreeCandidateNode first,
151 final List<DataTreeCandidateNode> input) {
152 // finalNode contains summarized changes
153 TerminalDataTreeCandidateNode finalNode = new TerminalDataTreeCandidateNode(null, first.dataBefore());
154 input.forEach(node -> compressNode(finalNode, node, null));
155 finalNode.setAfter(input.get(input.size() - 1).dataAfter());
156 return cleanUpTree(finalNode);
159 private static void compressNode(final TerminalDataTreeCandidateNode finalNode, final DataTreeCandidateNode node,
160 final PathArgument parent) {
161 PathArgument identifier;
163 identifier = node.name();
164 } catch (IllegalStateException e) {
167 // Check if finalNode has stored any changes for node
168 if (finalNode.getNode(identifier).isEmpty()) {
169 TerminalDataTreeCandidateNode parentNode = finalNode.getNode(parent)
170 .orElseThrow(() -> new IllegalArgumentException("No node found for " + parent + " identifier"));
171 TerminalDataTreeCandidateNode childNode = new TerminalDataTreeCandidateNode(
175 parentNode.addChildNode(childNode);
178 ModificationType nodeModification = node.modificationType();
179 switch (nodeModification) {
181 // If node is unmodified there is no need iterate through its child nodes
187 case SUBTREE_MODIFIED:
188 finalNode.setModification(identifier,
189 compressModifications(finalNode.getModification(identifier), nodeModification,
190 finalNode.dataAfter(identifier) == null));
191 finalNode.setData(identifier, node.dataAfter());
193 for (DataTreeCandidateNode child : node.childNodes()) {
194 compressNode(finalNode, child, identifier);
198 throw new IllegalStateException("Unsupported modification type " + nodeModification);
202 // Removes redundant changes
203 private static DataTreeCandidateNode cleanUpTree(final TerminalDataTreeCandidateNode finalNode) {
204 return cleanUpTree(finalNode, finalNode);
207 // Compare data before and after in order to find modified nodes without actual changes
208 private static DataTreeCandidateNode cleanUpTree(final TerminalDataTreeCandidateNode finalNode,
209 final TerminalDataTreeCandidateNode node) {
210 final var identifier = node.name();
211 final var nodeModification = node.modificationType();
212 final var childNodes = node.childNodes();
213 for (var childNode : childNodes) {
214 cleanUpTree(finalNode, (TerminalDataTreeCandidateNode) childNode);
216 final var dataBefore = finalNode.dataBefore(identifier);
218 switch (nodeModification) {
220 finalNode.deleteNode(identifier);
225 if (dataBefore == null) {
226 finalNode.deleteNode(identifier);
230 if (dataBefore != null) {
231 illegalModification(ModificationType.APPEARED, ModificationType.WRITE);
233 if (childNodes.isEmpty()) {
234 finalNode.deleteNode(identifier);
238 if (dataBefore == null || childNodes.isEmpty()) {
239 finalNode.deleteNode(identifier);
242 case SUBTREE_MODIFIED:
243 if (dataBefore == null) {
244 illegalModification(ModificationType.SUBTREE_MODIFIED, ModificationType.DELETE);
246 if (childNodes.isEmpty()) {
247 finalNode.deleteNode(identifier);
251 throw new IllegalStateException("Unsupported modification type " + nodeModification);
255 private static ModificationType compressModifications(final ModificationType firstModification,
256 final ModificationType secondModification,
257 final boolean hasNoDataBefore) {
258 switch (firstModification) {
260 if (hasNoDataBefore) {
261 return switch (secondModification) {
262 case UNMODIFIED, WRITE, APPEARED -> secondModification;
263 case DELETE -> illegalModification(ModificationType.DELETE, ModificationType.DELETE);
264 case SUBTREE_MODIFIED ->
265 illegalModification(ModificationType.SUBTREE_MODIFIED, ModificationType.DELETE);
266 case DISAPPEARED -> illegalModification(ModificationType.DISAPPEARED, ModificationType.DELETE);
269 if (secondModification == ModificationType.APPEARED) {
270 return illegalModification(ModificationType.APPEARED, ModificationType.WRITE);
272 return secondModification;
274 return switch (secondModification) {
275 case UNMODIFIED, WRITE, SUBTREE_MODIFIED -> ModificationType.WRITE;
276 case DELETE -> ModificationType.DELETE;
277 case DISAPPEARED -> ModificationType.DISAPPEARED;
278 case APPEARED -> illegalModification(ModificationType.APPEARED, firstModification);
281 return switch (secondModification) {
282 case UNMODIFIED -> ModificationType.DELETE;
283 case WRITE, APPEARED -> ModificationType.WRITE;
284 case DELETE -> illegalModification(ModificationType.DELETE, firstModification);
285 case DISAPPEARED -> illegalModification(ModificationType.DISAPPEARED, firstModification);
286 case SUBTREE_MODIFIED -> illegalModification(ModificationType.SUBTREE_MODIFIED, firstModification);
289 return switch (secondModification) {
290 case UNMODIFIED, SUBTREE_MODIFIED -> ModificationType.APPEARED;
291 case DELETE, DISAPPEARED -> ModificationType.UNMODIFIED;
292 case WRITE -> ModificationType.WRITE;
293 case APPEARED -> illegalModification(ModificationType.APPEARED, firstModification);
296 return switch (secondModification) {
297 case UNMODIFIED, WRITE -> secondModification;
298 case APPEARED -> ModificationType.SUBTREE_MODIFIED;
299 case DELETE -> illegalModification(ModificationType.DELETE, firstModification);
300 case DISAPPEARED -> illegalModification(ModificationType.DISAPPEARED, firstModification);
301 case SUBTREE_MODIFIED -> illegalModification(ModificationType.SUBTREE_MODIFIED, firstModification);
303 case SUBTREE_MODIFIED:
304 return switch (secondModification) {
305 case UNMODIFIED, SUBTREE_MODIFIED -> ModificationType.SUBTREE_MODIFIED;
306 case WRITE, DELETE, DISAPPEARED -> secondModification;
307 case APPEARED -> illegalModification(ModificationType.APPEARED, firstModification);
310 throw new IllegalStateException("Unsupported modification type " + firstModification);
314 private static ModificationType illegalModification(final ModificationType first, final ModificationType second) {
315 throw new IllegalArgumentException(first + " modification event on " + second + " node");
318 private static void applyToCursorAwareModification(final CursorAwareDataTreeModification modification,
319 final DataTreeCandidate candidate) {
320 final YangInstanceIdentifier candidatePath = candidate.getRootPath();
321 final YangInstanceIdentifier parent = candidatePath.getParent();
322 if (parent == null) {
323 try (DataTreeModificationCursor cursor = modification.openCursor()) {
324 DataTreeCandidateNodes.applyRootToCursor(cursor, candidate.getRootNode());
327 try (DataTreeModificationCursor cursor = modification.openCursor(parent).orElseThrow()) {
328 DataTreeCandidateNodes.applyRootedNodeToCursor(cursor, candidatePath, candidate.getRootNode());
333 private static final class NodeIterator {
334 private final Iterator<DataTreeCandidateNode> iterator;
335 private final YangInstanceIdentifier path;
336 private final NodeIterator parent;
338 NodeIterator(final @Nullable NodeIterator parent, final YangInstanceIdentifier path,
339 final Iterator<DataTreeCandidateNode> iterator) {
340 this.iterator = requireNonNull(iterator);
341 this.path = requireNonNull(path);
342 this.parent = parent;
345 NodeIterator next(final DataTreeModification modification) {
346 while (iterator.hasNext()) {
347 final DataTreeCandidateNode node = iterator.next();
348 final YangInstanceIdentifier child = path.node(node.name());
350 switch (node.modificationType()) {
352 modification.delete(child);
353 LOG.debug("Modification {} deleted path {}", modification, child);
357 case SUBTREE_MODIFIED:
358 LOG.debug("Modification {} modified path {}", modification, child);
359 return new NodeIterator(this, child, node.childNodes().iterator());
361 LOG.debug("Modification {} unmodified path {}", modification, child);
365 modification.write(child, verifyNotNull(node.dataAfter()));
366 LOG.debug("Modification {} written path {}", modification, child);
369 throw new IllegalArgumentException("Unsupported modification " + node.modificationType());