Allow emission of operation input/output 79/111279/5
authorRobert Varga <robert.varga@pantheon.tech>
Fri, 5 Apr 2024 09:49:23 +0000 (11:49 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Fri, 5 Apr 2024 13:21:23 +0000 (15:21 +0200)
Both XML and JSON codecs should be in a place where they can emit
RPC/action input/outputs -- except NormalizedNodeStreamWriterStack
cannot initialize there.

Rework NormalizedNodeStreamWriterStack internal tracking to work on
EffectiveStatements and lift this restriction.

JIRA: YANGTOOLS-1570
Change-Id: If45bb096ee30cc35bf2a40ec26e12217f71ca06c
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
codec/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONNormalizedNodeStreamWriter.java
codec/yang-data-codec-gson/src/test/java/org/opendaylight/yangtools/yang/data/codec/gson/YT1570Test.java [new file with mode: 0644]
codec/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/SchemaAwareXMLStreamNormalizedNodeStreamWriter.java
codec/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/YT1570Test.java [new file with mode: 0644]
data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/NormalizedNodeStreamWriterStack.java

index 465b895f2078362a1b8c8c1f2873a2544c0732d6..950c171a7da979e8e0209956e06488f67991f830 100644 (file)
@@ -30,12 +30,13 @@ import org.opendaylight.yangtools.yang.data.api.schema.NormalizedAnydata;
 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter.MountPointExtension;
 import org.opendaylight.yangtools.yang.data.util.NormalizedNodeStreamWriterStack;
-import org.opendaylight.yangtools.yang.model.api.AnydataSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
 import org.opendaylight.yangtools.yang.model.api.EffectiveStatementInference;
 import org.opendaylight.yangtools.yang.model.api.TypedDataSchemaNode;
+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.ContainerEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.PresenceEffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute;
 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
 import org.w3c.dom.Element;
@@ -77,8 +78,7 @@ public abstract class JSONNormalizedNodeStreamWriter implements NormalizedNodeSt
     }
 
     /**
-     * RFC6020 deviation: we are not required to emit empty containers unless they
-     * are marked as 'presence'.
+     * RFC6020 deviation: we are not required to emit empty containers unless they are marked as 'presence'.
      */
     private static final boolean DEFAULT_EMIT_EMPTY_CONTAINERS = true;
 
@@ -94,10 +94,12 @@ public abstract class JSONNormalizedNodeStreamWriter implements NormalizedNodeSt
     private final JSONCodecFactory codecs;
     private final JsonWriter writer;
     private final DefaultJSONValueWriter valueWriter;
+
     private JSONStreamWriterContext context;
 
-    JSONNormalizedNodeStreamWriter(final JSONCodecFactory codecFactory, final NormalizedNodeStreamWriterStack tracker,
-            final JsonWriter writer, final JSONStreamWriterRootContext rootContext) {
+    private JSONNormalizedNodeStreamWriter(final JSONCodecFactory codecFactory,
+            final NormalizedNodeStreamWriterStack tracker, final JsonWriter writer,
+            final JSONStreamWriterRootContext rootContext) {
         this.writer = requireNonNull(writer);
         codecs = requireNonNull(codecFactory);
         this.tracker = requireNonNull(tracker);
@@ -335,15 +337,12 @@ public abstract class JSONNormalizedNodeStreamWriter implements NormalizedNodeSt
         context = new JSONStreamWriterListContext(context, name);
     }
 
-    /*
-     * Warning suppressed due to static final constant which triggers a warning
-     * for the call to schema.isPresenceContainer().
-     */
     @Override
     public final void startContainerNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
-        final boolean isPresence = tracker.startContainerNode(name) instanceof ContainerSchemaNode container
-            ? container.isPresenceContainer() : DEFAULT_EMIT_EMPTY_CONTAINERS;
-        context = new JSONStreamWriterNamedObjectContext(context, name, isPresence);
+        final boolean emitIfEmpty = tracker.startContainerNode(name) instanceof ContainerEffectiveStatement container
+            ? container.findFirstEffectiveSubstatement(PresenceEffectiveStatement.class).isPresent()
+                : DEFAULT_EMIT_EMPTY_CONTAINERS;
+        context = new JSONStreamWriterNamedObjectContext(context, name, emitIfEmpty);
     }
 
     @Override
