Allow unqualified node-identifiers in instance-identifier 53/103953/1
authorRuslan Kashapov <ruslan.kashapov@pantheon.tech>
Mon, 24 Oct 2022 13:04:41 +0000 (16:04 +0300)
committerRobert Varga <robert.varga@pantheon.tech>
Fri, 6 Jan 2023 16:09:21 +0000 (17:09 +0100)
node-identifier has an optional prefix, make sure we reflect this in our
class design.

JIRA: YANGTOOLS-1361
Change-Id: I928eabf5d2508d9be1390f7662998829cb06af81
Signed-off-by: Ruslan Kashapov <ruslan.kashapov@pantheon.tech>
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
(cherry picked from commit 173787948ff24c4098602e351fb097a55a4ffd85)

xpath/yang-xpath-impl/src/main/antlr4/org/opendaylight/yangtools/yang/xpath/antlr/instanceIdentifierParser.g4
xpath/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/InstanceIdentifierParser.java
xpath/yang-xpath-impl/src/test/java/org/opendaylight/yangtools/yang/xpath/impl/InstanceIdentifierParserTest.java

index 223b0b4e8fa7f533da151826659f1f66e26981c5..21691573c453c5a06f0dd141132f7e7d06cc3f0f 100644 (file)
@@ -20,7 +20,7 @@ instanceIdentifier : (SLASH pathArgument)+ EOF
 pathArgument : nodeIdentifier predicate?
   ;
 
-nodeIdentifier : Identifier COLON Identifier
+nodeIdentifier : Identifier (COLON Identifier)?
   ;
 
 predicate : keyPredicate+
index 31fac3e9b0f09b85bf41ffa0442bde170b0c375d..ff9d33c6401ff7c9d5cfcf60f19e36f8b56a4cf5 100644 (file)
@@ -52,11 +52,21 @@ abstract class InstanceIdentifierParser {
             super(mathMode);
         }
 
+        @Override
+        YangQNameExpr createExpr(final String localName) {
+            return YangQNameExpr.of(UnresolvedQName.Unqualified.of(localName));
+        }
+
         @Override
         YangQNameExpr createExpr(final String prefix, final String localName) {
             return YangQNameExpr.of(UnresolvedQName.Qualified.of(prefix, localName));
         }
 
