Correct deref() leafref handling 49/86549/8
authorRobert Varga <robert.varga@pantheon.tech>
Mon, 23 Dec 2019 19:06:56 +0000 (20:06 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Tue, 24 Dec 2019 20:07:29 +0000 (21:07 +0100)
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 <robert.varga@pantheon.tech>
yang/pom.xml
yang/yang-model-util-ut/pom.xml [new file with mode: 0644]
yang/yang-model-util-ut/src/test/java/org/opendaylight/yangtools/yang/model/util/ut/LeafrefStaticAnalysisTest.java [new file with mode: 0644]
yang/yang-model-util-ut/src/test/java/org/opendaylight/yangtools/yang/model/util/ut/YT1050Test.java [new file with mode: 0644]
yang/yang-model-util-ut/src/test/java/org/opendaylight/yangtools/yang/model/util/ut/package-info.java [new file with mode: 0644]
yang/yang-model-util-ut/src/test/resources/leafrefs.yang [new file with mode: 0644]
yang/yang-model-util-ut/src/test/resources/yt1050.yang [new file with mode: 0644]
yang/yang-model-util/src/main/java/org/opendaylight/yangtools/yang/model/util/SchemaContextUtil.java
yang/yang-model-util/src/test/java/org/opendaylight/yangtools/yang/model/util/SchemaContextUtilTest.java

index cfa562fcb41e06b80da82ba1cda9055a7064a4e4..e749ed72cc43fffd199eb6da9bd3cc79b707edb2 100644 (file)
@@ -40,6 +40,7 @@
         <module>yang-model-api</module>
         <module>yang-model-export</module>
         <module>yang-model-util</module>
+        <module>yang-model-util-ut</module>
 
         <!-- YANG XPath API and implementation -->
         <module>yang-xpath-api</module>
diff --git a/yang/yang-model-util-ut/pom.xml b/yang/yang-model-util-ut/pom.xml
new file mode 100644 (file)
index 0000000..a7bfada
--- /dev/null
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- vi: set et smarttab sw=4 tabstop=4: -->
+<!--
+ 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
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.opendaylight.yangtools</groupId>
+        <artifactId>bundle-parent</artifactId>
+        <version>4.0.4-SNAPSHOT</version>
+        <relativePath>../../bundle-parent</relativePath>
+    </parent>
+
+    <!-- FIXME: YANGTOOLS-1052: merge this into yang-model-util -->
+    <artifactId>yang-model-util-ut</artifactId>
+    <packaging>jar</packaging>
+    <name>${project.artifactId}</name>
+    <description>${project.artifactId}</description>
+
+    <properties>
+        <maven.deploy.skip>true</maven.deploy.skip>
+        <maven.install.skip>true</maven.install.skip>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.opendaylight.yangtools</groupId>
+            <artifactId>yang-model-util</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.opendaylight.yangtools</groupId>
+            <artifactId>yang-test-util</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.opendaylight.yangtools</groupId>
+            <artifactId>mockito-configuration</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
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 (file)
index 0000000..bfd36e4
--- /dev/null
@@ -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 (file)
index 0000000..3a3da6e
--- /dev/null
@@ -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 (file)
index 0000000..0a67279
--- /dev/null
@@ -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 (file)
index 0000000..c923c58
--- /dev/null
@@ -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 (file)
index 0000000..90c9d8f
--- /dev/null
@@ -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";
+  }
+}
+
index 7aa3984334a4b6ac2a44110e05999179ad31d5b3..ebc3bc9b687c4d700dfe836ad580219bdb35cfe6 100644 (file)
@@ -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<QName> 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<DataSchemaNode> 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<QName> 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<String> 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<QName> resolveRelativePath(final SchemaContext context, final Module module,
+            final SchemaNode actualSchemaNode, final List<String> 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<String> xpaths = colCount == 0 ? steps : steps.subList(colCount, steps.size());
 
         final Iterable<QName> 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<String> 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<QName> 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<QName> 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<DataSchemaNode> pureData = context.findDataTreeChild(qnamePath);
+        return pureData.isPresent() ? pureData.get() : findNodeInSchemaContext(context, qnamePath);
+    }
+
     @VisibleForTesting
     static int normalizeXPath(final List<String> xpath) {
         LOG.trace("Normalize {}", xpath);
@@ -660,31 +753,8 @@ public final class SchemaContextUtil {
         return -1;
     }
 
-    private static void splitXPath(final String xpath, final List<String> 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<String> output) {
-        SLASH_SPLITTER.split(xpath).forEach(output::add);
+    private static List<String> doSplitXPath(final String xpath) {
+        return SLASH_SPLITTER.splitToList(xpath);
     }
 
     /**
index c9c60f6e58b13b5fad0410b0073a638a4c4a5e39..42848faba425dded21a8e21d93bc907f77c7a647 100644 (file)
@@ -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(""), "");