From: Robert Varga Date: Mon, 23 Dec 2019 19:06:56 +0000 (+0100) Subject: Correct deref() leafref handling X-Git-Tag: v4.0.4~17 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=commitdiff_plain;h=2ef2f65082021eab8213c4a9e0d71a2cc42a6962;p=yangtools.git Correct deref() leafref handling deref() invocation requires the target node to actually be examined, derefenced and any subsequent path evaluated on top of it. We correct the implementation to account for correct derefence and add a UT for a confusing test case. This is implemented using YangParserTestUtils in a separate internal artifact, hence future test cases are easy to implement. JIRA: YANGTOOLS-1050 Change-Id: I0777da339577f93ecc2786f5beab40b29554fe0e Signed-off-by: Robert Varga --- diff --git a/yang/pom.xml b/yang/pom.xml index cfa562fcb4..e749ed72cc 100644 --- a/yang/pom.xml +++ b/yang/pom.xml @@ -40,6 +40,7 @@ yang-model-api yang-model-export yang-model-util + yang-model-util-ut yang-xpath-api diff --git a/yang/yang-model-util-ut/pom.xml b/yang/yang-model-util-ut/pom.xml new file mode 100644 index 0000000000..a7bfadac7f --- /dev/null +++ b/yang/yang-model-util-ut/pom.xml @@ -0,0 +1,51 @@ + + + + + 4.0.0 + + org.opendaylight.yangtools + bundle-parent + 4.0.4-SNAPSHOT + ../../bundle-parent + + + + yang-model-util-ut + jar + ${project.artifactId} + ${project.artifactId} + + + true + true + + + + + org.opendaylight.yangtools + yang-model-util + + + + org.opendaylight.yangtools + yang-test-util + + + org.opendaylight.yangtools + mockito-configuration + test + + + ch.qos.logback + logback-classic + test + + + diff --git a/yang/yang-model-util-ut/src/test/java/org/opendaylight/yangtools/yang/model/util/ut/LeafrefStaticAnalysisTest.java b/yang/yang-model-util-ut/src/test/java/org/opendaylight/yangtools/yang/model/util/ut/LeafrefStaticAnalysisTest.java new file mode 100644 index 0000000000..bfd36e421c --- /dev/null +++ b/yang/yang-model-util-ut/src/test/java/org/opendaylight/yangtools/yang/model/util/ut/LeafrefStaticAnalysisTest.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2019 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.ut; + +import static org.hamcrest.Matchers.isA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.model.api.GroupingDefinition; +import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; +import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; +import org.opendaylight.yangtools.yang.model.api.Module; +import org.opendaylight.yangtools.yang.model.api.SchemaNode; +import org.opendaylight.yangtools.yang.model.api.SchemaPath; +import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition; +import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil; +import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils; + +public class LeafrefStaticAnalysisTest { + private static final QName FOO = QName.create("leafrefs", "foo"); + + private static EffectiveModelContext context; + private static GroupingDefinition grp; + private static ListSchemaNode foo; + private static ContainerSchemaNode bar; + private static Module module; + + @BeforeClass + public static void beforeClass() { + context = YangParserTestUtils.parseYangResource("/leafrefs.yang"); + module = context.getModules().iterator().next(); + + foo = (ListSchemaNode) module.findDataChildByName(FOO).get(); + bar = (ContainerSchemaNode) foo.findDataChildByName(QName.create(FOO, "bar")).get(); + grp = module.getGroupings().iterator().next(); + } + + @Test + public void testGrpOuterId() { + final LeafSchemaNode leaf = (LeafSchemaNode) grp.findDataChildByName(QName.create(FOO, "outer-id")).get(); + // Cannot be found as the reference goes outside of the grouping + assertNull(SchemaContextUtil.findDataSchemaNodeForRelativeXPath(context, module, leaf, + ((LeafrefTypeDefinition) leaf.getType()).getPathStatement())); + } + + @Test + public void testFooOuterId() { + final LeafSchemaNode leaf = (LeafSchemaNode) bar.findDataChildByName(QName.create(FOO, "outer-id")).get(); + final SchemaNode found = SchemaContextUtil.findDataSchemaNodeForRelativeXPath(context, module, leaf, + ((LeafrefTypeDefinition) leaf.getType()).getPathStatement()); + + assertThat(found, isA(LeafSchemaNode.class)); + assertEquals(SchemaPath.create(true, FOO, QName.create(FOO, "id")), found.getPath()); + } + + @Test + public void testGrpOuterIndirectProp() { + final LeafSchemaNode leaf = (LeafSchemaNode) grp.findDataChildByName( + QName.create(FOO, "outer-indirect-prop")).get(); + // Cannot resolve deref outer-id + assertNull(SchemaContextUtil.findDataSchemaNodeForRelativeXPath(context, module, leaf, + ((LeafrefTypeDefinition) leaf.getType()).getPathStatement())); + } + + @Test + public void testFooOuterIndirectProp() { + final LeafSchemaNode leaf = (LeafSchemaNode) bar.findDataChildByName( + QName.create(FOO, "outer-indirect-prop")).get(); + final SchemaNode found = SchemaContextUtil.findDataSchemaNodeForRelativeXPath(context, module, leaf, + ((LeafrefTypeDefinition) leaf.getType()).getPathStatement()); + + assertThat(found, isA(LeafSchemaNode.class)); + assertEquals(QName.create(FOO, "prop"), found.getQName()); + } + + @Test + public void testGrpIndirect() { + final LeafSchemaNode leaf = (LeafSchemaNode) grp.findDataChildByName(QName.create(FOO, "indirect")).get(); + final SchemaNode found = SchemaContextUtil.findDataSchemaNodeForRelativeXPath(context, module, leaf, + ((LeafrefTypeDefinition) leaf.getType()).getPathStatement()); + + assertThat(found, isA(LeafSchemaNode.class)); + assertEquals(QName.create(FOO, "prop"), found.getQName()); + } + + @Test + public void testFooIndirect() { + final LeafSchemaNode leaf = (LeafSchemaNode) bar.findDataChildByName(QName.create(FOO, "indirect")).get(); + final SchemaNode found = SchemaContextUtil.findDataSchemaNodeForRelativeXPath(context, module, leaf, + ((LeafrefTypeDefinition) leaf.getType()).getPathStatement()); + + assertThat(found, isA(LeafSchemaNode.class)); + assertEquals(QName.create(FOO, "prop"), found.getQName()); + } + + @Test + public void testGrpDerefNonExistent() { + final LeafSchemaNode leaf = (LeafSchemaNode) grp.findDataChildByName( + QName.create(FOO, "deref-non-existent")).get(); + assertNull(SchemaContextUtil.findDataSchemaNodeForRelativeXPath(context, module, leaf, + ((LeafrefTypeDefinition) leaf.getType()).getPathStatement())); + } + + @Test + public void testFooDerefNonExistent() { + final LeafSchemaNode leaf = (LeafSchemaNode) bar.findDataChildByName( + QName.create(FOO, "deref-non-existent")).get(); + assertNull(SchemaContextUtil.findDataSchemaNodeForRelativeXPath(context, module, leaf, + ((LeafrefTypeDefinition) leaf.getType()).getPathStatement())); + } + + @Test + public void testGrpNonExistentDeref() { + final LeafSchemaNode leaf = (LeafSchemaNode) grp.findDataChildByName( + QName.create(FOO, "non-existent-deref")).get(); + assertNull(SchemaContextUtil.findDataSchemaNodeForRelativeXPath(context, module, leaf, + ((LeafrefTypeDefinition) leaf.getType()).getPathStatement())); + } + + @Test + public void testFooNonExistentDeref() { + final LeafSchemaNode leaf = (LeafSchemaNode) bar.findDataChildByName( + QName.create(FOO, "non-existent-deref")).get(); + assertNull(SchemaContextUtil.findDataSchemaNodeForRelativeXPath(context, module, leaf, + ((LeafrefTypeDefinition) leaf.getType()).getPathStatement())); + } +} diff --git a/yang/yang-model-util-ut/src/test/java/org/opendaylight/yangtools/yang/model/util/ut/YT1050Test.java b/yang/yang-model-util-ut/src/test/java/org/opendaylight/yangtools/yang/model/util/ut/YT1050Test.java new file mode 100644 index 0000000000..3a3da6e46e --- /dev/null +++ b/yang/yang-model-util-ut/src/test/java/org/opendaylight/yangtools/yang/model/util/ut/YT1050Test.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2019 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.ut; + +import static org.hamcrest.Matchers.isA; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThat; + +import org.junit.Before; +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.GroupingDefinition; +import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; +import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; +import org.opendaylight.yangtools.yang.model.api.Module; +import org.opendaylight.yangtools.yang.model.api.SchemaNode; +import org.opendaylight.yangtools.yang.model.api.TypeDefinition; +import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition; +import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil; +import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils; + +public class YT1050Test { + private static final QName SECONDARY = QName.create("yt1050", "secondary"); + private static final QName TYPE = QName.create(SECONDARY, "type"); + private static final QName GRP_USES = QName.create(SECONDARY, "grp-uses"); + + private EffectiveModelContext context; + private LeafSchemaNode secondaryType; + private LeafSchemaNode primaryType; + private Module module; + + @Before + public void before() { + context = YangParserTestUtils.parseYangResource("/yt1050.yang"); + module = context.getModules().iterator().next(); + + final ListSchemaNode grpUses = (ListSchemaNode) module.findDataChildByName(GRP_USES).get(); + primaryType = (LeafSchemaNode) grpUses.findDataChildByName(TYPE).get(); + + final GroupingDefinition grp = module.getGroupings().iterator().next(); + secondaryType = (LeafSchemaNode) ((ListSchemaNode) grp.findDataChildByName(SECONDARY).get()) + .findDataChildByName(TYPE).get(); + } + + @Test + public void testFindDataSchemaNodeForRelativeXPathWithDeref() { + final TypeDefinition typeNodeType = secondaryType.getType(); + assertThat(typeNodeType, isA(LeafrefTypeDefinition.class)); + + final SchemaNode found = SchemaContextUtil.findDataSchemaNodeForRelativeXPath(context, module, secondaryType, + ((LeafrefTypeDefinition) typeNodeType).getPathStatement()); + assertSame(primaryType, found); + } +} diff --git a/yang/yang-model-util-ut/src/test/java/org/opendaylight/yangtools/yang/model/util/ut/package-info.java b/yang/yang-model-util-ut/src/test/java/org/opendaylight/yangtools/yang/model/util/ut/package-info.java new file mode 100644 index 0000000000..0a672793de --- /dev/null +++ b/yang/yang-model-util-ut/src/test/java/org/opendaylight/yangtools/yang/model/util/ut/package-info.java @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2019 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 + */ +/** + * Additional unit test suite for {@code yang-model-util}, which are using YANG parser to construct the testing data + * and thus cannot live directly in that artifact. + */ +// FIXME: YANTTOOLS-1052: this should be eliminated +package org.opendaylight.yangtools.yang.model.util.ut; \ No newline at end of file diff --git a/yang/yang-model-util-ut/src/test/resources/leafrefs.yang b/yang/yang-model-util-ut/src/test/resources/leafrefs.yang new file mode 100644 index 0000000000..c923c589a8 --- /dev/null +++ b/yang/yang-model-util-ut/src/test/resources/leafrefs.yang @@ -0,0 +1,93 @@ +module leafrefs { + yang-version 1.1; + namespace "leafrefs"; + prefix "lrs"; + + typedef str-type { + type string { + length 1..max; + } + } + + typedef int-type { + type int8 { + range min..0; + } + } + + grouping grp { + leaf outer-id { + type leafref { + // points outside of the grouping + path ../../id; + } + } + + leaf outer-indirect-prop { + type leafref { + path deref(../outer-id)/../prop; + } + } + + leaf absolute { + type leafref { + // direct path to an instantiation + path /foo/id; + } + } + + leaf indirect { + type leafref { + // deref through absolute to prop + path deref(../absolute)/../prop; + } + } + + leaf non-existent-abs { + type leafref { + path /xyzzy; + } + } + + leaf non-existent-rel { + type leafref { + path ../xyzzy; + } + } + + leaf deref-non-existent { + type leafref { + path deref(../non-existent-rel)/../absolute; + } + } + + leaf non-existent-deref { + type leafref { + path deref(../absolute)/../xyzzy; + } + } + } + + list foo { + key id; + + leaf id { + type str-type; + } + + leaf id-copy { + type leafref { + path ../id; + } + } + + leaf prop { + type int-type; + } + + container bar { + uses grp; + } + } +} + diff --git a/yang/yang-model-util-ut/src/test/resources/yt1050.yang b/yang/yang-model-util-ut/src/test/resources/yt1050.yang new file mode 100644 index 0000000000..90c9d8f552 --- /dev/null +++ b/yang/yang-model-util-ut/src/test/resources/yt1050.yang @@ -0,0 +1,42 @@ +module yt1050 { + yang-version 1.1; + namespace "yt1050"; + prefix "yt1050"; + + identity target-base; + + typedef target-type { + type identityref { + base target-base; + } + } + + grouping grp { + leaf id { + type string; + } + leaf type { + type target-type; + } + + list secondary { + key "id type"; + leaf id { + type leafref { + path "/grp-uses/id"; + } + } + leaf type { + type leafref { + path "deref(../id)/../type"; + } + } + } + } + + list grp-uses { + uses grp; + key "id type"; + } +} + diff --git a/yang/yang-model-util/src/main/java/org/opendaylight/yangtools/yang/model/util/SchemaContextUtil.java b/yang/yang-model-util/src/main/java/org/opendaylight/yangtools/yang/model/util/SchemaContextUtil.java index 7aa3984334..ebc3bc9b68 100644 --- a/yang/yang-model-util/src/main/java/org/opendaylight/yangtools/yang/model/util/SchemaContextUtil.java +++ b/yang/yang-model-util/src/main/java/org/opendaylight/yangtools/yang/model/util/SchemaContextUtil.java @@ -15,7 +15,9 @@ import com.google.common.annotations.Beta; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Splitter; import com.google.common.collect.Iterables; +import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Deque; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -24,7 +26,10 @@ import java.util.Set; import java.util.regex.Pattern; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; +import org.opendaylight.yangtools.yang.common.AbstractQName; import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.QNameModule; +import org.opendaylight.yangtools.yang.common.UnqualifiedQName; import org.opendaylight.yangtools.yang.model.api.ActionNodeContainer; import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode; import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode; @@ -41,15 +46,24 @@ import org.opendaylight.yangtools.yang.model.api.NotificationDefinition; import org.opendaylight.yangtools.yang.model.api.NotificationNodeContainer; import org.opendaylight.yangtools.yang.model.api.OperationDefinition; import org.opendaylight.yangtools.yang.model.api.PathExpression; +import org.opendaylight.yangtools.yang.model.api.PathExpression.LocationPathSteps; import org.opendaylight.yangtools.yang.model.api.RpcDefinition; import org.opendaylight.yangtools.yang.model.api.SchemaContext; import org.opendaylight.yangtools.yang.model.api.SchemaNode; import org.opendaylight.yangtools.yang.model.api.SchemaPath; import org.opendaylight.yangtools.yang.model.api.TypeDefinition; import org.opendaylight.yangtools.yang.model.api.TypedDataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition; import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition; import org.opendaylight.yangtools.yang.model.repo.api.RevisionSourceIdentifier; import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier; +import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath; +import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath.AxisStep; +import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath.QNameStep; +import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath.ResolvedQNameStep; +import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath.Step; +import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath.UnresolvedQNameStep; +import org.opendaylight.yangtools.yang.xpath.api.YangXPathAxis; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -197,17 +211,12 @@ public final class SchemaContextUtil { // // which would then be passed in to a method similar to this one. In static contexts, like MD-SAL codegen, // that feels like an overkill. + // FIXME: YANGTOOLS-1052: this is a static analysis util, move it to yang-model-sa public static SchemaNode findDataSchemaNodeForRelativeXPath(final SchemaContext context, final Module module, final SchemaNode actualSchemaNode, final PathExpression relativeXPath) { checkState(!relativeXPath.isAbsolute(), "Revision Aware XPath MUST be relative i.e. MUST contains ../, " + "for non relative Revision Aware XPath use findDataSchemaNode method"); - - final Iterable qnamePath = resolveRelativeXPath(context, module, relativeXPath, actualSchemaNode); - - // We do not have enough information about resolution context, hence cannot account for actions, RPCs - // and notifications. We therefore attempt to make a best estimate, but this can still fail. - final Optional pureData = context.findDataTreeChild(qnamePath); - return pureData.isPresent() ? pureData.get() : findNodeInSchemaContext(context, qnamePath); + return resolveRelativeXPath(context, module, relativeXPath, actualSchemaNode); } /** @@ -590,26 +599,27 @@ public final class SchemaContextUtil { * Non conditional Revision Aware Relative XPath * @param actualSchemaNode * actual schema node - * @return list of QName + * @return target schema node * @throws IllegalArgumentException if any arguments are null */ - private static Iterable resolveRelativeXPath(final SchemaContext context, final Module module, + private static @Nullable SchemaNode resolveRelativeXPath(final SchemaContext context, final Module module, final PathExpression relativeXPath, final SchemaNode actualSchemaNode) { checkState(!relativeXPath.isAbsolute(), "Revision Aware XPath MUST be relative i.e. MUST contains ../, " + "for non relative Revision Aware XPath use findDataSchemaNode method"); checkState(actualSchemaNode.getPath() != null, "Schema Path reference for Leafref cannot be NULL"); - List xpaths = new ArrayList<>(); - splitXPath(relativeXPath.getOriginalString(), xpaths); + final String orig = relativeXPath.getOriginalString(); + return orig.startsWith("deref(") ? resolveDerefPath(context, module, actualSchemaNode, orig) + : findTargetNode(context, resolveRelativePath(context, module, actualSchemaNode, doSplitXPath(orig))); + } + private static Iterable resolveRelativePath(final SchemaContext context, final Module module, + final SchemaNode actualSchemaNode, final List steps) { // Find out how many "parent" components there are and trim them - final int colCount = normalizeXPath(xpaths); - if (colCount != 0) { - xpaths = xpaths.subList(colCount, xpaths.size()); - } + final int colCount = normalizeXPath(steps); + final List xpaths = colCount == 0 ? steps : steps.subList(colCount, steps.size()); final Iterable schemaNodePath = actualSchemaNode.getPath().getPathFromRoot(); - if (Iterables.size(schemaNodePath) - colCount >= 0) { return Iterables.concat(Iterables.limit(schemaNodePath, Iterables.size(schemaNodePath) - colCount), Iterables.transform(xpaths, input -> stringPathPartToQName(context, module, input))); @@ -618,6 +628,89 @@ public final class SchemaContextUtil { Iterables.transform(xpaths, input -> stringPathPartToQName(context, module, input))); } + private static SchemaNode resolveDerefPath(final SchemaContext context, final Module module, + final SchemaNode actualSchemaNode, final String xpath) { + final int paren = xpath.indexOf(')', 6); + checkArgument(paren != -1, "Cannot find matching parentheses in %s", xpath); + + final String derefArg = xpath.substring(6, paren); + // Look up the node which we need to reference + final SchemaNode derefTarget = findTargetNode(context, resolveRelativePath(context, module, actualSchemaNode, + doSplitXPath(derefArg))); + checkArgument(derefTarget != null, "Cannot find deref(%s) target node %s in context of %s", derefArg, + actualSchemaNode); + checkArgument(derefTarget instanceof TypedDataSchemaNode, "deref(%s) resolved to non-typed %s", derefArg, + derefTarget); + + // We have a deref() target, decide what to do about it + final TypeDefinition targetType = ((TypedDataSchemaNode) derefTarget).getType(); + if (targetType instanceof InstanceIdentifierTypeDefinition) { + // Static inference breaks down, we cannot determine where this points to + // FIXME: dedicated exception, users can recover from it, derive from IAE + throw new UnsupportedOperationException("Cannot infer instance-identifier reference " + targetType); + } + + // deref() is define only for instance-identifier and leafref types, handle the latter + checkArgument(targetType instanceof LeafrefTypeDefinition, "Illegal target type %s", targetType); + + final PathExpression targetPath = ((LeafrefTypeDefinition) targetType).getPathStatement(); + LOG.debug("Derefencing path {}", targetPath); + + final SchemaNode deref = targetPath.isAbsolute() + ? findTargetNode(context, actualSchemaNode, + ((LocationPathSteps) targetPath.getSteps()).getLocationPath()) + : findDataSchemaNodeForRelativeXPath(context, module, actualSchemaNode, targetPath); + if (deref == null) { + LOG.debug("Path {} could not be derefenced", targetPath); + return null; + } + + checkArgument(deref instanceof LeafSchemaNode, "Unexpected %s reference in %s", deref, targetPath); + + final List qnames = doSplitXPath(xpath.substring(paren + 1).stripLeading()); + return findTargetNode(context, resolveRelativePath(context, module, deref, qnames)); + } + + private static @Nullable SchemaNode findTargetNode(final SchemaContext context, final SchemaNode actualSchemaNode, + final YangLocationPath path) { + final QNameModule defaultModule = actualSchemaNode.getQName().getModule(); + final Deque ret = new ArrayDeque<>(); + for (Step step : path.getSteps()) { + if (step instanceof AxisStep) { + // We only support parent axis steps + final YangXPathAxis axis = ((AxisStep) step).getAxis(); + checkState(axis == YangXPathAxis.PARENT, "Unexpected axis %s", axis); + ret.removeLast(); + continue; + } + + // This has to be a QNameStep + checkState(step instanceof QNameStep, "Unhandled step %s in %s", step, path); + final QName qname; + if (step instanceof ResolvedQNameStep) { + qname = ((ResolvedQNameStep) step).getQName(); + } else if (step instanceof UnresolvedQNameStep) { + final AbstractQName toResolve = ((UnresolvedQNameStep) step).getQName(); + // TODO: should handle qualified QNames, too? parser should have resolved them when we get here... + checkState(toResolve instanceof UnqualifiedQName, "Unhandled qname %s in %s", toResolve, path); + qname = QName.create(defaultModule, toResolve.getLocalName()); + } else { + throw new IllegalStateException("Unhandled step " + step); + } + + ret.addLast(qname); + } + + return findTargetNode(context, ret); + } + + private static @Nullable SchemaNode findTargetNode(final SchemaContext context, final Iterable qnamePath) { + // We do not have enough information about resolution context, hence cannot account for actions, RPCs + // and notifications. We therefore attempt to make a best estimate, but this can still fail. + final Optional pureData = context.findDataTreeChild(qnamePath); + return pureData.isPresent() ? pureData.get() : findNodeInSchemaContext(context, qnamePath); + } + @VisibleForTesting static int normalizeXPath(final List xpath) { LOG.trace("Normalize {}", xpath); @@ -660,31 +753,8 @@ public final class SchemaContextUtil { return -1; } - private static void splitXPath(final String xpath, final List output) { - // This is a major hack, but should do the trick for now. - final int deref = xpath.indexOf("deref("); - if (deref == -1) { - doSplitXPath(xpath, output); - return; - } - - // Interpret leading part - doSplitXPath(xpath.substring(0, deref), output); - - // Find matching parentheses - final int start = deref + 6; - final int paren = xpath.indexOf(')', start); - checkArgument(paren != -1, "Cannot find matching parentheses in %s", xpath); - - // Interpret the argument - doSplitXPath(xpath.substring(start, paren), output); - - // And now the last bit - splitXPath(xpath.substring(paren + 1), output); - } - - private static void doSplitXPath(final String xpath, final List output) { - SLASH_SPLITTER.split(xpath).forEach(output::add); + private static List doSplitXPath(final String xpath) { + return SLASH_SPLITTER.splitToList(xpath); } /** diff --git a/yang/yang-model-util/src/test/java/org/opendaylight/yangtools/yang/model/util/SchemaContextUtilTest.java b/yang/yang-model-util/src/test/java/org/opendaylight/yangtools/yang/model/util/SchemaContextUtilTest.java index c9c60f6e58..42848faba4 100644 --- a/yang/yang-model-util/src/test/java/org/opendaylight/yangtools/yang/model/util/SchemaContextUtilTest.java +++ b/yang/yang-model-util/src/test/java/org/opendaylight/yangtools/yang/model/util/SchemaContextUtilTest.java @@ -38,23 +38,6 @@ public class SchemaContextUtilTest { private static final Splitter SPACE_SPLITTER = Splitter.on(' '); private static final URI NAMESPACE = URI.create("abc"); - // The idea is: - // container baz { - // leaf xyzzy { - // type leafref; - // } - // leaf foo { - // type string; - // } - // leaf bar { - // type string; - // } - // } - private static final QName FOO = QName.create(NAMESPACE, "foo"); - private static final QName BAR = QName.create(NAMESPACE, "bar"); - private static final QName BAZ = QName.create(NAMESPACE, "baz"); - private static final QName XYZZY = QName.create(NAMESPACE, "xyzzy"); - @Mock private SchemaContext mockSchemaContext; @Mock @@ -73,8 +56,6 @@ public class SchemaContextUtilTest { doReturn(NAMESPACE).when(mockModule).getNamespace(); doReturn(QNameModule.create(NAMESPACE)).when(mockModule).getQNameModule(); doReturn(Optional.empty()).when(mockModule).getRevision(); - - doReturn(SchemaPath.create(true, BAZ, XYZZY)).when(schemaNode).getPath(); } @Test @@ -101,13 +82,6 @@ public class SchemaContextUtilTest { assertNull("Should be null.", SchemaContextUtil.findParentModule(mockSchemaContext, int32node)); } - @Test - public void testDeref() { - PathExpression xpath = new PathExpressionImpl("deref(../foo)/../bar", false); - assertNull(SchemaContextUtil.findDataSchemaNodeForRelativeXPath(mockSchemaContext, mockModule, schemaNode, - xpath)); - } - @Test public void testNormalizeXPath() { assertNormalizedPath(0, ImmutableList.of(""), "");