QName is a YANG identifier 73/80573/8
authorRobert Varga <robert.varga@pantheon.tech>
Mon, 25 Feb 2019 12:53:39 +0000 (13:53 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Mon, 25 Feb 2019 15:51:24 +0000 (16:51 +0100)
Move argument enforcement to QName, as it is required to conform
to YANG identifier.

JIRA: YANGTOOLS-862
Change-Id: Ia1a5adb6921831476872d14e1e5c6caffc2af2d9
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
yang/yang-common/src/main/java/org/opendaylight/yangtools/yang/common/QName.java
yang/yang-model-util/src/test/java/org/opendaylight/yangtools/yang/model/util/type/BitImplTest.java
yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/stmt/extension/UnrecognizedEffectiveStatementImpl.java
yang/yang-parser-rfc7950/src/test/java/org/opendaylight/yangtools/yang/parser/stmt/rfc7950/Bug6868Test.java
yang/yang-parser-spi/src/main/java/org/opendaylight/yangtools/yang/parser/spi/meta/StmtContextUtils.java

index bb6ebc49a3f6ead3b2fae4b064c14e98a2c3d8fc..28f8c8349ac56434272fe557527287d35f908552 100644 (file)
@@ -10,6 +10,7 @@ package org.opendaylight.yangtools.yang.common;
 import static com.google.common.base.Preconditions.checkArgument;
 import static java.util.Objects.requireNonNull;
 
+import com.google.common.base.CharMatcher;
 import com.google.common.collect.Interner;
 import com.google.common.collect.Interners;
 import java.io.DataInput;
@@ -30,14 +31,16 @@ import org.opendaylight.yangtools.concepts.Immutable;
 import org.opendaylight.yangtools.concepts.WritableObject;
 
 /**
- * The QName from XML consists of local name of element and XML namespace, but
- * for our use, we added module revision to it.
+ * The QName from XML consists of local name of element and XML namespace, but for our use, we added module revision to
+ * it.
  *
  * <p>
- * In YANG context QName is full name of defined node, type, procedure or
- * notification. QName consists of XML namespace, YANG model revision and local
- * name of defined type. It is used to prevent name clashes between nodes with
- * same local name, but from different schemas.
+ * In YANG context QName is full name of defined node, type, procedure or notification. QName consists of XML namespace,
+ * YANG model revision and local name of defined type. It is used to prevent name clashes between nodes with same local
+ * name, but from different schemas.
+ *
+ * <p>
+ * The local name must conform to <a href="https://tools.ietf.org/html/rfc7950#section-6.2">RFC7950 Section 6.2</a>.
  *
  * <ul>
  * <li><b>XMLNamespace</b> - {@link #getNamespace()} - the namespace assigned to the YANG module which
@@ -47,11 +50,6 @@ import org.opendaylight.yangtools.concepts.WritableObject;
  * <li><b>LocalName</b> - {@link #getLocalName()} - the YANG schema identifier which were defined for this
  * node in the YANG module</li>
  * </ul>
- *
- * <p>
- * QName may also have <code>prefix</code> assigned, but prefix does not
- * affect equality and identity of two QNames and carry only information
- * which may be useful for serializers / deserializers.
  */
 public final class QName implements Immutable, Serializable, Comparable<QName>, Identifier, WritableObject {
     private static final Interner<QName> INTERNER = Interners.newWeakInterner();
@@ -69,7 +67,10 @@ public final class QName implements Immutable, Serializable, Comparable<QName>,
     private static final String QNAME_STRING_NO_REVISION = "^\\((.+)\\)(.+)$";
     private static final Pattern QNAME_PATTERN_NO_REVISION = Pattern.compile(QNAME_STRING_NO_REVISION);
 
-    private static final char[] ILLEGAL_CHARACTERS = { '?', '(', ')', '&', ':' };
+    private static final CharMatcher IDENTIFIER_START =
+            CharMatcher.inRange('A', 'Z').or(CharMatcher.inRange('a', 'z').or(CharMatcher.is('_'))).precomputed();
+    private static final CharMatcher NOT_IDENTIFIER_PART =
+            IDENTIFIER_START.or(CharMatcher.inRange('0', '9')).or(CharMatcher.anyOf("-.")).negate().precomputed();
 
     private final @NonNull QNameModule module;
     private final @NonNull String localName;
@@ -95,13 +96,8 @@ public final class QName implements Immutable, Serializable, Comparable<QName>,
     private static @NonNull String checkLocalName(final String localName) {
         checkArgument(localName != null, "Parameter 'localName' may not be null.");
         checkArgument(!localName.isEmpty(), "Parameter 'localName' must be a non-empty string.");
-
-        for (final char c : ILLEGAL_CHARACTERS) {
-            if (localName.indexOf(c) != -1) {
-                throw new IllegalArgumentException("Parameter 'localName':'" + localName
-                    + "' contains illegal character '" + c + "'");
-            }
-        }
+        checkArgument(IDENTIFIER_START.matches(localName.charAt(0)) && NOT_IDENTIFIER_PART.indexIn(localName, 1) == -1,
+                "String '%s' is not a valid identifier", localName);
         return localName;
     }
 
index 9b2ac4b5c649f66607f1b98fc148b75d01df3829..53e1a15be40063aeb9a20edaed007267e379772e 100644 (file)
@@ -33,12 +33,12 @@ public class BitImplTest {
         final URI uriB1 = new URI("some:uriB1");
         final URI uriB2 = new URI("some:uriB2");
 
-        QName qnameA1 = QName.create(uriA1, Revision.of("2000-01-01"), "some nameA1");
-        QName qnameA2 = QName.create(uriA2, Revision.of("2002-01-01"), "some nameA2");
+        QName qnameA1 = QName.create(uriA1, Revision.of("2000-01-01"), "someNameA1");
+        QName qnameA2 = QName.create(uriA2, Revision.of("2002-01-01"), "someNameA2");
         SchemaPath schemaPathA = SchemaPath.create(true, qnameA1, qnameA2);
 
-        final QName qnameB1 = QName.create(uriB1, Revision.of("2000-01-01"), "some nameB1");
-        final QName qnameB2 = QName.create(uriB2, Revision.of("2002-01-01"), "some nameB2");
+        final QName qnameB1 = QName.create(uriB1, Revision.of("2000-01-01"), "someNameB1");
+        final QName qnameB2 = QName.create(uriB2, Revision.of("2002-01-01"), "someNameB2");
         final SchemaPath schemaPathB = SchemaPath.create(true, qnameB1, qnameB2);
 
         BitImpl biA = new BitImpl(schemaPathA, 55L, "description", "reference", Status.CURRENT, emptyList());
@@ -75,7 +75,7 @@ public class BitImplTest {
         assertEquals("Incorrect value for unknown nodes.", emptyList(), biA.getUnknownSchemaNodes());
 
         // test of toString method
-        assertEquals("toString method doesn't return correct value", "Bit[name=some nameA2, position=55]",
+        assertEquals("toString method doesn't return correct value", "Bit[name=someNameA2, position=55]",
                 biA.toString());
     }
 }
index b68fd4effc230b00a25ccd06e5ee93e6442dd6fb..e3d76a9c694bb07b287c68843cc24a50f616e86a 100644 (file)
@@ -17,9 +17,13 @@ import org.opendaylight.yangtools.yang.model.api.stmt.UnrecognizedStatement;
 import org.opendaylight.yangtools.yang.parser.rfc7950.stmt.UnknownEffectiveStatementBase;
 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContextUtils;
+import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 final class UnrecognizedEffectiveStatementImpl extends UnknownEffectiveStatementBase<String, UnrecognizedStatement>
         implements UnrecognizedEffectiveStatement {
+    private static final Logger LOG = LoggerFactory.getLogger(UnrecognizedEffectiveStatementImpl.class);
 
     private final QName maybeQNameArgument;
     private final @NonNull SchemaPath path;
@@ -36,7 +40,8 @@ final class UnrecognizedEffectiveStatementImpl extends UnknownEffectiveStatement
             QName maybeQNameArgumentInit = null;
             try {
                 maybeQNameArgumentInit = StmtContextUtils.qnameFromArgument(ctx, argument());
-            } catch (IllegalArgumentException e) {
+            } catch (SourceException e) {
+                LOG.debug("Not constructing QName from {}", argument(), e);
                 maybeQNameArgumentInit = getNodeType();
             }
             this.maybeQNameArgument = maybeQNameArgumentInit;
index 842a67a2df680802af85be812e0b1234b35f674d..7b6d58514659e3c0d0f020caa502fd7638c3cb19 100644 (file)
@@ -97,7 +97,7 @@ public class Bug6868Test {
             fail("Test should fail due to invalid Yang 1.0");
         } catch (final SomeModifiersUnresolvedException e) {
             assertTrue(e.getCause().getMessage()
-                    .startsWith("String '(not foo) or (bar and baz)' is not a valid identifier "));
+                    .startsWith("Invalid identifier '(not foo) or (bar and baz)' [at "));
         }
     }
 }
\ No newline at end of file
index 030185d7c77196c08506fddf250e8db881593d23..61ea33a917a52f599455e0662505828aa27bfbf6 100644 (file)
@@ -10,7 +10,6 @@ package org.opendaylight.yangtools.yang.parser.spi.meta;
 import static com.google.common.base.Preconditions.checkArgument;
 import static java.util.Objects.requireNonNull;
 
-import com.google.common.base.CharMatcher;
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
@@ -45,11 +44,6 @@ import org.opendaylight.yangtools.yang.parser.spi.source.ModuleNameToModuleQName
 import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
 
 public final class StmtContextUtils {
-    private static final CharMatcher IDENTIFIER_START =
-            CharMatcher.inRange('A', 'Z').or(CharMatcher.inRange('a', 'z').or(CharMatcher.is('_'))).precomputed();
-    private static final CharMatcher NOT_IDENTIFIER_PART =
-            IDENTIFIER_START.or(CharMatcher.inRange('0', '9')).or(CharMatcher.anyOf("-.")).negate().precomputed();
-
     private StmtContextUtils() {
         throw new UnsupportedOperationException("Utility class");
     }
@@ -519,7 +513,6 @@ public final class StmtContextUtils {
     public static QName parseIdentifier(final StmtContext<?, ?, ?> ctx, final String str) {
         SourceException.throwIf(str.isEmpty(), ctx.getStatementSourceReference(),
                 "Identifier may not be an empty string");
-        checkIdentifierString(ctx, str);
         return internedQName(ctx, str);
     }
 
@@ -538,7 +531,6 @@ public final class StmtContextUtils {
 
         final int colon = str.indexOf(':');
         if (colon == -1) {
-            checkIdentifierString(ctx, str);
             return internedQName(ctx, str);
         }
 
@@ -548,7 +540,6 @@ public final class StmtContextUtils {
         final String localName = str.substring(colon + 1);
         SourceException.throwIf(localName.isEmpty(), ctx.getStatementSourceReference(),
             "String '%s' has an empty identifier", str);
-        checkIdentifierString(ctx, localName);
 
         final QNameModule module = StmtContextUtils.getModuleQNameByPrefix(ctx, prefix);
         if (module != null) {
@@ -569,18 +560,20 @@ public final class StmtContextUtils {
         throw new InferenceException(ctx.getStatementSourceReference(), "Cannot resolve QNameModule for '%s'", str);
     }
 
-    private static void checkIdentifierString(final StmtContext<?, ?, ?> ctx, final String str) {
-        SourceException.throwIf(!IDENTIFIER_START.matches(str.charAt(0)) || NOT_IDENTIFIER_PART.indexIn(str, 1) != -1,
-            ctx.getStatementSourceReference(), "String '%s' is not a valid identifier", str);
-    }
-
     private static QName internedQName(final StmtContext<?, ?, ?> ctx, final String localName) {
         return internedQName(ctx, StmtContextUtils.getRootModuleQName(ctx), localName);
     }
 
     private static QName internedQName(final StmtContext<?, ?, ?> ctx, final QNameModule module,
             final String localName) {
-        return ctx.getFromNamespace(QNameCacheNamespace.class, QName.create(module, localName));
+        final QName template;
+        try {
+            template = QName.create(module, localName);
+        } catch (IllegalArgumentException e) {
+            throw new SourceException(ctx.getStatementSourceReference(), e, "Invalid identifier '%s'", localName);
+        }
+
+        return ctx.getFromNamespace(QNameCacheNamespace.class, template);
     }
 
     public static QNameModule getRootModuleQName(final StmtContext<?, ?, ?> ctx) {