@@ -436,10 +435,10 @@ public abstract class JSONNormalizedNodeStreamWriter implements NormalizedNodeSt
 
     @Override
     public void scalarValue(final Object value) throws IOException {
-        final Object current = tracker.getParent();
+        final var current = tracker.currentStatement();
         if (current instanceof TypedDataSchemaNode typed) {
             writeValue(value, codecs.codecFor(typed, tracker));
-        } else if (current instanceof AnydataSchemaNode) {
+        } else if (current instanceof AnydataEffectiveStatement) {
             writeAnydataValue(value);
         } else {
             throw new IllegalStateException(String.format("Cannot emit scalar %s for %s", value, current));
@@ -448,8 +447,8 @@ public abstract class JSONNormalizedNodeStreamWriter implements NormalizedNodeSt
 
     @Override
     public void domSourceValue(final DOMSource value) throws IOException {
-        final Object current = tracker.getParent();
-        checkState(current instanceof AnyxmlSchemaNode, "Cannot emit DOMSource %s for %s", value, current);
+        final var current = tracker.currentStatement();
+        checkState(current instanceof AnyxmlEffectiveStatement, "Cannot emit DOMSource %s for %s", value, current);
         // FIXME: should have a codec based on this :)
         writeAnyXmlValue(value);
     }
diff --git a/codec/yang-data-codec-gson/src/test/java/org/opendaylight/yangtools/yang/data/codec/gson/YT1570Test.java b/codec/yang-data-codec-gson/src/test/java/org/opendaylight/yangtools/yang/data/codec/gson/YT1570Test.java
new file mode 100644 (file)
index 0000000..31a14b6
--- /dev/null
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2024 PANTHEON.tech, s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.data.codec.gson;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import org.junit.jupiter.api.Test;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.Uint64;
+import org.opendaylight.yangtools.yang.common.Uint8;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter;
+import org.opendaylight.yangtools.yang.data.spi.node.ImmutableNodes;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
+import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
+
+class YT1570Test {
+    private static final QName INPUT = QName.create("foo", "input");
+    private static final QName OUTPUT = QName.create("foo", "output");
+    private static final QName FOO = QName.create("foo", "foo");
+    private static final QName BAR = QName.create("foo", "bar");
+    private static final QName BAZ = QName.create("foo", "baz");
+    private static final QName UINT = QName.create("foo", "uint");
+
+    private static final EffectiveModelContext MODEL_CONTEXT = YangParserTestUtils.parseYang("""
+        module foo {
+          namespace foo;
+          prefix foo;
+          yang-version 1.1;
+
+          rpc foo {
+            input {
+              leaf uint {
+                type uint8;
+              }
+            }
+            output {
+              leaf uint {
+                type uint64;
+              }
+            }
+          }
+
+          container bar {
+            action baz {
+              input {
+                leaf uint {
+                  type uint8;
+                }
+              }
+              output {
+                leaf uint {
+                  type uint64;
+                }
+              }
+            }
+          }
+        }""");
+
+    @Test
+    void testRpcInput() {
+        assertEquals("""
+            {
+              "foo:input": {
+                "uint": 1
+              }
+            }""",
+            serialize(ImmutableNodes.newContainerBuilder()
+                .withNodeIdentifier(new NodeIdentifier(INPUT))
+                .withChild(ImmutableNodes.leafNode(UINT, Uint8.ONE))
+                .build(), FOO));
+    }
+
+    @Test
+    void testRpcOutput() {
+        assertEquals("""
+            {
+              "foo:output": {
+                "uint": "1"
+              }
+            }""",
+            serialize(ImmutableNodes.newContainerBuilder()
+                .withNodeIdentifier(new NodeIdentifier(OUTPUT))
+                .withChild(ImmutableNodes.leafNode(UINT, Uint64.ONE))
+                .build(), FOO));
+    }
+
+    @Test
+    void testActionInput() {
+        assertEquals("""
+            {
+              "foo:input": {
+                "uint": 2
+              }
+            }""",
+            serialize(ImmutableNodes.newContainerBuilder()
+                .withNodeIdentifier(new NodeIdentifier(INPUT))
+                .withChild(ImmutableNodes.leafNode(UINT, Uint8.TWO))
+                .build(), BAR, BAZ));
+    }
+
+    @Test
+    void testActionOutput() {
+        assertEquals("""
+            {
+              "foo:output": {
+                "uint": "2"
+              }
+            }""",
+            serialize(ImmutableNodes.newContainerBuilder()
+                .withNodeIdentifier(new NodeIdentifier(OUTPUT))
+                .withChild(ImmutableNodes.leafNode(UINT, Uint64.TWO))
+                .build(), BAR, BAZ));
+    }
+
+    private static String serialize(final ContainerNode container, final QName... nodeIdentifiers) {
+        final var writer = new StringWriter();
+        final var jsonStream = JSONNormalizedNodeStreamWriter.createExclusiveWriter(
+            JSONCodecFactorySupplier.RFC7951.getShared(MODEL_CONTEXT),
+            SchemaInferenceStack.of(MODEL_CONTEXT, Absolute.of(nodeIdentifiers)).toInference(), null,
+            JsonWriterFactory.createJsonWriter(writer, 2));
+        try (var nodeWriter = NormalizedNodeWriter.forStreamWriter(jsonStream)) {
+            nodeWriter.write(container);
+        } catch (IOException e) {
+            throw new AssertionError(e);
+        }
+        return writer.toString();
+    }
+}
index 9bcf452041c1b8c78b1a9aca0ffeb987746b66b3..2d73959ab08cb07e0070f11f6056b4928d231110 100644 (file)
@@ -24,15 +24,18 @@ import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdent
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.data.util.NormalizedNodeStreamWriterStack;
-import org.opendaylight.yangtools.yang.model.api.AnydataSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.ContainerLike;
 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
-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.TypeDefinition;
 import org.opendaylight.yangtools.yang.model.api.TypedDataSchemaNode;
+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.ContainerEffectiveStatement;
+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.type.LeafrefTypeDefinition;
 
 final class SchemaAwareXMLStreamNormalizedNodeStreamWriter
@@ -92,15 +95,16 @@ final class SchemaAwareXMLStreamNormalizedNodeStreamWriter
 
     @Override
     public void endNode() throws IOException {
-        final Object schema = tracker.endNode();
-        if (schema instanceof ListSchemaNode || schema instanceof LeafListSchemaNode) {
+        final var schema = tracker.endNode();
+        if (schema instanceof ListEffectiveStatement || schema instanceof LeafListEffectiveStatement) {
             // For lists, we only emit end element on the inner frame
-            final Object parent = tracker.getParent();
-            if (parent == schema) {
+            if (tracker.currentStatement() == schema) {
                 endElement();
             }
-        } else if (schema instanceof ContainerLike || schema instanceof LeafSchemaNode
-                || schema instanceof AnydataSchemaNode || schema instanceof AnyxmlSchemaNode) {
+        } else if (schema instanceof ContainerEffectiveStatement || schema instanceof LeafEffectiveStatement
+                || schema instanceof AnydataEffectiveStatement || schema instanceof AnyxmlEffectiveStatement
+                || schema instanceof InputEffectiveStatement || schema instanceof OutputEffectiveStatement
+                || schema instanceof NotificationEffectiveStatement) {
             endElement();
         }
     }
@@ -150,10 +154,10 @@ final class SchemaAwareXMLStreamNormalizedNodeStreamWriter
 
     @Override
     public void scalarValue(final Object value) throws IOException {
-        final Object current = tracker.getParent();
+        final var current = tracker.currentStatement();
         if (current instanceof TypedDataSchemaNode typedSchema) {
             writeValue(value, typedSchema);
-        } else if (current instanceof AnydataSchemaNode) {
+        } else if (current instanceof AnydataEffectiveStatement) {
             anydataValue(value);
         } else {
             throw new IllegalStateException("Unexpected scalar value " + value + " with " + current);
@@ -162,8 +166,8 @@ final class SchemaAwareXMLStreamNormalizedNodeStreamWriter
 
     @Override
     public void domSourceValue(final DOMSource value) throws IOException {
-        final Object current = tracker.getParent();
-        checkState(current instanceof AnyxmlSchemaNode, "Unexpected value %s with %s", value, current);
+        final var current = tracker.currentStatement();
+        checkState(current instanceof AnyxmlEffectiveStatement, "Unexpected value %s with %s", value, current);
         anyxmlValue(value);
     }
 
diff --git a/codec/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/YT1570Test.java b/codec/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/YT1570Test.java
new file mode 100644 (file)
index 0000000..9be0a76
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2024 PANTHEON.tech, s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.data.codec.xml;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import javax.xml.stream.XMLStreamException;
+import org.junit.jupiter.api.Test;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.Uint64;
+import org.opendaylight.yangtools.yang.common.Uint8;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter;
+import org.opendaylight.yangtools.yang.data.spi.node.ImmutableNodes;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
+import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
+
+class YT1570Test {
+    private static final QName INPUT = QName.create("foo", "input");
+    private static final QName OUTPUT = QName.create("foo", "output");
+    private static final QName FOO = QName.create("foo", "foo");
+    private static final QName BAR = QName.create("foo", "bar");
+    private static final QName BAZ = QName.create("foo", "baz");
+    private static final QName UINT = QName.create("foo", "uint");
+
+    private static final EffectiveModelContext MODEL_CONTEXT = YangParserTestUtils.parseYang("""
+        module foo {
+          namespace foo;
+          prefix foo;
+          yang-version 1.1;
+
+          rpc foo {
+            input {
+              leaf uint {
+                type uint8;
+              }
+            }
+            output {
+              leaf uint {
+                type uint64;
+              }
+            }
+          }
+
+          container bar {
+            action baz {
+              input {
+                leaf uint {
+                  type uint8;
+                }
+              }
+              output {
+                leaf uint {
+                  type uint64;
+                }
+              }
+            }
+          }
+        }""");
+
+    @Test
+    void testRpcInput() {
+        assertEquals("""
+            <input xmlns="foo"><uint>1</uint></input>""",
+            serialize(ImmutableNodes.newContainerBuilder()
+                .withNodeIdentifier(new NodeIdentifier(INPUT))
+                .withChild(ImmutableNodes.leafNode(UINT, Uint8.ONE))
+                .build(), FOO));
+    }
+
+    @Test
+    void testRpcOutput() {
+        assertEquals("""
+            <output xmlns="foo"><uint>1</uint></output>""",
+            serialize(ImmutableNodes.newContainerBuilder()
+                .withNodeIdentifier(new NodeIdentifier(OUTPUT))
+                .withChild(ImmutableNodes.leafNode(UINT, Uint64.ONE))
+                .build(), FOO));
+    }
+
+    @Test
+    void testActionInput() {
+        assertEquals("""
+            <input xmlns="foo"><uint>2</uint></input>""",
+            serialize(ImmutableNodes.newContainerBuilder()
+                .withNodeIdentifier(new NodeIdentifier(INPUT))
+                .withChild(ImmutableNodes.leafNode(UINT, Uint8.TWO))
+                .build(), BAR, BAZ));
+    }
+
+    @Test
+    void testActionOutput() {
+        assertEquals("""
+            <output xmlns="foo"><uint>2</uint></output>""",
+            serialize(ImmutableNodes.newContainerBuilder()
+                .withNodeIdentifier(new NodeIdentifier(OUTPUT))
+                .withChild(ImmutableNodes.leafNode(UINT, Uint64.TWO))
+                .build(), BAR, BAZ));
+    }
+
+    private static String serialize(final ContainerNode container, final QName... nodeIdentifiers) {
+        final var writer = new StringWriter();
+        try {
+            final var xmlStream = XMLStreamNormalizedNodeStreamWriter.create(
+                TestFactories.DEFAULT_OUTPUT_FACTORY.createXMLStreamWriter(writer),
+                SchemaInferenceStack.of(MODEL_CONTEXT, Absolute.of(nodeIdentifiers)).toInference());
+            try (var nodeWriter = NormalizedNodeWriter.forStreamWriter(xmlStream)) {
+                nodeWriter.write(container);
+            }
+        } catch (IOException | XMLStreamException e) {
+            throw new AssertionError(e);
+        }
+        return writer.toString();
+    }
+}
index c03aae08a55d23dd326fe4091029d577b7615ba9..1b48afe72800a157e6024c20b9744dbd98be5d93 100644 (file)
@@ -13,11 +13,11 @@ import static com.google.common.base.Verify.verify;
 import static java.util.Objects.requireNonNull;
 
 import com.google.common.annotations.Beta;
-import com.google.common.base.VerifyException;
 import java.io.IOException;
 import java.util.ArrayDeque;
 import java.util.Deque;
 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.NodeIdentifier;
@@ -25,22 +25,23 @@ import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithV
 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.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.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.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;
@@ -57,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<DataSchemaNode> 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);
@@ -70,7 +71,7 @@ public final class NormalizedNodeStreamWriterStack implements LeafrefResolver {
         this.dataTree = requireNonNull(dataTree);
         if (!dataTree.isEmpty()) {
             final var current = dataTree.currentStatement();
-            if (current instanceof DataNodeContainer container) {
+            if (current instanceof DataTreeAwareEffectiveStatement container) {
                 root = container;
             } else {
                 throw new IllegalArgumentException("Cannot instantiate on " + current);
@@ -170,28 +171,29 @@ public final class NormalizedNodeStreamWriterStack implements LeafrefResolver {
         return dataTree.resolveLeafref(type);
     }
 
-    public Object getParent() {
-        final var 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 @NonNull SchemaNode enterDataTree(final PathArgument name) {
+    private @NonNull DataTreeEffectiveStatement<?> enterDataTree(final PathArgument name) {
         final var qname = name.getNodeType();
         final var stmt = dataTree.enterDataTree(qname);
-        if (!(stmt instanceof SchemaNode ret)) {
-            throw new VerifyException("Unexpected result " + stmt);
+        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);
         }
-        if (getParent() instanceof ChoiceSchemaNode choice) {
-            final var check = choice.findDataSchemaChild(qname).orElse(null);
-            verify(check == ret, "Data tree result %s does not match choice result %s", ret, check);
-        }
-        return ret;
+        return stmt;
     }
 
-    private <T extends DataSchemaNode> @NonNull T enterDataTree(final PathArgument name,
+    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 T casted;
+        final @NonNull T casted;
         try {
             casted = expectedClass.cast(schema);
         } catch (ClassCastException e) {
@@ -202,32 +204,36 @@ public final class NormalizedNodeStreamWriterStack implements LeafrefResolver {
     }
 
     public void startList(final PathArgument name) {
-        enterDataTree(name, ListSchemaNode.class, "a list");
+        enterDataTree(name, ListEffectiveStatement.class, "a list");
     }
 
     public void startListItem(final PathArgument name) throws IOException {
-        if (!(getParent() instanceof ListSchemaNode parentList)) {
+        if (!(currentStatement() instanceof ListEffectiveStatement parentList)) {
             throw new IllegalArgumentException("List item is not appropriate");
         }
         schemaStack.push(parentList);
     }
 
     public void startLeafNode(final NodeIdentifier name) throws IOException {
-        enterDataTree(name, LeafSchemaNode.class, "a leaf");
+        enterDataTree(name, LeafEffectiveStatement.class, "a leaf");
     }
 
     public void startLeafSet(final NodeIdentifier name) {
-        enterDataTree(name, LeafListSchemaNode.class, "a leaf-list");
+        enterDataTree(name, LeafListEffectiveStatement.class, "a leaf-list");
+    }
+
+    public void startLeafSetEntryNode(final NodeWithValue<?> name) {
+        schemaStack.push(leafSetEntryNode(name.getNodeType()));
     }
 
-    private @NonNull LeafListSchemaNode leafSetEntryNode(final QName qname) {
-        final Object parent = getParent();
-        if (parent instanceof LeafListSchemaNode leafList) {
+    private @NonNull LeafListEffectiveStatement leafSetEntryNode(final QName qname) {
+        final var parent = currentStatement();
+        if (parent instanceof LeafListEffectiveStatement leafList) {
             return leafList;
         }
-        if (parent instanceof DataNodeContainer parentContainer) {
-            final var child = parentContainer.dataChildByName(qname);
-            if (child instanceof LeafListSchemaNode childLeafList) {
+        if (parent instanceof DataTreeAwareEffectiveStatement parentContainer) {
+            final var child = parentContainer.findDataTreeNode(qname).orElse(null);
+            if (child instanceof LeafListEffectiveStatement childLeafList) {
                 return childLeafList;
             }
             throw new IllegalArgumentException(
@@ -236,52 +242,53 @@ public final class NormalizedNodeStreamWriterStack implements LeafrefResolver {
         throw new IllegalArgumentException("Cannot lookup " + qname + " in parent " + parent);
     }
 
-    public void startLeafSetEntryNode(final NodeWithValue<?> name) {
-        schemaStack.push(leafSetEntryNode(name.getNodeType()));
-    }
-
     public void startChoiceNode(final NodeIdentifier name) {
         LOG.debug("Enter choice {}", name);
-        final var stmt = dataTree.enterChoice(name.getNodeType());
-        if (stmt instanceof ChoiceSchemaNode choice) {
-            schemaStack.push(choice);
-        } else {
-            throw new VerifyException("Node " + stmt + " is not a choice");
-        }
+        schemaStack.push(dataTree.enterChoice(name.getNodeType()));
     }
 
-    public @NonNull ContainerLike startContainerNode(final NodeIdentifier name) {
+    public @NonNull DataTreeAwareEffectiveStatement<QName, ?> startContainerNode(final NodeIdentifier name) {
         LOG.debug("Enter container {}", name);
 
-        final ContainerLike schema;
-        if (schemaStack.isEmpty() && root instanceof NotificationDefinition notification
-            && name.getNodeType().equals(notification.getQName())) {
+        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 = notification.toContainerLike();
-            schemaStack.push(schema);
+            ret = notification;
         } else {
-            schema = enterDataTree(name, ContainerLike.class, "a container");
+            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");
+            }
         }
 
-        return schema;
+        schemaStack.push(ret);
+        return ret;
     }
 
     public void startAnyxmlNode(final NodeIdentifier name) {
-        enterDataTree(name, AnyxmlSchemaNode.class, "anyxml");
+        enterDataTree(name, AnyxmlEffectiveStatement.class, "anyxml");
     }
 
     public void startAnydataNode(final NodeIdentifier name) {
-        enterDataTree(name, AnydataSchemaNode.class, "anydata");
+        enterDataTree(name, AnydataEffectiveStatement.class, "anydata");
     }
 
-    public Object endNode() {
+    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 (getParent() != ret) {
+        if (currentStatement() != ret) {
             dataTree.exit();
         }
         return ret;