Add XMLNamespace
[yangtools.git] / yang / yang-common / src / main / java / org / opendaylight / yangtools / yang / common / QName.java
index fc3f5db03c2897ae7f17bde27e811608aef782a3..d74786b910fabc6ad8efa51eabb6b9381928731d 100644 (file)
@@ -8,16 +8,13 @@
 package org.opendaylight.yangtools.yang.common;
 
 import static java.util.Objects.requireNonNull;
-import static org.opendaylight.yangtools.yang.common.AbstractQName.checkLocalName;
 
+import com.google.common.annotations.Beta;
 import com.google.common.collect.Interner;
 import com.google.common.collect.Interners;
 import java.io.DataInput;
 import java.io.DataOutput;
 import java.io.IOException;
-import java.io.Serializable;
-import java.net.URI;
-import java.net.URISyntaxException;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.regex.Matcher;
@@ -25,9 +22,6 @@ import java.util.regex.Pattern;
 import org.checkerframework.checker.regex.qual.Regex;
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
-import org.opendaylight.yangtools.concepts.Identifier;
-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
@@ -50,13 +44,35 @@ import org.opendaylight.yangtools.concepts.WritableObject;
  * node in the YANG module</li>
  * </ul>
  */
-/*
- * FIXME: 4.0.0: make this class subclass AbstractQName, which will break serialization compatibility with versions
- *               <3.0.0, which did not use an Externalizable proxy.
- */
-public final class QName implements Immutable, Serializable, Comparable<QName>, Identifier, WritableObject {
+public final class QName extends AbstractQName implements Comparable<QName> {
+    /**
+     * A {@link DataInput} which has an understanding of {@link QName}'s semantics.
+     */
+    @Beta
+    public interface QNameAwareDataInput extends DataInput {
+        /**
+         * Read a {@link QName} from the stream.
+         *
+         * @return A QName
+         * @throws IOException if an I/O error occurs.
+         */
+        @NonNull QName readQName() throws IOException;
+    }
+
+    @Beta
+    public interface QNameAwareDataOutput extends DataOutput {
+        /**
+         * Write a {@link QName} into the stream.
+         *
+         * @param qname A QName
+         * @throws  IOException if an I/O error occurs.
+         */
+        void writeQName(@NonNull QName qname) throws IOException;
+    }
+
     private static final Interner<QName> INTERNER = Interners.newWeakInterner();
-    private static final long serialVersionUID = 5398411242927766414L;
+    // Note: 5398411242927766414L is used for versions < 3.0.0 without writeReplace
+    private static final long serialVersionUID = 1L;
 
     static final String QNAME_REVISION_DELIMITER = "?revision=";
     static final String QNAME_LEFT_PARENTHESIS = "(";
@@ -71,12 +87,11 @@ public final class QName implements Immutable, Serializable, Comparable<QName>,
     private static final Pattern QNAME_PATTERN_NO_REVISION = Pattern.compile(QNAME_STRING_NO_REVISION);
 
     private final @NonNull QNameModule module;
-    private final @NonNull String localName;
     private transient int hash = 0;
 
     QName(final QNameModule module, final @NonNull String localName) {
+        super(localName);
         this.module = requireNonNull(module);
-        this.localName = requireNonNull(localName);
     }
 
     /**
@@ -87,7 +102,7 @@ public final class QName implements Immutable, Serializable, Comparable<QName>,
      * @param localName
      *            YANG schema identifier
      */
-    private QName(final URI namespace, final String localName) {
+    private QName(final XMLNamespace namespace, final String localName) {
         this(QNameModule.create(namespace), checkLocalName(localName));
     }
 
@@ -101,7 +116,7 @@ public final class QName implements Immutable, Serializable, Comparable<QName>,
         }
         matcher = QNAME_PATTERN_NO_REVISION.matcher(input);
         if (matcher.matches()) {
-            final URI namespace = URI.create(matcher.group(1));
+            final XMLNamespace namespace = XMLNamespace.of(matcher.group(1));
             final String localName = matcher.group(2);
             return new QName(namespace, localName);
         }
@@ -115,11 +130,11 @@ public final class QName implements Immutable, Serializable, Comparable<QName>,
     /**
      * Creates new QName.
      *
-     * @param qnameModule
-     *            Namespace and revision enclosed as a QNameModule
-     * @param localName
-     *            Local name part of QName. MUST NOT BE null.
+     * @param qnameModule Namespace and revision enclosed as a QNameModule
+     * @param localName Local name part of QName. MUST NOT BE null.
      * @return Instance of QName
+     * @throws NullPointerException if any argument is null
+     * @throws IllegalArgumentException if localName is not a valid YANG identifier
      */
     public static @NonNull QName create(final QNameModule qnameModule, final String localName) {
         return new QName(requireNonNull(qnameModule, "module may not be null"), checkLocalName(localName));
@@ -133,7 +148,7 @@ public final class QName implements Immutable, Serializable, Comparable<QName>,
      * @param localName Local name part of QName. MUST NOT BE null.
      * @return Instance of QName
      */
-    public static @NonNull QName create(final URI namespace, final @Nullable Revision revision,
+    public static @NonNull QName create(final XMLNamespace namespace, final @Nullable Revision revision,
             final String localName) {
         return create(QNameModule.create(namespace, revision), localName);
     }
@@ -146,7 +161,7 @@ public final class QName implements Immutable, Serializable, Comparable<QName>,
      * @param localName Local name part of QName. MUST NOT BE null.
      * @return Instance of QName
      */
-    public static @NonNull QName create(final URI namespace, final Optional<Revision> revision,
+    public static @NonNull QName create(final XMLNamespace namespace, final Optional<Revision> revision,
             final String localName) {
         return create(QNameModule.create(namespace, revision), localName);
     }
@@ -160,7 +175,7 @@ public final class QName implements Immutable, Serializable, Comparable<QName>,
      * @return Instance of QName
      */
     public static @NonNull QName create(final String namespace, final String localName, final Revision revision) {
-        return create(QNameModule.create(parseNamespace(namespace), revision), localName);
+        return create(QNameModule.create(XMLNamespace.of(namespace), revision), localName);
     }
 
     /**
@@ -175,7 +190,7 @@ public final class QName implements Immutable, Serializable, Comparable<QName>,
      *         to {@code YYYY-mm-dd}.
      */
     public static @NonNull QName create(final String namespace, final String revision, final String localName) {
-        return create(parseNamespace(namespace), Revision.of(revision), localName);
+        return create(XMLNamespace.of(namespace), Revision.of(revision), localName);
     }
 
     /**
@@ -188,7 +203,7 @@ public final class QName implements Immutable, Serializable, Comparable<QName>,
      * @throws IllegalArgumentException If {@code namespace} is not valid URI.
      */
     public static @NonNull QName create(final String namespace, final String localName) {
-        return create(parseNamespace(namespace), localName);
+        return create(XMLNamespace.of(namespace), localName);
     }
 
     /**
@@ -200,7 +215,7 @@ public final class QName implements Immutable, Serializable, Comparable<QName>,
      * @throws NullPointerException If any of parameters is null.
      * @throws IllegalArgumentException If <code>namespace</code> is not valid URI.
      */
-    public static @NonNull QName create(final URI namespace, final String localName) {
+    public static @NonNull QName create(final XMLNamespace namespace, final String localName) {
         return new QName(namespace, localName);
     }
 
@@ -211,11 +226,32 @@ public final class QName implements Immutable, Serializable, Comparable<QName>,
      * @return A QName instance
      * @throws IOException if I/O error occurs
      */
-    public static QName readFrom(final DataInput in) throws IOException {
+    public static @NonNull QName readFrom(final DataInput in) throws IOException {
+        if (in instanceof QNameAwareDataInput) {
+            return ((QNameAwareDataInput) in).readQName();
+        }
+
         final QNameModule module = QNameModule.readFrom(in);
         return new QName(module, checkLocalName(in.readUTF()));
     }
 
+    /**
+     * Creates new QName composed of specified module and local name. This method does not perform lexical checking of
+     * localName, and it is the caller's responsibility to performs these checks.
+     *
+     * <p>
+     * When in doubt, use {@link #create(QNameModule, String)} instead.
+     *
+     * @param qnameModule Namespace and revision enclosed as a QNameModule
+     * @param localName Local name part of QName, required to have been validated
+     * @return Instance of QName
+     * @throws NullPointerException if any of the arguments is null
+     */
+    @Beta
+    public static @NonNull QName unsafeOf(final @NonNull QNameModule qnameModule, final @NonNull String localName) {
+        return new QName(qnameModule, localName);
+    }
+
     /**
      * Get the module component of the QName.
      *
@@ -230,21 +266,10 @@ public final class QName implements Immutable, Serializable, Comparable<QName>,
      *
      * @return XMLNamespace assigned to the YANG module.
      */
-    public @NonNull URI getNamespace() {
+    public @NonNull XMLNamespace getNamespace() {
         return module.getNamespace();
     }
 
-    /**
-     * Returns YANG schema identifier which were defined for this node in the
-     * YANG module.
-     *
-     * @return YANG schema identifier which were defined for this node in the
-     *         YANG module
-     */
-    public @NonNull String getLocalName() {
-        return localName;
-    }
-
     /**
      * Returns revision of the YANG module if the module has defined revision.
      *
@@ -254,11 +279,7 @@ public final class QName implements Immutable, Serializable, Comparable<QName>,
         return module.getRevision();
     }
 
-    /**
-     * Return an interned reference to a equivalent QName.
-     *
-     * @return Interned reference, or this object if it was interned.
-     */
+    @Override
     public @NonNull QName intern() {
         // We also want to make sure we keep the QNameModule cached
         final QNameModule cacheMod = module.intern();
@@ -266,7 +287,7 @@ public final class QName implements Immutable, Serializable, Comparable<QName>,
         // Identity comparison is here on purpose, as we are deciding whether to potentially store 'qname' into the
         // interner. It is important that it does not hold user-supplied reference (such a String instance from
         // parsing of an XML document).
-        final QName template = cacheMod == module ? this : new QName(cacheMod, localName.intern());
+        final QName template = cacheMod == module ? this : new QName(cacheMod, getLocalName().intern());
 
         return INTERNER.intern(template);
     }
@@ -274,7 +295,7 @@ public final class QName implements Immutable, Serializable, Comparable<QName>,
     @Override
     public int hashCode() {
         if (hash == 0) {
-            hash = Objects.hash(module, localName);
+            hash = Objects.hash(module, getLocalName());
         }
         return hash;
     }
@@ -296,40 +317,22 @@ public final class QName implements Immutable, Serializable, Comparable<QName>,
             return false;
         }
         final QName other = (QName) obj;
-        return Objects.equals(localName, other.localName) && module.equals(other.module);
-    }
-
-    private static @NonNull URI parseNamespace(final String namespace) {
-        try {
-            return new URI(namespace);
-        } catch (final URISyntaxException ue) {
-            throw new IllegalArgumentException("Namespace '" + namespace + "' is not a valid URI", ue);
-        }
+        return Objects.equals(getLocalName(), other.getLocalName()) && module.equals(other.module);
     }
 
     @Override
     public @NonNull String toString() {
-        final StringBuilder sb = new StringBuilder();
-        if (getNamespace() != null) {
-            sb.append(QNAME_LEFT_PARENTHESIS).append(getNamespace());
-
-            final Optional<Revision> rev = getRevision();
-            if (rev.isPresent()) {
-                sb.append(QNAME_REVISION_DELIMITER).append(rev.get());
-            }
-            sb.append(QNAME_RIGHT_PARENTHESIS);
+        final StringBuilder sb = new StringBuilder().append(QNAME_LEFT_PARENTHESIS).append(getNamespace());
+        final Optional<Revision> rev = getRevision();
+        if (rev.isPresent()) {
+            sb.append(QNAME_REVISION_DELIMITER).append(rev.get());
         }
-        return sb.append(localName).toString();
+        return sb.append(QNAME_RIGHT_PARENTHESIS).append(getLocalName()).toString();
     }
 
-    /**
-     * Returns a QName with the specified QNameModule and the same localname as this one.
-     *
-     * @param newModule New QNameModule to use
-     * @return a QName with specified QNameModule and same local name as this one
-     */
-    public @NonNull QName withModule(final QNameModule newModule) {
-        return new QName(newModule, localName);
+    @Override
+    public @NonNull QName bindTo(final QNameModule namespace) {
+        return module.equals(namespace) ? this : super.bindTo(namespace);
     }
 
     /**
@@ -340,7 +343,7 @@ public final class QName implements Immutable, Serializable, Comparable<QName>,
      */
     public @NonNull QName withoutRevision() {
         final QNameModule newModule;
-        return (newModule = module.withoutRevision()) == module ? this : new QName(newModule, localName);
+        return (newModule = module.withoutRevision()) == module ? this : new QName(newModule, getLocalName());
     }
 
     /**
@@ -369,7 +372,7 @@ public final class QName implements Immutable, Serializable, Comparable<QName>,
      * @throws NullPointerException if {@code other} is null.
      */
     public boolean isEqualWithoutRevision(final QName other) {
-        return localName.equals(other.getLocalName()) && Objects.equals(getNamespace(), other.getNamespace());
+        return getLocalName().equals(other.getLocalName()) && Objects.equals(getNamespace(), other.getNamespace());
     }
 
     // FIXME: this comparison function looks odd. We are sorting first by local name and then by module? What is
@@ -378,7 +381,7 @@ public final class QName implements Immutable, Serializable, Comparable<QName>,
     @SuppressWarnings("checkstyle:parameterName")
     public int compareTo(final QName o) {
         // compare mandatory localName parameter
-        int result = localName.compareTo(o.localName);
+        int result = getLocalName().compareTo(o.getLocalName());
         if (result != 0) {
             return result;
         }
@@ -387,10 +390,15 @@ public final class QName implements Immutable, Serializable, Comparable<QName>,
 
     @Override
     public void writeTo(final DataOutput out) throws IOException {
-        module.writeTo(out);
-        out.writeUTF(localName);
+        if (out instanceof QNameAwareDataOutput) {
+            ((QNameAwareDataOutput) out).writeQName(this);
+        } else {
+            module.writeTo(out);
+            out.writeUTF(getLocalName());
+        }
     }
 
+    @Override
     Object writeReplace() {
         return new QNv1(this);
     }