3865b5863ca2c20137699e75bdc961aa28e9e5a4
[yangtools.git] / common / yang-common / src / main / java / org / opendaylight / yangtools / yang / common / UnresolvedQName.java
1 /*
2  * Copyright (c) 2021 PANTHEON.tech, s.r.o. 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.base.MoreObjects;
13 import com.google.common.collect.Interner;
14 import com.google.common.collect.Interners;
15 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
16 import java.io.DataInput;
17 import java.io.DataOutput;
18 import java.io.IOException;
19 import java.util.Optional;
20 import org.eclipse.jdt.annotation.NonNull;
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.eclipse.jdt.annotation.Nullable;
23
24 /**
25  * A {@link QName} equivalent which has not been resolved. There are two subclasses:
26  * <ol>
27  *   <li>{@link Unqualified}, which holds only a local name available through {@link #getLocalName()}.<li>
28  *   <li>{@link Qualified}, which also holds a string prefix available via {@link Qualified#getPrefix()}.</li>
29  * </ol>
30  */
31 @NonNullByDefault
32 public abstract sealed class UnresolvedQName extends AbstractQName {
33     /**
34      * An unresolved, qualified {@link QName}. It is guaranteed to hold a valid {@link #getLocalName()} bound to a
35      * namespace identified through a prefix string, but remains unresolved. A resolved {@link QName} can be obtained
36      * through {@link #bindTo(YangNamespaceContext)}.
37      */
38     public static final class Qualified extends UnresolvedQName implements Comparable<Qualified> {
39         private static final long serialVersionUID = 1L;
40         private static final Interner<Qualified> INTERNER = Interners.newWeakInterner();
41
42         private final String prefix;
43
44         private Qualified(final String prefix, final String localName) {
45             super(localName);
46             this.prefix = requireNonNull(prefix);
47         }
48
49         /**
50          * Read an QualifiedQName from a DataInput. The format is expected to match the output format of
51          * {@link #writeTo(DataOutput)}.
52          *
53          * @param in DataInput to read
54          * @return An QualifiedQName instance
55          * @throws IOException if I/O error occurs
56          */
57         public static Qualified readFrom(final DataInput in) throws IOException {
58             return qualified(in.readUTF(), in.readUTF());
59         }
60
61         @Override
62         public @NonNull String getPrefix() {
63             return prefix;
64         }
65
66         public Optional<QName> bindTo(final YangNamespaceContext namespaceContext) {
67             return namespaceContext.findNamespaceForPrefix(prefix).map(this::bindTo);
68         }
69
70         @Override
71         @SuppressFBWarnings(value = "ES_COMPARING_STRINGS_WITH_EQ", justification = "Interning identity check")
72         public Qualified intern() {
73             // Make sure to intern the string and check whether it refers to the same name as we are
74             final String name = getLocalName();
75             final String internedName = name.intern();
76             final Qualified template = internedName == name ? this : new Qualified(prefix.intern(), internedName);
77             return INTERNER.intern(template);
78         }
79
80         @Override
81         public Qualified withPrefix(final String newPrefix) {
82             return prefix.equals(newPrefix) ? this : new Qualified(newPrefix, getLocalName());
83         }
84
85         @Override
86         @SuppressWarnings("checkstyle:parameterName")
87         public int compareTo(final Qualified o) {
88             return getLocalName().compareTo(o.getLocalName());
89         }
90
91         @Override
92         public void writeTo(final DataOutput out) throws IOException {
93             out.writeUTF(getLocalName());
94         }
95
96         @Override
97         public int hashCode() {
98             return getLocalName().hashCode();
99         }
100
101         @Override
102         public boolean equals(final @Nullable Object obj) {
103             return this == obj || obj instanceof Qualified && getLocalName().equals(((Qualified) obj).getLocalName());
104         }
105
106         @Override
107         public String toString() {
108             return MoreObjects.toStringHelper(this).add("localName", getLocalName()).toString();
109         }
110
111         @Override
112         Object writeReplace() {
113             return new QQNv1(this);
114         }
115     }
116
117     /**
118      * An unresolved, unqualified {@link QName}. It is guaranteed to hold a valid {@link #getLocalName()}, in the
119      * default namespace, which is not resolved. A resolved {@link QName} can be constructed through
120      * {@link #bindTo(QNameModule)}.
121      */
122     public static final class Unqualified extends UnresolvedQName implements Comparable<Unqualified> {
123         private static final long serialVersionUID = 1L;
124         private static final Interner<Unqualified> INTERNER = Interners.newWeakInterner();
125
126         private Unqualified(final String localName) {
127             super(localName);
128         }
129
130         /**
131          * Read an UnqualifiedQName from a DataInput. The format is expected to match the output format of
132          * {@link #writeTo(DataOutput)}.
133          *
134          * @param in DataInput to read
135          * @return An UnqualifiedQName instance
136          * @throws IOException if I/O error occurs
137          */
138         public static Unqualified readFrom(final DataInput in) throws IOException {
139             return unqualified(in.readUTF());
140         }
141
142         @Override
143         @SuppressFBWarnings(value = "ES_COMPARING_STRINGS_WITH_EQ", justification = "Interning identity check")
144         public Unqualified intern() {
145             // Make sure to intern the string and check whether it refers to the same name as we are
146             final String name = getLocalName();
147             final String internedName = name.intern();
148             final Unqualified template = internedName == name ? this : new Unqualified(internedName);
149             return INTERNER.intern(template);
150         }
151
152         @Override
153         @SuppressFBWarnings(value = "NP_NONNULL_RETURN_VIOLATION", justification = "Non-grok of @Nullable")
154         public @Nullable String getPrefix() {
155             return null;
156         }
157
158         @Override
159         public Qualified withPrefix(final String newPrefix) {
160             return new Qualified(newPrefix, getLocalName());
161         }
162
163         @Override
164         @SuppressWarnings("checkstyle:parameterName")
165         public int compareTo(final Unqualified o) {
166             return getLocalName().compareTo(o.getLocalName());
167         }
168
169         @Override
170         public void writeTo(final DataOutput out) throws IOException {
171             out.writeUTF(getLocalName());
172         }
173
174         @Override
175         public int hashCode() {
176             return getLocalName().hashCode();
177         }
178
179         @Override
180         public boolean equals(final @Nullable Object obj) {
181             return this == obj || obj instanceof Unqualified
182                     && getLocalName().equals(((AbstractQName) obj).getLocalName());
183         }
184
185         @Override
186         public String toString() {
187             return MoreObjects.toStringHelper(this).add("localName", getLocalName()).toString();
188         }
189
190         @Override
191         Object writeReplace() {
192             return new UQNv1(this);
193         }
194     }
195
196     private static final long serialVersionUID = 1L;
197
198     private UnresolvedQName(final String localName) {
199         super(localName);
200     }
201
202     /**
203      * Create a new qualified unresolved QName.
204      *
205      * @param prefix The prefix on this qualified QName
206      * @param localName The local name of this qualified QName
207      * @return An UnqualifiedQName instance
208      * @throws NullPointerException if any argument is {@code null}
209      * @throws IllegalArgumentException if {@code localName} is not a valid YANG identifier
210      */
211     public static Qualified qualified(final String prefix, final String localName) {
212         return new Qualified(checkLocalName(prefix), checkLocalName(localName));
213     }
214
215     /**
216      * Create a new unqualified unresolved QName.
217      *
218      * @param localName The local name of this unqualified QName
219      * @return An UnqualifiedQName instance
220      * @throws NullPointerException if localName is {@code null}
221      * @throws IllegalArgumentException if {@code localName} is not a valid YANG identifier
222      */
223     public static Unqualified unqualified(final String localName) {
224         return new Unqualified(checkLocalName(localName));
225     }
226
227     /**
228      * Try to create a new unqualified QName.
229      *
230      * @param localName The local name of this unqualified QName
231      * @return An UnqualifiedQName instance, or null if localName is not valid
232      */
233     @SuppressFBWarnings(value = "NP_NONNULL_RETURN_VIOLATION", justification = "Non-grok of @Nullable")
234     public static @Nullable Unqualified tryLocalName(final String localName) {
235         return isValidLocalName(localName) ? new Unqualified(localName) : null;
236     }
237
238     @Override
239     public abstract UnresolvedQName intern();
240
241     /**
242      * Return the prefix of this unresolved QName.
243      *
244      * @return This QName's prefix
245      */
246     public abstract @Nullable String getPrefix();
247
248     /**
249      * Return a {@link Qualified} object bound to specified {@code prefix}.
250      *
251      * @return a {@link Qualified} object bound to specified {@code prefix}
252      * @throws NullPointerException if {@code newPrefix} is null
253      */
254     public abstract Qualified withPrefix(String newPrefix);
255 }