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 java.io.Serializable;
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;
20 import org.opendaylight.yangtools.concepts.Immutable;
21 import org.slf4j.Logger;
22 import org.slf4j.LoggerFactory;
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.
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.
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
38 * <li><b>LocalName</b> - the YANG schema identifier which were defined for this
39 * node in the YANG module</li>
44 public final class QName implements Immutable,Serializable {
46 private static final long serialVersionUID = 5398411242927766414L;
48 protected static final Logger LOGGER = LoggerFactory.getLogger(QName.class);
50 private static final ThreadLocal<SimpleDateFormat> REVISION_FORMAT = new ThreadLocal<SimpleDateFormat>() {
52 protected SimpleDateFormat initialValue() {
53 return new SimpleDateFormat("yyyy-MM-dd");
56 public void set(SimpleDateFormat value) {
57 throw new UnsupportedOperationException();
61 static final String QNAME_REVISION_DELIMITER = "?revision=";
62 static final String QNAME_LEFT_PARENTHESIS = "(";
63 static final String QNAME_RIGHT_PARENTHESIS = ")";
67 private final URI namespace;
69 private final String localName;
71 private final String prefix;
73 private final String formattedRevision;
75 private final Date revision;
81 * the namespace assigned to the YANG module
83 * the revision of the YANG module
85 * locally defined prefix assigned to local name
87 * YANG schema identifier
89 public QName(URI namespace, Date revision, String prefix, String localName) {
90 this.localName = checkLocalName(localName);
91 this.namespace = namespace;
92 this.revision = revision;
94 if(revision != null) {
95 this.formattedRevision = REVISION_FORMAT.get().format(revision);
97 this.formattedRevision = null;
105 * the namespace assigned to the YANG module
107 * YANG schema identifier
109 public QName(URI namespace, String localName) {
110 this(namespace, null, "", localName);
113 private static String checkLocalName(String localName) {
114 if (localName == null) {
115 throw new IllegalArgumentException("Parameter 'localName' may not be null.");
117 if (localName.length() == 0) {
118 throw new IllegalArgumentException("Parameter 'localName' must be a non-empty string.");
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));
135 * the namespace assigned to the YANG module
137 * the revision of the YANG module
139 * YANG schema identifier
141 public QName(URI namespace, Date revision, String localName) {
142 this(namespace, revision, null, localName);
145 public QName(QName base, String localName) {
146 this(base.getNamespace(), base.getRevision(), base.getPrefix(), localName);
150 * @deprecated Use {@link #create(String)} instead.
151 * This implementation is broken.
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]);
162 this.namespace = URI.create(nsAndRev);
165 this.localName = checkLocalName(input.substring(input.indexOf(")") + 1));
166 this.revision = revision;
168 if (revision != null) {
169 this.formattedRevision = REVISION_FORMAT.get().format(revision);
171 this.formattedRevision = null;
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(
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);
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);
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);
202 throw new IllegalArgumentException("Invalid input:" + input);
206 * Returns XMLNamespace assigned to the YANG module.
208 * @return XMLNamespace assigned to the YANG module.
210 public URI getNamespace() {
215 * Returns YANG schema identifier which were defined for this node in the
218 * @return YANG schema identifier which were defined for this node in the
221 public String getLocalName() {
226 * Returns revision of the YANG module if the module has defined revision,
227 * otherwise returns <code>null</code>
229 * @return revision of the YANG module if the module has defined revision,
230 * otherwise returns <code>null</code>
232 public Date getRevision() {
237 * Returns locally defined prefix assigned to local name
239 * @return locally defined prefix assigned to local name
241 public String getPrefix() {
246 public int hashCode() {
247 final int prime = 31;
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());
256 public boolean equals(Object obj) {
263 if (getClass() != obj.getClass()) {
266 QName other = (QName) obj;
267 if (localName == null) {
268 if (other.localName != null) {
271 } else if (!localName.equals(other.localName)) {
274 if (namespace == null) {
275 if (other.namespace != null) {
278 } else if (!namespace.equals(other.namespace)) {
281 if (formattedRevision == null) {
282 if (other.formattedRevision != null) {
285 } else if (!revision.equals(other.revision)) {
292 public static QName create(QName base, String localName){
293 return new QName(base, localName);
296 public static QName create(URI namespace, Date revision, String localName){
297 return new QName(namespace, revision, localName);
301 public static QName create(String namespace, String revision, String localName) throws IllegalArgumentException{
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);
312 public String toString() {
313 StringBuilder sb = new StringBuilder();
314 if (namespace != null) {
315 sb.append(QNAME_LEFT_PARENTHESIS + namespace);
317 if (revision != null) {
318 sb.append(QNAME_REVISION_DELIMITER + REVISION_FORMAT.get().format(revision));
320 sb.append(QNAME_RIGHT_PARENTHESIS);
322 sb.append(localName);
323 return sb.toString();
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"
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>
337 URI getRevisionNamespace() {
339 if (namespace == null) {
344 if (revision != null) {
345 query = "revision=" + formattedRevision;
348 URI compositeURI = null;
350 compositeURI = new URI(namespace.getScheme(), namespace.getUserInfo(), namespace.getHost(),
351 namespace.getPort(), namespace.getPath(), query, namespace.getFragment());
352 } catch (URISyntaxException e) {
358 public String getFormattedRevision() {
359 return formattedRevision;
362 public QName withoutRevision() {
363 return QName.create(namespace, null, localName);
366 public static Date parseRevision(String formatedDate) {
368 return REVISION_FORMAT.get().parse(formatedDate);
369 } catch (ParseException| RuntimeException e) {
370 throw new IllegalArgumentException("Revision is not in supported format:" + formatedDate,e);
374 public static String formattedRevision(Date revision) {
375 if(revision == null) {
378 return REVISION_FORMAT.get().format(revision);
381 public boolean isEqualWithoutRevision(QName other) {
382 return localName.equals(other.getLocalName()) && Objects.equals(namespace, other.getNamespace());