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=9d8a5e8e43c6f92a7516f89bc6bc828ecb54c43d;hb=7fedd460044be8d15547f65b713ecbeec0f74296;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..9d8a5e8e43 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. + * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.Fpre * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, @@ -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; +import org.opendaylight.yangtools.objcache.ObjectCache; +import org.opendaylight.yangtools.objcache.ObjectCacheFactory; /** * The QName from XML consists of local name of element and XML namespace, but @@ -31,98 +30,63 @@ 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 ObjectCache CACHE = ObjectCacheFactory.getObjectCache(QName.class); 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("^(.+)$"); + private static final char[] ILLEGAL_CHARACTERS = new char[] { '?', '(', ')', '&' }; - //Nullable - private final URI namespace; - //Mandatory + // Mandatory + private final QNameModule module; + // Mandatory private final String localName; - //Nullable - private final String prefix; - //Nullable - private final String formattedRevision; - //Nullable - private final Date revision; - /** - * QName Constructor. - * - * @param namespace - * the namespace assigned to the YANG module - * @param revision - * the revision of the YANG module - * @param prefix - * locally defined prefix assigned to local name - * @param localName - * YANG schema identifier - */ - public QName(URI namespace, Date revision, String prefix, String localName) { + private QName(final QNameModule module, final 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; - } + this.module = module; } /** - * QName Constructor. + * Look up specified QName in the global cache and return a shared reference. * - * @param namespace - * the namespace assigned to the YANG module - * @param localName - * YANG schema identifier + * @param qname QName instance + * @return Cached instance, according to {@link ObjectCache} policy. */ - public QName(URI namespace, 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."); - } - String [] illegalSubstrings = new String[] {"?", "(", ")", "&"}; - for(String illegalSubstring: illegalSubstrings) { - if (localName.contains(illegalSubstring)) { - throw new IllegalArgumentException(String.format( - "Parameter 'localName':'%s' contains illegal sequence '%s'", - localName, illegalSubstring)); - } + public static QName cachedReference(final QName qname) { + // We also want to make sure we keep the QNameModule cached + final QNameModule myMod = qname.getModule(); + final QNameModule cacheMod = QNameModule.cachedReference(myMod); + + final QName what; + if (cacheMod == myMod) { + what = qname; + } else { + what = QName.create(cacheMod, qname.localName); } - return localName; + + return CACHE.getReference(what); } /** @@ -130,82 +94,68 @@ public final class QName implements Immutable,Serializable { * * @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); + public QName(final URI namespace, final String localName) { + this(QNameModule.create(namespace, null), 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); + 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."); } - 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; + for (final char c : ILLEGAL_CHARACTERS) { + if (localName.indexOf(c) != -1) { + throw new IllegalArgumentException(String.format( + "Parameter 'localName':'%s' contains illegal character '%s'", localName, c)); + } } + return localName; } - - 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); - String revision = matcher.group(2); - String localName = matcher.group(3); + final String namespace = matcher.group(1); + final String revision = matcher.group(2); + final String localName = matcher.group(3); return create(namespace, revision, localName); } matcher = QNAME_PATTERN_NO_REVISION.matcher(input); if (matcher.matches()) { - URI namespace = URI.create(matcher.group(1)); - String localName = matcher.group(2); + final URI namespace = URI.create(matcher.group(1)); + final String localName = matcher.group(2); return new QName(namespace, localName); } matcher = QNAME_PATTERN_NO_NAMESPACE_NO_REVISION.matcher(input); if (matcher.matches()) { - String localName = matcher.group(1); - return new QName((URI)null, localName); + final String localName = matcher.group(1); + 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,16 +177,7 @@ public final class QName implements Immutable,Serializable { * otherwise returns null */ public Date getRevision() { - return revision; - } - - /** - * Returns locally defined prefix assigned to local name - * - * @return locally defined prefix assigned to local name - */ - public String getPrefix() { - return prefix; + return module.getRevision(); } @Override @@ -244,23 +185,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 obj 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) { - return false; - } - if (getClass() != obj.getClass()) { + if (!(obj instanceof QName)) { return false; } - QName other = (QName) obj; + final QName other = (QName) obj; if (localName == null) { if (other.localName != null) { return false; @@ -268,51 +216,102 @@ 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 create(base.getModule(), 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 localName + * Local name part of QName. MUST NOT BE null. + * @return Instance of QName + */ + public static QName create(final QNameModule qnameModule, final String localName) { + if (qnameModule == null) { + throw new NullPointerException("module may not be null"); + } + return new QName(qnameModule, localName); } - public static QName create(URI namespace, Date revision, String localName){ - return new QName(namespace, revision, 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 create(QNameModule.create(namespace, revision), localName); } + /** + * + * 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 parameters 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) { + final URI namespaceUri = parseNamespace(namespace); + final Date revisionDate = parseRevision(revision); + return create(namespaceUri, revisionDate, localName); + } - public static QName create(String namespace, String revision, String localName) throws IllegalArgumentException{ + private static URI parseNamespace(final String namespace) { 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); + return new URI(namespace); + } catch (final URISyntaxException ue) { + throw new IllegalArgumentException(String.format("Namespace '%s' is not a valid URI", namespace), ue); } } + /** + * Creates new QName. + * + * @param namespace + * Namespace of QName, MUST NOT BE Null. + * @param localName + * Local name part of QName. MUST NOT BE null. + * @return + * @throws NullPointerException + * If any of parameters is null. + * @throws IllegalArgumentException + * If namespace is not valid URI. + */ + public static QName create(final String namespace, final String localName) { + return create(parseNamespace(namespace), null, localName); + } + @Override public String toString() { - StringBuilder sb = new StringBuilder(); - if (namespace != null) { - sb.append(QNAME_LEFT_PARENTHESIS + namespace); + final StringBuilder sb = new StringBuilder(); + 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 +320,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 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); + } + + /** + * + * 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()); } - public boolean isEqualWithoutRevision(QName other) { - return localName.equals(other.getLocalName()) && Objects.equals(namespace, 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; } + }