Bug 5946: Unique statement is not exposed by the YANG parser 81/39481/4
authorPeter Kajsa <pkajsa@cisco.com>
Thu, 26 May 2016 12:50:13 +0000 (14:50 +0200)
committerRobert Varga <rovarga@cisco.com>
Thu, 2 Jun 2016 16:02:25 +0000 (18:02 +0200)
The YANG parser did not expose the unique constraint of list schema node.
This patch adds API definition for unique constraints exposing and provides
also its implementation in yang statement parser.

Change-Id: If844248c81651a96c13487ea5b5eacdcd80107a5
Signed-off-by: Peter Kajsa <pkajsa@cisco.com>
Signed-off-by: Robert Varga <rovarga@cisco.com>
yang/yang-model-api/src/main/java/org/opendaylight/yangtools/yang/model/api/ListSchemaNode.java
yang/yang-model-api/src/main/java/org/opendaylight/yangtools/yang/model/api/UniqueConstraint.java [new file with mode: 0644]
yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/UniqueStatementImpl.java
yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/Utils.java
yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/effective/ListEffectiveStatementImpl.java
yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/effective/UniqueEffectiveStatementImpl.java
yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/stmt/Bug5946Test.java [new file with mode: 0644]
yang/yang-parser-impl/src/test/resources/bugs/bug5946/foo-invalid.yang [new file with mode: 0644]
yang/yang-parser-impl/src/test/resources/bugs/bug5946/foo.yang [new file with mode: 0644]

index ad8d999816d3e2e9751a330ceb8f8421f890dc17..83b6712c2c450fc6fc51934ab78ee37cb2afd280 100644 (file)
@@ -7,8 +7,10 @@
  */
 package org.opendaylight.yangtools.yang.model.api;
 
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
 import java.util.List;
-
+import javax.annotation.Nonnull;
 import org.opendaylight.yangtools.yang.common.QName;
 
 /**
@@ -30,9 +32,16 @@ public interface ListSchemaNode extends DataNodeContainer, AugmentationTarget, D
      * YANG 'ordered-by' statement. It defines whether the order of entries
      * within a list are determined by the user or the system. If not present,
      * default is false.
-     * 
+     *
      * @return true if ordered-by argument is "user", false otherwise
      */
     boolean isUserOrdered();
 
+    /**
+     * @return Collection of unique constraints of this list schema node
+     */
+    @Nonnull
+    default Collection<UniqueConstraint> getUniqueConstraints() {
+        return ImmutableList.of();
+    }
 }
diff --git a/yang/yang-model-api/src/main/java/org/opendaylight/yangtools/yang/model/api/UniqueConstraint.java b/yang/yang-model-api/src/main/java/org/opendaylight/yangtools/yang/model/api/UniqueConstraint.java
new file mode 100644 (file)
index 0000000..009d25a
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. 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.api;
+
+import com.google.common.annotations.Beta;
+import java.util.Collection;
+import javax.annotation.Nonnull;
+import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Relative;
+
+/**
+ * Interface describing YANG 'unique' constraint.
+ *
+ * The 'unique' constraint specifies that the combined values of all the leaf
+ * instances specified in the argument string, including leafs with default
+ * values, MUST be unique within all list entry instances in which all
+ * referenced leafs exist (for more information see RFC-6020 section 7.8.3.).
+ */
+@Beta
+public interface UniqueConstraint {
+    @Nonnull Collection<Relative> getTag();
+}
index 465d1a6253fb66ad5c9647720c304703271e709e..d3294a3f3702b2a64af044da2971ea0596f39323 100644 (file)
@@ -19,6 +19,7 @@ import org.opendaylight.yangtools.yang.parser.spi.SubstatementValidator;
 import org.opendaylight.yangtools.yang.parser.spi.meta.AbstractDeclaredStatement;
 import org.opendaylight.yangtools.yang.parser.spi.meta.AbstractStatementSupport;
 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
