Add DataSchemaContextNode/SchemaInferenceStack integration 26/100226/7
authorRobert Varga <robert.varga@pantheon.tech>
Wed, 23 Mar 2022 13:29:23 +0000 (14:29 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Thu, 24 Mar 2022 11:04:43 +0000 (12:04 +0100)
The removal of SchemaNode.getPath() opens a functionality gap, where it
becomes impossible to initialize a SchemaInferenceStack via use of
DataSchemaContextTree. Add enterChild/enterPath methods which maintain
or return a SchemaInferenceStack, so that cognizant callers can use them
to talk to other schema-informed APIs.

JIRA: YANGTOOLS-1412
Change-Id: Ia3562909e322992a3ee84fd0b3f2f0cc7ce5183d
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
16 files changed:
data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/AbstractLeafContextNode.java
data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/AbstractListItemContextNode.java [new file with mode: 0644]
data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/AbstractListLikeContextNode.java [new file with mode: 0644]
data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/AugmentationContextNode.java
data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/ChoiceNodeContextNode.java
data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/DataContainerContextNode.java
data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/DataSchemaContextNode.java
data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/DataSchemaContextTree.java
data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/LeafListEntryContextNode.java
data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/ListItemContextNode.java
data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/UnkeyedListItemContextNode.java
data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/UnkeyedListMixinContextNode.java
data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/UnorderedLeafListMixinContextNode.java
data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/UnorderedMapMixinContextNode.java
data/yang-data-util/src/test/java/org/opendaylight/yangtools/yang/data/util/YT1412Test.java [new file with mode: 0644]
data/yang-data-util/src/test/resources/yt1412.yang [new file with mode: 0644]

index 7acdbade5a7436d12f7a849646e59238ab03e9bf..9a274467b833b7b735cdd52c8e6901893f5db124 100644 (file)
@@ -10,10 +10,11 @@ package org.opendaylight.yangtools.yang.data.util;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
 
 abstract class AbstractLeafContextNode<T extends PathArgument, S extends DataSchemaNode>
         extends DataSchemaContextNode<T> {
-    AbstractLeafContextNode(T identifier, S schema) {
+    AbstractLeafContextNode(final T identifier, final S schema) {
         super(identifier, schema);
     }
 
@@ -26,4 +27,14 @@ abstract class AbstractLeafContextNode<T extends PathArgument, S extends DataSch
     public final DataSchemaContextNode<?> getChild(final QName child) {
         return null;
     }
+
+    @Override
+    protected final DataSchemaContextNode<?> enterChild(final QName child, final SchemaInferenceStack stack) {
+        return null;
+    }
+
+    @Override
+    protected final DataSchemaContextNode<?> enterChild(final PathArgument child, final SchemaInferenceStack stack) {
+        return null;
+    }
 }
diff --git a/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/AbstractListItemContextNode.java b/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/AbstractListItemContextNode.java
new file mode 100644 (file)
index 0000000..523bdb9
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2022 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.util;
+
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
+
+/**
+ * Abstract superclass for individual list items -- be it {@link ListItemContextNode} or
+ * {@link UnkeyedListItemContextNode}.
+ */
+abstract class AbstractListItemContextNode<T extends PathArgument> extends DataContainerContextNode<T> {
+    AbstractListItemContextNode(final T identifier, final DataNodeContainer container, final DataSchemaNode schema) {
+        super(identifier, container, schema);
+    }
+
+    @Override
+    protected void pushToStack(final SchemaInferenceStack stack) {
+        // No-op
+    }
+}
diff --git a/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/AbstractListLikeContextNode.java b/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/AbstractListLikeContextNode.java
new file mode 100644 (file)
index 0000000..1ff4875
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2022 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.util;
+
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
+
+/**
+ * An {@link AbstractMixinContextNode} which corresponding to a {@code list} or {@code leaf-list} node. NormalizedNode
+ * representation of these nodes is similar to JSON encoding and therefore we have two {@link DataSchemaContextNode}
+ * levels backed by a single {@link DataSchemaNode}.
+ */
+abstract class AbstractListLikeContextNode<T extends PathArgument> extends AbstractMixinContextNode<T> {
+    AbstractListLikeContextNode(final T identifier, final DataSchemaNode schema) {
+        super(identifier, schema);
+    }
+
+    @Override
+    protected final DataSchemaContextNode<?> enterChild(final QName child, final SchemaInferenceStack stack) {
+        // Stack is already pointing to the corresponding statement, now we are just working with the child
+        return getChild(child);
+    }
+
+    @Override
+    protected final DataSchemaContextNode<?> enterChild(final PathArgument child, final SchemaInferenceStack stack) {
+        return getChild(child);
+    }
+}
index bf3e3a8bad839303c68b69b95efea03fd1cd64ea..2482631c6f6015831ad22e2b96f3e37d56ef795f 100644 (file)
@@ -15,6 +15,7 @@ import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
 import org.opendaylight.yangtools.yang.model.util.EffectiveAugmentationSchema;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
 
 final class AugmentationContextNode extends DataContainerContextNode<AugmentationIdentifier> {
     AugmentationContextNode(final AugmentationSchemaNode augmentation, final DataNodeContainer target) {
@@ -40,4 +41,9 @@ final class AugmentationContextNode extends DataContainerContextNode<Augmentatio
     protected Set<QName> getQNameIdentifiers() {
         return getIdentifier().getPossibleChildNames();
     }
+
+    @Override
+    protected void pushToStack(final SchemaInferenceStack stack) {
+        // No-op
+    }
 }
index a93d1f1883b8b3900dea5fe6351b434687ae3517..c2b3d694ea43fea7aad8d5e6f0cd81b90dc8209b 100644 (file)
@@ -7,21 +7,28 @@
  */
 package org.opendaylight.yangtools.yang.data.util;
 
+import static com.google.common.base.Verify.verifyNotNull;
+
 import com.google.common.collect.ImmutableMap;
 import java.util.Set;
+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.NodeIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
 
 final class ChoiceNodeContextNode extends AbstractMixinContextNode<NodeIdentifier> {
-    private final ImmutableMap<QName, DataSchemaContextNode<?>> byQName;
     private final ImmutableMap<PathArgument, DataSchemaContextNode<?>> byArg;
+    private final ImmutableMap<QName, DataSchemaContextNode<?>> byQName;
+    private final ImmutableMap<DataSchemaContextNode<?>, QName> childToCase;
 
     ChoiceNodeContextNode(final ChoiceSchemaNode schema) {
         super(NodeIdentifier.create(schema.getQName()), schema);
+        ImmutableMap.Builder<DataSchemaContextNode<?>, QName> childToCaseBuilder = ImmutableMap.builder();
         ImmutableMap.Builder<QName, DataSchemaContextNode<?>> byQNameBuilder = ImmutableMap.builder();
         ImmutableMap.Builder<PathArgument, DataSchemaContextNode<?>> byArgBuilder = ImmutableMap.builder();
 
@@ -29,11 +36,14 @@ final class ChoiceNodeContextNode extends AbstractMixinContextNode<NodeIdentifie
             for (DataSchemaNode cazeChild : caze.getChildNodes()) {
                 DataSchemaContextNode<?> childOp = DataSchemaContextNode.of(cazeChild);
                 byArgBuilder.put(childOp.getIdentifier(), childOp);
+                childToCaseBuilder.put(childOp, caze.getQName());
                 for (QName qname : childOp.getQNameIdentifiers()) {
                     byQNameBuilder.put(qname, childOp);
                 }
             }
         }
+
+        childToCase = childToCaseBuilder.build();
         byQName = byQNameBuilder.build();
         byArg = byArgBuilder.build();
     }
@@ -52,4 +62,29 @@ final class ChoiceNodeContextNode extends AbstractMixinContextNode<NodeIdentifie
     protected Set<QName> getQNameIdentifiers() {
         return byQName.keySet();
     }
-}
+
+    @Override
+    protected DataSchemaContextNode<?> enterChild(final QName child, final SchemaInferenceStack stack) {
+        return pushToStack(getChild(child), stack);
+    }
+
+    @Override
+    protected DataSchemaContextNode<?> enterChild(final PathArgument child, final SchemaInferenceStack stack) {
+        return pushToStack(getChild(child), stack);
+    }
+
+    @Override
+    protected void pushToStack(final @NonNull SchemaInferenceStack stack) {
+        stack.enterChoice(getIdentifier().getNodeType());
+    }
+
+    private @Nullable DataSchemaContextNode<?> pushToStack(final @Nullable DataSchemaContextNode<?> child,
+            final @NonNull SchemaInferenceStack stack) {
+        if (child != null) {
+            final var caseName = verifyNotNull(childToCase.get(child), "No case statement for %s in %s", child, this);
+            stack.enterSchemaTree(caseName);
+            child.pushToStack(stack);
+        }
+        return child;
+    }
+}
\ No newline at end of file
index 96869c1b8d7a8cd623e3ee32e51e301b3d35928b..5059deae83d794ade47c55db8027caaec7bddafb 100644 (file)
@@ -11,11 +11,14 @@ import static java.util.Objects.requireNonNull;
 
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
+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.AugmentationIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
 
 abstract class DataContainerContextNode<T extends PathArgument> extends AbstractInteriorContextNode<T> {
     private final ConcurrentMap<PathArgument, DataSchemaContextNode<?>> byArg = new ConcurrentHashMap<>();
@@ -47,6 +50,25 @@ abstract class DataContainerContextNode<T extends PathArgument> extends Abstract
         return register(potential);
     }
 
+    @Override
+    protected final DataSchemaContextNode<?> enterChild(final QName child, final SchemaInferenceStack stack) {
+        return pushToStack(getChild(child), stack);
+    }
+
+
+    @Override
+    protected final DataSchemaContextNode<?> enterChild(final PathArgument child, final SchemaInferenceStack stack) {
+        return pushToStack(getChild(child), stack);
+    }
+
+    private static @Nullable DataSchemaContextNode<?> pushToStack(final @Nullable DataSchemaContextNode<?> child,
+            final @NonNull SchemaInferenceStack stack) {
+        if (child != null) {
+            child.pushToStack(stack);
+        }
+        return child;
+    }
+
     private DataSchemaContextNode<?> fromLocalSchema(final PathArgument child) {
         if (child instanceof AugmentationIdentifier) {
             return fromSchemaAndQNameChecked(container, ((AugmentationIdentifier) child).getPossibleChildNames()
@@ -55,8 +77,8 @@ abstract class DataContainerContextNode<T extends PathArgument> extends Abstract
         return fromSchemaAndQNameChecked(container, child.getNodeType());
     }
 
-    protected DataSchemaContextNode<?> fromLocalSchemaAndQName(final DataNodeContainer schema2, final QName child) {
-        return fromSchemaAndQNameChecked(schema2, child);
+    protected DataSchemaContextNode<?> fromLocalSchemaAndQName(final DataNodeContainer schema, final QName child) {
+        return fromSchemaAndQNameChecked(schema, child);
     }
 
     private DataSchemaContextNode<?> register(final DataSchemaContextNode<?> potential) {
@@ -69,5 +91,4 @@ abstract class DataContainerContextNode<T extends PathArgument> extends Abstract
         }
         return potential;
     }
-
 }
index ac6e43e808d3dd6d9e60c8b159d9a56227ab835f..0a92a61c7c4ee676ef24db36e408689a15d7d136 100644 (file)
@@ -7,6 +7,8 @@
  */
 package org.opendaylight.yangtools.yang.data.util;
 
+import static java.util.Objects.requireNonNull;
+
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import java.util.Optional;
@@ -19,6 +21,12 @@ 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.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
+import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode;
 import org.opendaylight.yangtools.yang.model.api.AnydataSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
@@ -33,6 +41,7 @@ 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.SchemaNode;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
 
 /**
  * Schema derived data providing necessary information for mapping between
@@ -41,6 +50,15 @@ import org.opendaylight.yangtools.yang.model.api.SchemaNode;
  *
  * @param <T> Path Argument type
  */
+// FIXME: YANGTOOLS-1413: this really should be an interface, as there is a ton of non-trivial composition going on:
+//        - getDataSchemaNode() cannot return AugmentationSchemaNode, which is guarded by isMixinNode() and users should
+//          not be touching mixin details anyway
+//        - the idea of getIdentifier() is wrong -- if does the wrong thing for items of leaf-list and keyed list
+//          because those identifiers need a value. We also do not expect users to store the results in a Map, which
+//          defeats the idea of Identifiable
+//        - the generic argument is really an implementation detail and we really would like to also make dataSchemaNode
+//          (or rather: underlying SchemaNode) an argument. Both of these are not something users can influence and
+//          therefore we should not burden them with <?> on each reference to this class
 public abstract class DataSchemaContextNode<T extends PathArgument> extends AbstractSimpleIdentifiable<T> {
     // FIXME: this can be null only for AugmentationContextNode and in that case the interior part is handled by a
     //        separate field in DataContainerContextNode. We need to re-examine our base interface class hierarchy
@@ -52,12 +70,27 @@ public abstract class DataSchemaContextNode<T extends PathArgument> extends Abst
         this.dataSchemaNode = schema;
     }
 
+    // FIXME: remove this constructor. Once we do, adjust 'enterChild' visibility to package-private
     @Deprecated(forRemoval = true, since = "8.0.2")
     protected DataSchemaContextNode(final T identifier, final SchemaNode schema) {
         this(identifier, schema instanceof DataSchemaNode ? (DataSchemaNode) schema : null);
     }
 
-    // FIXME: document this method
+    /**
+     * This node is a {@link NormalizedNode} intermediate, not represented in RFC7950 XML encoding. This is typically
+     * one of
+     * <ul>
+     *   <li>{@link AugmentationNode} backed by an {@link AugmentationSchemaNode}, or</li>
+     *   <li>{@link ChoiceNode} backed by a {@link ChoiceSchemaNode}, or</li>
+     *   <li>{@link LeafSetNode} backed by a {@link LeafListSchemaNode}, or</li>
+     *   <li>{@link MapNode} backed by a {@link ListSchemaNode} with a non-empty
+     *       {@link ListSchemaNode#getKeyDefinition()}, or</li>
+     *   <li>{@link UnkeyedListNode} backed by a {@link ListSchemaNode} with an empty
+     *       {@link ListSchemaNode#getKeyDefinition()}</li>
+     * </ul>
+     *
+     * @return {@code} false if this node corresponds to an XML element, or {@code true} if it is an encapsulation node.
+     */
     public boolean isMixin() {
         return false;
     }
@@ -84,9 +117,69 @@ public abstract class DataSchemaContextNode<T extends PathArgument> extends Abst
     // FIXME: document PathArgument type mismatch
     public abstract @Nullable DataSchemaContextNode<?> getChild(PathArgument child);
 
+    /**
+     * Find a child node identifier by its {code data tree} {@link QName}. This method returns intermediate nodes
+     * significant from {@link YangInstanceIdentifier} hierarchy of {@link PathArgument}s. If the returned node
+     * indicates {@code true} via {@link #isMixin()}, it represents a {@link NormalizedNode} encapsulation which is
+     * not visible in RFC7950 XML encoding, and a further call to this method with the same {@code child} argument will
+     * provide the next step.
+     *
+     * @param child Child data tree QName
+     * @return A child node, or null if not found
+     */
     // FIXME: document child == null
     public abstract @Nullable DataSchemaContextNode<?> getChild(QName child);
 
+    /**
+     * Attempt to enter a child {@link DataSchemaContextNode} towards the {@link DataSchemaNode} child identified by
+     * specified {@code data tree} {@link QName}, adjusting provided {@code stack} with inference steps corresponding to
+     * the transition to the returned node. The stack is expected to be correctly pointing at this node's schema,
+     * otherwise the results of this method are undefined.
+     *
+     * @param stack {@link SchemaInferenceStack} to update
+     * @param child Child QName
+     * @return A DataSchemaContextNode on the path towards the specified child
+     * @throws NullPointerException if any argument is {@code null}
+     */
+    public final @Nullable DataSchemaContextNode<?> enterChild(final SchemaInferenceStack stack, final QName child) {
+        return enterChild(requireNonNull(child), requireNonNull(stack));
+    }
+
+    // FIXME: make this method package-private once the protected constructor is gone
+    protected abstract @Nullable DataSchemaContextNode<?> enterChild(@NonNull QName child,
+        @NonNull SchemaInferenceStack stack);
+
+    /**
+     * Attempt to enter a child {@link DataSchemaContextNode} towards the {@link DataSchemaNode} child identified by
+     * specified {@link PathArgument}, adjusting provided {@code stack} with inference steps corresponding to
+     * the transition to the returned node. The stack is expected to be correctly pointing at this node's schema,
+     * otherwise the results of this method are undefined.
+     *
+     * @param stack {@link SchemaInferenceStack} to update
+     * @param child Child path argument
+     * @return A DataSchemaContextNode for the specified child
+     * @throws NullPointerException if any argument is {@code null}
+     */
+    public final @Nullable DataSchemaContextNode<?> enterChild(final SchemaInferenceStack stack,
+            final PathArgument child) {
+        return enterChild(requireNonNull(child), requireNonNull(stack));
+    }
+
+    // FIXME: make this method package-private once the protected constructor is gone
+    protected abstract @Nullable DataSchemaContextNode<?> enterChild(@NonNull PathArgument child,
+        @NonNull SchemaInferenceStack stack);
+
+    /**
+     * Push this node into specified {@link SchemaInferenceStack}.
+     *
+     * @param stack {@link SchemaInferenceStack}
+     */
+    // FIXME: make this method package-private once the protected constructor is gone
+    protected void pushToStack(final @NonNull SchemaInferenceStack stack) {
+        // Accurate for most subclasses
+        stack.enterSchemaTree(getIdentifier().getNodeType());
+    }
+
     // FIXME: final
     public @Nullable DataSchemaNode getDataSchemaNode() {
         return dataSchemaNode;
index 1a66488615db13892e747a172ee0225e60b39305..ccdd646082ef2dcf9053fc32de908dfc8956b5c1 100644 (file)
@@ -7,15 +7,19 @@
  */
 package org.opendaylight.yangtools.yang.data.util;
 
+import static java.util.Objects.requireNonNull;
+
 import com.google.common.cache.CacheBuilder;
 import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
 import java.util.Optional;
 import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yangtools.concepts.CheckedValue;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
 import org.opendaylight.yangtools.yang.model.spi.AbstractEffectiveModelContextProvider;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
 
 /**
  * Semantic tree binding a {@link EffectiveModelContext} to a {@link NormalizedNode} tree. Since the layout of the
@@ -24,6 +28,25 @@ import org.opendaylight.yangtools.yang.model.spi.AbstractEffectiveModelContextPr
  * @author Robert Varga
  */
 public final class DataSchemaContextTree extends AbstractEffectiveModelContextProvider {
+    // FIXME: record once we have JDK17+
+    public static final class NodeAndStack {
+        private final @NonNull DataSchemaContextNode<?> node;
+        private final @NonNull SchemaInferenceStack stack;
+
+        NodeAndStack(final DataSchemaContextNode<?> node, final @NonNull SchemaInferenceStack stack) {
+            this.node = requireNonNull(node);
+            this.stack = requireNonNull(stack);
+        }
+
+        public @NonNull DataSchemaContextNode<?> node() {
+            return node;
+        }
+
+        public @NonNull SchemaInferenceStack stack() {
+            return stack;
+        }
+    }
+
     private static final LoadingCache<EffectiveModelContext, @NonNull DataSchemaContextTree> TREES =
         CacheBuilder.newBuilder().weakKeys().weakValues().build(new CacheLoader<>() {
             @Override
@@ -54,6 +77,29 @@ public final class DataSchemaContextTree extends AbstractEffectiveModelContextPr
         return root.findChild(path);
     }
 
+    /**
+     * Find a child node as identified by an absolute {@link YangInstanceIdentifier} and return it along with a suitably
+     * initialized {@link SchemaInferenceStack}.
+     *
+     * @param path Path towards the child node
+     * @return A {@link NodeAndStack}, or empty when corresponding child is not found.
+     * @throws NullPointerException if {@code path} is null
+     */
+    public @NonNull CheckedValue<@NonNull NodeAndStack, @NonNull IllegalArgumentException> enterPath(
+            final YangInstanceIdentifier path) {
+        final var stack = SchemaInferenceStack.of((EffectiveModelContext) root.getDataSchemaNode());
+        DataSchemaContextNode<?> node = root;
+        for (var arg : path.getPathArguments()) {
+            final var child = node.enterChild(arg, stack);
+            if (child == null) {
+                return CheckedValue.ofException(new IllegalArgumentException("Failed to find " + arg + " in " + node));
+            }
+            node = child;
+        }
+
+        return CheckedValue.ofValue(new NodeAndStack(node, stack));
+    }
+
     public @NonNull DataSchemaContextNode<?> getRoot() {
         return root;
     }
index e86e4741dff3f7d12b57f2aa88d9329f36e4756e..7ec0f57b74ce17e3db51c5eed0ff7d01407f220d 100644 (file)
@@ -10,6 +10,7 @@ package org.opendaylight.yangtools.yang.data.util;
 import org.opendaylight.yangtools.yang.common.Empty;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
 
 final class LeafListEntryContextNode extends AbstractLeafNodeContext<NodeWithValue<?>, LeafListSchemaNode> {
     LeafListEntryContextNode(final LeafListSchemaNode schema) {
@@ -21,4 +22,9 @@ final class LeafListEntryContextNode extends AbstractLeafNodeContext<NodeWithVal
     public boolean isKeyedEntry() {
         return true;
     }
+
+    @Override
+    protected void pushToStack(final SchemaInferenceStack stack) {
+        // No-op
+    }
 }
index 18cfd0aac18db6a6f0626ef573ee82c6906ffeb1..e1ccbc2a7efa2b4ace0192df41cdacddcb311d0d 100644 (file)
@@ -10,9 +10,10 @@ package org.opendaylight.yangtools.yang.data.util;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
 
-final class ListItemContextNode extends DataContainerContextNode<NodeIdentifierWithPredicates> {
-    ListItemContextNode(final NodeIdentifierWithPredicates identifier, final ListSchemaNode schema) {
-        super(identifier, schema, schema);
+final class ListItemContextNode extends AbstractListItemContextNode<NodeIdentifierWithPredicates> {
+    ListItemContextNode(final ListSchemaNode schema) {
+        // FIXME: this is wrong: we have no predicates at all!
+        super(NodeIdentifierWithPredicates.of(schema.getQName()), schema, schema);
     }
 
     @Override
index a4ffbc1fe90378c9cfca419b3f4504b04621adf3..2a4867d41b8d1d428f32040c1c7ad7b211e3eae2 100644 (file)
@@ -10,7 +10,7 @@ package org.opendaylight.yangtools.yang.data.util;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
 
-final class UnkeyedListItemContextNode extends DataContainerContextNode<NodeIdentifier> {
+final class UnkeyedListItemContextNode extends AbstractListItemContextNode<NodeIdentifier> {
     UnkeyedListItemContextNode(final ListSchemaNode schema) {
         super(NodeIdentifier.create(schema.getQName()), schema, schema);
     }
index 00ba8b48109613d9e9c5ba13a9efc98a9c5eb31b..4aa74cc207b09b10d77860d23c3e01506de666f2 100644 (file)
@@ -13,7 +13,7 @@ import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdent
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
 
-final class UnkeyedListMixinContextNode extends AbstractMixinContextNode<NodeIdentifier> {
+final class UnkeyedListMixinContextNode extends AbstractListLikeContextNode<NodeIdentifier> {
     private final UnkeyedListItemContextNode innerNode;
 
     UnkeyedListMixinContextNode(final ListSchemaNode list) {
index 9d6618c2fac608d28e7c36e28e65143eabd38968..538a76f65b0459588d56c96b61f8baad970d3f8d 100644 (file)
@@ -13,7 +13,7 @@ import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithV
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
 
-class UnorderedLeafListMixinContextNode extends AbstractMixinContextNode<NodeIdentifier> {
+class UnorderedLeafListMixinContextNode extends AbstractListLikeContextNode<NodeIdentifier> {
     private final LeafListEntryContextNode innerOp;
 
     UnorderedLeafListMixinContextNode(final LeafListSchemaNode schema) {
index 4b76598303e885efc3c4883c6a6f1cbf7199e790..dae9e71b0bb3661619c434c20d98540cdf0e070b 100644 (file)
@@ -10,16 +10,15 @@ package org.opendaylight.yangtools.yang.data.util;
 import org.eclipse.jdt.annotation.Nullable;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
 
-class UnorderedMapMixinContextNode extends AbstractMixinContextNode<NodeIdentifier> {
+class UnorderedMapMixinContextNode extends AbstractListLikeContextNode<NodeIdentifier> {
     private final ListItemContextNode innerNode;
 
     UnorderedMapMixinContextNode(final ListSchemaNode list) {
         super(NodeIdentifier.create(list.getQName()), list);
-        innerNode = new ListItemContextNode(NodeIdentifierWithPredicates.of(list.getQName()), list);
+        innerNode = new ListItemContextNode(list);
     }
 
     @Override
diff --git a/data/yang-data-util/src/test/java/org/opendaylight/yangtools/yang/data/util/YT1412Test.java b/data/yang-data-util/src/test/java/org/opendaylight/yangtools/yang/data/util/YT1412Test.java
new file mode 100644 (file)
index 0000000..82ba294
--- /dev/null
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2022 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.util;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import java.util.Set;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.common.XMLNamespace;
+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.model.api.stmt.ChoiceEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.ContainerEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.LeafEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.ListEffectiveStatement;
+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;
+
+public class YT1412Test {
+    private static final QNameModule MODULE = QNameModule.create(XMLNamespace.of("foo"));
+    private static final QName ONE = QName.create(MODULE, "one");
+    private static final QName TWO = QName.create(MODULE, "two");
+    private static final QName THREE = QName.create(MODULE, "three");
+    private static final QName FOUR = QName.create(MODULE, "four");
+    private static final QName FIVE = QName.create(MODULE, "five");
+    private static final QName SIX = QName.create(MODULE, "six");
+
+    private static DataSchemaContextTree CONTEXT;
+
+    private final SchemaInferenceStack stack = SchemaInferenceStack.of(CONTEXT.getEffectiveModelContext());
+
+    @BeforeClass
+    public static void init() {
+        CONTEXT = DataSchemaContextTree.from(YangParserTestUtils.parseYangResource("/yt1412.yang"));
+    }
+
+    @AfterClass
+    public static void cleanup() {
+        CONTEXT = null;
+    }
+
+    @Test
+    public void testEnterThroughChoice() {
+        final var one = CONTEXT.getRoot().enterChild(stack, ONE);
+        assertThat(one, instanceOf(ContainerContextNode.class));
+        assertThat(stack.currentStatement(), instanceOf(ContainerEffectiveStatement.class));
+
+        final var two = one.enterChild(FOUR, stack);
+        assertThat(two, instanceOf(ChoiceNodeContextNode.class));
+        assertThat(stack.currentStatement(), instanceOf(ChoiceEffectiveStatement.class));
+
+        final var three = two.enterChild(FOUR, stack);
+        assertThat(three, instanceOf(ChoiceNodeContextNode.class));
+        assertThat(stack.currentStatement(), instanceOf(ChoiceEffectiveStatement.class));
+
+        final var four = three.enterChild(FOUR, stack);
+        assertThat(four, instanceOf(LeafContextNode.class));
+        assertThat(stack.currentStatement(), instanceOf(LeafEffectiveStatement.class));
+
+        assertEquals(Absolute.of(ONE, TWO, THREE, THREE, FOUR, FOUR), stack.toSchemaNodeIdentifier());
+    }
+
+    @Test
+    public void testEnterThroughAugment() {
+        final var one = CONTEXT.getRoot().enterChild(stack, ONE);
+        assertThat(one, instanceOf(ContainerContextNode.class));
+        assertThat(stack.currentStatement(), instanceOf(ContainerEffectiveStatement.class));
+
+        final var augment = one.enterChild(FIVE, stack);
+        assertThat(augment, instanceOf(AugmentationContextNode.class));
+        assertThat(stack.currentStatement(), instanceOf(ContainerEffectiveStatement.class));
+
+        final var five = augment.enterChild(FIVE, stack);
+        assertThat(five, instanceOf(UnkeyedListMixinContextNode.class));
+        assertThat(stack.currentStatement(), instanceOf(ListEffectiveStatement.class));
+
+        final var fiveItem = five.enterChild(FIVE, stack);
+        assertThat(fiveItem, instanceOf(UnkeyedListItemContextNode.class));
+        assertThat(stack.currentStatement(), instanceOf(ListEffectiveStatement.class));
+
+        assertEquals(Absolute.of(ONE, FIVE), stack.toSchemaNodeIdentifier());
+    }
+
+    @Test
+    public void testEnterThroughAugmentChoiceAugment() {
+        final var one = CONTEXT.getRoot().enterChild(stack, ONE);
+        assertThat(one, instanceOf(ContainerContextNode.class));
+        assertThat(stack.currentStatement(), instanceOf(ContainerEffectiveStatement.class));
+
+        final var two = one.enterChild(SIX, stack);
+        assertThat(two, instanceOf(ChoiceNodeContextNode.class));
+        assertThat(stack.currentStatement(), instanceOf(ChoiceEffectiveStatement.class));
+
+        final var three = two.enterChild(SIX, stack);
+        assertThat(three, instanceOf(ChoiceNodeContextNode.class));
+        assertThat(stack.currentStatement(), instanceOf(ChoiceEffectiveStatement.class));
+
+        final var six = three.enterChild(SIX, stack);
+        assertThat(six, instanceOf(LeafContextNode.class));
+        assertThat(stack.currentStatement(), instanceOf(LeafEffectiveStatement.class));
+
+        assertEquals(Absolute.of(ONE, TWO, THREE, THREE, SIX, SIX), stack.toSchemaNodeIdentifier());
+    }
+
+    @Test
+    public void testEnterChoicePath() {
+        final var result = CONTEXT.enterPath(YangInstanceIdentifier.create(
+            new NodeIdentifier(ONE),
+            new NodeIdentifier(TWO),
+            new NodeIdentifier(THREE),
+            new NodeIdentifier(FOUR)))
+            .orElseThrow();
+
+        assertThat(result.node(), instanceOf(LeafContextNode.class));
+        assertEquals(Absolute.of(ONE, TWO, THREE, THREE, FOUR, FOUR), result.stack().toSchemaNodeIdentifier());
+    }
+
+    @Test
+    public void testEnterAugmentPath() {
+        final var result = CONTEXT.enterPath(YangInstanceIdentifier.create(
+            new NodeIdentifier(ONE),
+            new AugmentationIdentifier(Set.of(FIVE)),
+            new NodeIdentifier(FIVE),
+            new NodeIdentifier(FIVE)))
+            .orElseThrow();
+
+        assertThat(result.node(), instanceOf(UnkeyedListItemContextNode.class));
+        assertEquals(Absolute.of(ONE, FIVE), result.stack().toSchemaNodeIdentifier());
+    }
+
+    @Test
+    public void testEnterAugmentChoicePath() {
+        final var result = CONTEXT.enterPath(YangInstanceIdentifier.create(
+            new NodeIdentifier(ONE),
+            new NodeIdentifier(TWO),
+            new NodeIdentifier(THREE),
+            new NodeIdentifier(SIX)))
+            .orElseThrow();
+
+        assertThat(result.node(), instanceOf(LeafContextNode.class));
+        assertEquals(Absolute.of(ONE, TWO, THREE, THREE, SIX, SIX), result.stack().toSchemaNodeIdentifier());
+    }
+}
diff --git a/data/yang-data-util/src/test/resources/yt1412.yang b/data/yang-data-util/src/test/resources/yt1412.yang
new file mode 100644 (file)
index 0000000..d40c1b2
--- /dev/null
@@ -0,0 +1,25 @@
+module foo {
+  namespace foo;
+  prefix foo;
+  yang-version 1.1;
+
+  container one {
+    choice two {
+      choice three {
+        leaf four {
+          type string;
+        }
+      }
+    }
+  }
+
+  augment /one {
+    list five;
+  }
+
+  augment /one/two/three/three {
+    leaf six {
+      type string;
+    }
+  }
+}