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