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 static org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil.getRevisionFormat;
12 import java.io.Serializable;
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;
21 import org.opendaylight.yangtools.concepts.Immutable;
22 import org.slf4j.Logger;
23 import org.slf4j.LoggerFactory;
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> {
46 private static final long serialVersionUID = 5398411242927766414L;
47 private static final Logger LOGGER = LoggerFactory.getLogger(QName.class);
49 static final String QNAME_REVISION_DELIMITER = "?revision=";
50 static final String QNAME_LEFT_PARENTHESIS = "(";
51 static final String QNAME_RIGHT_PARENTHESIS = ")";
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(
57 private static final Pattern QNAME_PATTERN_NO_NAMESPACE_NO_REVISION = Pattern.compile(
61 private final URI namespace;
63 private final String localName;
65 private final String prefix;
67 private final String formattedRevision;
69 private final Date revision;
75 * the namespace assigned to the YANG module
77 * the revision of the YANG module
79 * locally defined prefix assigned to local name
81 * YANG schema identifier
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;
88 if(revision != null) {
89 this.formattedRevision = getRevisionFormat().format(revision);
91 this.formattedRevision = null;
99 * the namespace assigned to the YANG module
101 * YANG schema identifier
103 public QName(final URI namespace, final String localName) {
104 this(namespace, null, "", localName);
107 private static String checkLocalName(final String localName) {
108 if (localName == null) {
109 throw new IllegalArgumentException("Parameter 'localName' may not be null.");
111 if (localName.length() == 0) {
112 throw new IllegalArgumentException("Parameter 'localName' must be a non-empty string.");
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));
129 * the namespace assigned to the YANG module
131 * the revision of the YANG module
133 * YANG schema identifier
135 public QName(final URI namespace, final Date revision, final String localName) {
136 this(namespace, revision, null, localName);
139 public QName(final QName base, final String localName) {
140 this(base.getNamespace(), base.getRevision(), base.getPrefix(), localName);
144 * @deprecated Use {@link #create(String)} instead.
145 * This implementation is broken.
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]);
156 this.namespace = URI.create(nsAndRev);
159 this.localName = checkLocalName(input.substring(input.indexOf(")") + 1));
160 this.revision = revision;
162 if (revision != null) {
163 this.formattedRevision = getRevisionFormat().format(revision);
165 this.formattedRevision = null;
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);
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);
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);
188 throw new IllegalArgumentException("Invalid input:" + input);
192 * Returns XMLNamespace assigned to the YANG module.
194 * @return XMLNamespace assigned to the YANG module.
196 public URI getNamespace() {
201 * Returns YANG schema identifier which were defined for this node in the
204 * @return YANG schema identifier which were defined for this node in the
207 public String getLocalName() {
212 * Returns revision of the YANG module if the module has defined revision,
213 * otherwise returns <code>null</code>
215 * @return revision of the YANG module if the module has defined revision,
216 * otherwise returns <code>null</code>
218 public Date getRevision() {
223 * Returns locally defined prefix assigned to local name
225 * @return locally defined prefix assigned to local name
227 public String getPrefix() {
232 public int hashCode() {
233 final int prime = 31;
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());
242 public boolean equals(final Object obj) {
249 if (getClass() != obj.getClass()) {
252 QName other = (QName) obj;
253 if (localName == null) {
254 if (other.localName != null) {
257 } else if (!localName.equals(other.localName)) {
260 if (namespace == null) {
261 if (other.namespace != null) {
264 } else if (!namespace.equals(other.namespace)) {
267 if (formattedRevision == null) {
268 if (other.formattedRevision != null) {
271 } else if (!revision.equals(other.revision)) {
278 public static QName create(final QName base, final String localName){
279 return new QName(base, localName);
282 public static QName create(final URI namespace, final Date revision, final String localName){
283 return new QName(namespace, revision, localName);
287 public static QName create(final String namespace, final String revision, final String localName) throws IllegalArgumentException{
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);
298 public String toString() {
299 StringBuilder sb = new StringBuilder();
300 if (namespace != null) {
301 sb.append(QNAME_LEFT_PARENTHESIS + namespace);
303 if (revision != null) {
304 sb.append(QNAME_REVISION_DELIMITER + getRevisionFormat().format(revision));
306 sb.append(QNAME_RIGHT_PARENTHESIS);
308 sb.append(localName);
309 return sb.toString();
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"
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>
323 URI getRevisionNamespace() {
325 if (namespace == null) {
330 if (revision != null) {
331 query = "revision=" + formattedRevision;
334 URI compositeURI = null;
336 compositeURI = new URI(namespace.getScheme(), namespace.getUserInfo(), namespace.getHost(),
337 namespace.getPort(), namespace.getPath(), query, namespace.getFragment());
338 } catch (URISyntaxException e) {
344 public String getFormattedRevision() {
345 return formattedRevision;
348 public QName withoutRevision() {
349 return QName.create(namespace, null, localName);
352 public static Date parseRevision(final String formatedDate) {
354 return getRevisionFormat().parse(formatedDate);
355 } catch (ParseException| RuntimeException e) {
356 throw new IllegalArgumentException("Revision is not in supported format:" + formatedDate,e);
360 public static String formattedRevision(final Date revision) {
361 if(revision == null) {
364 return getRevisionFormat().format(revision);
367 public boolean isEqualWithoutRevision(final QName other) {
368 return localName.equals(other.getLocalName()) && Objects.equals(namespace, other.getNamespace());
372 public int compareTo(final QName other) {
373 // compare mandatory localName parameter
374 int result = localName.compareTo(other.localName);
379 // compare nullable namespace parameter
380 if (namespace == null) {
381 if (other.namespace != null) {
385 if (other.namespace == null) {
388 result = namespace.compareTo(other.namespace);
394 // compare nullable revision parameter
395 if (revision == null) {
396 if (other.revision != null) {
400 if (other.revision == null) {
403 result = revision.compareTo(other.revision);