Fix eclipse/checkstyle warnings
[yangtools.git] / yang / yang-common / src / main / java / org / opendaylight / yangtools / yang / common / QName.java
1 /*
2  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.yangtools.yang.common;
9
10 import static org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil.getRevisionFormat;
11
12 import com.google.common.base.Preconditions;
13 import com.google.common.base.Strings;
14 import com.google.common.collect.Interner;
15 import com.google.common.collect.Interners;
16 import java.io.Serializable;
17 import java.net.URI;
18 import java.net.URISyntaxException;
19 import java.text.ParseException;
20 import java.util.Date;
21 import java.util.Objects;
22 import java.util.regex.Matcher;
23 import java.util.regex.Pattern;
24 import javax.annotation.Nonnull;
25 import javax.annotation.RegEx;
26 import org.opendaylight.yangtools.concepts.Immutable;
27
28 /**
29  * The QName from XML consists of local name of element and XML namespace, but
30  * for our use, we added module revision to it.
31  *
32  * <p>
33  * In YANG context QName is full name of defined node, type, procedure or
34  * notification. QName consists of XML namespace, YANG model revision and local
35  * name of defined type. It is used to prevent name clashes between nodes with
36  * same local name, but from different schemas.
37  *
38  * <ul>
39  * <li><b>XMLNamespace</b> - {@link #getNamespace()} - the namespace assigned to the YANG module which
40  * defined element, type, procedure or notification.</li>
41  * <li><b>Revision</b> - {@link #getRevision()} - the revision of the YANG module which describes the
42  * element</li>
43  * <li><b>LocalName</b> - {@link #getLocalName()} - the YANG schema identifier which were defined for this
44  * node in the YANG module</li>
45  * </ul>
46  *
47  * <p>
48  * QName may also have <code>prefix</code> assigned, but prefix does not
49  * affect equality and identity of two QNames and carry only information
50  * which may be useful for serializers / deserializers.
51  */
52 public final class QName implements Immutable, Serializable, Comparable<QName> {
53     private static final Interner<QName> INTERNER = Interners.newWeakInterner();
54     private static final long serialVersionUID = 5398411242927766414L;
55
56     static final String QNAME_REVISION_DELIMITER = "?revision=";
57     static final String QNAME_LEFT_PARENTHESIS = "(";
58     static final String QNAME_RIGHT_PARENTHESIS = ")";
59
60     @RegEx
61     private static final String QNAME_STRING_FULL = "^\\((.+)\\?revision=(.+)\\)(.+)$";
62     private static final Pattern QNAME_PATTERN_FULL = Pattern.compile(QNAME_STRING_FULL);
63
64     @RegEx
65     private static final String QNAME_STRING_NO_REVISION = "^\\((.+)\\)(.+)$";
66     private static final Pattern QNAME_PATTERN_NO_REVISION = Pattern.compile(QNAME_STRING_NO_REVISION);
67
68     @RegEx
69     private static final String QNAME_STRING_NO_NAMESPACE_NO_REVISION = "^(.+)$";
70     private static final Pattern QNAME_PATTERN_NO_NAMESPACE_NO_REVISION =
71         Pattern.compile(QNAME_STRING_NO_NAMESPACE_NO_REVISION);
72
73     private static final char[] ILLEGAL_CHARACTERS = new char[] { '?', '(', ')', '&', ':' };
74
75     // Non-null
76     private final QNameModule module;
77     // Non-null
78     private final String localName;
79     private transient int hash;
80
81     private QName(final QNameModule module, final String localName) {
82         this.localName = checkLocalName(localName);
83         this.module = module;
84     }
85
86     /**
87      * QName Constructor.
88      *
89      * @param namespace
90      *            the namespace assigned to the YANG module
91      * @param localName
92      *            YANG schema identifier
93      */
94     public QName(final URI namespace, final String localName) {
95         this(QNameModule.create(namespace, null), localName);
96     }
97
98     private static String checkLocalName(final String localName) {
99         Preconditions.checkArgument(localName != null, "Parameter 'localName' may not be null.");
100         Preconditions.checkArgument(!Strings.isNullOrEmpty(localName),
101                 "Parameter 'localName' must be a non-empty string.");
102
103         for (final char c : ILLEGAL_CHARACTERS) {
104             if (localName.indexOf(c) != -1) {
105                 throw new IllegalArgumentException(String.format(
106                         "Parameter 'localName':'%s' contains illegal character '%s'", localName, c));
107             }
108         }
109         return localName;
110     }
111
112     public static QName create(final String input) {
113         Matcher matcher = QNAME_PATTERN_FULL.matcher(input);
114         if (matcher.matches()) {
115             final String namespace = matcher.group(1);
116             final String revision = matcher.group(2);
117             final String localName = matcher.group(3);
118             return create(namespace, revision, localName);
119         }
120         matcher = QNAME_PATTERN_NO_REVISION.matcher(input);
121         if (matcher.matches()) {
122             final URI namespace = URI.create(matcher.group(1));
123             final String localName = matcher.group(2);
124             return new QName(namespace, localName);
125         }
126         matcher = QNAME_PATTERN_NO_NAMESPACE_NO_REVISION.matcher(input);
127         if (matcher.matches()) {
128             final String localName = matcher.group(1);
129             return new QName((URI) null, localName);
130         }
131         throw new IllegalArgumentException("Invalid input:" + input);
132     }
133
134     public static QName create(final QName base, final String localName) {
135         return create(base.getModule(), localName);
136     }
137
138     /**
139      * Creates new QName.
140      *
141      * @param qnameModule
142      *            Namespace and revision enclosed as a QNameModule
143      * @param localName
144      *            Local name part of QName. MUST NOT BE null.
145      * @return Instance of QName
146      */
147     public static QName create(final QNameModule qnameModule, final String localName) {
148         return new QName(Preconditions.checkNotNull(qnameModule,"module may not be null"), localName);
149     }
150
151     /**
152      * Creates new QName.
153      *
154      * @param namespace
155      *            Namespace of QName or null if namespace is undefined.
156      * @param revision
157      *            Revision of namespace or null if revision is unspecified.
158      * @param localName
159      *            Local name part of QName. MUST NOT BE null.
160      * @return Instance of QName
161      */
162     public static QName create(final URI namespace, final Date revision, final String localName) {
163         return create(QNameModule.create(namespace, revision), localName);
164     }
165
166     /**
167      * Creates new QName.
168      *
169      * @param namespace
170      *            Namespace of QName or null if namespace is undefined.
171      * @param revision
172      *            Revision of namespace or null if revision is unspecified.
173      * @param localName
174      *            Local name part of QName. MUST NOT BE null.
175      * @return Instance of QName
176      */
177     public static QName create(final String namespace, final String localName, final Date revision) {
178         final URI namespaceUri = parseNamespace(namespace);
179         return create(QNameModule.create(namespaceUri, revision), localName);
180     }
181
182     /**
183      * Creates new QName.
184      *
185      * @param namespace
186      *            Namespace of QName, MUST NOT BE Null.
187      * @param revision
188      *            Revision of namespace / YANG module. MUST NOT BE null, MUST BE
189      *            in format <code>YYYY-mm-dd</code>.
190      * @param localName
191      *            Local name part of QName. MUST NOT BE null.
192      * @return A new QName
193      * @throws NullPointerException
194      *             If any of parameters is null.
195      * @throws IllegalArgumentException
196      *             If <code>namespace</code> is not valid URI or
197      *             <code>revision</code> is not according to format
198      *             <code>YYYY-mm-dd</code>.
199      */
200     public static QName create(final String namespace, final String revision, final String localName) {
201         final URI namespaceUri = parseNamespace(namespace);
202         final Date revisionDate = parseRevision(revision);
203         return create(namespaceUri, revisionDate, localName);
204     }
205
206     /**
207      * Creates new QName.
208      *
209      * @param namespace
210      *            Namespace of QName, MUST NOT BE Null.
211      * @param localName
212      *            Local name part of QName. MUST NOT BE null.
213      * @return A new QName
214      * @throws NullPointerException
215      *             If any of parameters is null.
216      * @throws IllegalArgumentException
217      *             If <code>namespace</code> is not valid URI.
218      */
219     public static QName create(final String namespace, final String localName) {
220         return create(parseNamespace(namespace), null, localName);
221     }
222
223     /**
224      * Get the module component of the QName.
225      *
226      * @return Module component
227      */
228     public QNameModule getModule() {
229         return module;
230     }
231
232     /**
233      * Returns XMLNamespace assigned to the YANG module.
234      *
235      * @return XMLNamespace assigned to the YANG module.
236      */
237     public URI getNamespace() {
238         return module.getNamespace();
239     }
240
241     /**
242      * Returns YANG schema identifier which were defined for this node in the
243      * YANG module.
244      *
245      * @return YANG schema identifier which were defined for this node in the
246      *         YANG module
247      */
248     public String getLocalName() {
249         return localName;
250     }
251
252     /**
253      * Returns revision of the YANG module if the module has defined revision,
254      * otherwise returns <code>null</code>.
255      *
256      * @return revision of the YANG module if the module has defined revision,
257      *         otherwise returns <code>null</code>
258      */
259     public Date getRevision() {
260         return module.getRevision();
261     }
262
263     /**
264      * Return an interned reference to a equivalent QName.
265      *
266      * @return Interned reference, or this object if it was interned.
267      */
268     public QName intern() {
269         // We also want to make sure we keep the QNameModule cached
270         final QNameModule cacheMod = module.intern();
271
272         // Identity comparison is here on purpose, as we are deciding whether to potentially store 'qname' into the
273         // interner. It is important that it does not hold user-supplied reference (such a String instance from
274         // parsing of an XML document).
275         final QName template = cacheMod == module ? this : QName.create(cacheMod, localName.intern());
276
277         return INTERNER.intern(template);
278     }
279
280     @Override
281     public int hashCode() {
282         if (hash == 0) {
283             hash = Objects.hash(module, localName);
284         }
285         return hash;
286     }
287
288     /**
289      * Compares the specified object with this list for equality.  Returns
290      * <tt>true</tt> if and only if the specified object is also instance of
291      * {@link QName} and its {@link #getLocalName()}, {@link #getNamespace()} and
292      * {@link #getRevision()} are equals to same properties of this instance.
293      *
294      * @param obj the object to be compared for equality with this QName
295      * @return <tt>true</tt> if the specified object is equal to this QName
296      *
297      */
298     @Override
299     public boolean equals(final Object obj) {
300         if (this == obj) {
301             return true;
302         }
303         if (!(obj instanceof QName)) {
304             return false;
305         }
306         final QName other = (QName) obj;
307         return Objects.equals(localName, other.localName) && module.equals(other.module);
308     }
309
310     private static URI parseNamespace(final String namespace) {
311         try {
312             return new URI(namespace);
313         } catch (final URISyntaxException ue) {
314             throw new IllegalArgumentException(String.format("Namespace '%s' is not a valid URI", namespace), ue);
315         }
316     }
317
318     @Override
319     public String toString() {
320         final StringBuilder sb = new StringBuilder();
321         if (getNamespace() != null) {
322             sb.append(QNAME_LEFT_PARENTHESIS).append(getNamespace());
323
324             if (getFormattedRevision() != null) {
325                 sb.append(QNAME_REVISION_DELIMITER).append(getFormattedRevision());
326             }
327             sb.append(QNAME_RIGHT_PARENTHESIS);
328         }
329         sb.append(localName);
330         return sb.toString();
331     }
332
333     /**
334      * Return string representation of revision in format <code>YYYY-mm-dd</code>
335      *
336      * <p>
337      * YANG Specification defines format for <code>revision</code> as
338      * YYYY-mm-dd. This format for revision is reused accross multiple places
339      * such as capabilities URI, YANG modules, etc.
340      *
341      * @return String representation of revision or null, if revision is not
342      *         set.
343      */
344     public String getFormattedRevision() {
345         return module.getFormattedRevision();
346     }
347
348     /**
349      * Creates copy of this with revision and prefix unset.
350      *
351      * @return copy of this QName with revision and prefix unset.
352      */
353     public QName withoutRevision() {
354         return create(getNamespace(), null, localName);
355     }
356
357     @SuppressWarnings("checkstyle:illegalCatch")
358     public static Date parseRevision(final String formatedDate) {
359         try {
360             return getRevisionFormat().parse(formatedDate);
361         } catch (ParseException | RuntimeException e) {
362             throw new IllegalArgumentException(
363                     String.format("Revision '%s'is not in a supported format", formatedDate), e);
364         }
365     }
366
367     /**
368      * Formats {@link Date} representing revision to format
369      * <code>YYYY-mm-dd</code>
370      *
371      * <p>
372      * YANG Specification defines format for <code>revision</code> as
373      * YYYY-mm-dd. This format for revision is reused accross multiple places
374      * such as capabilities URI, YANG modules, etc.
375      *
376      * @param revision
377      *            Date object to format or null
378      * @return String representation or null if the input was null.
379      */
380     public static String formattedRevision(final Date revision) {
381         if (revision == null) {
382             return null;
383         }
384         return getRevisionFormat().format(revision);
385     }
386
387     /**
388      * Compares this QName to other, without comparing revision.
389      *
390      * <p>
391      * Compares instance of this to other instance of QName and returns true if
392      * both instances have equal <code>localName</code> ({@link #getLocalName()}
393      * ) and <code>namespace</code> ({@link #getNamespace()}).
394      *
395      * @param other
396      *            Other QName. Must not be null.
397      * @return true if this instance and other have equals localName and
398      *         namespace.
399      * @throws NullPointerException
400      *             if <code>other</code> is null.
401      */
402     public boolean isEqualWithoutRevision(final QName other) {
403         return localName.equals(other.getLocalName()) && Objects.equals(getNamespace(), other.getNamespace());
404     }
405
406     @Override
407     public int compareTo(@Nonnull final QName other) {
408         // compare mandatory localName parameter
409         int result = localName.compareTo(other.localName);
410         if (result != 0) {
411             return result;
412         }
413
414         // compare nullable namespace parameter
415         if (getNamespace() == null) {
416             if (other.getNamespace() != null) {
417                 return -1;
418             }
419         } else {
420             if (other.getNamespace() == null) {
421                 return 1;
422             }
423             result = getNamespace().compareTo(other.getNamespace());
424             if (result != 0) {
425                 return result;
426             }
427         }
428
429         // compare nullable revision parameter
430         if (getRevision() == null) {
431             if (other.getRevision() != null) {
432                 return -1;
433             }
434         } else {
435             if (other.getRevision() == null) {
436                 return 1;
437             }
438             result = getRevision().compareTo(other.getRevision());
439             if (result != 0) {
440                 return result;
441             }
442         }
443
444         return result;
445     }
446
447 }