2 * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.yangtools.yang.common;
10 import org.opendaylight.yangtools.concepts.Immutable;
11 import org.slf4j.Logger;
12 import org.slf4j.LoggerFactory;
14 import java.io.Serializable;
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;
23 import static org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil.getRevisionFormat;
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.
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.
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
39 * <li><b>LocalName</b> - the YANG schema identifier which were defined for this
40 * node in the YANG module</li>
45 public final class QName implements Immutable, Serializable, Comparable<QName> {
47 private static final long serialVersionUID = 5398411242927766414L;
49 protected static final Logger LOGGER = LoggerFactory.getLogger(QName.class);
52 static final String QNAME_REVISION_DELIMITER = "?revision=";
53 static final String QNAME_LEFT_PARENTHESIS = "(";
54 static final String QNAME_RIGHT_PARENTHESIS = ")";
58 private final URI namespace;
60 private final String localName;
62 private final String prefix;
64 private final String formattedRevision;
66 private final Date revision;
72 * the namespace assigned to the YANG module
74 * the revision of the YANG module
76 * locally defined prefix assigned to local name
78 * YANG schema identifier
80 public QName(URI namespace, Date revision, String prefix, String localName) {
81 this.localName = checkLocalName(localName);
82 this.namespace = namespace;
83 this.revision = revision;
85 if(revision != null) {
86 this.formattedRevision = getRevisionFormat().format(revision);
88 this.formattedRevision = null;
96 * the namespace assigned to the YANG module
98 * YANG schema identifier
100 public QName(URI namespace, String localName) {
101 this(namespace, null, "", localName);
104 private static String checkLocalName(String localName) {
105 if (localName == null) {
106 throw new IllegalArgumentException("Parameter 'localName' may not be null.");
108 if (localName.length() == 0) {
109 throw new IllegalArgumentException("Parameter 'localName' must be a non-empty string.");
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));
126 * the namespace assigned to the YANG module
128 * the revision of the YANG module
130 * YANG schema identifier
132 public QName(URI namespace, Date revision, String localName) {
133 this(namespace, revision, null, localName);
136 public QName(QName base, String localName) {
137 this(base.getNamespace(), base.getRevision(), base.getPrefix(), localName);
141 * @deprecated Use {@link #create(String)} instead.
142 * This implementation is broken.
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]);
153 this.namespace = URI.create(nsAndRev);
156 this.localName = checkLocalName(input.substring(input.indexOf(")") + 1));
157 this.revision = revision;
159 if (revision != null) {
160 this.formattedRevision = getRevisionFormat().format(revision);
162 this.formattedRevision = null;
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(
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);
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);
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);
193 throw new IllegalArgumentException("Invalid input:" + input);
197 * Returns XMLNamespace assigned to the YANG module.
199 * @return XMLNamespace assigned to the YANG module.
201 public URI getNamespace() {
206 * Returns YANG schema identifier which were defined for this node in the
209 * @return YANG schema identifier which were defined for this node in the
212 public String getLocalName() {
217 * Returns revision of the YANG module if the module has defined revision,
218 * otherwise returns <code>null</code>
220 * @return revision of the YANG module if the module has defined revision,
221 * otherwise returns <code>null</code>
223 public Date getRevision() {
228 * Returns locally defined prefix assigned to local name
230 * @return locally defined prefix assigned to local name
232 public String getPrefix() {
237 public int hashCode() {
238 final int prime = 31;
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());
247 public boolean equals(Object obj) {
254 if (getClass() != obj.getClass()) {
257 QName other = (QName) obj;
258 if (localName == null) {
259 if (other.localName != null) {
262 } else if (!localName.equals(other.localName)) {
265 if (namespace == null) {
266 if (other.namespace != null) {
269 } else if (!namespace.equals(other.namespace)) {
272 if (formattedRevision == null) {
273 if (other.formattedRevision != null) {
276 } else if (!revision.equals(other.revision)) {
283 public static QName create(QName base, String localName){
284 return new QName(base, localName);
287 public static QName create(URI namespace, Date revision, String localName){
288 return new QName(namespace, revision, localName);
292 public static QName create(String namespace, String revision, String localName) throws IllegalArgumentException{
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);
303 public String toString() {
304 StringBuilder sb = new StringBuilder();
305 if (namespace != null) {
306 sb.append(QNAME_LEFT_PARENTHESIS + namespace);
308 if (revision != null) {
309 sb.append(QNAME_REVISION_DELIMITER + getRevisionFormat().format(revision));
311 sb.append(QNAME_RIGHT_PARENTHESIS);
313 sb.append(localName);
314 return sb.toString();
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"
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>
328 URI getRevisionNamespace() {
330 if (namespace == null) {
335 if (revision != null) {
336 query = "revision=" + formattedRevision;
339 URI compositeURI = null;
341 compositeURI = new URI(namespace.getScheme(), namespace.getUserInfo(), namespace.getHost(),
342 namespace.getPort(), namespace.getPath(), query, namespace.getFragment());
343 } catch (URISyntaxException e) {
349 public String getFormattedRevision() {
350 return formattedRevision;
353 public QName withoutRevision() {
354 return QName.create(namespace, null, localName);
357 public static Date parseRevision(String formatedDate) {
359 return getRevisionFormat().parse(formatedDate);
360 } catch (ParseException| RuntimeException e) {
361 throw new IllegalArgumentException("Revision is not in supported format:" + formatedDate,e);
365 public static String formattedRevision(Date revision) {
366 if(revision == null) {
369 return getRevisionFormat().format(revision);
372 public boolean isEqualWithoutRevision(QName other) {
373 return localName.equals(other.getLocalName()) && Objects.equals(namespace, other.getNamespace());
377 public int compareTo(QName other) {
378 // compare mandatory localName parameter
379 int result = localName.compareTo(other.localName);
384 // compare nullable namespace parameter
385 if (namespace == null) {
386 if (other.namespace != null) {
390 if (other.namespace == null) {
393 result = namespace.compareTo(other.namespace);
399 // compare nullable revision parameter
400 if (revision == null) {
401 if (other.revision != null) {
405 if (other.revision == null) {
408 result = revision.compareTo(other.revision);