+        @Override
+        QNameStep createChildStep(final String localName, final Collection<YangExpr> predicates) {
+            return YangXPathAxis.CHILD.asStep(UnresolvedQName.Unqualified.of(localName), predicates);
+        }
+
         @Override
         QNameStep createChildStep(final String prefix, final String localName, final Collection<YangExpr> predicates) {
             return YangXPathAxis.CHILD.asStep(UnresolvedQName.Qualified.of(prefix, localName), predicates);
@@ -64,7 +74,7 @@ abstract class InstanceIdentifierParser {
     }
 
     static final class Qualified extends InstanceIdentifierParser {
-        final YangNamespaceContext namespaceContext;
+        private final YangNamespaceContext namespaceContext;
 
         Qualified(final YangXPathMathMode mathMode, final YangNamespaceContext namespaceContext) {
             super(mathMode);
@@ -72,16 +82,24 @@ abstract class InstanceIdentifierParser {
         }
 
         @Override
-        QNameStep createChildStep(final String prefix, final String localName, final Collection<YangExpr> predicates) {
-            return YangXPathAxis.CHILD.asStep(namespaceContext.createQName(prefix, localName), predicates);
+        YangQNameExpr createExpr(final String localName) {
+            return YangQNameExpr.of(UnresolvedQName.Unqualified.of(localName));
         }
 
-
         @Override
         YangQNameExpr createExpr(final String prefix, final String localName) {
             return YangQNameExpr.of(namespaceContext.createQName(prefix, localName));
         }
 
+        @Override
+        QNameStep createChildStep(final String localName, final Collection<YangExpr> predicates) {
+            return YangXPathAxis.CHILD.asStep(UnresolvedQName.Unqualified.of(localName), predicates);
+        }
+
+        @Override
+        QNameStep createChildStep(final String prefix, final String localName, final Collection<YangExpr> predicates) {
+            return YangXPathAxis.CHILD.asStep(namespaceContext.createQName(prefix, localName), predicates);
+        }
     }
 
     private final YangXPathMathSupport mathSupport;
@@ -111,19 +129,28 @@ abstract class InstanceIdentifierParser {
         return YangLocationPath.absolute(steps);
     }
 
+    abstract YangQNameExpr createExpr(String localName);
+
     abstract YangQNameExpr createExpr(String prefix, String localName);
 
+    abstract QNameStep createChildStep(String localName, Collection<YangExpr> predicates);
+
     abstract QNameStep createChildStep(String prefix, String localName, Collection<YangExpr> predicates);
 
     private QNameStep parsePathArgument(final PathArgumentContext expr) {
+        final var predicates = switch (expr.getChildCount()) {
+            case 1 -> ImmutableSet.<YangExpr>of();
+            case 2 -> parsePredicate(getChild(expr, PredicateContext.class, 1));
+            default -> throw illegalShape(expr);
+        };
+
         final NodeIdentifierContext childExpr = getChild(expr, NodeIdentifierContext.class, 0);
-        final String prefix = verifyIdentifier(childExpr, 0);
-        final String localName = verifyIdentifier(childExpr, 2);
+        final String first = verifyIdentifier(childExpr, 0);
 
-        return switch (expr.getChildCount()) {
-            case 1 -> createChildStep(prefix, localName, ImmutableSet.of());
-            case 2 -> createChildStep(prefix, localName, parsePredicate(getChild(expr, PredicateContext.class, 1)));
-            default -> throw illegalShape(expr);
+        return switch (childExpr.getChildCount()) {
+            case 1 -> createChildStep(first, predicates);
+            case 3 -> createChildStep(first, verifyIdentifier(childExpr, 2), predicates);
+            default -> throw illegalShape(childExpr);
         };
     }
 
@@ -151,7 +178,12 @@ abstract class InstanceIdentifierParser {
     }
 
     private YangQNameExpr createChildExpr(final NodeIdentifierContext expr) {
-        return createExpr(verifyIdentifier(expr, 0), verifyIdentifier(expr, 2));
+        final var first = verifyIdentifier(expr, 0);
+        return switch (expr.getChildCount()) {
+            case 1 -> createExpr(first);
+            case 3 -> createExpr(first, verifyIdentifier(expr, 2));
+            default -> throw illegalShape(expr);
+        };
     }
 
     private static String verifyIdentifier(final NodeIdentifierContext expr, final int child) {
index 242b9bea84012abdac3117919bf171c0aab54efc..d25444ca7401b5fc71d92df12def2f9d45ce8d7a 100644 (file)
@@ -7,14 +7,37 @@
  */
 package org.opendaylight.yangtools.yang.xpath.impl;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
 import com.google.common.collect.ImmutableBiMap;
-import org.junit.jupiter.api.Test;
+import java.util.List;
+import java.util.stream.Stream;
+import javax.xml.xpath.XPathExpressionException;
+import org.eclipse.jdt.annotation.Nullable;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.opendaylight.yangtools.yang.common.AbstractQName;
 import org.opendaylight.yangtools.yang.common.BiMapYangNamespaceContext;
+import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.common.UnresolvedQName;
 import org.opendaylight.yangtools.yang.common.XMLNamespace;
 import org.opendaylight.yangtools.yang.common.YangNamespaceContext;
+import org.opendaylight.yangtools.yang.xpath.api.YangBinaryExpr;
+import org.opendaylight.yangtools.yang.xpath.api.YangBinaryOperator;
+import org.opendaylight.yangtools.yang.xpath.api.YangExpr;
 import org.opendaylight.yangtools.yang.xpath.api.YangLiteralExpr;
+import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath;
 import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath.Absolute;
+import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath.QNameStep;
+import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath.Step;
+import org.opendaylight.yangtools.yang.xpath.api.YangQNameExpr;
 import org.opendaylight.yangtools.yang.xpath.api.YangXPathMathMode;
 
 class InstanceIdentifierParserTest {
@@ -24,17 +47,150 @@ class InstanceIdentifierParserTest {
         "foo", QNameModule.create(XMLNamespace.of("foo")),
         "bar", QNameModule.create(XMLNamespace.of("bar"))));
 
-    private static final InstanceIdentifierParser PARSER =
+    private static final QName FOO_FOO_QUALIFIED = CONTEXT.createQName("foo", "foo");
+    private static final QName FOO_X_QUALIFIED = CONTEXT.createQName("foo", "x");
+    private static final UnresolvedQName FOO_FOO_UNRESOLVED = UnresolvedQName.Qualified.of("foo", "foo");
+    private static final UnresolvedQName FOO_X_UNRESOLVED = UnresolvedQName.Qualified.of("foo", "x");
+    private static final UnresolvedQName FOO_UNRESOLVED = UnresolvedQName.Unqualified.of("foo");
+    private static final UnresolvedQName X_UNRESOLVED = UnresolvedQName.Unqualified.of("x");
+
+    private static final YangExpr POSITION_EXPR = FunctionSupport.POSITION;
+    private static final YangExpr SELF_EXPR = YangLocationPath.self();
+
+    private static final InstanceIdentifierParser BASE = new InstanceIdentifierParser.Base(YangXPathMathMode.IEEE754);
+    private static final InstanceIdentifierParser QUALIFIED =
         new InstanceIdentifierParser.Qualified(YangXPathMathMode.IEEE754, CONTEXT);
 
-    @Test
-    void smokeTests() throws Exception {
-        assertPath("/foo:foo");
-        assertPath("/foo:foo[.='abcd']");
-        assertPath("/foo:foo[.=\"abcd\"]");
+    @ParameterizedTest(name = "Smoke test: {0}")
+    @MethodSource("smokeTestArgs")
+    void smokeTest(final String input) throws Exception {
+        parseBase(input);
+        parseQualified(input);
+    }
+
+    private static List<String> smokeTestArgs() {
+        return List.of("/foo:foo/bar:bar", "/foo/bar", "/foo/bar[ 1 ]", "/foo/bar[11]", "/foo:foo[.='abc']",
+                "/foo[ . = \"abc\" ]", "/bar:bar[foo:x = 'a'][foo:y = 'b']", "/a/b/c");
+    }
+
+    @ParameterizedTest(name = "Qualified parsing: {0}")
+    @MethodSource("qualifiedParsingArgs")
+    void qualifiedParsing(final String input, final AbstractQName expectedQName,
+            final @Nullable YangExpr expectedLeft, final @Nullable YangExpr expectedRight) throws Exception {
+        assertParsed(parseQualified(input), expectedQName, expectedLeft, expectedRight);
+    }
+
+    private static Stream<Arguments> qualifiedParsingArgs() {
+        return Stream.of(
+                // input, expected QName, expected left expr of predicate, expected right expr of predicate
+                Arguments.of("/foo:foo", FOO_FOO_QUALIFIED, null, null),
+                Arguments.of("/foo", FOO_UNRESOLVED, null, null),
+                Arguments.of("/foo:foo[1]", FOO_FOO_QUALIFIED, POSITION_EXPR, toNumberExpr(1)),
+                Arguments.of("/foo[ 101 ]", FOO_UNRESOLVED, POSITION_EXPR, toNumberExpr(101)),
+                Arguments.of("/foo:foo[foo:x='abc']", FOO_FOO_QUALIFIED,
+                        YangQNameExpr.of(FOO_X_QUALIFIED), YangLiteralExpr.of("abc")),
+                Arguments.of("/foo[ x = \"xyz\" ]", FOO_UNRESOLVED,
+                        YangQNameExpr.of(X_UNRESOLVED), YangLiteralExpr.of("xyz")),
+                Arguments.of("/foo:foo[.=\"\"]", FOO_FOO_QUALIFIED, SELF_EXPR, YangLiteralExpr.of("")),
+                Arguments.of("/foo[ . = \"value\" ]", FOO_UNRESOLVED, SELF_EXPR, YangLiteralExpr.of("value"))
+        );
+    }
+
+    @ParameterizedTest(name = "Base parsing: {0}")
+    @MethodSource("baseParsingArgs")
+    void baseParsing(final String input, final AbstractQName expectedQName,
+            @Nullable final YangExpr expectedLeft, @Nullable final YangExpr expectedRight) throws Exception {
+        assertParsed(parseBase(input), expectedQName, expectedLeft, expectedRight);
+    }
+
+    private static Stream<Arguments> baseParsingArgs() {
+        return Stream.of(
+                // input, expected QName, expected left expr of predicate, expected right expr of predicate
+                Arguments.of("/foo:foo", FOO_FOO_UNRESOLVED, null, null),
+                Arguments.of("/foo", FOO_UNRESOLVED, null, null),
+                Arguments.of("/foo:foo[1]", FOO_FOO_UNRESOLVED, POSITION_EXPR, toNumberExpr(1)),
+                Arguments.of("/foo[ 101 ]", FOO_UNRESOLVED, POSITION_EXPR, toNumberExpr(101)),
+                Arguments.of("/foo:foo[foo:x='abc']", FOO_FOO_UNRESOLVED,
+                        YangQNameExpr.of(FOO_X_UNRESOLVED), YangLiteralExpr.of("abc")),
+                Arguments.of("/foo[ x = \"xyz\" ]", FOO_UNRESOLVED,
+                        YangQNameExpr.of(X_UNRESOLVED), YangLiteralExpr.of("xyz")),
+                Arguments.of("/foo:foo[.=\"\"]", FOO_FOO_UNRESOLVED, SELF_EXPR, YangLiteralExpr.of("")),
+                Arguments.of("/foo[ . = \"value\" ]", FOO_UNRESOLVED, SELF_EXPR, YangLiteralExpr.of("value"))
+        );
+    }
+
+    private static void assertParsed(final Absolute parsed, final AbstractQName expectedQName,
+            final @Nullable YangExpr expectedLeft, final @Nullable YangExpr expectedRight) throws Exception {
+        final var step = assertInstanceOf(QNameStep.class, extractFirstStep(parsed));
+        assertEquals(expectedQName, step.getQName());
+
+        if (expectedLeft == null) {
+            return;
+        }
+
+        final var predicate = assertInstanceOf(YangBinaryExpr.class, extractFirstPredicate(step));
+        assertEquals(YangBinaryOperator.EQUALS, predicate.getOperator());
+        assertEquals(expectedLeft, predicate.getLeftExpr());
+        assertEquals(expectedRight, predicate.getRightExpr());
+    }
+
+    @ParameterizedTest(name = "Unrecognized prefix: {0}")
+    @ValueSource(strings = {"/x:foo", "/foo:foo[x:bar = 'a']", "/foo[x:bar = 'b']"})
+    void unrecognizedPrefix(final String input) {
+        assertThrows(IllegalArgumentException.class, () -> parseQualified(input));
+    }
+
+    @ParameterizedTest(name = "Literals with escaped quotes: {0}")
+    @MethodSource("escapedQuotesLiteralArgs")
+    void escapedQuotesLiteral(final String input, final String expectedText) throws Exception {
+
+        // expected 1 step with predicate having literal
+        final var step = extractFirstStep(parseQualified(input));
+        final var predicate = assertInstanceOf(YangBinaryExpr.class, extractFirstPredicate(step));
+        final var right = assertInstanceOf(YangLiteralExpr.class, predicate.getRightExpr());
+
+        // ensure the string literal value unquoted properly
+        assertEquals(expectedText, right.getLiteral());
+    }
+
+    private static Stream<Arguments> escapedQuotesLiteralArgs() {
+        return Stream.of(
+                Arguments.of("/foo[.= \"quo\\\"tes\\\\\"]", "quo\"tes\\"),
+                Arguments.of("/foo[.= \"quo\\ntes\\t\"]", "quo\ntes\t"),
+                Arguments.of("/foo[.= 'quo\"tes']", "quo\"tes"),
+                Arguments.of("/foo[.= 'quo\\tes']", "quo\\tes"),
+                Arguments.of("/foo[.= \"\"]", ""),
+                Arguments.of("/foo[.= '']", ""),
+                Arguments.of("/foo[.= \"\"]", "")
+        );
+    }
+
+    private static Absolute parseQualified(final String literal) throws XPathExpressionException {
+        return QUALIFIED.interpretAsInstanceIdentifier(YangLiteralExpr.of(literal));
+    }
+
+    private static Absolute parseBase(final String literal) throws XPathExpressionException {
+        return BASE.interpretAsInstanceIdentifier(YangLiteralExpr.of(literal));
+    }
+
+    private static YangExpr toNumberExpr(final int number) {
+        return YangXPathMathMode.IEEE754.getSupport().createNumber(number);
+    }
+
+    private static Step extractFirstStep(final Absolute parsed) {
+        assertNotNull(parsed);
+        assertNotNull(parsed.getSteps());
+        assertFalse(parsed.getSteps().isEmpty());
+        final var step = parsed.getSteps().get(0);
+        assertNotNull(step);
+        return step;
     }
 
-    private static Absolute assertPath(final String literal) throws Exception {
-        return PARSER.interpretAsInstanceIdentifier(YangLiteralExpr.of(literal));
+    private static YangExpr extractFirstPredicate(final Step step) {
+        assertNotNull(step.getPredicates());
+        assertFalse(step.getPredicates().isEmpty());
+        final var predicate = step.getPredicates().iterator().next();
+        assertNotNull(predicate);
+        return predicate;
     }
 }