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