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