*/
package org.opendaylight.yangtools.yang.data.api.schema.tree;
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
+
import com.google.common.annotations.Beta;
-import com.google.common.base.Preconditions;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.Iterator;
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
+import java.util.List;
+import java.util.Optional;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Beta
public final class DataTreeCandidates {
private static final Logger LOG = LoggerFactory.getLogger(DataTreeCandidates.class);
+
private DataTreeCandidates() {
- throw new UnsupportedOperationException();
+ // Hidden on purpose
}
- public static DataTreeCandidate newDataTreeCandidate(final YangInstanceIdentifier rootPath, final DataTreeCandidateNode rootNode) {
+ public static @NonNull DataTreeCandidate newDataTreeCandidate(final YangInstanceIdentifier rootPath,
+ final DataTreeCandidateNode rootNode) {
return new DefaultDataTreeCandidate(rootPath, rootNode);
}
- public static DataTreeCandidate fromNormalizedNode(final YangInstanceIdentifier rootPath, final NormalizedNode<?, ?> node) {
+ public static @NonNull DataTreeCandidate fromNormalizedNode(final YangInstanceIdentifier rootPath,
+ final NormalizedNode node) {
return new DefaultDataTreeCandidate(rootPath, new NormalizedNodeDataTreeCandidateNode(node));
}
final DataTreeCandidateNode node = candidate.getRootNode();
final YangInstanceIdentifier path = candidate.getRootPath();
switch (node.getModificationType()) {
- case DELETE:
- modification.delete(path);
- LOG.debug("Modification {} deleted path {}", modification, path);
- break;
- case SUBTREE_MODIFIED:
- LOG.debug("Modification {} modified path {}", modification, path);
-
- NodeIterator iterator = new NodeIterator(null, path, node.getChildNodes().iterator());
- do {
- iterator = iterator.next(modification);
- } while (iterator != null);
- break;
- case UNMODIFIED:
- LOG.debug("Modification {} unmodified path {}", modification, path);
- // No-op
- break;
- case WRITE:
- modification.write(path, node.getDataAfter().get());
- LOG.debug("Modification {} written path {}", modification, path);
- break;
- default:
- throw new IllegalArgumentException("Unsupported modification " + node.getModificationType());
+ case DELETE:
+ modification.delete(path);
+ LOG.debug("Modification {} deleted path {}", modification, path);
+ break;
+ case SUBTREE_MODIFIED:
+ LOG.debug("Modification {} modified path {}", modification, path);
+
+ NodeIterator iterator = new NodeIterator(null, path, node.getChildNodes().iterator());
+ do {
+ iterator = iterator.next(modification);
+ } while (iterator != null);
+ break;
+ case UNMODIFIED:
+ LOG.debug("Modification {} unmodified path {}", modification, path);
+ // No-op
+ break;
+ case WRITE:
+ modification.write(path, node.getDataAfter().get());
+ LOG.debug("Modification {} written path {}", modification, path);
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported modification " + node.getModificationType());
+ }
+ }
+
+ /**
+ * Compress a list of DataTreeCandidates into a single DataTreeCandidate. The resulting candidate is a summarization
+ * of changes recorded in the input candidates.
+ *
+ * @param candidates Input list, must be non-empty
+ * @return Summarized DataTreeCandidate
+ * @throws IllegalArgumentException if candidates is empty, or contains candidates with mismatched root path
+ * @throws NullPointerException if {@code candidates} is null or contains a null entry
+ */
+ public static @NonNull DataTreeCandidate aggregate(@NonNull final List<? extends DataTreeCandidate> candidates) {
+ final Iterator<? extends DataTreeCandidate> it = candidates.iterator();
+ checkArgument(it.hasNext(), "Input must not be empty");
+ final DataTreeCandidate first = requireNonNull(it.next(), "Input must not contain null entries");
+ if (!it.hasNext()) {
+ // Short-circuit
+ return first;
+ }
+ final YangInstanceIdentifier rootPath = first.getRootPath();
+ final List<DataTreeCandidateNode> roots = new ArrayList<>(candidates.size());
+ roots.add(first.getRootNode());
+ it.forEachRemaining(candidate -> {
+ final YangInstanceIdentifier root = candidate.getRootPath();
+ checkArgument(rootPath.equals(root), "Expecting root path %s, encountered %s", rootPath, root);
+ roots.add(candidate.getRootNode());
+ });
+ return DataTreeCandidates.newDataTreeCandidate(rootPath, fastCompressNode(roots.get(0), roots));
+ }
+
+ private static DataTreeCandidateNode fastCompressNode(final DataTreeCandidateNode first,
+ final List<DataTreeCandidateNode> input) {
+ final DataTreeCandidateNode last = input.get(input.size() - 1);
+ ModificationType nodeModification = last.getModificationType();
+ Optional<NormalizedNode> dataBefore = first.getDataBefore();
+ Optional<NormalizedNode> dataAfter = last.getDataAfter();
+ switch (nodeModification) {
+ case DELETE:
+ ModificationType previous = first.getModificationType();
+ // Check if node had data before
+ if (previous == ModificationType.DELETE
+ || previous == ModificationType.DISAPPEARED
+ || previous == ModificationType.UNMODIFIED && dataBefore.isEmpty()) {
+ illegalModification(ModificationType.DELETE, ModificationType.DELETE);
+ }
+ if (dataBefore.isEmpty()) {
+ return new TerminalDataTreeCandidateNode(null, ModificationType.UNMODIFIED, null, null);
+ }
+ return new TerminalDataTreeCandidateNode(null, nodeModification, dataBefore.get(), null);
+ case WRITE:
+ return new TerminalDataTreeCandidateNode(null, nodeModification, dataBefore.orElse(null),
+ dataAfter.orElseThrow());
+ case APPEARED:
+ case DISAPPEARED:
+ case SUBTREE_MODIFIED:
+ case UNMODIFIED:
+ // No luck, we need to iterate
+ return slowCompressNodes(first, input);
+ default:
+ throw new IllegalStateException("Unsupported modification type " + nodeModification);
+ }
+ }
+
+ private static DataTreeCandidateNode slowCompressNodes(final DataTreeCandidateNode first,
+ final List<DataTreeCandidateNode> input) {
+ // finalNode contains summarized changes
+ TerminalDataTreeCandidateNode finalNode = new TerminalDataTreeCandidateNode(
+ null, first.getDataBefore().orElse(null));
+ input.forEach(node -> compressNode(finalNode, node, null));
+ finalNode.setAfter(input.get(input.size() - 1).getDataAfter().orElse(null));
+ return cleanUpTree(finalNode);
+ }
+
+ private static void compressNode(final TerminalDataTreeCandidateNode finalNode, final DataTreeCandidateNode node,
+ final PathArgument parent) {
+ PathArgument identifier;
+ try {
+ identifier = node.getIdentifier();
+ } catch (IllegalStateException e) {
+ identifier = null;
+ }
+ // Check if finalNode has stored any changes for node
+ if (finalNode.getNode(identifier).isEmpty()) {
+ TerminalDataTreeCandidateNode parentNode = finalNode.getNode(parent)
+ .orElseThrow(() -> new IllegalArgumentException("No node found for " + parent + " identifier"));
+ TerminalDataTreeCandidateNode childNode = new TerminalDataTreeCandidateNode(
+ identifier,
+ node.getDataBefore().orElse(null),
+ parentNode);
+ parentNode.addChildNode(childNode);
+ }
+
+ ModificationType nodeModification = node.getModificationType();
+ switch (nodeModification) {
+ case UNMODIFIED:
+ // If node is unmodified there is no need iterate through its child nodes
+ break;
+ case WRITE:
+ case DELETE:
+ case APPEARED:
+ case DISAPPEARED:
+ case SUBTREE_MODIFIED:
+ finalNode.setModification(identifier,
+ compressModifications(finalNode.getModification(identifier), nodeModification,
+ finalNode.getDataAfter(identifier).isEmpty()));
+ finalNode.setData(identifier, node.getDataAfter().orElse(null));
+
+ for (DataTreeCandidateNode child : node.getChildNodes()) {
+ compressNode(finalNode, child, identifier);
+ }
+ break;
+ default:
+ throw new IllegalStateException("Unsupported modification type " + nodeModification);
+ }
+ }
+
+ // Removes redundant changes
+ private static DataTreeCandidateNode cleanUpTree(final TerminalDataTreeCandidateNode finalNode) {
+ return cleanUpTree(finalNode, finalNode);
+ }
+
+ // Compare data before and after in order to find modified nodes without actual changes
+ private static DataTreeCandidateNode cleanUpTree(final TerminalDataTreeCandidateNode finalNode,
+ final TerminalDataTreeCandidateNode node) {
+ PathArgument identifier = node.getIdentifier();
+ ModificationType nodeModification = node.getModificationType();
+ Collection<DataTreeCandidateNode> childNodes = node.getChildNodes();
+ for (Iterator<DataTreeCandidateNode> iterator = childNodes.iterator(); iterator.hasNext(); ) {
+ cleanUpTree(finalNode, (TerminalDataTreeCandidateNode) iterator.next());
+ }
+ Optional<NormalizedNode> dataBefore = finalNode.getDataBefore(identifier);
+
+ switch (nodeModification) {
+ case UNMODIFIED:
+ finalNode.deleteNode(identifier);
+ return finalNode;
+ case WRITE:
+ return finalNode;
+ case DELETE:
+ if (dataBefore.isEmpty()) {
+ finalNode.deleteNode(identifier);
+ }
+ return finalNode;
+ case APPEARED:
+ if (dataBefore.isPresent()) {
+ illegalModification(ModificationType.APPEARED, ModificationType.WRITE);
+ }
+ if (childNodes.isEmpty()) {
+ finalNode.deleteNode(identifier);
+ }
+ return finalNode;
+ case DISAPPEARED:
+ if (dataBefore.isEmpty() || childNodes.isEmpty()) {
+ finalNode.deleteNode(identifier);
+ }
+ return finalNode;
+ case SUBTREE_MODIFIED:
+ if (dataBefore.isEmpty()) {
+ illegalModification(ModificationType.SUBTREE_MODIFIED, ModificationType.DELETE);
+ }
+ if (childNodes.isEmpty()) {
+ finalNode.deleteNode(identifier);
+ }
+ return finalNode;
+ default:
+ throw new IllegalStateException("Unsupported modification type " + nodeModification);
+ }
+ }
+
+ private static ModificationType compressModifications(final ModificationType firstModification,
+ final ModificationType secondModification,
+ final boolean hasNoDataBefore) {
+ switch (firstModification) {
+ case UNMODIFIED:
+ if (hasNoDataBefore) {
+ switch (secondModification) {
+ case UNMODIFIED:
+ case WRITE:
+ case APPEARED:
+ return secondModification;
+ case DELETE:
+ return illegalModification(ModificationType.DELETE, ModificationType.DELETE);
+ case SUBTREE_MODIFIED:
+ return illegalModification(ModificationType.SUBTREE_MODIFIED, ModificationType.DELETE);
+ case DISAPPEARED:
+ return illegalModification(ModificationType.DISAPPEARED, ModificationType.DELETE);
+ default:
+ throw new IllegalStateException("Unsupported modification type " + secondModification);
+ }
+ }
+ if (secondModification == ModificationType.APPEARED) {
+ return illegalModification(ModificationType.APPEARED, ModificationType.WRITE);
+ }
+ return secondModification;
+ case WRITE:
+ switch (secondModification) {
+ case UNMODIFIED:
+ case WRITE:
+ case SUBTREE_MODIFIED:
+ return ModificationType.WRITE;
+ case DELETE:
+ return ModificationType.DELETE;
+ case DISAPPEARED:
+ return ModificationType.DISAPPEARED;
+ case APPEARED:
+ return illegalModification(ModificationType.APPEARED, firstModification);
+ default:
+ throw new IllegalStateException("Unsupported modification type " + secondModification);
+ }
+ case DELETE:
+ switch (secondModification) {
+ case UNMODIFIED:
+ return ModificationType.DELETE;
+ case WRITE:
+ case APPEARED:
+ return ModificationType.WRITE;
+ case DELETE:
+ return illegalModification(ModificationType.DELETE, firstModification);
+ case DISAPPEARED:
+ return illegalModification(ModificationType.DISAPPEARED, firstModification);
+ case SUBTREE_MODIFIED:
+ return illegalModification(ModificationType.SUBTREE_MODIFIED, firstModification);
+ default:
+ throw new IllegalStateException("Unsupported modification type " + secondModification);
+ }
+ case APPEARED:
+ switch (secondModification) {
+ case UNMODIFIED:
+ case SUBTREE_MODIFIED:
+ return ModificationType.APPEARED;
+ case DELETE:
+ case DISAPPEARED:
+ return ModificationType.UNMODIFIED;
+ case WRITE:
+ return ModificationType.WRITE;
+ case APPEARED:
+ return illegalModification(ModificationType.APPEARED, firstModification);
+ default:
+ throw new IllegalStateException("Unsupported modification type " + secondModification);
+ }
+ case DISAPPEARED:
+ switch (secondModification) {
+ case UNMODIFIED:
+ case WRITE:
+ return secondModification;
+ case APPEARED:
+ return ModificationType.SUBTREE_MODIFIED;
+ case DELETE:
+ return illegalModification(ModificationType.DELETE, firstModification);
+ case DISAPPEARED:
+ return illegalModification(ModificationType.DISAPPEARED, firstModification);
+
+ case SUBTREE_MODIFIED:
+ return illegalModification(ModificationType.SUBTREE_MODIFIED, firstModification);
+ default:
+ throw new IllegalStateException("Unsupported modification type " + secondModification);
+ }
+ case SUBTREE_MODIFIED:
+ switch (secondModification) {
+ case UNMODIFIED:
+ case SUBTREE_MODIFIED:
+ return ModificationType.SUBTREE_MODIFIED;
+ case WRITE:
+ case DELETE:
+ case DISAPPEARED:
+ return secondModification;
+ case APPEARED:
+ return illegalModification(ModificationType.APPEARED, firstModification);
+ default:
+ throw new IllegalStateException("Unsupported modification type " + secondModification);
+ }
+ default:
+ throw new IllegalStateException("Unsupported modification type " + secondModification);
}
}
+ private static ModificationType illegalModification(final ModificationType first, final ModificationType second) {
+ throw new IllegalArgumentException(first + " modification event on " + second + " node");
+ }
+
private static void applyToCursorAwareModification(final CursorAwareDataTreeModification modification,
- final DataTreeCandidate candidate) {
+ final DataTreeCandidate candidate) {
final YangInstanceIdentifier candidatePath = candidate.getRootPath();
- if (candidatePath.isEmpty()) {
- try (DataTreeModificationCursor cursor =
- ((CursorAwareDataTreeModification) modification).createCursor(candidatePath)) {
+ final YangInstanceIdentifier parent = candidatePath.getParent();
+ if (parent == null) {
+ try (DataTreeModificationCursor cursor = modification.openCursor()) {
DataTreeCandidateNodes.applyRootToCursor(cursor, candidate.getRootNode());
}
} else {
- try (DataTreeModificationCursor cursor =
- ((CursorAwareDataTreeModification) modification).createCursor(candidatePath.getParent())) {
- DataTreeCandidateNodes.applyToCursor(cursor, candidate.getRootNode());
+ try (DataTreeModificationCursor cursor = modification.openCursor(parent).orElseThrow()) {
+ DataTreeCandidateNodes.applyRootedNodeToCursor(cursor, candidatePath, candidate.getRootNode());
}
}
}
private final YangInstanceIdentifier path;
private final NodeIterator parent;
- public NodeIterator(@Nullable final NodeIterator parent, @Nonnull final YangInstanceIdentifier path,
- @Nonnull final Iterator<DataTreeCandidateNode> iterator) {
- this.iterator = Preconditions.checkNotNull(iterator);
- this.path = Preconditions.checkNotNull(path);
+ NodeIterator(final @Nullable NodeIterator parent, final YangInstanceIdentifier path,
+ final Iterator<DataTreeCandidateNode> iterator) {
+ this.iterator = requireNonNull(iterator);
+ this.path = requireNonNull(path);
this.parent = parent;
}
final YangInstanceIdentifier child = path.node(node.getIdentifier());
switch (node.getModificationType()) {
- case DELETE:
- modification.delete(child);
- LOG.debug("Modification {} deleted path {}", modification, child);
- break;
- case SUBTREE_MODIFIED:
- LOG.debug("Modification {} modified path {}", modification, child);
- return new NodeIterator(this, child, node.getChildNodes().iterator());
- case UNMODIFIED:
- LOG.debug("Modification {} unmodified path {}", modification, child);
- // No-op
- break;
- case WRITE:
- modification.write(child, node.getDataAfter().get());
- LOG.debug("Modification {} written path {}", modification, child);
- break;
- default:
- throw new IllegalArgumentException("Unsupported modification " + node.getModificationType());
+ case DELETE:
+ modification.delete(child);
+ LOG.debug("Modification {} deleted path {}", modification, child);
+ break;
+ case APPEARED:
+ case DISAPPEARED:
+ case SUBTREE_MODIFIED:
+ LOG.debug("Modification {} modified path {}", modification, child);
+ return new NodeIterator(this, child, node.getChildNodes().iterator());
+ case UNMODIFIED:
+ LOG.debug("Modification {} unmodified path {}", modification, child);
+ // No-op
+ break;
+ case WRITE:
+ modification.write(child, node.getDataAfter().get());
+ LOG.debug("Modification {} written path {}", modification, child);
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported modification " + node.getModificationType());
}
}
-
return parent;
}
}