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