+import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
 import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.effective.UniqueEffectiveStatementImpl;
 
 public class UniqueStatementImpl extends AbstractDeclaredStatement<Collection<SchemaNodeIdentifier.Relative>> implements UniqueStatement {
@@ -41,7 +42,11 @@ public class UniqueStatementImpl extends AbstractDeclaredStatement<Collection<Sc
 
         @Override
         public Collection<SchemaNodeIdentifier.Relative> parseArgumentValue(StmtContext<?, ?, ?> ctx, String value) {
-            return Utils.transformKeysStringToKeyNodes(ctx, value);
+            final Collection<Relative> uniqueConstraints = Utils.parseUniqueConstraintArgument(ctx, value);
+            SourceException.throwIf(uniqueConstraints.isEmpty(), ctx.getStatementSourceReference(),
+                    "Invalid argument value '%s' of unique statement. The value must contains at least "
+                            + "one descendant schema node identifier.", value);
+            return uniqueConstraints;
         }
 
         @Override
index a41a1a9de3f6cbe0a20cbd2abcaf2514e31278fd..593899da89495e7306efb31ba120cfc0a1a66a49 100644 (file)
@@ -341,6 +341,19 @@ public final class Utils {
         return keyNodes;
     }
 
+    static Collection<SchemaNodeIdentifier.Relative> parseUniqueConstraintArgument(final StmtContext<?, ?, ?> ctx,
+            final String argumentValue) {
+        final Set<SchemaNodeIdentifier.Relative> uniqueConstraintNodes = new HashSet<>();
+        for (String uniqueArgToken : SPACE_SPLITTER.split(argumentValue)) {
+            final SchemaNodeIdentifier nodeIdentifier = Utils.nodeIdentifierFromPath(ctx, uniqueArgToken);
+            SourceException.throwIf(nodeIdentifier.isAbsolute(), ctx.getStatementSourceReference(),
+                    "Unique statement argument '%s' contains schema node identifier '%s' "
+                            + "which is not in the descendant node identifier form.", argumentValue, uniqueArgToken);
+            uniqueConstraintNodes.add((SchemaNodeIdentifier.Relative) nodeIdentifier);
+        }
+        return ImmutableSet.copyOf(uniqueConstraintNodes);
+    }
+
     private static String trimSingleLastSlashFromXPath(final String path) {
         return path.endsWith("/") ? path.substring(0, path.length() - 1) : path;
     }
index 7dc2c2361e7a082e9e0c12905807cb4fa9cc8e3a..5df2006fbc80bf569e3498098e54061997cea25f 100644 (file)
@@ -15,10 +15,12 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
+import javax.annotation.Nonnull;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.model.api.DerivableSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.UniqueConstraint;
 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.ListStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier;
@@ -27,11 +29,12 @@ import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
 
 public final class ListEffectiveStatementImpl extends AbstractEffectiveSimpleDataNodeContainer<ListStatement> implements
         ListSchemaNode, DerivableSchemaNode {
+    private static final String ORDER_BY_USER_KEYWORD = "user";
 
     private final boolean userOrdered;
     private final List<QName> keyDefinition;
-    private static final String ORDER_BY_USER_KEYWORD = "user";
     private final ListSchemaNode original;
+    private final Collection<UniqueConstraint> uniqueConstraints;
 
     public ListEffectiveStatementImpl(
             final StmtContext<QName, ListStatement, EffectiveStatement<QName, ListStatement>> ctx) {
@@ -40,7 +43,7 @@ public final class ListEffectiveStatementImpl extends AbstractEffectiveSimpleDat
         this.original = ctx.getOriginalCtx() == null ? null : (ListSchemaNode) ctx.getOriginalCtx().buildEffective();
 
         OrderedByEffectiveStatementImpl orderedByStmt = firstEffective(OrderedByEffectiveStatementImpl.class);
-        if (orderedByStmt != null && orderedByStmt.argument().equals(ORDER_BY_USER_KEYWORD)) {
+        if (orderedByStmt != null && ORDER_BY_USER_KEYWORD.equals(orderedByStmt.argument())) {
             this.userOrdered = true;
         } else {
             this.userOrdered = false;
@@ -59,8 +62,7 @@ public final class ListEffectiveStatementImpl extends AbstractEffectiveSimpleDat
                 }
             }
 
-            Collection<SchemaNodeIdentifier> keys = keyEffectiveSubstatement.argument();
-            for (SchemaNodeIdentifier key : keys) {
+            for (SchemaNodeIdentifier key : keyEffectiveSubstatement.argument()) {
                 final QName keyQName = key.getLastComponent();
 
                 if (!possibleLeafQNamesForKey.contains(keyQName)) {
@@ -74,6 +76,7 @@ public final class ListEffectiveStatementImpl extends AbstractEffectiveSimpleDat
         }
 
         this.keyDefinition = ImmutableList.copyOf(keyDefinitionInit);
+        this.uniqueConstraints = ImmutableList.copyOf(allSubstatementsOfType(UniqueConstraint.class));
     }
 
     @Override
@@ -86,6 +89,12 @@ public final class ListEffectiveStatementImpl extends AbstractEffectiveSimpleDat
         return keyDefinition;
     }
 
+    @Override
+    @Nonnull
+    public Collection<UniqueConstraint> getUniqueConstraints() {
+        return uniqueConstraints;
+    }
+
     @Override
     public boolean isUserOrdered() {
         return userOrdered;
index 7aed19de4c69b557c0bb2ba16ab93c4aa86e00d1..754c907a189e97bfa5fcae645035f88ca9c3434f 100644 (file)
@@ -9,13 +9,24 @@
 package org.opendaylight.yangtools.yang.parser.stmt.rfc6020.effective;
 
 import java.util.Collection;
+import javax.annotation.Nonnull;
+import org.opendaylight.yangtools.yang.model.api.UniqueConstraint;
 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier;
+import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Relative;
 import org.opendaylight.yangtools.yang.model.api.stmt.UniqueStatement;
 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
 
 public final class UniqueEffectiveStatementImpl extends
-        DeclaredEffectiveStatementBase<Collection<SchemaNodeIdentifier.Relative>, UniqueStatement> {
-    public UniqueEffectiveStatementImpl(final StmtContext<Collection<SchemaNodeIdentifier.Relative>, UniqueStatement, ?> ctx) {
+        DeclaredEffectiveStatementBase<Collection<SchemaNodeIdentifier.Relative>, UniqueStatement> implements
+        UniqueConstraint {
+    public UniqueEffectiveStatementImpl(
+            final StmtContext<Collection<SchemaNodeIdentifier.Relative>, UniqueStatement, ?> ctx) {
         super(ctx);
     }
+
+    @Nonnull
+    @Override
+    public Collection<Relative> getTag() {
+        return argument();
+    }
 }
diff --git a/yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/stmt/Bug5946Test.java b/yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/stmt/Bug5946Test.java
new file mode 100644 (file)
index 0000000..235d207
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. 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.stmt;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.net.URISyntaxException;
+import java.util.Collection;
+import org.junit.Test;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.UniqueConstraint;
+import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier;
+import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Relative;
+import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException;
+import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
+
+public class Bug5946Test {
+    private static final String NS = "foo";
+    private static final String REV = "2016-05-26";
+    private static final QName L1 = QName.create(NS, REV, "l1");
+    private static final QName L2 = QName.create(NS, REV, "l2");
+    private static final QName L3 = QName.create(NS, REV, "l3");
+    private static final QName C = QName.create(NS, REV, "c");
+    private static final QName WITHOUT_UNIQUE = QName.create(NS, REV, "without-unique");
+    private static final QName SIMPLE_UNIQUE = QName.create(NS, REV, "simple-unique");
+    private static final QName MULTIPLE_UNIQUE = QName.create(NS, REV, "multiple-unique");
+    private static final SchemaNodeIdentifier L1_ID = SchemaNodeIdentifier.create(false, L1);
+    private static final SchemaNodeIdentifier L2_ID = SchemaNodeIdentifier.create(false, L2);
+    private static final SchemaNodeIdentifier C_L3_ID = SchemaNodeIdentifier.create(false, C, L3);
+
+    @Test
+    public void test() throws SourceException, FileNotFoundException, ReactorException, URISyntaxException {
+        SchemaContext context = StmtTestUtils.parseYangSources(new File(getClass()
+                .getResource("/bugs/bug5946/foo.yang").toURI()));
+        assertNotNull(context);
+
+        Collection<UniqueConstraint> uniqueConstraints = getListConstraints(context, WITHOUT_UNIQUE);
+        assertNotNull(uniqueConstraints);
+        assertTrue(uniqueConstraints.isEmpty());
+
+        Collection<UniqueConstraint> simpleUniqueConstraints = getListConstraints(context, SIMPLE_UNIQUE);
+        assertNotNull(simpleUniqueConstraints);
+        assertEquals(1, simpleUniqueConstraints.size());
+        Collection<Relative> simpleUniqueConstraintTag = simpleUniqueConstraints.iterator().next().getTag();
+        assertTrue(simpleUniqueConstraintTag.contains(L1_ID));
+        assertTrue(simpleUniqueConstraintTag.contains(C_L3_ID));
+
+        Collection<UniqueConstraint> multipleUniqueConstraints = getListConstraints(context, MULTIPLE_UNIQUE);
+        assertNotNull(multipleUniqueConstraints);
+        assertEquals(3, multipleUniqueConstraints.size());
+        boolean l1l2 = false;
+        boolean l1cl3 = false;
+        boolean cl3l2 = false;
+        for (UniqueConstraint uniqueConstraint : multipleUniqueConstraints) {
+            Collection<Relative> uniqueConstraintTag = uniqueConstraint.getTag();
+            if (uniqueConstraintTag.contains(L1_ID) && uniqueConstraintTag.contains(L2_ID)) {
+                l1l2 = true;
+            } else if (uniqueConstraintTag.contains(L1_ID) && uniqueConstraintTag.contains(C_L3_ID)) {
+                l1cl3 = true;
+            } else if (uniqueConstraintTag.contains(C_L3_ID) && uniqueConstraintTag.contains(L2_ID)) {
+                cl3l2 = true;
+            }
+        }
+        assertTrue(l1l2 && l1cl3 && cl3l2);
+    }
+
+    @Test
+    public void testInvalid() throws SourceException, FileNotFoundException, ReactorException, URISyntaxException {
+        try {
+            StmtTestUtils.parseYangSources(new File(getClass().getResource("/bugs/bug5946/foo-invalid.yang").toURI()));
+            fail("Should fail due to invalid argument of unique constraint");
+        } catch (SourceException e) {
+            assertTrue(e.getMessage().startsWith(
+                    "Unique statement argument '/simple-unique/l1' contains schema node identifier '/simple-unique/l1'"
+                            + " which is not in the descendant node identifier form."));
+        }
+    }
+
+    private static Collection<UniqueConstraint> getListConstraints(SchemaContext context, QName listQName) {
+        DataSchemaNode dataChildByName = context.getDataChildByName(listQName);
+        assertTrue(dataChildByName instanceof ListSchemaNode);
+        return ((ListSchemaNode) dataChildByName).getUniqueConstraints();
+    }
+}
diff --git a/yang/yang-parser-impl/src/test/resources/bugs/bug5946/foo-invalid.yang b/yang/yang-parser-impl/src/test/resources/bugs/bug5946/foo-invalid.yang
new file mode 100644 (file)
index 0000000..c5cdf73
--- /dev/null
@@ -0,0 +1,20 @@
+module foo-invalid {
+    namespace "foo";
+    prefix foo;
+    yang-version 1;
+
+    revision 2016-05-26 {
+        description "test";
+    }
+
+    list simple-unique {
+        key "k";
+        leaf k {
+            type string;
+        }
+        unique "/simple-unique/l1";
+        leaf l1 {
+            type string;
+        }
+    }
+}
diff --git a/yang/yang-parser-impl/src/test/resources/bugs/bug5946/foo.yang b/yang/yang-parser-impl/src/test/resources/bugs/bug5946/foo.yang
new file mode 100644 (file)
index 0000000..28abb46
--- /dev/null
@@ -0,0 +1,56 @@
+module foo {
+    namespace "foo";
+    prefix foo;
+    yang-version 1;
+
+    revision 2016-05-26 {
+        description "test";
+    }
+
+    list without-unique {
+        key "k";
+        leaf k {
+            type string;
+        }
+    }
+
+    list simple-unique {
+        key "k";
+        leaf k {
+            type string;
+        }
+        unique "l1 c/l3";
+        leaf l1 {
+            type string;
+        }
+        leaf l2 {
+            type string;
+        }
+        container c {
+            leaf l3 {
+                type string;
+            }
+        }
+    }
+
+    list multiple-unique {
+        key "k";
+        leaf k {
+            type string;
+        }
+        unique "l1 c/l3";
+        unique "c/l3 l2";
+        unique "l1 l2";
+        leaf l1 {
+            type string;
+        }
+        leaf l2 {
+            type string;
+        }
+        container c {
+            leaf l3 {
+                type string;
+            }
+        }
+    }
+}