Move NormalizedNode builders
[yangtools.git] / yang / yang-data-api / src / main / java / org / opendaylight / yangtools / yang / data / api / schema / tree / DataTreeCandidateNodes.java
index 2b133a8765d36de32c30fbd645ce24b5734e044c..6671cba32fe3d5f6c7026813c6939697c6ccfef1 100644 (file)
@@ -10,33 +10,135 @@ package org.opendaylight.yangtools.yang.data.api.schema.tree;
 import static java.util.Objects.requireNonNull;
 
 import com.google.common.annotations.Beta;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Iterator;
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
+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.DistinctNodeContainer;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
 
 @Beta
 public final class DataTreeCandidateNodes {
     private DataTreeCandidateNodes() {
-        throw new UnsupportedOperationException();
+
     }
 
     /**
      * Return an empty {@link DataTreeCandidateNode} identified by specified {@link PathArgument}.
+     *
      * @param identifier Node identifier
      * @return An empty DataTreeCandidateNode
      */
-    public static DataTreeCandidateNode empty(final PathArgument identifier) {
+    public static @NonNull DataTreeCandidateNode empty(final PathArgument identifier) {
         return new EmptyDataTreeCandidateNode(identifier);
     }
 
-    public static DataTreeCandidateNode fromNormalizedNode(final NormalizedNode<?, ?> node) {
+    /**
+     * Return an unmodified {@link DataTreeCandidateNode} identified by specified {@link NormalizedNode}.
+     *
+     * @param node Unchanged normalized node
+     * @return An empty DataTreeCandidateNode
+     */
+    public static @NonNull DataTreeCandidateNode unmodified(final NormalizedNode node) {
+        if (node instanceof DistinctNodeContainer) {
+            return new RecursiveUnmodifiedCandidateNode((DistinctNodeContainer<PathArgument, NormalizedNode>) node);
+        }
+        return new UnmodifiedLeafCandidateNode(node);
+    }
+
+    /**
+     * Return a {@link DataTreeCandidateNode} pretending specified node was written without the data exsting beforehand.
+     *
+     * @param node Unchanged normalized node
+     * @return An empty DataTreeCandidateNode
+     * @throws NullPointerException if {@code node} is null
+     */
+    public static @NonNull DataTreeCandidateNode written(final NormalizedNode node) {
         return new NormalizedNodeDataTreeCandidateNode(node);
     }
 
+    /**
+     * Return a collection of {@link DataTreeCandidateNode}s summarizing the changes between the contents of two
+     * {@link NormalizedNodeContainer}s.
+     *
+     * @param oldData Old data container, may be null
+     * @param newData New data container, may be null
+     * @return Collection of changes
+     */
+    public static @NonNull Collection<DataTreeCandidateNode> containerDelta(
+            final @Nullable DistinctNodeContainer<PathArgument, NormalizedNode> oldData,
+            final @Nullable DistinctNodeContainer<PathArgument, NormalizedNode> newData) {
+        if (newData == null) {
+            return oldData == null ? ImmutableList.of()
+                    : Collections2.transform(oldData.body(), DataTreeCandidateNodes::deleteNode);
+        }
+        if (oldData == null) {
+            return Collections2.transform(newData.body(), DataTreeCandidateNodes::writeNode);
+        }
+
+        /*
+         * This is slightly inefficient, as it requires N*F(M)+M*F(N) lookup operations, where
+         * F is dependent on the implementation of NormalizedNodeContainer.getChild().
+         *
+         * We build the return collection by iterating over new data and looking each child up
+         * in old data. Based on that we construct replaced/written nodes. We then proceed to
+         * iterate over old data and looking up each child in new data.
+         */
+        final Collection<DataTreeCandidateNode> result = new ArrayList<>();
+        for (NormalizedNode child : newData.body()) {
+            final DataTreeCandidateNode node;
+            final NormalizedNode oldChild = oldData.childByArg(child.getIdentifier());
+            if (oldChild != null) {
+                // This does not find children which have not in fact been modified, as doing that
+                // reliably would require us running a full equals() on the two nodes.
+                node = replaceNode(oldChild, child);
+            } else {
+                node = writeNode(child);
+            }
+
+            result.add(node);
+        }
+
+        // Process removals next, looking into new data to see if we processed it
+        for (NormalizedNode child : oldData.body()) {
+            if (newData.childByArg(child.getIdentifier()) == null) {
+                result.add(deleteNode(child));
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Return a collection of {@link DataTreeCandidateNode}s summarizing the change in a child, identified by a
+     * {@link PathArgument}, between two {@link NormalizedNodeContainer}s.
+     *
+     * @param oldData Old data container, may be null
+     * @param newData New data container, may be null
+     * @return A {@link DataTreeCandidateNode} describing the change, or empty if the node is not present
+     */
+    public static @NonNull Optional<DataTreeCandidateNode> containerDelta(
+            final @Nullable DistinctNodeContainer<PathArgument, NormalizedNode> oldData,
+            final @Nullable DistinctNodeContainer<PathArgument, NormalizedNode> newData,
+            final @NonNull PathArgument child) {
+        final NormalizedNode newChild = getChild(newData, child);
+        final NormalizedNode oldChild = getChild(oldData, child);
+        if (oldChild != null) {
+            return Optional.of(newChild != null ? replaceNode(oldChild, newChild) : deleteNode(oldChild));
+        } else if (newChild != null) {
+            return Optional.of(DataTreeCandidateNodes.writeNode(newChild));
+        } else {
+            return Optional.empty();
+        }
+    }
+
     /**
      * Applies the {@code node} to the {@code cursor}, note that if the top node of (@code node} is RootNode
      * you need to use {@link #applyRootedNodeToCursor(DataTreeModificationCursor, YangInstanceIdentifier,
@@ -119,6 +221,40 @@ public final class DataTreeCandidateNodes {
         }
     }
 
+    private static @Nullable NormalizedNode getChild(
+            final DistinctNodeContainer<PathArgument, ?> container, final PathArgument identifier) {
+        return container == null ? null : container.childByArg(identifier);
+    }
+
+    @SuppressWarnings("unchecked")
+    private static @NonNull DataTreeCandidateNode deleteNode(final NormalizedNode data) {
+        if (data instanceof NormalizedNodeContainer) {
+            return new RecursiveDeleteCandidateNode(
+                (DistinctNodeContainer<PathArgument, NormalizedNode>) data);
+        }
+        return new DeleteLeafCandidateNode(data);
+    }
+
+
+    @SuppressWarnings("unchecked")
+    private static @NonNull DataTreeCandidateNode replaceNode(final NormalizedNode oldData,
+            final NormalizedNode newData) {
+        if (oldData instanceof DistinctNodeContainer) {
+            return new RecursiveReplaceCandidateNode(
+                (DistinctNodeContainer<PathArgument, NormalizedNode>) oldData,
+                (DistinctNodeContainer<PathArgument, NormalizedNode>) newData);
+        }
+        return new ReplaceLeafCandidateNode(oldData, newData);
+    }
+
+    @SuppressWarnings("unchecked")
+    private static @NonNull DataTreeCandidateNode writeNode(final NormalizedNode data) {
+        if (data instanceof DistinctNodeContainer) {
+            return new RecursiveWriteCandidateNode((DistinctNodeContainer<PathArgument, NormalizedNode>) data);
+        }
+        return new WriteLeafCandidateNode(data);
+    }
+
     private abstract static class AbstractNodeIterator {
         private final Iterator<DataTreeCandidateNode> iterator;
 
@@ -126,7 +262,7 @@ public final class DataTreeCandidateNodes {
             this.iterator = requireNonNull(iterator);
         }
 
-        AbstractNodeIterator next(final DataTreeModificationCursor cursor) {
+        final AbstractNodeIterator next(final DataTreeModificationCursor cursor) {
             while (iterator.hasNext()) {
                 final DataTreeCandidateNode node = iterator.next();
                 switch (node.getModificationType()) {
@@ -156,45 +292,42 @@ public final class DataTreeCandidateNodes {
             return getParent();
         }
 
-        protected abstract @Nullable AbstractNodeIterator getParent();
+        abstract @Nullable AbstractNodeIterator getParent();
 
-        protected abstract void exitNode(DataTreeModificationCursor cursor);
+        abstract void exitNode(DataTreeModificationCursor cursor);
     }
 
     private static final class RootNonExitingIterator extends AbstractNodeIterator {
-
-        protected RootNonExitingIterator(@Nonnull final Iterator<DataTreeCandidateNode> iterator) {
+        RootNonExitingIterator(final Iterator<DataTreeCandidateNode> iterator) {
             super(iterator);
         }
 
         @Override
-        protected void exitNode(final DataTreeModificationCursor cursor) {
+        void exitNode(final DataTreeModificationCursor cursor) {
             // Intentional noop.
         }
 
         @Override
-        protected AbstractNodeIterator getParent() {
+        AbstractNodeIterator getParent() {
             return null;
         }
     }
 
     private static final class ExitingNodeIterator extends AbstractNodeIterator {
-
         private final AbstractNodeIterator parent;
 
-        ExitingNodeIterator(@Nullable final AbstractNodeIterator parent,
-                @Nonnull final Iterator<DataTreeCandidateNode> iterator) {
+        ExitingNodeIterator(final AbstractNodeIterator parent, final Iterator<DataTreeCandidateNode> iterator) {
             super(iterator);
             this.parent = parent;
         }
 
         @Override
-        protected AbstractNodeIterator getParent() {
+        AbstractNodeIterator getParent() {
             return parent;
         }
 
         @Override
-        protected void exitNode(final DataTreeModificationCursor cursor) {
+        void exitNode(final DataTreeModificationCursor cursor) {
             cursor.exit();
         }
     }