Allow emission of operation input/output
[yangtools.git] / data / yang-data-util / src / main / java / org / opendaylight / yangtools / yang / data / util / NormalizedNodeStreamWriterStack.java
index dfc838944129073613c5d3a540eebaab3eea6e9b..1b48afe72800a157e6024c20b9744dbd98be5d93 100644 (file)
@@ -13,48 +13,38 @@ import static com.google.common.base.Verify.verify;
 import static java.util.Objects.requireNonNull;
 
 import com.google.common.annotations.Beta;
-import com.google.common.collect.Iterables;
 import java.io.IOException;
 import java.util.ArrayDeque;
 import java.util.Deque;
-import java.util.Set;
-import java.util.stream.Collectors;
 import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
 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.stream.NormalizedNodeStreamWriter;
-import org.opendaylight.yangtools.yang.model.api.AnydataSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
-import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.ContainerLike;
-import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
-import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.DocumentedNode.WithStatus;
 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
 import org.opendaylight.yangtools.yang.model.api.EffectiveStatementInference;
-import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
-import org.opendaylight.yangtools.yang.model.api.SchemaNode;
-import org.opendaylight.yangtools.yang.model.api.SchemaPath;
 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.ActionEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.AnydataEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.AnyxmlEffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.ChoiceEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.ContainerEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.DataTreeAwareEffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.DataTreeEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.InputEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.LeafEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.LeafListEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.ListEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.NotificationEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.OutputEffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.RpcEffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute;
 import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
-import org.opendaylight.yangtools.yang.model.util.EffectiveAugmentationSchema;
 import org.opendaylight.yangtools.yang.model.util.LeafrefResolver;
 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
