From: Robert Varga Date: Wed, 23 Mar 2022 13:29:23 +0000 (+0100) Subject: Add DataSchemaContextNode/SchemaInferenceStack integration X-Git-Tag: v8.0.2~3 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=yangtools.git;a=commitdiff_plain;h=6e3a97a77736137f798bd712ae2652e13df07935 Add DataSchemaContextNode/SchemaInferenceStack integration 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 --- diff --git a/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/AbstractLeafContextNode.java b/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/AbstractLeafContextNode.java index 7acdbade5a..9a274467b8 100644 --- a/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/AbstractLeafContextNode.java +++ b/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/AbstractLeafContextNode.java @@ -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 extends DataSchemaContextNode { - AbstractLeafContextNode(T identifier, S schema) { + AbstractLeafContextNode(final T identifier, final S schema) { super(identifier, schema); } @@ -26,4 +27,14 @@ abstract class AbstractLeafContextNode 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 index 0000000000..523bdb96ab --- /dev/null +++ b/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/AbstractListItemContextNode.java @@ -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 extends DataContainerContextNode { + 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 index 0000000000..1ff48754ab --- /dev/null +++ b/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/AbstractListLikeContextNode.java @@ -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 extends AbstractMixinContextNode { + 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); + } +} diff --git a/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/AugmentationContextNode.java b/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/AugmentationContextNode.java index bf3e3a8bad..2482631c6f 100644 --- a/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/AugmentationContextNode.java +++ b/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/AugmentationContextNode.java @@ -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 { AugmentationContextNode(final AugmentationSchemaNode augmentation, final DataNodeContainer target) { @@ -40,4 +41,9 @@ final class AugmentationContextNode extends DataContainerContextNode getQNameIdentifiers() { return getIdentifier().getPossibleChildNames(); } + + @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/ChoiceNodeContextNode.java b/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/ChoiceNodeContextNode.java index a93d1f1883..c2b3d694ea 100644 --- a/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/ChoiceNodeContextNode.java +++ b/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/ChoiceNodeContextNode.java @@ -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 { - private final ImmutableMap> byQName; private final ImmutableMap> byArg; + private final ImmutableMap> byQName; + private final ImmutableMap, QName> childToCase; ChoiceNodeContextNode(final ChoiceSchemaNode schema) { super(NodeIdentifier.create(schema.getQName()), schema); + ImmutableMap.Builder, QName> childToCaseBuilder = ImmutableMap.builder(); ImmutableMap.Builder> byQNameBuilder = ImmutableMap.builder(); ImmutableMap.Builder> byArgBuilder = ImmutableMap.builder(); @@ -29,11 +36,14 @@ final class ChoiceNodeContextNode extends AbstractMixinContextNode 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 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 diff --git a/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/DataContainerContextNode.java b/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/DataContainerContextNode.java index 96869c1b8d..5059deae83 100644 --- a/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/DataContainerContextNode.java +++ b/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/DataContainerContextNode.java @@ -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 extends AbstractInteriorContextNode { private final ConcurrentMap> byArg = new ConcurrentHashMap<>(); @@ -47,6 +50,25 @@ abstract class DataContainerContextNode 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 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 extends Abstract } return potential; } - } diff --git a/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/DataSchemaContextNode.java b/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/DataSchemaContextNode.java index ac6e43e808..0a92a61c7c 100644 --- a/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/DataSchemaContextNode.java +++ b/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/DataSchemaContextNode.java @@ -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 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 extends AbstractSimpleIdentifiable { // 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 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 + *
    + *
  • {@link AugmentationNode} backed by an {@link AugmentationSchemaNode}, or
  • + *
  • {@link ChoiceNode} backed by a {@link ChoiceSchemaNode}, or
  • + *
  • {@link LeafSetNode} backed by a {@link LeafListSchemaNode}, or
  • + *
  • {@link MapNode} backed by a {@link ListSchemaNode} with a non-empty + * {@link ListSchemaNode#getKeyDefinition()}, or
  • + *
  • {@link UnkeyedListNode} backed by a {@link ListSchemaNode} with an empty + * {@link ListSchemaNode#getKeyDefinition()}
  • + *
+ * + * @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 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; diff --git a/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/DataSchemaContextTree.java b/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/DataSchemaContextTree.java index 1a66488615..ccdd646082 100644 --- a/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/DataSchemaContextTree.java +++ b/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/DataSchemaContextTree.java @@ -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 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; } diff --git a/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/LeafListEntryContextNode.java b/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/LeafListEntryContextNode.java index e86e4741df..7ec0f57b74 100644 --- a/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/LeafListEntryContextNode.java +++ b/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/LeafListEntryContextNode.java @@ -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, LeafListSchemaNode> { LeafListEntryContextNode(final LeafListSchemaNode schema) { @@ -21,4 +22,9 @@ final class LeafListEntryContextNode extends AbstractLeafNodeContext { - ListItemContextNode(final NodeIdentifierWithPredicates identifier, final ListSchemaNode schema) { - super(identifier, schema, schema); +final class ListItemContextNode extends AbstractListItemContextNode { + ListItemContextNode(final ListSchemaNode schema) { + // FIXME: this is wrong: we have no predicates at all! + super(NodeIdentifierWithPredicates.of(schema.getQName()), schema, schema); } @Override diff --git a/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/UnkeyedListItemContextNode.java b/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/UnkeyedListItemContextNode.java index a4ffbc1fe9..2a4867d41b 100644 --- a/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/UnkeyedListItemContextNode.java +++ b/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/UnkeyedListItemContextNode.java @@ -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 { +final class UnkeyedListItemContextNode extends AbstractListItemContextNode { UnkeyedListItemContextNode(final ListSchemaNode schema) { super(NodeIdentifier.create(schema.getQName()), schema, schema); } diff --git a/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/UnkeyedListMixinContextNode.java b/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/UnkeyedListMixinContextNode.java index 00ba8b4810..4aa74cc207 100644 --- a/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/UnkeyedListMixinContextNode.java +++ b/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/UnkeyedListMixinContextNode.java @@ -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 { +final class UnkeyedListMixinContextNode extends AbstractListLikeContextNode { private final UnkeyedListItemContextNode innerNode; UnkeyedListMixinContextNode(final ListSchemaNode list) { diff --git a/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/UnorderedLeafListMixinContextNode.java b/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/UnorderedLeafListMixinContextNode.java index 9d6618c2fa..538a76f65b 100644 --- a/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/UnorderedLeafListMixinContextNode.java +++ b/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/UnorderedLeafListMixinContextNode.java @@ -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 { +class UnorderedLeafListMixinContextNode extends AbstractListLikeContextNode { private final LeafListEntryContextNode innerOp; UnorderedLeafListMixinContextNode(final LeafListSchemaNode schema) { diff --git a/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/UnorderedMapMixinContextNode.java b/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/UnorderedMapMixinContextNode.java index 4b76598303..dae9e71b0b 100644 --- a/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/UnorderedMapMixinContextNode.java +++ b/data/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/UnorderedMapMixinContextNode.java @@ -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 { +class UnorderedMapMixinContextNode extends AbstractListLikeContextNode { 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 index 0000000000..82ba2943da --- /dev/null +++ b/data/yang-data-util/src/test/java/org/opendaylight/yangtools/yang/data/util/YT1412Test.java @@ -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 index 0000000000..d40c1b2c87 --- /dev/null +++ b/data/yang-data-util/src/test/resources/yt1412.yang @@ -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; + } + } +}