Remove useless UnsupportedOperationException throws
[yangtools.git] / yang / yang-data-api / src / main / java / org / opendaylight / yangtools / yang / data / api / schema / tree / DataTreeCandidateNodes.java
index d36a4816a598580348fcfad18e9029ed83b5761d..6b1636572c20c1f780c2c12384fdc5c9fc2b5c8f 100644 (file)
  */
 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.base.Preconditions;
+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.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 @NonNull DataTreeCandidateNode empty(final PathArgument identifier) {
+        return new EmptyDataTreeCandidateNode(identifier);
+    }
+
+    /**
+     * 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 NormalizedNodeContainer) {
+            return new RecursiveUnmodifiedCandidateNode(
+                (NormalizedNodeContainer<?, PathArgument, NormalizedNode<?, ?>>) node);
+        }
+        return new UnmodifiedLeafCandidateNode(node);
     }
 
-    public static DataTreeCandidateNode fromNormalizedNode(final NormalizedNode<?, ?> 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 NormalizedNodeContainer<?, PathArgument, NormalizedNode<?, ?>> oldData,
+            final @Nullable NormalizedNodeContainer<?, PathArgument, NormalizedNode<?, ?>> newData) {
+        if (newData == null) {
+            return oldData == null ? ImmutableList.of()
+                    : Collections2.transform(oldData.getValue(), DataTreeCandidateNodes::deleteNode);
+        }
+        if (oldData == null) {
+            return Collections2.transform(newData.getValue(), 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.getValue()) {
+            final DataTreeCandidateNode node;
+            final Optional<NormalizedNode<?, ?>> maybeOldChild = oldData.getChild(child.getIdentifier());
+
+            if (maybeOldChild.isPresent()) {
+                // 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(maybeOldChild.get(), 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.getValue()) {
+            if (newData.getChild(child.getIdentifier()).isEmpty()) {
+                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 NormalizedNodeContainer<?, PathArgument, NormalizedNode<?, ?>> oldData,
+            final @Nullable NormalizedNodeContainer<?, PathArgument, NormalizedNode<?, ?>> newData,
+            final @NonNull PathArgument child) {
+        final Optional<NormalizedNode<?, ?>> maybeNewChild = getChild(newData, child);
+        final Optional<NormalizedNode<?, ?>> maybeOldChild = getChild(oldData, child);
+        if (maybeOldChild.isPresent()) {
+            final NormalizedNode<?, ?> oldChild = maybeOldChild.get();
+            return Optional.of(maybeNewChild.isPresent() ? replaceNode(oldChild, maybeNewChild.get())
+                    : deleteNode(oldChild));
+        }
+
+        return maybeNewChild.map(DataTreeCandidateNodes::writeNode);
+    }
+
     /**
      * 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, DataTreeCandidateNode) applyRootedNodeToCursor}
-     * method that works with rooted node candidates
+     * you need to use {@link #applyRootedNodeToCursor(DataTreeModificationCursor, YangInstanceIdentifier,
+     * DataTreeCandidateNode) applyRootedNodeToCursor} method that works with rooted node candidates.
+     *
      * @param cursor cursor from the modification we want to apply the {@code node} to
      * @param node candidate tree to apply
      */
     public static void applyToCursor(final DataTreeModificationCursor cursor, final DataTreeCandidateNode node) {
         switch (node.getModificationType()) {
-        case DELETE:
-            cursor.delete(node.getIdentifier());
-            break;
-        case SUBTREE_MODIFIED:
+            case DELETE:
+                cursor.delete(node.getIdentifier());
+                break;
+            case SUBTREE_MODIFIED:
                 cursor.enter(node.getIdentifier());
                 AbstractNodeIterator iterator = new ExitingNodeIterator(null, node.getChildNodes().iterator());
-            do {
-                iterator = iterator.next(cursor);
-            } while (iterator != null);
-            break;
-        case UNMODIFIED:
-            // No-op
-            break;
-        case WRITE:
-            cursor.write(node.getIdentifier(), node.getDataAfter().get());
-            break;
-        default:
-            throw new IllegalArgumentException("Unsupported modification " + node.getModificationType());
+                do {
+                    iterator = iterator.next(cursor);
+                } while (iterator != null);
+                break;
+            case UNMODIFIED:
+                // No-op
+                break;
+            case WRITE:
+                cursor.write(node.getIdentifier(), node.getDataAfter().get());
+                break;
+            default:
+                throw new IllegalArgumentException("Unsupported modification " + node.getModificationType());
         }
     }
 
     /**
      * Applies the {@code node} that is rooted(doesn't have an identifier) in tree A to tree B's {@code cursor}
-     * at location specified by {@code rootPath}
+     * at location specified by {@code rootPath}.
+     *
      * @param cursor cursor from the modification we want to apply the {@code node} to
      * @param rootPath path in the {@code cursor}'s tree we want to apply to candidate to
      * @param node candidate tree to apply
      */
