Move NormalizedNode builders
[yangtools.git] / yang / yang-data-api / src / main / java / org / opendaylight / yangtools / yang / data / api / schema / tree / DataTreeCandidates.java
index 441f92776373383156f78479ff9838a91e578cc1..bcd8ff80b024c36fa4f99b65193b842bb4ba400e 100644 (file)
  */
 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 java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+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;
 
 /**
  * Utility class holding methods useful when dealing with {@link DataTreeCandidate} instances.
  */
 @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));
     }
 
-    public static void applyToModification(final DataTreeModification modification, final DataTreeCandidate candidate) {
-        applyNode(modification, candidate.getRootPath(), candidate.getRootNode());
+    public static void applyToCursor(final DataTreeModificationCursor cursor, final DataTreeCandidate candidate) {
+        DataTreeCandidateNodes.applyToCursor(cursor, candidate.getRootNode());
     }
 
-    private static void applyNode(final DataTreeModification modification, final YangInstanceIdentifier path, final DataTreeCandidateNode node) {
+    public static void applyToModification(final DataTreeModification modification, final DataTreeCandidate candidate) {
+        if (modification instanceof CursorAwareDataTreeModification) {
+            applyToCursorAwareModification((CursorAwareDataTreeModification) modification, candidate);
+            return;
+        }
+
+        final DataTreeCandidateNode node = candidate.getRootNode();
+        final YangInstanceIdentifier path = candidate.getRootPath();
         switch (node.getModificationType()) {
-        case DELETE:
-            modification.delete(path);
-            break;
-        case SUBTREE_MODIFIED:
-            for (DataTreeCandidateNode child : node.getChildNodes()) {
-                applyNode(modification, path.node(child.getIdentifier()), child);
+            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 YangInstanceIdentifier candidatePath = candidate.getRootPath();
+        final YangInstanceIdentifier parent = candidatePath.getParent();
+        if (parent == null) {
+            try (DataTreeModificationCursor cursor = modification.openCursor()) {
+                DataTreeCandidateNodes.applyRootToCursor(cursor, candidate.getRootNode());
+            }
+        } else {
+            try (DataTreeModificationCursor cursor = modification.openCursor(parent).orElseThrow()) {
+                DataTreeCandidateNodes.applyRootedNodeToCursor(cursor, candidatePath, candidate.getRootNode());
+            }
+        }
+    }
+
+    private static final class NodeIterator {
+        private final Iterator<DataTreeCandidateNode> iterator;
+        private final YangInstanceIdentifier path;
+        private final NodeIterator parent;
+
+        NodeIterator(final @Nullable NodeIterator parent, final YangInstanceIdentifier path,
+                     final Iterator<DataTreeCandidateNode> iterator) {
+            this.iterator = requireNonNull(iterator);
+            this.path = requireNonNull(path);
+            this.parent = parent;
+        }
+
+        NodeIterator next(final DataTreeModification modification) {
+            while (iterator.hasNext()) {
+                final DataTreeCandidateNode node = iterator.next();
+                final YangInstanceIdentifier child = path.node(node.getIdentifier());
+
+                switch (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());
+                }
             }
-            break;
-        case UNMODIFIED:
-            // No-op
-            break;
-        case WRITE:
-            modification.write(path, node.getDataAfter().get());
-            break;
-        default:
-            throw new IllegalArgumentException("Unsupported modification " + node.getModificationType());
+            return parent;
         }
     }
 }