@@ -68,9 +58,9 @@ import org.slf4j.LoggerFactory;
 public final class NormalizedNodeStreamWriterStack implements LeafrefResolver {
     private static final Logger LOG = LoggerFactory.getLogger(NormalizedNodeStreamWriterStack.class);
 
-    private final Deque<WithStatus> schemaStack = new ArrayDeque<>();
+    private final Deque<EffectiveStatement<?, ?>> schemaStack = new ArrayDeque<>();
     private final SchemaInferenceStack dataTree;
-    private final DataNodeContainer root;
+    private final Object root;
 
     private NormalizedNodeStreamWriterStack(final EffectiveModelContext context) {
         dataTree = SchemaInferenceStack.of(context);
@@ -80,12 +70,14 @@ public final class NormalizedNodeStreamWriterStack implements LeafrefResolver {
     private NormalizedNodeStreamWriterStack(final SchemaInferenceStack dataTree) {
         this.dataTree = requireNonNull(dataTree);
         if (!dataTree.isEmpty()) {
-            final EffectiveStatement<?, ?> current = dataTree.currentStatement();
-            checkArgument(current instanceof DataNodeContainer, "Cannot instantiate on %s", current);
-
-            root = (DataNodeContainer) current;
+            final var current = dataTree.currentStatement();
+            if (current instanceof DataTreeAwareEffectiveStatement container) {
+                root = container;
+            } else {
+                throw new IllegalArgumentException("Cannot instantiate on " + current);
+            }
         } else {
-            root = dataTree.getEffectiveModelContext();
+            root = dataTree.modelContext();
         }
     }
 
@@ -153,21 +145,6 @@ public final class NormalizedNodeStreamWriterStack implements LeafrefResolver {
             .stack());
     }
 
-    /**
-     * Create a new writer with the specified context and rooted in the specified schema path.
-     *
-     * @param context Associated {@link EffectiveModelContext}
-     * @param path schema path
-     * @return A new {@link NormalizedNodeStreamWriterStack}
-     * @throws NullPointerException if any argument is null
-     * @throws IllegalArgumentException if {@code path} does not point to a valid root
-     */
-    @Deprecated
-    public static @NonNull NormalizedNodeStreamWriterStack of(final EffectiveModelContext context,
-            final SchemaPath path) {
-        return new NormalizedNodeStreamWriterStack(SchemaInferenceStack.ofSchemaPath(context, path));
-    }
-
     /**
      * Create a new writer with the specified context and rooted in the specified schema path.
      *
@@ -194,155 +171,126 @@ public final class NormalizedNodeStreamWriterStack implements LeafrefResolver {
         return dataTree.resolveLeafref(type);
     }
 
-    public Object getParent() {
-        final WithStatus schema = schemaStack.peek();
-        return schema == null ? root : schema;
+    /**
+     * Return the current {@link EffectiveStatement}, or {@code null}.
+     *
+     * @return the current {@link EffectiveStatement}, or {@code null}
+     */
+    public @Nullable EffectiveStatement<?, ?> currentStatement() {
+        return schemaStack.peek();
     }
 
-    private SchemaNode enterDataTree(final PathArgument name) {
-        final QName qname = name.getNodeType();
-        final DataTreeEffectiveStatement<?> stmt = dataTree.enterDataTree(qname);
-        verify(stmt instanceof SchemaNode, "Unexpected result %s", stmt);
-        final SchemaNode ret = (SchemaNode) stmt;
-        final Object parent = getParent();
-        if (parent instanceof ChoiceSchemaNode) {
-            final DataSchemaNode check = ((ChoiceSchemaNode) parent).findDataSchemaChild(qname).orElse(null);
-            verify(check == ret, "Data tree result %s does not match choice result %s", ret, check);
+    private @NonNull DataTreeEffectiveStatement<?> enterDataTree(final PathArgument name) {
+        final var qname = name.getNodeType();
+        final var stmt = dataTree.enterDataTree(qname);
+        if (currentStatement() instanceof ChoiceEffectiveStatement choice) {
+            final var check = choice.findDataTreeNode(qname).orElse(null);
+            verify(check == stmt, "Data tree result %s does not match choice result %s", stmt, check);
         }
-        return ret;
+        return stmt;
+    }
+
+    private <T extends DataTreeEffectiveStatement<?>> @NonNull T enterDataTree(final PathArgument name,
+            final @NonNull Class<T> expectedClass, final @NonNull String humanString) {
+        final var schema = enterDataTree(name);
+        final @NonNull T casted;
+        try {
+            casted = expectedClass.cast(schema);
+        } catch (ClassCastException e) {
+            throw new IllegalArgumentException("Node " + schema + " is not " + humanString, e);
+        }
+        schemaStack.push(casted);
+        return casted;
     }
 
     public void startList(final PathArgument name) {
-        final SchemaNode schema = enterDataTree(name);
-        checkArgument(schema instanceof ListSchemaNode, "Node %s is not a list", schema);
-        schemaStack.push(schema);
+        enterDataTree(name, ListEffectiveStatement.class, "a list");
     }
 
     public void startListItem(final PathArgument name) throws IOException {
-        final Object schema = getParent();
-        checkArgument(schema instanceof ListSchemaNode, "List item is not appropriate");
-        schemaStack.push((ListSchemaNode) schema);
+        if (!(currentStatement() instanceof ListEffectiveStatement parentList)) {
+            throw new IllegalArgumentException("List item is not appropriate");
+        }
+        schemaStack.push(parentList);
     }
 
     public void startLeafNode(final NodeIdentifier name) throws IOException {
-        final SchemaNode schema = enterDataTree(name);
-        checkArgument(schema instanceof LeafSchemaNode, "Node %s is not a leaf", schema);
-        schemaStack.push(schema);
-    }
-
-    public LeafListSchemaNode startLeafSet(final NodeIdentifier name) {
-        final SchemaNode schema = enterDataTree(name);
-        checkArgument(schema instanceof LeafListSchemaNode, "Node %s is not a leaf-list", schema);
-        schemaStack.push(schema);
-        return (LeafListSchemaNode) schema;
+        enterDataTree(name, LeafEffectiveStatement.class, "a leaf");
     }
 
-    public LeafListSchemaNode leafSetEntryNode(final QName qname) {
-        final Object parent = getParent();
-        if (parent instanceof LeafListSchemaNode) {
-            return (LeafListSchemaNode) parent;
-        }
-        checkArgument(parent instanceof DataNodeContainer, "Cannot lookup %s in parent %s", qname, parent);
-        final DataSchemaNode child = ((DataNodeContainer) parent).dataChildByName(qname);
-        checkArgument(child instanceof LeafListSchemaNode,
-            "Node %s is neither a leaf-list nor currently in a leaf-list", child);
-        return (LeafListSchemaNode) child;
+    public void startLeafSet(final NodeIdentifier name) {
+        enterDataTree(name, LeafListEffectiveStatement.class, "a leaf-list");
     }
 
     public void startLeafSetEntryNode(final NodeWithValue<?> name) {
         schemaStack.push(leafSetEntryNode(name.getNodeType()));
     }
 
-    public ChoiceSchemaNode startChoiceNode(final NodeIdentifier name) {
+    private @NonNull LeafListEffectiveStatement leafSetEntryNode(final QName qname) {
+        final var parent = currentStatement();
+        if (parent instanceof LeafListEffectiveStatement leafList) {
+            return leafList;
+        }
+        if (parent instanceof DataTreeAwareEffectiveStatement parentContainer) {
+            final var child = parentContainer.findDataTreeNode(qname).orElse(null);
+            if (child instanceof LeafListEffectiveStatement childLeafList) {
+                return childLeafList;
+            }
+            throw new IllegalArgumentException(
+                "Node " + child + " is neither a leaf-list nor currently in a leaf-list");
+        }
+        throw new IllegalArgumentException("Cannot lookup " + qname + " in parent " + parent);
+    }
+
+    public void startChoiceNode(final NodeIdentifier name) {
         LOG.debug("Enter choice {}", name);
-        final ChoiceEffectiveStatement stmt = dataTree.enterChoice(name.getNodeType());
-        verify(stmt instanceof ChoiceSchemaNode, "Node %s is not a choice", stmt);
-        final ChoiceSchemaNode ret = (ChoiceSchemaNode) stmt;
-        schemaStack.push(ret);
-        return ret;
+        schemaStack.push(dataTree.enterChoice(name.getNodeType()));
     }
 
-    public SchemaNode startContainerNode(final NodeIdentifier name) {
+    public @NonNull DataTreeAwareEffectiveStatement<QName, ?> startContainerNode(final NodeIdentifier name) {
         LOG.debug("Enter container {}", name);
 
-        final SchemaNode schema;
-        if (schemaStack.isEmpty() && root instanceof NotificationDefinition) {
+        final DataTreeAwareEffectiveStatement<QName, ?> ret;
+        if (schemaStack.isEmpty() && root instanceof NotificationEffectiveStatement notification
+            && name.getNodeType().equals(notification.argument())) {
             // Special case for stacks initialized at notification. We pretend the first container is contained within
             // itself.
             // FIXME: 8.0.0: factor this special case out to something more reasonable, like being initialized at the
             //               Notification's parent and knowing to enterSchemaTree() instead of enterDataTree().
-            schema = (NotificationDefinition) root;
+            ret = notification;
         } else {
-            schema = enterDataTree(name);
-            checkArgument(schema instanceof ContainerLike, "Node %s is not a container", schema);
+            final var child = enterDataTree(name);
+            if (child instanceof ContainerEffectiveStatement container) {
+                ret = container;
+            } else if (child instanceof InputEffectiveStatement input) {
+                ret = input;
+            } else if (child instanceof OutputEffectiveStatement output) {
+                ret = output;
+            } else {
+                dataTree.exitToDataTree();
+                throw new IllegalArgumentException("Node " + child + " is not a container");
+            }
         }
 
-        schemaStack.push(schema);
-        return schema;
+        schemaStack.push(ret);
+        return ret;
     }
 
     public void startAnyxmlNode(final NodeIdentifier name) {
-        final SchemaNode schema = enterDataTree(name);
-        checkArgument(schema instanceof AnyxmlSchemaNode, "Node %s is not anyxml", schema);
-        schemaStack.push(schema);
+        enterDataTree(name, AnyxmlEffectiveStatement.class, "anyxml");
     }
 
     public void startAnydataNode(final NodeIdentifier name) {
-        final SchemaNode schema = enterDataTree(name);
-        checkArgument(schema instanceof AnydataSchemaNode, "Node %s is not anydata", schema);
-        schemaStack.push(schema);
+        enterDataTree(name, AnydataEffectiveStatement.class, "anydata");
     }
 
-    public Object endNode() {
-        final Object ret = schemaStack.pop();
+    public EffectiveStatement<?, ?> endNode() {
+        final var ret = schemaStack.pop();
         // If this is a data tree node, make sure it is updated. Before that, though, we need to check if this is not
         // actually listEntry -> list or leafListEntry -> leafList exit.
-        if (!(ret instanceof AugmentationSchemaNode) && getParent() != ret) {
+        if (currentStatement() != ret) {
             dataTree.exit();
         }
         return ret;
     }
-
-    public AugmentationSchemaNode startAugmentationNode(final AugmentationIdentifier identifier) {
-        LOG.debug("Enter augmentation {}", identifier);
-        Object parent = getParent();
-
-        checkArgument(parent instanceof AugmentationTarget, "Augmentation not allowed under %s", parent);
-        if (parent instanceof ChoiceSchemaNode) {
-            final QName name = Iterables.get(identifier.getPossibleChildNames(), 0);
-            parent = findCaseByChild((ChoiceSchemaNode) parent, name);
-        }
-        checkArgument(parent instanceof DataNodeContainer, "Augmentation allowed only in DataNodeContainer", parent);
-        final AugmentationSchemaNode schema = findSchemaForAugment((AugmentationTarget) parent,
-            identifier.getPossibleChildNames());
-        final AugmentationSchemaNode resolvedSchema = new EffectiveAugmentationSchema(schema,
-            (DataNodeContainer) parent);
-        schemaStack.push(resolvedSchema);
-        return resolvedSchema;
-    }
-
-    // FIXME: 7.0.0: can we get rid of this?
-    private static @NonNull AugmentationSchemaNode findSchemaForAugment(final AugmentationTarget schema,
-            final Set<QName> qnames) {
-        for (final AugmentationSchemaNode augment : schema.getAvailableAugmentations()) {
-            if (qnames.equals(augment.getChildNodes().stream()
-                .map(DataSchemaNode::getQName)
-                .collect(Collectors.toUnmodifiableSet()))) {
-                return augment;
-            }
-        }
-
-        throw new IllegalStateException(
-            "Unknown augmentation node detected, identified by: " + qnames + ", in: " + schema);
-    }
-
-    // FIXME: 7.0.0: can we get rid of this?
-    private static SchemaNode findCaseByChild(final ChoiceSchemaNode parent, final QName qname) {
-        for (final CaseSchemaNode caze : parent.getCases()) {
-            if (caze.dataChildByName(qname) != null) {
-                return caze;
-            }
-        }
-        return null;
-    }
 }