+ 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);