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