X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=yang%2Fyang-common%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fyangtools%2Fyang%2Fcommon%2FQName.java;h=0ac89ae8da2c1d634d134bc07977877e5a9c4484;hb=27555bc40477a5db7a6d453f47a589705b2d5362;hp=faa0324ea55df1bee8b5ddddabd452537fde71cf;hpb=c0d3b660005a0c5b0ad907799431ea18cc11e771;p=yangtools.git diff --git a/yang/yang-common/src/main/java/org/opendaylight/yangtools/yang/common/QName.java b/yang/yang-common/src/main/java/org/opendaylight/yangtools/yang/common/QName.java index faa0324ea5..0ac89ae8da 100644 --- a/yang/yang-common/src/main/java/org/opendaylight/yangtools/yang/common/QName.java +++ b/yang/yang-common/src/main/java/org/opendaylight/yangtools/yang/common/QName.java @@ -7,19 +7,18 @@ */ package org.opendaylight.yangtools.yang.common; +import static org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil.getRevisionFormat; + import java.io.Serializable; import java.net.URI; import java.net.URISyntaxException; import java.text.ParseException; -import java.text.SimpleDateFormat; import java.util.Date; import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.opendaylight.yangtools.concepts.Immutable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * The QName from XML consists of local name of element and XML namespace, but @@ -31,48 +30,46 @@ import org.slf4j.LoggerFactory; * same local name, but from different schemas. * * * + * QName may also have prefix 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 { - +public final class QName implements Immutable, Serializable, Comparable { private static final long serialVersionUID = 5398411242927766414L; - protected static final Logger LOGGER = LoggerFactory.getLogger(QName.class); - - private static final ThreadLocal REVISION_FORMAT = new ThreadLocal() { - - protected SimpleDateFormat initialValue() { - return new SimpleDateFormat("yyyy-MM-dd"); - }; - - public void set(SimpleDateFormat value) { - throw new UnsupportedOperationException(); - }; - - }; static final String QNAME_REVISION_DELIMITER = "?revision="; static final String QNAME_LEFT_PARENTHESIS = "("; static final String QNAME_RIGHT_PARENTHESIS = ")"; + private static final Pattern QNAME_PATTERN_FULL = Pattern.compile("^\\((.+)\\" + QNAME_REVISION_DELIMITER + + "(.+)\\)(.+)$"); + private static final Pattern QNAME_PATTERN_NO_REVISION = Pattern.compile("^\\((.+)\\)(.+)$"); + private static final Pattern QNAME_PATTERN_NO_NAMESPACE_NO_REVISION = Pattern.compile("^(.+)$"); - //Nullable - private final URI namespace; - //Mandatory + private static final char[] ILLEGAL_CHARACTERS = new char[] { '?', '(', ')', '&' }; + + // Mandatory + private final QNameModule module; + // Mandatory private final String localName; - //Nullable + // Nullable private final String prefix; - //Nullable - private final String formattedRevision; - //Nullable - private final Date revision; + + private QName(final QNameModule module, final String prefix, final String localName) { + this.localName = checkLocalName(localName); + this.prefix = prefix; + this.module = module; + } /** * QName Constructor. @@ -85,17 +82,10 @@ public final class QName implements Immutable,Serializable { * locally defined prefix assigned to local name * @param localName * YANG schema identifier + * */ - public QName(URI namespace, Date revision, String prefix, String localName) { - this.localName = checkLocalName(localName); - this.namespace = namespace; - this.revision = revision; - this.prefix = prefix; - if(revision != null) { - this.formattedRevision = REVISION_FORMAT.get().format(revision); - } else { - this.formattedRevision = null; - } + public QName(final URI namespace, final Date revision, final String prefix, final String localName) { + this(QNameModule.create(namespace, revision), prefix, localName); } /** @@ -106,78 +96,28 @@ public final class QName implements Immutable,Serializable { * @param localName * YANG schema identifier */ - public QName(URI namespace, String localName) { + public QName(final URI namespace, final String localName) { this(namespace, null, "", localName); } - private static String checkLocalName(String localName) { - if (localName == null || localName.length() == 0) { - throw new IllegalArgumentException("Parameter 'localName' must be non empty string."); + private static String checkLocalName(final String localName) { + if (localName == null) { + throw new IllegalArgumentException("Parameter 'localName' may not be null."); + } + if (localName.length() == 0) { + throw new IllegalArgumentException("Parameter 'localName' must be a non-empty string."); } - String [] illegalSubstrings = new String[] {"?", "(", ")", "&"}; - for(String illegalSubstring: illegalSubstrings) { - if (localName.contains(illegalSubstring)) { + + for (char c : ILLEGAL_CHARACTERS) { + if (localName.indexOf(c) != -1) { throw new IllegalArgumentException(String.format( - "Parameter 'localName':'%s' contains illegal sequence '%s'", - localName, illegalSubstring)); + "Parameter 'localName':'%s' contains illegal character '%s'", localName, c)); } } return localName; } - /** - * QName Constructor. - * - * @param namespace - * the namespace assigned to the YANG module - * @param revision - * the revision of the YANG module - * @param localName - * YANG schema identifier - */ - public QName(URI namespace, Date revision, String localName) { - this(namespace, revision, null, localName); - } - - public QName(QName base, String localName) { - this(base.getNamespace(), base.getRevision(), base.getPrefix(), localName); - } - - /** - * @deprecated Use {@link #create(String)} instead. - * This implementation is broken. - */ - @Deprecated - public QName(String input) throws ParseException { - Date revision = null; - String nsAndRev = input.substring(input.indexOf("(") + 1, input.indexOf(")")); - if (nsAndRev.contains("?")) { - String[] splitted = nsAndRev.split("\\?"); - this.namespace = URI.create(splitted[0]); - revision = REVISION_FORMAT.get().parse(splitted[1]); - } else { - this.namespace = URI.create(nsAndRev); - } - - this.localName = checkLocalName(input.substring(input.indexOf(")") + 1)); - this.revision = revision; - this.prefix = null; - if (revision != null) { - this.formattedRevision = REVISION_FORMAT.get().format(revision); - } else { - this.formattedRevision = null; - } - } - - - private static Pattern QNAME_PATTERN_FULL = Pattern.compile( - "^\\((.+)\\" + QNAME_REVISION_DELIMITER + "(.+)\\)(.+)$"); - private static Pattern QNAME_PATTERN_NO_REVISION = Pattern.compile( - "^\\((.+)\\)(.+)$" ); - private static Pattern QNAME_PATTERN_NO_NAMESPACE_NO_REVISION = Pattern.compile( - "^(.+)$" ); - - public static QName create(String input) { + public static QName create(final String input) { Matcher matcher = QNAME_PATTERN_FULL.matcher(input); if (matcher.matches()) { String namespace = matcher.group(1); @@ -194,18 +134,27 @@ public final class QName implements Immutable,Serializable { matcher = QNAME_PATTERN_NO_NAMESPACE_NO_REVISION.matcher(input); if (matcher.matches()) { String localName = matcher.group(1); - return new QName((URI)null, localName); + return new QName((URI) null, localName); } throw new IllegalArgumentException("Invalid input:" + input); } + /** + * Get the module component of the QName. + * + * @return Module component + */ + public QNameModule getModule() { + return module; + } + /** * Returns XMLNamespace assigned to the YANG module. * * @return XMLNamespace assigned to the YANG module. */ public URI getNamespace() { - return namespace; + return module.getNamespace(); } /** @@ -227,7 +176,7 @@ public final class QName implements Immutable,Serializable { * otherwise returns null */ public Date getRevision() { - return revision; + return module.getRevision(); } /** @@ -244,23 +193,30 @@ public final class QName implements Immutable,Serializable { final int prime = 31; int result = 1; result = prime * result + ((localName == null) ? 0 : localName.hashCode()); - result = prime * result + ((namespace == null) ? 0 : namespace.hashCode()); - result = prime * result + ((formattedRevision == null) ? 0 : formattedRevision.hashCode()); + result = prime * result + module.hashCode(); return result; } + /** + * + * Compares the specified object with this list for equality. Returns + * true if and only if the specified object is also instance of + * {@link QName} and its {@link #getLocalName()}, {@link #getNamespace()} and + * {@link #getRevision()} are equals to same properties of this instance. + * + * @param o the object to be compared for equality with this QName + * @return true if the specified object is equal to this QName + * + */ @Override - public boolean equals(Object obj) { + public boolean equals(final Object obj) { if (this == obj) { return true; } - if (obj == null) { + if (!(obj instanceof QName)) { return false; } - if (getClass() != obj.getClass()) { - return false; - } - QName other = (QName) obj; + final QName other = (QName) obj; if (localName == null) { if (other.localName != null) { return false; @@ -268,51 +224,99 @@ public final class QName implements Immutable,Serializable { } else if (!localName.equals(other.localName)) { return false; } - if (namespace == null) { - if (other.namespace != null) { - return false; - } - } else if (!namespace.equals(other.namespace)) { - return false; - } - if (formattedRevision == null) { - if (other.formattedRevision != null) { - return false; - } - } else if (!revision.equals(other.revision)) { - return false; - } - return true; + return module.equals(other.module); } + public static QName create(final QName base, final String localName) { + return new QName(base.getModule(), base.getPrefix(), localName); + } - public static QName create(QName base, String localName){ - return new QName(base, localName); + /** + * Creates new QName. + * + * @param qnameModule + * Namespace and revision enclosed as a QNameModule + * @param prefix + * Namespace prefix + * @param localName + * Local name part of QName. MUST NOT BE null. + * @return Instance of QName + */ + public static QName create(final QNameModule module, final String prefix, final String localName) { + if (module == null) { + throw new NullPointerException("module may not be null"); + } + return new QName(module, prefix, localName); } - public static QName create(URI namespace, Date revision, String localName){ - return new QName(namespace, revision, localName); + /** + * Creates new QName. + * + * @param qnameModule + * Namespace and revision enclosed as a QNameModule + * @param localName + * Local name part of QName. MUST NOT BE null. + * @return Instance of QName + */ + public static QName create(final QNameModule qnameModule, final String localName) { + return new QName(qnameModule, null, localName); } + /** + * Creates new QName. + * + * @param namespace + * Namespace of QName or null if namespace is undefined. + * @param revision + * Revision of namespace or null if revision is unspecified. + * @param localName + * Local name part of QName. MUST NOT BE null. + * @return Instance of QName + */ + public static QName create(final URI namespace, final Date revision, final String localName) { + return new QName(QNameModule.create(namespace, revision), null, localName); + } - public static QName create(String namespace, String revision, String localName) throws IllegalArgumentException{ + /** + * + * Creates new QName. + * + * @param namespace + * Namespace of QName, MUST NOT BE Null. + * @param revision + * Revision of namespace / YANG module. MUST NOT BE null, MUST BE + * in format YYYY-mm-dd. + * @param localName + * Local name part of QName. MUST NOT BE null. + * @return + * @throws NullPointerException + * If any of paramaters is null. + * @throws IllegalArgumentException + * If namespace is not valid URI or + * revision is not according to format + * YYYY-mm-dd. + */ + public static QName create(final String namespace, final String revision, final String localName) + throws IllegalArgumentException { + final URI namespaceUri; try { - URI namespaceUri = new URI(namespace); - Date revisionDate = parseRevision(revision); - return create(namespaceUri, revisionDate, localName); - } catch (URISyntaxException ue) { - throw new IllegalArgumentException("Namespace is is not valid URI", ue); + namespaceUri = new URI(namespace); + } catch (URISyntaxException ue) { + throw new IllegalArgumentException(String.format("Namespace '%s' is not a valid URI", namespace), ue); } + + Date revisionDate = parseRevision(revision); + return create(namespaceUri, revisionDate, localName); } @Override public String toString() { StringBuilder sb = new StringBuilder(); - if (namespace != null) { - sb.append(QNAME_LEFT_PARENTHESIS + namespace); + if (getNamespace() != null) { + sb.append(QNAME_LEFT_PARENTHESIS + getNamespace()); - if (revision != null) { - sb.append(QNAME_REVISION_DELIMITER + REVISION_FORMAT.get().format(revision)); + if (getFormattedRevision() != null) { + sb.append(QNAME_REVISION_DELIMITER + getFormattedRevision()); } sb.append(QNAME_RIGHT_PARENTHESIS); } @@ -321,61 +325,115 @@ public final class QName implements Immutable,Serializable { } /** - * Returns a namespace in form defined by section 5.6.4. of {@link https - * ://tools.ietf.org/html/rfc6020}, if namespace is not correctly defined, - * the method will return null
- * example "http://example.acme.com/system?revision=2008-04-01" + * Return string representation of revision in format + * YYYY-mm-dd * - * @return namespace in form defined by section 5.6.4. of {@link https - * ://tools.ietf.org/html/rfc6020}, if namespace is not correctly - * defined, the method will return null + * YANG Specification defines format for revision as + * YYYY-mm-dd. This format for revision is reused accross multiple places + * such as capabilities URI, YANG modules, etc. * + * @return String representation of revision or null, if revision is not + * set. */ - URI getRevisionNamespace() { - - if (namespace == null) { - return null; - } - - String query = ""; - if (revision != null) { - query = "revision=" + formattedRevision; - } - - URI compositeURI = null; - try { - compositeURI = new URI(namespace.getScheme(), namespace.getUserInfo(), namespace.getHost(), - namespace.getPort(), namespace.getPath(), query, namespace.getFragment()); - } catch (URISyntaxException e) { - LOGGER.error("", e); - } - return compositeURI; - } - public String getFormattedRevision() { - return formattedRevision; + return module.getFormattedRevision(); } + /** + * Creates copy of this with revision and prefix unset. + * + * @return copy of this QName with revision and prefix unset. + */ public QName withoutRevision() { - return QName.create(namespace, null, localName); + return QName.create(getNamespace(), null, localName); } - public static Date parseRevision(String formatedDate) { + public static Date parseRevision(final String formatedDate) { try { - return REVISION_FORMAT.get().parse(formatedDate); - } catch (ParseException| RuntimeException e) { - throw new IllegalArgumentException("Revision is not in supported format:" + formatedDate,e); + return getRevisionFormat().parse(formatedDate); + } catch (ParseException | RuntimeException e) { + throw new IllegalArgumentException( + String.format("Revision '%s'is not in a supported format", formatedDate), e); } } - public static String formattedRevision(Date revision) { - if(revision == null) { + /** + * Formats {@link Date} representing revision to format + * YYYY-mm-dd + * + * YANG Specification defines format for revision as + * YYYY-mm-dd. This format for revision is reused accross multiple places + * such as capabilities URI, YANG modules, etc. + * + * @param revision + * Date object to format or null + * @return String representation or null if the input was null. + */ + public static String formattedRevision(final Date revision) { + if (revision == null) { return null; } - return REVISION_FORMAT.get().format(revision); + return getRevisionFormat().format(revision); } - public boolean isEqualWithoutRevision(QName other) { - return localName.equals(other.getLocalName()) && Objects.equals(namespace, other.getNamespace()); + /** + * + * Compares this QName to other, without comparing revision. + * + * Compares instance of this to other instance of QName and returns true if + * both instances have equal localName ({@link #getLocalName()} + * ) and namespace ({@link #getNamespace()}). + * + * @param other + * Other QName. Must not be null. + * @return true if this instance and other have equals localName and + * namespace. + * @throws NullPointerException + * if other is null. + */ + public boolean isEqualWithoutRevision(final QName other) { + return localName.equals(other.getLocalName()) && Objects.equals(getNamespace(), other.getNamespace()); } + + @Override + public int compareTo(final QName other) { + // compare mandatory localName parameter + int result = localName.compareTo(other.localName); + if (result != 0) { + return result; + } + + // compare nullable namespace parameter + if (getNamespace() == null) { + if (other.getNamespace() != null) { + return -1; + } + } else { + if (other.getNamespace() == null) { + return 1; + } + result = getNamespace().compareTo(other.getNamespace()); + if (result != 0) { + return result; + } + } + + // compare nullable revision parameter + if (getRevision() == null) { + if (other.getRevision() != null) { + return -1; + } + } else { + if (other.getRevision() == null) { + return 1; + } + result = getRevision().compareTo(other.getRevision()); + if (result != 0) { + return result; + } + } + + return result; + } + }