Maintain SchemaInferenceStack in SchemaTracker 32/95132/15
authorRobert Varga <robert.varga@pantheon.tech>
Thu, 11 Feb 2021 12:53:42 +0000 (13:53 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Sun, 14 Feb 2021 12:47:50 +0000 (13:47 +0100)
SchemaTracker is performing schema lookups around NormalizedNode
streaming structure as seen by codecs. It interacts with
yang-model-util indirectly with SchemaContextUtil, with is not
entirely nice.

Update its design to integrate with SchemaInferenceStack, where
it can get most of the lookups it needs in a neat package.

JIRA: YANGTOOLS-1233
Change-Id: Ic074b99cc0413729a1755bbe458bf0f5297b5164
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONNormalizedNodeStreamWriter.java
yang/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/StreamWriterFacade.java
yang/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/XMLStreamNormalizedNodeStreamWriter.java
yang/yang-data-impl/src/main/java/module-info.java
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/codec/SchemaTracker.java
yang/yang-model-util/src/main/java/org/opendaylight/yangtools/yang/model/util/SchemaInferenceStack.java
yang/yang-model-util/src/test/java/org/opendaylight/yangtools/yang/model/util/YT1231Test.java
yang/yang-model-util/src/test/java/org/opendaylight/yangtools/yang/model/util/YT1233Test.java [new file with mode: 0644]
yang/yang-model-util/src/test/resources/yt1233.yang [new file with mode: 0644]

index 4a2206d7f9d32ea851630acef702eb2f08599e7a..e1b3f1833ec033eb4a827e27bc190c02358c5c93 100644 (file)
@@ -8,16 +8,13 @@
 package org.opendaylight.yangtools.yang.data.codec.gson;
 
 import static com.google.common.base.Preconditions.checkState;
-import static com.google.common.base.Verify.verify;
 import static java.util.Objects.requireNonNull;
-import static org.w3c.dom.Node.ELEMENT_NODE;
-import static org.w3c.dom.Node.TEXT_NODE;
 
 import com.google.common.collect.ClassToInstanceMap;
 import com.google.common.collect.ImmutableClassToInstanceMap;
 import com.google.gson.stream.JsonWriter;
 import java.io.IOException;
-import java.util.List;
+import java.util.NoSuchElementException;
 import java.util.regex.Pattern;
 import javax.xml.transform.dom.DOMSource;
 import org.checkerframework.checker.regex.qual.Regex;
@@ -36,14 +33,13 @@ import org.opendaylight.yangtools.yang.data.impl.codec.SchemaTracker;
 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.DataNodeContainer;
 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
 import org.opendaylight.yangtools.yang.model.api.EffectiveStatementInference;
 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
 import org.opendaylight.yangtools.yang.model.api.TypedDataSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
 import org.w3c.dom.Element;
 import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
@@ -153,15 +149,15 @@ public abstract class JSONNormalizedNodeStreamWriter implements NormalizedNodeSt
      * the writer too.
      *
      * @param codecFactory JSON codec factory
-     * @param path Schema Path
+     * @param rootNode Root node inference
      * @param initialNs Initial namespace
      * @param jsonWriter JsonWriter
      * @return A stream writer instance
      */
     public static NormalizedNodeStreamWriter createExclusiveWriter(final JSONCodecFactory codecFactory,
-            final Absolute path, final XMLNamespace initialNs, final JsonWriter jsonWriter) {
-        return new Exclusive(codecFactory, SchemaTracker.create(codecFactory.getEffectiveModelContext(), path),
-            jsonWriter, new JSONStreamWriterExclusiveRootContext(initialNs));
+            final EffectiveStatementInference rootNode, final XMLNamespace initialNs, final JsonWriter jsonWriter) {
+        return new Exclusive(codecFactory, SchemaTracker.create(rootNode), jsonWriter,
+            new JSONStreamWriterExclusiveRootContext(initialNs));
     }
 
     /**
@@ -180,15 +176,15 @@ public abstract class JSONNormalizedNodeStreamWriter implements NormalizedNodeSt
      * the writer too.
      *
      * @param codecFactory JSON codec factory
-     * @param rootNode Root node
+     * @param path Schema Path
      * @param initialNs Initial namespace
      * @param jsonWriter JsonWriter
      * @return A stream writer instance
      */
     public static NormalizedNodeStreamWriter createExclusiveWriter(final JSONCodecFactory codecFactory,
-            final DataNodeContainer rootNode, final XMLNamespace initialNs, final JsonWriter jsonWriter) {
-        return new Exclusive(codecFactory, SchemaTracker.create(rootNode), jsonWriter,
-            new JSONStreamWriterExclusiveRootContext(initialNs));
+            final Absolute path, final XMLNamespace initialNs, final JsonWriter jsonWriter) {
+        return new Exclusive(codecFactory, SchemaTracker.create(codecFactory.getEffectiveModelContext(), path),
+            jsonWriter, new JSONStreamWriterExclusiveRootContext(initialNs));
     }
 
     /**
@@ -255,13 +251,13 @@ public abstract class JSONNormalizedNodeStreamWriter implements NormalizedNodeSt
      * close the wrapped writer; the caller must take care of that.
      *
      * @param codecFactory JSON codec factory
-     * @param rootNode Root node
+     * @param rootNode Root node inference
      * @param initialNs Initial namespace
      * @param jsonWriter JsonWriter
      * @return A stream writer instance
      */
     public static NormalizedNodeStreamWriter createNestedWriter(final JSONCodecFactory codecFactory,
-            final DataNodeContainer rootNode, final XMLNamespace initialNs, final JsonWriter jsonWriter) {
+            final EffectiveStatementInference rootNode, final XMLNamespace initialNs, final JsonWriter jsonWriter) {
         return new Nested(codecFactory, SchemaTracker.create(rootNode), jsonWriter,
             new JSONStreamWriterSharedRootContext(initialNs));
     }
@@ -436,19 +432,18 @@ public abstract class JSONNormalizedNodeStreamWriter implements NormalizedNodeSt
     }
 
     private void writeNormalizedAnydata(final NormalizedAnydata anydata) throws IOException {
-        final EffectiveStatementInference inference = anydata.getInference();
-        final List<? extends EffectiveStatement<?, ?>> path = inference.statementPath();
-        final DataNodeContainer parent;
-        if (path.size() > 1) {
-            final EffectiveStatement<?, ?> stmt = path.get(path.size() - 2);
-            verify(stmt instanceof DataNodeContainer, "Unexpected statement %s", stmt);
-            parent = (DataNodeContainer) stmt;
-        } else {
-            parent = inference.getEffectiveModelContext();
+        // Adjust state to point to parent node and ensure it can handle data tree nodes
+        final SchemaInferenceStack.Inference inference;
+        try {
+            final SchemaInferenceStack stack = SchemaInferenceStack.ofInference(anydata.getInference());
+            stack.exitToDataTree();
+            inference = stack.toInference();
+        } catch (IllegalArgumentException | IllegalStateException | NoSuchElementException e) {
+            throw new IOException("Cannot emit " + anydata, e);
         }
 
         anydata.writeTo(JSONNormalizedNodeStreamWriter.createNestedWriter(
-            codecs.rebaseTo(inference.getEffectiveModelContext()), parent, context.getNamespace(), writer));
+            codecs.rebaseTo(inference.getEffectiveModelContext()), inference, context.getNamespace(), writer));
     }
 
     private void writeAnyXmlValue(final DOMSource anyXmlValue) throws IOException {
@@ -481,10 +476,10 @@ public abstract class JSONNormalizedNodeStreamWriter implements NormalizedNodeSt
     }
 
     private static boolean isArrayElement(final Node node) {
-        if (ELEMENT_NODE == node.getNodeType()) {
+        if (Node.ELEMENT_NODE == node.getNodeType()) {
             final String nodeName = node.getNodeName();
             for (Node nextNode = node.getNextSibling(); nextNode != null; nextNode = nextNode.getNextSibling()) {
-                if (ELEMENT_NODE == nextNode.getNodeType() && nodeName.equals(nextNode.getNodeName())) {
+                if (Node.ELEMENT_NODE == nextNode.getNodeType() && nodeName.equals(nextNode.getNodeName())) {
                     return true;
                 }
             }
@@ -513,7 +508,7 @@ public abstract class JSONNormalizedNodeStreamWriter implements NormalizedNodeSt
     private void writeObject(Node node) throws IOException {
         String previousNodeName = "";
         while (node != null) {
-            if (ELEMENT_NODE == node.getNodeType()) {
+            if (Node.ELEMENT_NODE == node.getNodeType()) {
                 if (!node.getNodeName().equals(previousNodeName)) {
                     previousNodeName = node.getNodeName();
                     writer.name(node.getNodeName());
@@ -552,7 +547,7 @@ public abstract class JSONNormalizedNodeStreamWriter implements NormalizedNodeSt
         final NodeList children = node.getChildNodes();
         for (int i = 0, length = children.getLength(); i < length; i++) {
             final Node childNode = children.item(i);
-            if (ELEMENT_NODE == childNode.getNodeType()) {
+            if (Node.ELEMENT_NODE == childNode.getNodeType()) {
                 return (Element) childNode;
             }
         }
@@ -563,7 +558,7 @@ public abstract class JSONNormalizedNodeStreamWriter implements NormalizedNodeSt
         final NodeList children = node.getChildNodes();
         for (int i = 0, length = children.getLength(); i < length; i++) {
             final Node childNode = children.item(i);
-            if (TEXT_NODE == childNode.getNodeType()) {
+            if (Node.TEXT_NODE == childNode.getNodeType()) {
                 return (Text) childNode;
             }
         }
index 798057c359858f71fee28adf45909a8c5bbe1542..8f0b84451b7b24d5a57ed4468aeb7dbdc13671a8 100644 (file)
@@ -7,12 +7,11 @@
  */
 package org.opendaylight.yangtools.yang.data.codec.xml;
 
-import static com.google.common.base.Verify.verify;
 import static java.util.Objects.requireNonNull;
 
 import com.google.common.base.Strings;
 import java.io.IOException;
-import java.util.List;
+import java.util.NoSuchElementException;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import javax.xml.XMLConstants;
@@ -24,9 +23,7 @@ import javax.xml.stream.XMLStreamWriter;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.XMLNamespace;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedAnydata;
-import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
-import org.opendaylight.yangtools.yang.model.api.EffectiveStatementInference;
-import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -288,20 +285,18 @@ final class StreamWriterFacade extends ValueWriter {
     void emitNormalizedAnydata(final NormalizedAnydata anydata) throws XMLStreamException {
         flushElement();
 
-        final EffectiveStatementInference inference = anydata.getInference();
-        final List<? extends EffectiveStatement<?, ?>> path = inference.statementPath();
-        final DataNodeContainer parent;
-        if (path.size() > 1) {
-            final EffectiveStatement<?, ?> stmt = path.get(path.size() - 2);
-            verify(stmt instanceof DataNodeContainer, "Unexpected statement %s", stmt);
-            parent = (DataNodeContainer) stmt;
-        } else {
-            parent = inference.getEffectiveModelContext();
+        // Adjust state to point to parent node and ensure it can handle data tree nodes
+        final SchemaInferenceStack.Inference inference;
+        try {
+            final SchemaInferenceStack stack = SchemaInferenceStack.ofInference(anydata.getInference());
+            stack.exitToDataTree();
+            inference = stack.toInference();
+        } catch (IllegalArgumentException | IllegalStateException | NoSuchElementException e) {
+            throw new XMLStreamException("Cannot emit " + anydata, e);
         }
 
         try {
-            anydata.writeTo(XMLStreamNormalizedNodeStreamWriter.create(writer, inference.getEffectiveModelContext(),
-                parent));
+            anydata.writeTo(XMLStreamNormalizedNodeStreamWriter.create(writer, inference));
         } catch (IOException e) {
             throw new XMLStreamException("Failed to emit anydata " + anydata, e);
         }
index 6fa8238ca8c38b806684bd24ea653db96c8530cf..1152bb9744eb936c8693767c3d88715e87029192 100644 (file)
@@ -30,8 +30,8 @@ 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.NormalizedNodeStreamWriterExtension;
 import org.opendaylight.yangtools.yang.data.impl.codec.SchemaTracker;
-import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+import org.opendaylight.yangtools.yang.model.api.EffectiveStatementInference;
 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute;
 import org.slf4j.Logger;
@@ -71,20 +71,20 @@ public abstract class XMLStreamNormalizedNodeStreamWriter<T> implements Normaliz
      */
     public static @NonNull NormalizedNodeStreamWriter create(final XMLStreamWriter writer,
             final EffectiveModelContext context) {
-        return create(writer, context, context);
+        return new SchemaAwareXMLStreamNormalizedNodeStreamWriter(writer, context, SchemaTracker.create(context));
     }
 
     /**
      * Create a new writer with the specified context and rooted at the specified node.
      *
      * @param writer Output {@link XMLStreamWriter}
-     * @param context Associated {@link EffectiveModelContext}.
-     * @param rootNode Root node
+     * @param inference root node inference
      * @return A new {@link NormalizedNodeStreamWriter}
      */
     public static @NonNull NormalizedNodeStreamWriter create(final XMLStreamWriter writer,
-            final EffectiveModelContext context, final DataNodeContainer rootNode) {
-        return new SchemaAwareXMLStreamNormalizedNodeStreamWriter(writer, context, SchemaTracker.create(rootNode));
+            final EffectiveStatementInference inference) {
+        return new SchemaAwareXMLStreamNormalizedNodeStreamWriter(writer, inference.getEffectiveModelContext(),
+            SchemaTracker.create(inference));
     }
 
     /**
index c36c2df55f99cf9b69e71c6e90e7ddebaf826ec5..d735a407cc8ed9f81ede646673bae71e032fdce4 100644 (file)
@@ -24,12 +24,12 @@ module org.opendaylight.yangtools.yang.data.impl {
     requires transitive org.opendaylight.yangtools.yang.data.api;
     requires transitive org.opendaylight.yangtools.rfc7952.data.api;
     requires transitive org.opendaylight.yangtools.rfc7952.data.util;
+    requires transitive org.opendaylight.yangtools.yang.model.util;
     requires org.opendaylight.yangtools.util;
     requires org.opendaylight.yangtools.rfc8528.data.util;
     requires org.opendaylight.yangtools.yang.common;
     requires org.opendaylight.yangtools.yang.model.api;
     requires org.opendaylight.yangtools.yang.model.spi;
-    requires org.opendaylight.yangtools.yang.model.util;
     requires org.slf4j;
 
     // Annotations
index a7b0696c41c5d9492c35e1776eb9e988d5ac4978..d1d9adba0c3d32639e11c1b2422498fcb00d9e65 100644 (file)
@@ -8,6 +8,7 @@
 package org.opendaylight.yangtools.yang.data.impl.codec;
 
 import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Verify.verify;
 import static java.util.Objects.requireNonNull;
 
 import com.google.common.annotations.Beta;
@@ -35,7 +36,7 @@ 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.GroupingDefinition;
+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;
@@ -44,10 +45,13 @@ import org.opendaylight.yangtools.yang.model.api.SchemaNode;
 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
 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.ChoiceEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.DataTreeEffectiveStatement;
 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.util.EffectiveAugmentationSchema;
 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -59,31 +63,57 @@ public final class SchemaTracker {
     private static final Logger LOG = LoggerFactory.getLogger(SchemaTracker.class);
 
     private final Deque<WithStatus> schemaStack = new ArrayDeque<>();
+    private final SchemaInferenceStack dataTree;
     private final DataNodeContainer root;
 
-    private SchemaTracker(final DataNodeContainer root) {
-        this.root = requireNonNull(root);
+    private SchemaTracker(final EffectiveModelContext context) {
+        root = requireNonNull(context);
+        dataTree = SchemaInferenceStack.of(context);
     }
 
-    private static @NonNull SchemaTracker create(final SchemaInferenceStack root) {
-        if (root.isEmpty()) {
-            return new SchemaTracker(root.getEffectiveModelContext());
+    private SchemaTracker(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;
+        } else {
+            root = dataTree.getEffectiveModelContext();
         }
+    }
 
-        final EffectiveStatement<?, ?> current = root.currentStatement();
-        checkArgument(current instanceof DataNodeContainer, "Cannot instantiate on %s", current);
-        return new SchemaTracker((DataNodeContainer) current);
+    /**
+     * Create a new writer with the specified inference state as its root.
+     *
+     * @param root Root inference state
+     * @return A new {@link NormalizedNodeStreamWriter}
+     * @throws NullPointerException if {@code root} is null
+     */
+    public static @NonNull SchemaTracker create(final EffectiveStatementInference root) {
+        return new SchemaTracker(SchemaInferenceStack.ofInference(root));
     }
 
     /**
-     * Create a new writer with the specified node as its root.
+     * Create a new writer with the specified inference state as its root.
      *
-     * @param root Root node
+     * @param root Root inference state
      * @return A new {@link NormalizedNodeStreamWriter}
      * @throws NullPointerException if {@code root} is null
      */
-    public static @NonNull SchemaTracker create(final DataNodeContainer root) {
-        return new SchemaTracker(root);
+    public static @NonNull SchemaTracker create(final Inference root) {
+        return new SchemaTracker(root.toSchemaInferenceStack());
+    }
+
+    /**
+     * Create a new writer at the root of specified {@link EffectiveModelContext}.
+     *
+     * @param context effective model context
+     * @return A new {@link NormalizedNodeStreamWriter}
+     * @throws NullPointerException if {@code context} is null
+     */
+    public static @NonNull SchemaTracker create(final EffectiveModelContext context) {
+        return new SchemaTracker(context);
     }
 
     /**
@@ -96,7 +126,7 @@ public final class SchemaTracker {
      * @throws IllegalArgumentException if {@code path} does not point to a valid root
      */
     public static @NonNull SchemaTracker create(final EffectiveModelContext context, final Absolute path) {
-        return create(SchemaInferenceStack.of(context, path));
+        return new SchemaTracker(SchemaInferenceStack.of(context, path));
     }
 
     /**
@@ -109,7 +139,7 @@ public final class SchemaTracker {
      * @throws IllegalArgumentException if {@code path} does not point to a valid root
      */
     public static @NonNull SchemaTracker create(final EffectiveModelContext context, final SchemaPath path) {
-        return create(SchemaInferenceStack.ofInstantiatedPath(context, path));
+        return new SchemaTracker(SchemaInferenceStack.ofInstantiatedPath(context, path));
     }
 
     /**
@@ -130,61 +160,29 @@ public final class SchemaTracker {
         checkArgument(current instanceof RpcEffectiveStatement || current instanceof ActionEffectiveStatement,
             "Path %s resolved into non-operation %s", operation, current);
         stack.enterSchemaTree(qname);
-        return create(stack);
+        return new SchemaTracker(stack);
     }
 
     public Object getParent() {
-        if (schemaStack.isEmpty()) {
-            return root;
-        }
-        return schemaStack.peek();
+        final WithStatus schema = schemaStack.peek();
+        return schema == null ? root : schema;
     }
 
-    private SchemaNode getSchema(final PathArgument name) {
-        final Object parent = getParent();
-        SchemaNode schema = null;
+    private SchemaNode enterDataTree(final PathArgument name) {
         final QName qname = name.getNodeType();
-        if (parent instanceof DataNodeContainer) {
-            schema = ((DataNodeContainer)parent).dataChildByName(qname);
-            if (schema == null) {
-                if (parent instanceof GroupingDefinition) {
-                    schema = (GroupingDefinition) parent;
-                } else if (parent instanceof NotificationDefinition) {
-                    schema = (NotificationDefinition) parent;
-                }
-            }
-        } else if (parent instanceof ChoiceSchemaNode) {
-            schema = findChildInCases((ChoiceSchemaNode) parent, qname);
-        } else {
-            throw new IllegalStateException("Unsupported schema type " + parent.getClass() + " on stack.");
-        }
-
-        checkArgument(schema != null, "Could not find schema for node %s in %s", qname, parent);
-        return schema;
-    }
-
-    private static SchemaNode findChildInCases(final ChoiceSchemaNode parent, final QName qname) {
-        for (final CaseSchemaNode caze : parent.getCases()) {
-            final Optional<DataSchemaNode> potential = caze.findDataChildByName(qname);
-            if (potential.isPresent()) {
-                return potential.get();
-            }
-        }
-        return null;
-    }
-
-    private static SchemaNode findCaseByChild(final ChoiceSchemaNode parent, final QName qname) {
-        for (final CaseSchemaNode caze : parent.getCases()) {
-            final Optional<DataSchemaNode> potential = caze.findDataChildByName(qname);
-            if (potential.isPresent()) {
-                return caze;
-            }
+        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);
         }
-        return null;
+        return ret;
     }
 
     public void startList(final PathArgument name) {
-        final SchemaNode schema = getSchema(name);
+        final SchemaNode schema = enterDataTree(name);
         checkArgument(schema instanceof ListSchemaNode, "Node %s is not a list", schema);
         schemaStack.push(schema);
     }
@@ -195,18 +193,14 @@ public final class SchemaTracker {
         schemaStack.push((ListSchemaNode) schema);
     }
 
-    public LeafSchemaNode leafNode(final NodeIdentifier name) throws IOException {
-        final SchemaNode schema = getSchema(name);
-        checkArgument(schema instanceof LeafSchemaNode, "Node %s is not a leaf", schema);
-        return (LeafSchemaNode) schema;
-    }
-
     public void startLeafNode(final NodeIdentifier name) throws IOException {
-        schemaStack.push(leafNode(name));
+        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 = getSchema(name);
+        final SchemaNode schema = enterDataTree(name);
         checkArgument(schema instanceof LeafListSchemaNode, "Node %s is not a leaf-list", schema);
         schemaStack.push(schema);
         return (LeafListSchemaNode) schema;
@@ -218,6 +212,7 @@ public final class SchemaTracker {
             return (LeafListSchemaNode) parent;
         }
 
+        // FIXME: when would this trigger?
         final SchemaNode child = SchemaUtils.findDataChildSchemaByQName((SchemaNode) parent, qname);
         checkArgument(child instanceof LeafListSchemaNode,
             "Node %s is neither a leaf-list nor currently in a leaf-list", child);
@@ -230,19 +225,18 @@ public final class SchemaTracker {
 
     public ChoiceSchemaNode startChoiceNode(final NodeIdentifier name) {
         LOG.debug("Enter choice {}", name);
-        final SchemaNode schema = getSchema(name);
-
-        checkArgument(schema instanceof ChoiceSchemaNode, "Node %s is not a choice", schema);
-        schemaStack.push(schema);
-        return (ChoiceSchemaNode)schema;
+        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;
     }
 
     public SchemaNode startContainerNode(final NodeIdentifier name) {
         LOG.debug("Enter container {}", name);
-        final SchemaNode schema = getSchema(name);
-        final boolean isAllowed = schema instanceof ContainerLike || schema instanceof NotificationDefinition;
-
-        checkArgument(isAllowed, "Node %s is not a container nor a notification", schema);
+        final SchemaNode schema = enterDataTree(name);
+        checkArgument(schema instanceof ContainerLike || schema instanceof NotificationDefinition,
+            "Node %s is not a container nor a notification", schema);
         schemaStack.push(schema);
         return schema;
     }
@@ -265,27 +259,35 @@ public final class SchemaTracker {
         return resolvedSchema;
     }
 
-    public AnyxmlSchemaNode anyxmlNode(final NodeIdentifier name) {
-        final SchemaNode schema = getSchema(name);
-        checkArgument(schema instanceof AnyxmlSchemaNode, "Node %s is not anyxml", schema);
-        return (AnyxmlSchemaNode)schema;
+    private static SchemaNode findCaseByChild(final ChoiceSchemaNode parent, final QName qname) {
+        for (final CaseSchemaNode caze : parent.getCases()) {
+            final Optional<DataSchemaNode> potential = caze.findDataChildByName(qname);
+            if (potential.isPresent()) {
+                return caze;
+            }
+        }
+        return null;
     }
 
     public void startAnyxmlNode(final NodeIdentifier name) {
-        schemaStack.push(anyxmlNode(name));
-    }
-
-    public AnydataSchemaNode anydataNode(final NodeIdentifier name) {
-        final SchemaNode schema = getSchema(name);
-        checkArgument(schema instanceof AnydataSchemaNode, "Node %s is not anydata", schema);
-        return (AnydataSchemaNode)schema;
+        final SchemaNode schema = enterDataTree(name);
+        checkArgument(schema instanceof AnyxmlSchemaNode, "Node %s is not anyxml", schema);
+        schemaStack.push(schema);
     }
 
     public void startAnydataNode(final NodeIdentifier name) {
-        schemaStack.push(anydataNode(name));
+        final SchemaNode schema = enterDataTree(name);
+        checkArgument(schema instanceof AnydataSchemaNode, "Node %s is not anydata", schema);
+        schemaStack.push(schema);
     }
 
     public Object endNode() {
-        return schemaStack.pop();
+        final Object 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) {
+            dataTree.exit();
+        }
+        return ret;
     }
 }
index bbf05d54fe5e1d27ba406be15ba9124a25729fe6..708efcc914caf5b9e3c7578135e1fdc7145e2b8d 100644 (file)
@@ -125,16 +125,21 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex
         this.clean = clean;
     }
 
+    private SchemaInferenceStack(final EffectiveModelContext effectiveModel) {
+        this.effectiveModel = requireNonNull(effectiveModel);
+        this.deque = new ArrayDeque<>();
+        this.clean = true;
+    }
+
     /**
      * Create a new empty stack backed by an effective model.
      *
      * @param effectiveModel EffectiveModelContext to which this stack is attached
+     * @return A new stack
      * @throws NullPointerException if {@code effectiveModel} is null
      */
-    public SchemaInferenceStack(final EffectiveModelContext effectiveModel) {
-        this.deque = new ArrayDeque<>();
-        this.effectiveModel = requireNonNull(effectiveModel);
-        this.clean = true;
+    public static @NonNull SchemaInferenceStack of(final EffectiveModelContext effectiveModel) {
+        return new SchemaInferenceStack(effectiveModel);
     }
 
     /**
@@ -194,7 +199,7 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex
      * @throws IllegalArgumentException if {@code path} cannot be resolved in the effective model or if it is not an
      *                                  absolute path.
      */
-    // FIXME: 7.0.0: consider deprecating this method
+    @Deprecated
     public static @NonNull SchemaInferenceStack ofInstantiatedPath(final EffectiveModelContext effectiveModel,
             final SchemaPath path) {
         checkArgument(path.isAbsolute(), "Cannot operate on relative path %s", path);
@@ -365,6 +370,23 @@ public final class SchemaInferenceStack implements Mutable, EffectiveModelContex
         return prev;
     }
 
+    /**
+     * Pop the current statement from the stack, asserting it is a {@link DataTreeEffectiveStatement} and that
+     * subsequent {@link #enterDataTree(QName)} will find it again.
+     *
+     * @return Previous statement
+     * @throws NoSuchElementException if this stack is empty
+     * @throws IllegalStateException if current statement is not a DataTreeEffectiveStatement or if its parent is not
+     *                               a {@link DataTreeAwareEffectiveStatement}
+     */
+    public @NonNull DataTreeEffectiveStatement<?> exitToDataTree() {
+        final EffectiveStatement<?, ?> child = exit();
+        checkState(child instanceof DataTreeEffectiveStatement, "Unexpected current %s", child);
+        final EffectiveStatement<?, ?> parent = deque.peekFirst();
+        checkState(parent == null || parent instanceof DataTreeAwareEffectiveStatement, "Unexpected parent %s", parent);
+        return (DataTreeEffectiveStatement<?>) child;
+    }
+
     /**
      * Return an {@link Inference} equivalent of current state.
      *
index f08a5e760265befb2ce3baa26cf4f4fa6d80fb9f..33872caefb0f6f7481eb68b5b6e6989e4746c2d9 100644 (file)
@@ -29,7 +29,7 @@ public class YT1231Test {
     @Test
     public void testEnterDataTree() {
         final EffectiveModelContext context = YangParserTestUtils.parseYangResource("/yt1231.yang");
-        final SchemaInferenceStack stack = new SchemaInferenceStack(context);
+        final SchemaInferenceStack stack = SchemaInferenceStack.of(context);
 
         // Trivial
         assertThat(stack.enterDataTree(FOO), instanceOf(ContainerEffectiveStatement.class));
@@ -51,7 +51,7 @@ public class YT1231Test {
     @Test
     public void testEnterChoice() {
         final EffectiveModelContext context = YangParserTestUtils.parseYangResource("/yt1231.yang");
-        final SchemaInferenceStack stack = new SchemaInferenceStack(context);
+        final SchemaInferenceStack stack = SchemaInferenceStack.of(context);
 
         // Simple
         assertThat(stack.enterDataTree(FOO), instanceOf(ContainerEffectiveStatement.class));
diff --git a/yang/yang-model-util/src/test/java/org/opendaylight/yangtools/yang/model/util/YT1233Test.java b/yang/yang-model-util/src/test/java/org/opendaylight/yangtools/yang/model/util/YT1233Test.java
new file mode 100644 (file)
index 0000000..fb7823a
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2021 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.model.util;
+
+import static org.hamcrest.CoreMatchers.startsWith;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import java.util.NoSuchElementException;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+import org.opendaylight.yangtools.yang.model.api.stmt.DataTreeEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.GroupingEffectiveStatement;
+import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
+
+public class YT1233Test {
+    private static EffectiveModelContext context;
+
+    private final SchemaInferenceStack stack = SchemaInferenceStack.of(context);
+
+    @BeforeClass
+    public static void beforeClass() {
+        context = YangParserTestUtils.parseYangResource("/yt1233.yang");
+    }
+
+    @Test
+    public void testExitToDataTree() {
+        final DataTreeEffectiveStatement<?> foo = stack.enterDataTree(QName.create("foo", "foo"));
+        assertSame(foo, stack.exitToDataTree());
+        assertTrue(stack.isEmpty());
+        assertSame(foo, stack.enterDataTree(foo.argument()));
+    }
+
+    @Test
+    public void testExitToGrouping() {
+        final GroupingEffectiveStatement baz = stack.enterGrouping(QName.create("foo", "baz"));
+        final DataTreeEffectiveStatement<?> xyzzy = stack.enterDataTree(QName.create("foo", "xyzzy"));
+        assertSame(xyzzy, stack.exitToDataTree());
+        assertSame(baz, stack.currentStatement());
+        assertSame(xyzzy, stack.enterDataTree(xyzzy.argument()));
+    }
+
+    @Test
+    public void testEmptyExitToDataTree() {
+        assertThrows(NoSuchElementException.class, stack::exitToDataTree);
+    }
+
+    @Test
+    public void testSchemaExitToDataTree() {
+        stack.enterSchemaTree(QName.create("foo", "bar"));
+        final IllegalStateException ex = assertThrows(IllegalStateException.class, stack::exitToDataTree);
+        assertThat(ex.getMessage(), startsWith("Unexpected current "));
+    }
+}
diff --git a/yang/yang-model-util/src/test/resources/yt1233.yang b/yang/yang-model-util/src/test/resources/yt1233.yang
new file mode 100644 (file)
index 0000000..7703037
--- /dev/null
@@ -0,0 +1,13 @@
+module foo {
+  namespace foo;
+  prefix foo;
+
+  anyxml foo;
+
+  rpc bar;
+
+  grouping baz {
+    anyxml xyzzy;
+  }
+}
+