Merge changes Id6a27004,I7baa722d
[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 java.io.Serializable;
13 import java.net.URI;
14 import java.net.URISyntaxException;
15 import java.text.ParseException;
16 import java.util.Date;
17 import java.util.Objects;
18 import java.util.regex.Matcher;
19 import java.util.regex.Pattern;
20
21 import org.opendaylight.yangtools.concepts.Immutable;
22 import org.slf4j.Logger;
23 import org.slf4j.LoggerFactory;
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> - the namespace assigned to the YANG module which
36  * defined element, type, procedure or notification.</li>
37  * <li><b>Revision</b> - the revision of the YANG module which describes the
38  * element</li>
39  * <li><b>LocalName</b> - the YANG schema identifier which were defined for this
40  * node in the YANG module</li>
41  * </ul>
42  *
43  *
44  */
45 public final class QName implements Immutable, Serializable, Comparable<QName> {
46     private static final long serialVersionUID = 5398411242927766414L;
47     private static final Logger LOGGER = LoggerFactory.getLogger(QName.class);
48
49     static final String QNAME_REVISION_DELIMITER = "?revision=";
50     static final String QNAME_LEFT_PARENTHESIS = "(";
51     static final String QNAME_RIGHT_PARENTHESIS = ")";
52
53     private static final Pattern QNAME_PATTERN_FULL = Pattern.compile(
54             "^\\((.+)\\" + QNAME_REVISION_DELIMITER + "(.+)\\)(.+)$");
55     private static final Pattern QNAME_PATTERN_NO_REVISION = Pattern.compile(
56            "^\\((.+)\\)(.+)$");
57     private static final Pattern QNAME_PATTERN_NO_NAMESPACE_NO_REVISION = Pattern.compile(
58             "^(.+)$");
59
60     //Nullable
61     private final URI namespace;
62     //Mandatory
63     private final String localName;
64     //Nullable
65     private final String prefix;
66     //Nullable
67     private final String formattedRevision;
68     //Nullable
69     private final Date revision;
70
71     /**
72      * QName Constructor.
73      *
74      * @param namespace
75      *            the namespace assigned to the YANG module
76      * @param revision
77      *            the revision of the YANG module
78      * @param prefix
79      *            locally defined prefix assigned to local name
80      * @param localName
81      *            YANG schema identifier
82      */
83     public QName(final URI namespace, final Date revision, final String prefix, final String localName) {
84         this.localName = checkLocalName(localName);
85         this.namespace = namespace;
86         this.revision = revision;
87         this.prefix = prefix;
88         if(revision != null) {
89             this.formattedRevision = getRevisionFormat().format(revision);
90         } else {
91             this.formattedRevision = null;
92         }
93     }
94
95     /**
96      * QName Constructor.
97      *
98      * @param namespace
99      *            the namespace assigned to the YANG module
100      * @param localName
101      *            YANG schema identifier
102      */
103     public QName(final URI namespace, final String localName) {
104         this(namespace, null, "", localName);
105     }
106
107     private static String checkLocalName(final String localName) {
108         if (localName == null) {
109             throw new IllegalArgumentException("Parameter 'localName' may not be null.");
110         }
111         if (localName.length() == 0) {
112             throw new IllegalArgumentException("Parameter 'localName' must be a non-empty string.");
113         }
114         String [] illegalSubstrings = new String[] {"?", "(", ")", "&"};
115         for(String illegalSubstring: illegalSubstrings) {
116             if (localName.contains(illegalSubstring)) {
117                 throw new IllegalArgumentException(String.format(
118                         "Parameter 'localName':'%s' contains illegal sequence '%s'",
119                         localName, illegalSubstring));
120             }
121         }
122         return localName;
123     }
124
125     /**
126      * QName Constructor.
127      *
128      * @param namespace
129      *            the namespace assigned to the YANG module
130      * @param revision
131      *            the revision of the YANG module
132      * @param localName
133      *            YANG schema identifier
134      */
135     public QName(final URI namespace, final Date revision, final String localName) {
136         this(namespace, revision, null, localName);
137     }
138
139     public QName(final QName base, final String localName) {
140         this(base.getNamespace(), base.getRevision(), base.getPrefix(), localName);
141     }
142
143     /**
144      * @deprecated Use {@link #create(String)} instead.
145      * This implementation is broken.
146      */
147     @Deprecated
148     public QName(final String input) throws ParseException {
149         Date revision = null;
150         String nsAndRev = input.substring(input.indexOf("(") + 1, input.indexOf(")"));
151         if (nsAndRev.contains("?")) {
152             String[] splitted = nsAndRev.split("\\?");
153             this.namespace = URI.create(splitted[0]);
154             revision = getRevisionFormat().parse(splitted[1]);
155         } else {
156             this.namespace = URI.create(nsAndRev);
157         }
158
159         this.localName = checkLocalName(input.substring(input.indexOf(")") + 1));
160         this.revision = revision;
161         this.prefix = null;
162         if (revision != null) {
163             this.formattedRevision = getRevisionFormat().format(revision);
164         } else {
165             this.formattedRevision = null;
166         }
167     }
168
169     public static QName create(final String input) {
170         Matcher matcher = QNAME_PATTERN_FULL.matcher(input);
171         if (matcher.matches()) {
172             String namespace = matcher.group(1);
173             String revision = matcher.group(2);
174             String localName = matcher.group(3);
175             return create(namespace, revision, localName);
176         }
177         matcher = QNAME_PATTERN_NO_REVISION.matcher(input);
178         if (matcher.matches()) {
179             URI namespace = URI.create(matcher.group(1));
180             String localName = matcher.group(2);
181             return new QName(namespace, localName);
182         }
183         matcher = QNAME_PATTERN_NO_NAMESPACE_NO_REVISION.matcher(input);
184         if (matcher.matches()) {
185             String localName = matcher.group(1);
186             return new QName((URI)null, localName);
187         }
188         throw new IllegalArgumentException("Invalid input:" + input);
189     }
190
191     /**
192      * Returns XMLNamespace assigned to the YANG module.
193      *
194      * @return XMLNamespace assigned to the YANG module.
195      */
196     public URI getNamespace() {
197         return namespace;
198     }
199
200     /**
201      * Returns YANG schema identifier which were defined for this node in the
202      * YANG module
203      *
204      * @return YANG schema identifier which were defined for this node in the
205      *         YANG module
206      */
207     public String getLocalName() {
208         return localName;
209     }
210
211     /**
212      * Returns revision of the YANG module if the module has defined revision,
213      * otherwise returns <code>null</code>
214      *
215      * @return revision of the YANG module if the module has defined revision,
216      *         otherwise returns <code>null</code>
217      */
218     public Date getRevision() {
219         return revision;
220     }
221
222     /**
223      * Returns locally defined prefix assigned to local name
224      *
225      * @return locally defined prefix assigned to local name
226      */
227     public String getPrefix() {
228         return prefix;
229     }
230
231     @Override
232     public int hashCode() {
233         final int prime = 31;
234         int result = 1;
235         result = prime * result + ((localName == null) ? 0 : localName.hashCode());
236         result = prime * result + ((namespace == null) ? 0 : namespace.hashCode());
237         result = prime * result + ((formattedRevision == null) ? 0 : formattedRevision.hashCode());
238         return result;
239     }
240
241     @Override
242     public boolean equals(final Object obj) {
243         if (this == obj) {
244             return true;
245         }
246         if (obj == null) {
247             return false;
248         }
249         if (getClass() != obj.getClass()) {
250             return false;
251         }
252         QName other = (QName) obj;
253         if (localName == null) {
254             if (other.localName != null) {
255                 return false;
256             }
257         } else if (!localName.equals(other.localName)) {
258             return false;
259         }
260         if (namespace == null) {
261             if (other.namespace != null) {
262                 return false;
263             }
264         } else if (!namespace.equals(other.namespace)) {
265             return false;
266         }
267         if (formattedRevision == null) {
268             if (other.formattedRevision != null) {
269                 return false;
270             }
271         } else if (!revision.equals(other.revision)) {
272             return false;
273         }
274         return true;
275     }
276
277
278     public static QName create(final QName base, final String localName){
279         return new QName(base, localName);
280     }
281
282     public static QName create(final URI namespace, final Date revision, final String localName){
283         return new QName(namespace, revision, localName);
284     }
285
286
287     public static QName create(final String namespace, final String revision, final String localName) throws IllegalArgumentException{
288         try {
289             URI namespaceUri = new URI(namespace);
290             Date revisionDate = parseRevision(revision);
291             return create(namespaceUri, revisionDate, localName);
292         }  catch (URISyntaxException ue) {
293             throw new IllegalArgumentException("Namespace is is not valid URI", ue);
294         }
295     }
296
297     @Override
298     public String toString() {
299         StringBuilder sb = new StringBuilder();
300         if (namespace != null) {
301             sb.append(QNAME_LEFT_PARENTHESIS + namespace);
302
303             if (revision != null) {
304                 sb.append(QNAME_REVISION_DELIMITER + getRevisionFormat().format(revision));
305             }
306             sb.append(QNAME_RIGHT_PARENTHESIS);
307         }
308         sb.append(localName);
309         return sb.toString();
310     }
311
312     /**
313      * Returns a namespace in form defined by section 5.6.4. of {@link https
314      * ://tools.ietf.org/html/rfc6020}, if namespace is not correctly defined,
315      * the method will return <code>null</code> <br>
316      * example "http://example.acme.com/system?revision=2008-04-01"
317      *
318      * @return namespace in form defined by section 5.6.4. of {@link https
319      *         ://tools.ietf.org/html/rfc6020}, if namespace is not correctly
320      *         defined, the method will return <code>null</code>
321      *
322      */
323     URI getRevisionNamespace() {
324
325         if (namespace == null) {
326             return null;
327         }
328
329         String query = "";
330         if (revision != null) {
331             query = "revision=" + formattedRevision;
332         }
333
334         URI compositeURI = null;
335         try {
336             compositeURI = new URI(namespace.getScheme(), namespace.getUserInfo(), namespace.getHost(),
337                     namespace.getPort(), namespace.getPath(), query, namespace.getFragment());
338         } catch (URISyntaxException e) {
339             LOGGER.error("", e);
340         }
341         return compositeURI;
342     }
343
344     public String getFormattedRevision() {
345         return formattedRevision;
346     }
347
348     public QName withoutRevision() {
349         return QName.create(namespace, null, localName);
350     }
351
352     public static Date parseRevision(final String formatedDate) {
353         try {
354             return getRevisionFormat().parse(formatedDate);
355         } catch (ParseException| RuntimeException e) {
356             throw new IllegalArgumentException("Revision is not in supported format:" + formatedDate,e);
357         }
358     }
359
360     public static String formattedRevision(final Date revision) {
361         if(revision == null) {
362             return null;
363         }
364         return getRevisionFormat().format(revision);
365     }
366
367     public boolean isEqualWithoutRevision(final QName other) {
368         return localName.equals(other.getLocalName()) && Objects.equals(namespace, other.getNamespace());
369     }
370
371     @Override
372     public int compareTo(final QName other) {
373         // compare mandatory localName parameter
374         int result = localName.compareTo(other.localName);
375         if (result != 0) {
376             return result;
377         }
378
379         // compare nullable namespace parameter
380         if (namespace == null) {
381             if (other.namespace != null) {
382                 return -1;
383             }
384         } else {
385             if (other.namespace == null) {
386                 return 1;
387             }
388             result = namespace.compareTo(other.namespace);
389             if (result != 0) {
390                 return result;
391             }
392         }
393
394         // compare nullable revision parameter
395         if (revision == null) {
396             if (other.revision != null) {
397                 return -1;
398             }
399         } else {
400             if (other.revision == null) {
401                 return 1;
402             }
403             result = revision.compareTo(other.revision);
404             if (result != 0) {
405                 return result;
406             }
407         }
408
409         return result;
410     }
411
412 }