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