Fix CanonicalValueViolation.getMessage()
[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 static java.util.Objects.requireNonNull;
11
12 import com.google.common.annotations.Beta;
13 import com.google.common.collect.Interner;
14 import com.google.common.collect.Interners;
15 import java.io.DataInput;
16 import java.io.DataOutput;
17 import java.io.IOException;
18 import java.net.URI;
19 import java.net.URISyntaxException;
20 import java.util.Objects;
21 import java.util.Optional;
22 import java.util.regex.Matcher;
23 import java.util.regex.Pattern;
24 import org.checkerframework.checker.regex.qual.Regex;
25 import org.eclipse.jdt.annotation.NonNull;
26 import org.eclipse.jdt.annotation.Nullable;
27
28 /**
29  * The QName from XML consists of local name of element and XML namespace, but for our use, we added module revision to
30  * it.
31  *
32  * <p>
33  * In YANG context QName is full name of defined node, type, procedure or notification. QName consists of XML namespace,
34  * YANG model revision and local name of defined type. It is used to prevent name clashes between nodes with same local
35  * name, but from different schemas.
36  *
37  * <p>
38  * The local name must conform to <a href="https://tools.ietf.org/html/rfc7950#section-6.2">RFC7950 Section 6.2</a>.
39  *
40  * <ul>
41  * <li><b>XMLNamespace</b> - {@link #getNamespace()} - the namespace assigned to the YANG module which
42  * defined element, type, procedure or notification.</li>
43  * <li><b>Revision</b> - {@link #getRevision()} - the revision of the YANG module which describes the
44  * element</li>
45  * <li><b>LocalName</b> - {@link #getLocalName()} - the YANG schema identifier which were defined for this
46  * node in the YANG module</li>
47  * </ul>
48  */
49 public final class QName extends AbstractQName implements Comparable<QName> {
50     private static final Interner<QName> INTERNER = Interners.newWeakInterner();
51     // Note: 5398411242927766414L is used for versions < 3.0.0 without writeReplace
52     private static final long serialVersionUID = 1L;
53
54     static final String QNAME_REVISION_DELIMITER = "?revision=";
55     static final String QNAME_LEFT_PARENTHESIS = "(";
56     static final String QNAME_RIGHT_PARENTHESIS = ")";
57
58     @Regex
59     private static final String QNAME_STRING_FULL = "^\\((.+)\\?revision=(.+)\\)(.+)$";
60     private static final Pattern QNAME_PATTERN_FULL = Pattern.compile(QNAME_STRING_FULL);
61
62     @Regex
63     private static final String QNAME_STRING_NO_REVISION = "^\\((.+)\\)(.+)$";
64     private static final Pattern QNAME_PATTERN_NO_REVISION = Pattern.compile(QNAME_STRING_NO_REVISION);
65
66     private final @NonNull QNameModule module;
67     private transient int hash = 0;
68
69     QName(final QNameModule module, final @NonNull String localName) {
70         super(localName);
71         this.module = requireNonNull(module);
72     }
73
74     /**
75      * QName Constructor.
76      *
77      * @param namespace
78      *            the namespace assigned to the YANG module
79      * @param localName
80      *            YANG schema identifier
81      */
82     private QName(final URI namespace, final String localName) {
83         this(QNameModule.create(namespace), checkLocalName(localName));
84     }
85
86     public static @NonNull QName create(final String input) {
87         Matcher matcher = QNAME_PATTERN_FULL.matcher(input);
88         if (matcher.matches()) {
89             final String namespace = matcher.group(1);
90             final String revision = matcher.group(2);
91             final String localName = matcher.group(3);
92             return create(namespace, revision, localName);
93         }
94         matcher = QNAME_PATTERN_NO_REVISION.matcher(input);
95         if (matcher.matches()) {
96             final URI namespace = URI.create(matcher.group(1));
97             final String localName = matcher.group(2);
98             return new QName(namespace, localName);
99         }
100         throw new IllegalArgumentException("Invalid input: " + input);
101     }
102
103     public static @NonNull QName create(final QName base, final String localName) {
104         return create(base.getModule(), localName);
105     }
106
107     /**
108      * Creates new QName.
109      *
110      * @param qnameModule Namespace and revision enclosed as a QNameModule
111      * @param localName Local name part of QName. MUST NOT BE null.
112      * @return Instance of QName
113      * @throws NullPointerException if any argument is null
114      * @throws IllegalArgumentException if localName is not a valid YANG identifier
115      */
116     public static @NonNull QName create(final QNameModule qnameModule, final String localName) {
117         return new QName(requireNonNull(qnameModule, "module may not be null"), checkLocalName(localName));
118     }
119
120     /**
121      * Creates new QName.
122      *
123      * @param namespace Namespace of QName or null if namespace is undefined.
124      * @param revision Revision of namespace or null if revision is unspecified.
125      * @param localName Local name part of QName. MUST NOT BE null.
126      * @return Instance of QName
127      */
128     public static @NonNull QName create(final URI namespace, final @Nullable Revision revision,
129             final String localName) {
130         return create(QNameModule.create(namespace, revision), localName);
131     }
132
133     /**
134      * Creates new QName.
135      *
136      * @param namespace Namespace of QName or null if namespace is undefined.
137      * @param revision Revision of namespace.
138      * @param localName Local name part of QName. MUST NOT BE null.
139      * @return Instance of QName
140      */
141     public static @NonNull QName create(final URI namespace, final Optional<Revision> revision,
142             final String localName) {
143         return create(QNameModule.create(namespace, revision), localName);
144     }
145
146     /**
147      * Creates new QName.
148      *
149      * @param namespace Namespace of QName or null if namespace is undefined.
150      * @param revision Revision of namespace or null if revision is unspecified.
151      * @param localName Local name part of QName. MUST NOT BE null.
152      * @return Instance of QName
153      */
154     public static @NonNull QName create(final String namespace, final String localName, final Revision revision) {
155         return create(QNameModule.create(parseNamespace(namespace), revision), localName);
156     }
157
158     /**
159      * Creates new QName.
160      *
161      * @param namespace Namespace of QName, MUST NOT BE Null.
162      * @param revision Revision of namespace / YANG module. MUST NOT BE null, MUST BE in format {@code YYYY-mm-dd}.
163      * @param localName Local name part of QName. MUST NOT BE null.
164      * @return A new QName
165      * @throws NullPointerException If any of parameters is null.
166      * @throws IllegalArgumentException If {@code namespace} is not valid URI or {@code revision} does not conform
167      *         to {@code YYYY-mm-dd}.
168      */
169     public static @NonNull QName create(final String namespace, final String revision, final String localName) {
170         return create(parseNamespace(namespace), Revision.of(revision), localName);
171     }
172
173     /**
174      * Creates new QName.
175      *
176      * @param namespace Namespace of QName, MUST NOT BE Null.
177      * @param localName Local name part of QName. MUST NOT BE null.
178      * @return A new QName
179      * @throws NullPointerException If any of parameters is null.
180      * @throws IllegalArgumentException If {@code namespace} is not valid URI.
181      */
182     public static @NonNull QName create(final String namespace, final String localName) {
183         return create(parseNamespace(namespace), localName);
184     }
185
186     /**
187      * Creates new QName.
188      *
189      * @param namespace Namespace of QName, MUST NOT BE null.
190      * @param localName Local name part of QName. MUST NOT BE null.
191      * @return A new QName
192      * @throws NullPointerException If any of parameters is null.
193      * @throws IllegalArgumentException If <code>namespace</code> is not valid URI.
194      */
195     public static @NonNull QName create(final URI namespace, final String localName) {
196         return new QName(namespace, localName);
197     }
198
199     /**
200      * Read a QName from a DataInput. The format is expected to match the output format of {@link #writeTo(DataOutput)}.
201      *
202      * @param in DataInput to read
203      * @return A QName instance
204      * @throws IOException if I/O error occurs
205      */
206     public static @NonNull QName readFrom(final DataInput in) throws IOException {
207         final QNameModule module = QNameModule.readFrom(in);
208         return new QName(module, checkLocalName(in.readUTF()));
209     }
210
211     /**
212      * Creates new QName composed of specified module and local name. This method does not perform lexical checking of
213      * localName, and it is the caller's responsibility to performs these checks.
214      *
215      * <p>
216      * When in doubt, use {@link #create(QNameModule, String)} instead.
217      *
218      * @param qnameModule Namespace and revision enclosed as a QNameModule
219      * @param localName Local name part of QName, required to have been validated
220      * @return Instance of QName
221      * @throws NullPointerException if any of the arguments is null
222      */
223     @Beta
224     public static @NonNull QName unsafeOf(final @NonNull QNameModule qnameModule, final @NonNull String localName) {
225         return new QName(qnameModule, localName);
226     }
227
228     /**
229      * Get the module component of the QName.
230      *
231      * @return Module component
232      */
233     public @NonNull QNameModule getModule() {
234         return module;
235     }
236
237     /**
238      * Returns XMLNamespace assigned to the YANG module.
239      *
240      * @return XMLNamespace assigned to the YANG module.
241      */
242     public @NonNull URI getNamespace() {
243         return module.getNamespace();
244     }
245
246     /**
247      * Returns revision of the YANG module if the module has defined revision.
248      *
249      * @return revision of the YANG module if the module has defined revision.
250      */
251     public @NonNull Optional<Revision> getRevision() {
252         return module.getRevision();
253     }
254
255     @Override
256     public @NonNull QName intern() {
257         // We also want to make sure we keep the QNameModule cached
258         final QNameModule cacheMod = module.intern();
259
260         // Identity comparison is here on purpose, as we are deciding whether to potentially store 'qname' into the
261         // interner. It is important that it does not hold user-supplied reference (such a String instance from
262         // parsing of an XML document).
263         final QName template = cacheMod == module ? this : new QName(cacheMod, getLocalName().intern());
264
265         return INTERNER.intern(template);
266     }
267
268     @Override
269     public int hashCode() {
270         if (hash == 0) {
271             hash = Objects.hash(module, getLocalName());
272         }
273         return hash;
274     }
275
276     /**
277      * Compares the specified object with this list for equality.  Returns {@code true} if and only if the specified
278      * object is also instance of {@link QName} and its {@link #getLocalName()}, {@link #getNamespace()} and
279      * {@link #getRevision()} are equals to same properties of this instance.
280      *
281      * @param obj the object to be compared for equality with this QName
282      * @return {@code true} if the specified object is equal to this QName
283      */
284     @Override
285     public boolean equals(final Object obj) {
286         if (this == obj) {
287             return true;
288         }
289         if (!(obj instanceof QName)) {
290             return false;
291         }
292         final QName other = (QName) obj;
293         return Objects.equals(getLocalName(), other.getLocalName()) && module.equals(other.module);
294     }
295
296     private static @NonNull URI parseNamespace(final String namespace) {
297         try {
298             return new URI(namespace);
299         } catch (final URISyntaxException ue) {
300             throw new IllegalArgumentException("Namespace '" + namespace + "' is not a valid URI", ue);
301         }
302     }
303
304     @Override
305     public @NonNull String toString() {
306         final StringBuilder sb = new StringBuilder().append(QNAME_LEFT_PARENTHESIS).append(getNamespace());
307         final Optional<Revision> rev = getRevision();
308         if (rev.isPresent()) {
309             sb.append(QNAME_REVISION_DELIMITER).append(rev.get());
310         }
311         return sb.append(QNAME_RIGHT_PARENTHESIS).append(getLocalName()).toString();
312     }
313
314     @Override
315     public @NonNull QName bindTo(final QNameModule namespace) {
316         return module.equals(namespace) ? this : super.bindTo(namespace);
317     }
318
319     /**
320      * Returns a QName with the same namespace and local name, but with no revision. If this QName does not have
321      * a Revision, this object is returned.
322      *
323      * @return a QName with the same namespace and local name, but with no revision.
324      */
325     public @NonNull QName withoutRevision() {
326         final QNameModule newModule;
327         return (newModule = module.withoutRevision()) == module ? this : new QName(newModule, getLocalName());
328     }
329
330     /**
331      * Formats {@link Revision} representing revision to format {@code YYYY-mm-dd}
332      *
333      * <p>
334      * YANG Specification defines format for {@code revision<} as YYYY-mm-dd. This format for revision is reused across
335      * multiple places such as capabilities URI, YANG modules, etc.
336      *
337      * @param revision Date object to format
338      * @return String representation or null if the input was null.
339      */
340     public static @Nullable String formattedRevision(final Optional<Revision> revision) {
341         return revision.map(Revision::toString).orElse(null);
342     }
343
344     /**
345      * Compares this QName to other, without comparing revision.
346      *
347      * <p>
348      * Compares instance of this to other instance of QName and returns true if both instances have equal
349      * {@code localName} ({@link #getLocalName()}) and @{code namespace} ({@link #getNamespace()}).
350      *
351      * @param other Other QName. Must not be null.
352      * @return true if this instance and other have equals localName and namespace.
353      * @throws NullPointerException if {@code other} is null.
354      */
355     public boolean isEqualWithoutRevision(final QName other) {
356         return getLocalName().equals(other.getLocalName()) && Objects.equals(getNamespace(), other.getNamespace());
357     }
358
359     // FIXME: this comparison function looks odd. We are sorting first by local name and then by module? What is
360     //        the impact on iteration order of SortedMap<QName, ?>?
361     @Override
362     @SuppressWarnings("checkstyle:parameterName")
363     public int compareTo(final QName o) {
364         // compare mandatory localName parameter
365         int result = getLocalName().compareTo(o.getLocalName());
366         if (result != 0) {
367             return result;
368         }
369         return module.compareTo(o.module);
370     }
371
372     @Override
373     public void writeTo(final DataOutput out) throws IOException {
374         module.writeTo(out);
375         out.writeUTF(getLocalName());
376     }
377
378     @Override
379     Object writeReplace() {
380         return new QNv1(this);
381     }
382 }