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>
*/
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;
/**
* 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();
+ }
}
--- /dev/null
+/*
+ * 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();
+}
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 {
@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
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;
}
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;
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) {
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;
}
}
- Collection<SchemaNodeIdentifier> keys = keyEffectiveSubstatement.argument();
- for (SchemaNodeIdentifier key : keys) {
+ for (SchemaNodeIdentifier key : keyEffectiveSubstatement.argument()) {
final QName keyQName = key.getLastComponent();
if (!possibleLeafQNamesForKey.contains(keyQName)) {
}
this.keyDefinition = ImmutableList.copyOf(keyDefinitionInit);
+ this.uniqueConstraints = ImmutableList.copyOf(allSubstatementsOfType(UniqueConstraint.class));
}
@Override
return keyDefinition;
}
+ @Override
+ @Nonnull
+ public Collection<UniqueConstraint> getUniqueConstraints() {
+ return uniqueConstraints;
+ }
+
@Override
public boolean isUserOrdered() {
return userOrdered;
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();
+ }
}
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+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;
+ }
+ }
+}
--- /dev/null
+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;
+ }
+ }
+ }
+}