Migrate RFC references to rfc-editor.org
[yangtools.git] / model / yang-model-api / src / main / java / org / opendaylight / yangtools / yang / model / api / stmt / SchemaNodeIdentifier.java
1 /*
2  * Copyright (c) 2015 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.model.api.stmt;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static java.util.Objects.requireNonNull;
12
13 import com.google.common.base.MoreObjects;
14 import com.google.common.collect.ImmutableList;
15 import com.google.common.collect.Interner;
16 import com.google.common.collect.Interners;
17 import java.util.ArrayList;
18 import java.util.Arrays;
19 import java.util.Collection;
20 import java.util.List;
21 import org.eclipse.jdt.annotation.NonNull;
22 import org.opendaylight.yangtools.concepts.Immutable;
23 import org.opendaylight.yangtools.yang.common.QName;
24 import org.opendaylight.yangtools.yang.common.QNameModule;
25
26 /**
27  * Represents unique path to every schema node inside the schema node identifier namespace. This concept is defined
28  * in <a href="https://www.rfc-editor.org/rfc/rfc7950#section-6.5">RFC7950</a>.
29  */
30 public abstract sealed class SchemaNodeIdentifier implements Immutable {
31     /**
32      * An absolute schema node identifier.
33      */
34     public abstract static sealed class Absolute extends SchemaNodeIdentifier {
35         private static final Interner<@NonNull Absolute> INTERNER = Interners.newWeakInterner();
36
37         /**
38          * Create an absolute schema node identifier composed of a single node identifier.
39          *
40          * @param nodeIdentifier Single node identifier
41          * @return An absolute schema node identifier
42          * @throws NullPointerException if {@code nodeIdentifier} is null
43          */
44         public static @NonNull Absolute of(final QName nodeIdentifier) {
45             return new AbsoluteSingle(nodeIdentifier);
46         }
47
48         /**
49          * Create an absolute schema node identifier composed of multiple node identifiers.
50          *
51          * @param nodeIdentifiers Node identifiers
52          * @return An absolute schema node identifier
53          * @throws NullPointerException if {@code nodeIdentifiers} or any of its members is null
54          * @throws IllegalArgumentException if {@code nodeIdentifiers} is empty
55          */
56         public static @NonNull Absolute of(final QName... nodeIdentifiers) {
57             return of(Arrays.asList(nodeIdentifiers));
58         }
59
60         /**
61          * Create an absolute schema node identifier composed of multiple node identifiers.
62          *
63          * @param nodeIdentifiers Node identifiers
64          * @return An absolute schema node identifier
65          * @throws NullPointerException if {@code nodeIdentifiers} or any of its members is null
66          * @throws IllegalArgumentException if {@code nodeIdentifiers} is empty
67          */
68         public static @NonNull Absolute of(final Collection<QName> nodeIdentifiers) {
69             final var qnames = checkQNames(nodeIdentifiers);
70             return qnames.size() == 1 ? of(qnames.get(0)) : new AbsoluteMultiple(qnames);
71         }
72
73         /**
74          * Return an interned reference to an equivalent object.
75          *
76          * @return An interned reference, or this object if it was previously interned.
77          */
78         public final @NonNull Absolute intern() {
79             return INTERNER.intern(this);
80         }
81
82         @Override
83         final String className() {
84             return "Absolute";
85         }
86     }
87
88     /**
89      * A descendant schema node identifier.
90      */
91     public abstract static sealed class Descendant extends SchemaNodeIdentifier {
92         /**
93          * Create a descendant schema node identifier composed of a single node identifier.
94          *
95          * @param nodeIdentifier Single node identifier
96          * @return A descendant schema node identifier
97          * @throws NullPointerException if {@code nodeIdentifier} is null
98          */
99         public static @NonNull Descendant of(final QName nodeIdentifier) {
100             return new DescendantSingle(nodeIdentifier);
101         }
102
103         /**
104          * Create a descendant schema node identifier composed of multiple node identifiers.
105          *
106          * @param nodeIdentifiers Node identifiers
107          * @return A descendant schema node identifier
108          * @throws NullPointerException if {@code nodeIdentifiers} or any of its members is null
109          * @throws IllegalArgumentException if {@code nodeIdentifiers} is empty
110          */
111         public static @NonNull Descendant of(final QName... nodeIdentifiers) {
112             return of(Arrays.asList(nodeIdentifiers));
113         }
114
115         /**
116          * Create a descendant schema node identifier composed of multiple node identifiers.
117          *
118          * @param nodeIdentifiers Node identifiers
119          * @return A descendant schema node identifier
120          * @throws NullPointerException if {@code nodeIdentifiers} or any of its members is null
121          * @throws IllegalArgumentException if {@code nodeIdentifiers} is empty
122          */
123         public static @NonNull Descendant of(final Collection<QName> nodeIdentifiers) {
124             final var qnames = checkQNames(nodeIdentifiers);
125             return qnames.size() == 1 ? of(qnames.get(0)) : new DescandantMultiple(qnames);
126         }
127
128         @Override
129         final String className() {
130             return "Descendant";
131         }
132     }
133
134     private static final class AbsoluteSingle extends Absolute {
135         private final @NonNull QName qname;
136
137         AbsoluteSingle(final QName qname) {
138             this.qname = requireNonNull(qname);
139         }
140
141         @Override
142         public ImmutableList<QName> getNodeIdentifiers() {
143             return ImmutableList.of(qname);
144         }
145
146         @Override
147         public QName firstNodeIdentifier() {
148             return qname;
149         }
150
151         @Override
152         public QName lastNodeIdentifier() {
153             return qname;
154         }
155
156         @Override
157         Object pathObject() {
158             return qname;
159         }
160     }
161
162     private static final class AbsoluteMultiple extends Absolute {
163         private final @NonNull ImmutableList<QName> qnames;
164
165         AbsoluteMultiple(final ImmutableList<QName> qnames) {
166             this.qnames = requireNonNull(qnames);
167         }
168
169         @Override
170         public ImmutableList<QName> getNodeIdentifiers() {
171             return qnames;
172         }
173
174         @Override
175         Object pathObject() {
176             return qnames;
177         }
178     }
179
180     private static final class DescendantSingle extends Descendant {
181         private final @NonNull QName qname;
182
183         DescendantSingle(final QName qname) {
184             this.qname = requireNonNull(qname);
185         }
186
187         @Override
188         public ImmutableList<QName> getNodeIdentifiers() {
189             return ImmutableList.of(qname);
190         }
191
192         @Override
193         public QName firstNodeIdentifier() {
194             return qname;
195         }
196
197         @Override
198         public QName lastNodeIdentifier() {
199             return qname;
200         }
201
202         @Override
203         Object pathObject() {
204             return qname;
205         }
206     }
207
208     private static final class DescandantMultiple extends Descendant {
209         private final @NonNull ImmutableList<QName> qnames;
210
211         DescandantMultiple(final ImmutableList<QName> qnames) {
212             this.qnames = requireNonNull(qnames);
213         }
214
215         @Override
216         public ImmutableList<QName> getNodeIdentifiers() {
217             return qnames;
218         }
219
220         @Override
221         Object pathObject() {
222             return qnames;
223         }
224     }
225
226     // Cached hashCode
227     private volatile int hash;
228
229     /**
230      * Return the non-empty sequence of node identifiers which constitute this schema node identifier.
231      *
232      * @return Non-empty sequence of node identifiers
233      */
234     public abstract @NonNull List<QName> getNodeIdentifiers();
235
236     /**
237      * Return the first node identifier. This method is equivalent to {@code getNodeIdentifiers().get(0)}, but is
238      * potentially more efficient.
239      *
240      * @return The first node identifier
241      */
242     public @NonNull QName firstNodeIdentifier() {
243         return getNodeIdentifiers().get(0);
244     }
245
246     /**
247      * Return the last node identifier. This method is equivalent to {@code getNodeIdentifiers().get(size - 1)}, but
248      * is potentially more efficient.
249      *
250      * @return The last node identifier
251      */
252     public @NonNull QName lastNodeIdentifier() {
253         final var local = getNodeIdentifiers();
254         return local.get(local.size() - 1);
255     }
256
257     @Override
258     public final int hashCode() {
259         final int local;
260         return (local = hash) != 0 ? local : (hash = pathObject().hashCode());
261     }
262
263     @Override
264     public final boolean equals(final Object obj) {
265         return this == obj || obj != null && getClass() == obj.getClass()
266                 && pathObject().equals(((SchemaNodeIdentifier) obj).pathObject());
267     }
268
269     @Override
270     public final String toString() {
271         return MoreObjects.toStringHelper(className()).add("qnames", toStringQNames()).toString();
272     }
273
274     abstract @NonNull Object pathObject();
275
276     abstract @NonNull String className();
277
278     private List<?> toStringQNames() {
279         final var ids = getNodeIdentifiers();
280         return ids.size() < 2 ? ids : simplifyQNames(ids);
281     }
282
283     private static ImmutableList<QName> checkQNames(final Collection<QName> qnames) {
284         final var ret = ImmutableList.copyOf(qnames);
285         checkArgument(!ret.isEmpty(), "SchemaNodeIdentifier has to have at least one node identifier");
286         return ret;
287     }
288
289     private static List<?> simplifyQNames(final List<QName> qnames) {
290         final var ret = new ArrayList<>(qnames.size());
291
292         QNameModule prev = null;
293         for (var qname : qnames) {
294             final var module = qname.getModule();
295             ret.add(module.equals(prev) ? qname.getLocalName() : qname);
296             prev = module;
297         }
298
299         return ret;
300     }
301 }