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