2 * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.yangtools.yang.common;
10 import static org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil.getRevisionFormat;
12 import com.google.common.base.Preconditions;
13 import com.google.common.collect.Interner;
14 import com.google.common.collect.Interners;
15 import java.io.Serializable;
17 import java.net.URISyntaxException;
18 import java.text.ParseException;
19 import java.util.Date;
20 import java.util.Objects;
21 import java.util.regex.Matcher;
22 import java.util.regex.Pattern;
23 import javax.annotation.Nonnull;
24 import javax.annotation.RegEx;
25 import org.opendaylight.yangtools.concepts.Immutable;
28 * The QName from XML consists of local name of element and XML namespace, but
29 * for our use, we added module revision to it.
31 * In YANG context QName is full name of defined node, type, procedure or
32 * notification. QName consists of XML namespace, YANG model revision and local
33 * name of defined type. It is used to prevent name clashes between nodes with
34 * same local name, but from different schemas.
37 * <li><b>XMLNamespace</b> - {@link #getNamespace()} - the namespace assigned to the YANG module which
38 * defined element, type, procedure or notification.</li>
39 * <li><b>Revision</b> - {@link #getRevision()} - the revision of the YANG module which describes the
41 * <li><b>LocalName</b> - {@link #getLocalName()} - the YANG schema identifier which were defined for this
42 * node in the YANG module</li>
45 * QName may also have <code>prefix</code> assigned, but prefix does not
46 * affect equality and identity of two QNames and carry only information
47 * which may be useful for serializers / deserializers.
51 public final class QName implements Immutable, Serializable, Comparable<QName> {
52 private static final Interner<QName> INTERNER = Interners.newWeakInterner();
53 private static final long serialVersionUID = 5398411242927766414L;
55 static final String QNAME_REVISION_DELIMITER = "?revision=";
56 static final String QNAME_LEFT_PARENTHESIS = "(";
57 static final String QNAME_RIGHT_PARENTHESIS = ")";
60 private static final String QNAME_STRING_FULL = "^\\((.+)\\?revision=(.+)\\)(.+)$";
61 private static final Pattern QNAME_PATTERN_FULL = Pattern.compile(QNAME_STRING_FULL);
64 private static final String QNAME_STRING_NO_REVISION = "^\\((.+)\\)(.+)$";
65 private static final Pattern QNAME_PATTERN_NO_REVISION = Pattern.compile(QNAME_STRING_NO_REVISION);
68 private static final String QNAME_STRING_NO_NAMESPACE_NO_REVISION = "^(.+)$";
69 private static final Pattern QNAME_PATTERN_NO_NAMESPACE_NO_REVISION =
70 Pattern.compile(QNAME_STRING_NO_NAMESPACE_NO_REVISION);
72 private static final char[] ILLEGAL_CHARACTERS = { '?', '(', ')', '&', ':' };
75 private final QNameModule module;
77 private final String localName;
78 private transient int hash;
80 private QName(final QNameModule module, final String localName) {
81 this.localName = checkLocalName(localName);
89 * the namespace assigned to the YANG module
91 * YANG schema identifier
93 public QName(final URI namespace, final String localName) {
94 this(QNameModule.create(namespace, null), localName);
97 private static String checkLocalName(final String localName) {
98 Preconditions.checkArgument(localName != null, "Parameter 'localName' may not be null.");
99 Preconditions.checkArgument(!localName.isEmpty(), "Parameter 'localName' must be a non-empty string.");
101 for (final char c : ILLEGAL_CHARACTERS) {
102 if (localName.indexOf(c) != -1) {
103 throw new IllegalArgumentException("Parameter 'localName':'" + localName
104 + "' contains illegal character '" + c + "'");
110 public static QName create(final String input) {
111 Matcher matcher = QNAME_PATTERN_FULL.matcher(input);
112 if (matcher.matches()) {
113 final String namespace = matcher.group(1);
114 final String revision = matcher.group(2);
115 final String localName = matcher.group(3);
116 return create(namespace, revision, localName);
118 matcher = QNAME_PATTERN_NO_REVISION.matcher(input);
119 if (matcher.matches()) {
120 final URI namespace = URI.create(matcher.group(1));
121 final String localName = matcher.group(2);
122 return new QName(namespace, localName);
124 matcher = QNAME_PATTERN_NO_NAMESPACE_NO_REVISION.matcher(input);
125 if (matcher.matches()) {
126 final String localName = matcher.group(1);
127 return new QName((URI) null, localName);
129 throw new IllegalArgumentException("Invalid input:" + input);
133 * Get the module component of the QName.
135 * @return Module component
137 public QNameModule getModule() {
142 * Returns XMLNamespace assigned to the YANG module.
144 * @return XMLNamespace assigned to the YANG module.
146 public URI getNamespace() {
147 return module.getNamespace();
151 * Returns YANG schema identifier which were defined for this node in the
154 * @return YANG schema identifier which were defined for this node in the
157 public String getLocalName() {
162 * Returns revision of the YANG module if the module has defined revision,
163 * otherwise returns <code>null</code>
165 * @return revision of the YANG module if the module has defined revision,
166 * otherwise returns <code>null</code>
168 public Date getRevision() {
169 return module.getRevision();
173 * Return an interned reference to a equivalent QName.
175 * @return Interned reference, or this object if it was interned.
177 public QName intern() {
178 // We also want to make sure we keep the QNameModule cached
179 final QNameModule cacheMod = module.intern();
181 // Identity comparison is here on purpose, as we are deciding whether to potentially store 'qname' into the
182 // interner. It is important that it does not hold user-supplied reference (such a String instance from
183 // parsing of an XML document).
184 final QName template = cacheMod == module ? this : QName.create(cacheMod, localName.intern());
186 return INTERNER.intern(template);
190 public int hashCode() {
192 hash = Objects.hash(module, localName);
199 * Compares the specified object with this list for equality. Returns
200 * <tt>true</tt> if and only if the specified object is also instance of
201 * {@link QName} and its {@link #getLocalName()}, {@link #getNamespace()} and
202 * {@link #getRevision()} are equals to same properties of this instance.
204 * @param obj the object to be compared for equality with this QName
205 * @return <tt>true</tt> if the specified object is equal to this QName
209 public boolean equals(final Object obj) {
213 if (!(obj instanceof QName)) {
216 final QName other = (QName) obj;
217 return Objects.equals(localName, other.localName) && module.equals(other.module);
220 public static QName create(final QName base, final String localName) {
221 return create(base.getModule(), localName);
228 * Namespace and revision enclosed as a QNameModule
230 * Local name part of QName. MUST NOT BE null.
231 * @return Instance of QName
233 public static QName create(final QNameModule qnameModule, final String localName) {
234 return new QName(Preconditions.checkNotNull(qnameModule,"module may not be null"), localName);
241 * Namespace of QName or null if namespace is undefined.
243 * Revision of namespace or null if revision is unspecified.
245 * Local name part of QName. MUST NOT BE null.
246 * @return Instance of QName
248 public static QName create(final URI namespace, final Date revision, final String localName) {
249 return create(QNameModule.create(namespace, revision), localName);
256 * Namespace of QName or null if namespace is undefined.
258 * Revision of namespace or null if revision is unspecified.
260 * Local name part of QName. MUST NOT BE null.
261 * @return Instance of QName
263 public static QName create(final String namespace, final String localName, final Date revision) {
264 final URI namespaceUri = parseNamespace(namespace);
265 return create(QNameModule.create(namespaceUri, revision), localName);
272 * Namespace of QName, MUST NOT BE Null.
274 * Revision of namespace / YANG module. MUST NOT BE null, MUST BE
275 * in format <code>YYYY-mm-dd</code>.
277 * Local name part of QName. MUST NOT BE null.
278 * @return A new QName
279 * @throws NullPointerException
280 * If any of parameters is null.
281 * @throws IllegalArgumentException
282 * If <code>namespace</code> is not valid URI or
283 * <code>revision</code> is not according to format
284 * <code>YYYY-mm-dd</code>.
286 public static QName create(final String namespace, final String revision, final String localName) {
287 final URI namespaceUri = parseNamespace(namespace);
288 final Date revisionDate = parseRevision(revision);
289 return create(namespaceUri, revisionDate, localName);
292 private static URI parseNamespace(final String namespace) {
294 return new URI(namespace);
295 } catch (final URISyntaxException ue) {
296 throw new IllegalArgumentException("Namespace '" + namespace + "' is not a valid URI", ue);
304 * Namespace of QName, MUST NOT BE Null.
306 * Local name part of QName. MUST NOT BE null.
307 * @return A new QName
308 * @throws NullPointerException
309 * If any of parameters is null.
310 * @throws IllegalArgumentException
311 * If <code>namespace</code> is not valid URI.
313 public static QName create(final String namespace, final String localName) {
314 return create(parseNamespace(namespace), null, localName);
318 public String toString() {
319 final StringBuilder sb = new StringBuilder();
320 if (getNamespace() != null) {
321 sb.append(QNAME_LEFT_PARENTHESIS).append(getNamespace());
323 if (getFormattedRevision() != null) {
324 sb.append(QNAME_REVISION_DELIMITER).append(getFormattedRevision());
326 sb.append(QNAME_RIGHT_PARENTHESIS);
328 sb.append(localName);
329 return sb.toString();
333 * Return string representation of revision in format
334 * <code>YYYY-mm-dd</code>
336 * YANG Specification defines format for <code>revision</code> as
337 * YYYY-mm-dd. This format for revision is reused accross multiple places
338 * such as capabilities URI, YANG modules, etc.
340 * @return String representation of revision or null, if revision is not
343 public String getFormattedRevision() {
344 return module.getFormattedRevision();
348 * Creates copy of this with revision and prefix unset.
349 * Returns a QName with the specified QNameModule and the same localname as this one.
351 * @param newModule New QNameModule to use
352 * @return a QName with specified QNameModule and same local name as this one
354 public QName withModule(@Nonnull final QNameModule newModule) {
355 return new QName(newModule, localName);
359 * Returns a QName with the same namespace and local name, but with no revision. If this QName does not have
360 * a Revision, this object is retured.
362 * @return copy of this QName with revision and prefix unset.
364 public QName withoutRevision() {
365 return create(getNamespace(), null, localName);
368 public static Date parseRevision(final String formatedDate) {
370 return getRevisionFormat().parse(formatedDate);
371 } catch (ParseException | RuntimeException e) {
372 throw new IllegalArgumentException(
373 String.format("Revision '%s'is not in a supported format", formatedDate), e);
378 * Formats {@link Date} representing revision to format
379 * <code>YYYY-mm-dd</code>
381 * YANG Specification defines format for <code>revision</code> as
382 * YYYY-mm-dd. This format for revision is reused accross multiple places
383 * such as capabilities URI, YANG modules, etc.
386 * Date object to format or null
387 * @return String representation or null if the input was null.
389 public static String formattedRevision(final Date revision) {
390 if (revision == null) {
393 return getRevisionFormat().format(revision);
398 * Compares this QName to other, without comparing revision.
400 * Compares instance of this to other instance of QName and returns true if
401 * both instances have equal <code>localName</code> ({@link #getLocalName()}
402 * ) and <code>namespace</code> ({@link #getNamespace()}).
405 * Other QName. Must not be null.
406 * @return true if this instance and other have equals localName and
408 * @throws NullPointerException
409 * if <code>other</code> is null.
411 public boolean isEqualWithoutRevision(final QName other) {
412 return localName.equals(other.getLocalName()) && Objects.equals(getNamespace(), other.getNamespace());
416 public int compareTo(@Nonnull final QName other) {
417 // compare mandatory localName parameter
418 int result = localName.compareTo(other.localName);
423 // compare nullable namespace parameter
424 if (getNamespace() == null) {
425 if (other.getNamespace() != null) {
429 if (other.getNamespace() == null) {
432 result = getNamespace().compareTo(other.getNamespace());
438 // compare nullable revision parameter
439 if (getRevision() == null) {
440 if (other.getRevision() != null) {
444 if (other.getRevision() == null) {
447 result = getRevision().compareTo(other.getRevision());