-    public static void applyRootedNodeToCursor(final DataTreeModificationCursor cursor, final YangInstanceIdentifier rootPath, final DataTreeCandidateNode node) {
+    public static void applyRootedNodeToCursor(final DataTreeModificationCursor cursor,
+            final YangInstanceIdentifier rootPath, final DataTreeCandidateNode node) {
         switch (node.getModificationType()) {
             case DELETE:
                 cursor.delete(rootPath.getLastPathArgument());
@@ -105,82 +222,115 @@ public final class DataTreeCandidateNodes {
         }
     }
 
+    private static Optional<NormalizedNode<?, ?>> getChild(
+            final NormalizedNodeContainer<?, PathArgument, NormalizedNode<?, ?>> container,
+                    final PathArgument identifier) {
+        return container == null ? Optional.empty() : container.getChild(identifier);
+    }
+
+    @SuppressWarnings("unchecked")
+    private static @NonNull DataTreeCandidateNode deleteNode(final NormalizedNode<?, ?> data) {
+        if (data instanceof NormalizedNodeContainer) {
+            return new RecursiveDeleteCandidateNode(
+                (NormalizedNodeContainer<?, PathArgument, NormalizedNode<?, ?>>) data);
+        }
+        return new DeleteLeafCandidateNode(data);
+    }
+
+
+    @SuppressWarnings("unchecked")
+    private static @NonNull DataTreeCandidateNode replaceNode(final NormalizedNode<?, ?> oldData,
+            final NormalizedNode<?, ?> newData) {
+        if (oldData instanceof NormalizedNodeContainer) {
+            return new RecursiveReplaceCandidateNode(
+                (NormalizedNodeContainer<?, PathArgument, NormalizedNode<?, ?>>) oldData,
+                (NormalizedNodeContainer<?, PathArgument, NormalizedNode<?, ?>>) newData);
+        }
+        return new ReplaceLeafCandidateNode(oldData, newData);
+    }
+
+    @SuppressWarnings("unchecked")
+    private static @NonNull DataTreeCandidateNode writeNode(final NormalizedNode<?, ?> data) {
+        if (data instanceof NormalizedNodeContainer) {
+            return new RecursiveWriteCandidateNode(
+                (NormalizedNodeContainer<?, PathArgument, NormalizedNode<?, ?>>) data);
+        }
+        return new WriteLeafCandidateNode(data);
+    }
+
     private abstract static class AbstractNodeIterator {
         private final Iterator<DataTreeCandidateNode> iterator;
 
         AbstractNodeIterator(final Iterator<DataTreeCandidateNode> iterator) {
-            this.iterator = Preconditions.checkNotNull(iterator);
+            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()) {
-                case DELETE:
-                    cursor.delete(node.getIdentifier());
-                    break;
-                case APPEARED:
-                case DISAPPEARED:
-                case SUBTREE_MODIFIED:
-                    final Collection<DataTreeCandidateNode> children = node.getChildNodes();
-                    if (!children.isEmpty()) {
-                        cursor.enter(node.getIdentifier());
+                    case DELETE:
+                        cursor.delete(node.getIdentifier());
+                        break;
+                    case APPEARED:
+                    case DISAPPEARED:
+                    case SUBTREE_MODIFIED:
+                        final Collection<DataTreeCandidateNode> children = node.getChildNodes();
+                        if (!children.isEmpty()) {
+                            cursor.enter(node.getIdentifier());
                             return new ExitingNodeIterator(this, children.iterator());
-                    }
-                    break;
-                case UNMODIFIED:
-                    // No-op
-                    break;
-                case WRITE:
-                    cursor.write(node.getIdentifier(), node.getDataAfter().get());
-                    break;
-                default:
-                    throw new IllegalArgumentException("Unsupported modification " + node.getModificationType());
+                        }
+                        break;
+                    case UNMODIFIED:
+                        // No-op
+                        break;
+                    case WRITE:
+                        cursor.write(node.getIdentifier(), node.getDataAfter().get());
+                        break;
+                    default:
+                        throw new IllegalArgumentException("Unsupported modification " + node.getModificationType());
                 }
             }
             exitNode(cursor);
             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;
 
-        public 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();
         }
     }