dd4c0cb624f3bb2f01c7c74c9a9b7e3fe9e9fb61
[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 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         @Override
90         final SchemaPath implicitSchemaPathParent() {
91             return SchemaPath.ROOT;
92         }
93
94         @Override
95         final String className() {
96             return "Absolute";
97         }
98     }
99
100     /**
101      * A descendant schema node identifier.
102      */
103     public abstract static class Descendant extends SchemaNodeIdentifier {
104         Descendant() {
105             // Hidden on purpose
106         }
107
108         /**
109          * Create a descendant schema node identifier composed of a single node identifier.
110          *
111          * @param nodeIdentifier Single node identifier
112          * @return A descendant schema node identifier
113          * @throws NullPointerException if {@code nodeIdentifier} is null
114          */
115         public static @NonNull Descendant of(final QName nodeIdentifier) {
116             return new DescendantSingle(nodeIdentifier);
117         }
118
119         /**
120          * Create a descendant schema node identifier composed of multiple node identifiers.
121          *
122          * @param nodeIdentifiers Node identifiers
123          * @return A descendant schema node identifier
124          * @throws NullPointerException if {@code nodeIdentifiers} or any of its members is null
125          * @throws IllegalArgumentException if {@code nodeIdentifiers} is empty
126          */
127         public static @NonNull Descendant of(final QName... nodeIdentifiers) {
128             return of(Arrays.asList(nodeIdentifiers));
129         }
130
131         /**
132          * Create a descendant schema node identifier composed of multiple node identifiers.
133          *
134          * @param nodeIdentifiers Node identifiers
135          * @return A descendant schema node identifier
136          * @throws NullPointerException if {@code nodeIdentifiers} or any of its members is null
137          * @throws IllegalArgumentException if {@code nodeIdentifiers} is empty
138          */
139         public static @NonNull Descendant of(final Collection<QName> nodeIdentifiers) {
140             final ImmutableList<QName> qnames = checkQNames(nodeIdentifiers);
141             return qnames.size() == 1 ? of(qnames.get(0)) : new DescandantMultiple(qnames);
142         }
143
144         @Override
145         final SchemaPath implicitSchemaPathParent() {
146             return SchemaPath.SAME;
147         }
148
149         @Override
150         final String className() {
151             return "Descendant";
152         }
153     }
154
155     private static final class AbsoluteSingle extends Absolute {
156         private final @NonNull QName qname;
157
158         AbsoluteSingle(final QName qname) {
159             this.qname = requireNonNull(qname);
160         }
161
162         @Override
163         public ImmutableList<QName> getNodeIdentifiers() {
164             return ImmutableList.of(qname);
165         }
166
167         @Override
168         public QName firstNodeIdentifier() {
169             return qname;
170         }
171
172         @Override
173         public QName lastNodeIdentifier() {
174             return qname;
175         }
176
177         @Override
178         Object pathObject() {
179             return qname;
180         }
181     }
182
183     private static final class AbsoluteMultiple extends Absolute {
184         private final @NonNull ImmutableList<QName> qnames;
185
186         AbsoluteMultiple(final ImmutableList<QName> qnames) {
187             this.qnames = requireNonNull(qnames);
188         }
189
190         @Override
191         public ImmutableList<QName> getNodeIdentifiers() {
192             return qnames;
193         }
194
195         @Override
196         Object pathObject() {
197             return qnames;
198         }
199     }
200
201     private static final class DescendantSingle extends Descendant {
202         private final @NonNull QName qname;
203
204         DescendantSingle(final QName qname) {
205             this.qname = requireNonNull(qname);
206         }
207
208         @Override
209         public ImmutableList<QName> getNodeIdentifiers() {
210             return ImmutableList.of(qname);
211         }
212
213         @Override
214         public QName firstNodeIdentifier() {
215             return qname;
216         }
217
218         @Override
219         public QName lastNodeIdentifier() {
220             return qname;
221         }
222
223         @Override
224         Object pathObject() {
225             return qname;
226         }
227     }
228
229     private static final class DescandantMultiple extends Descendant {
230         private final @NonNull ImmutableList<QName> qnames;
231
232         DescandantMultiple(final ImmutableList<QName> qnames) {
233             this.qnames = requireNonNull(qnames);
234         }
235
236         @Override
237         public ImmutableList<QName> getNodeIdentifiers() {
238             return qnames;
239         }
240
241         @Override
242         Object pathObject() {
243             return qnames;
244         }
245     }
246
247     private static final AtomicReferenceFieldUpdater<SchemaNodeIdentifier, SchemaPath> SCHEMAPATH_UPDATER =
248             AtomicReferenceFieldUpdater.newUpdater(SchemaNodeIdentifier.class, SchemaPath.class, "schemaPath");
249
250     // Cached SchemaPath.
251     private volatile SchemaPath schemaPath;
252     // Cached hashCode
253     private volatile int hash;
254
255     SchemaNodeIdentifier() {
256         // Hidden on purpose
257     }
258
259     /**
260      * Return the non-empty sequence of node identifiers which constitute this schema node identifier.
261      *
262      * @return Non-empty sequence of node identifiers
263      */
264     public abstract @NonNull List<QName> getNodeIdentifiers();
265
266     /**
267      * Return the first node identifier. This method is equivalent to {@code getNodeIdentifiers().get(0)}, but is
268      * potentially more efficient.
269      *
270      * @return The first node identifier
271      */
272     public @NonNull QName firstNodeIdentifier() {
273         return getNodeIdentifiers().get(0);
274     }
275
276     /**
277      * Return the last node identifier. This method is equivalent to {@code getNodeIdentifiers().get(size - 1)}, but
278      * is potentially more efficient.
279      *
280      * @return The last node identifier
281      */
282     public @NonNull QName lastNodeIdentifier() {
283         final List<QName> local = getNodeIdentifiers();
284         return local.get(local.size() - 1);
285     }
286
287     /**
288      * Create the {@link SchemaPath} equivalent of this identifier.
289      *
290      * @return SchemaPath equivalent.
291      */
292     public final @NonNull SchemaPath asSchemaPath() {
293         final SchemaPath ret = schemaPath;
294         return ret != null ? ret : loadSchemaPath();
295     }
296
297     @Override
298     public final int hashCode() {
299         final int local;
300         return (local = hash) != 0 ? local : (hash = pathObject().hashCode());
301     }
302
303     @Override
304     public final boolean equals(final Object obj) {
305         return this == obj || obj != null && getClass() == obj.getClass()
306                 && pathObject().equals(((SchemaNodeIdentifier) obj).pathObject());
307     }
308
309     @Override
310     public final String toString() {
311         return MoreObjects.toStringHelper(className()).add("qnames", toStringQNames()).toString();
312     }
313
314     abstract @NonNull SchemaPath implicitSchemaPathParent();
315
316     abstract @NonNull Object pathObject();
317
318     abstract @NonNull String className();
319
320     private @NonNull SchemaPath loadSchemaPath() {
321         final SchemaPath newPath = implicitSchemaPathParent().createChild(getNodeIdentifiers());
322         return SCHEMAPATH_UPDATER.compareAndSet(this, null, newPath) ? newPath : schemaPath;
323     }
324
325     private List<?> toStringQNames() {
326         final List<QName> ids = getNodeIdentifiers();
327         return ids.size() < 2 ? ids : simplifyQNames(ids);
328     }
329
330     @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD",
331             justification = "https://github.com/spotbugs/spotbugs/issues/811")
332     private static ImmutableList<QName> checkQNames(final Collection<QName> qnames) {
333         final ImmutableList<QName> ret = ImmutableList.copyOf(qnames);
334         checkArgument(!ret.isEmpty(), "SchemaNodeIdentifier has to have at least one node identifier");
335         return ret;
336     }
337
338     private static List<?> simplifyQNames(final List<QName> qnames) {
339         final List<Object> ret = new ArrayList<>(qnames.size());
340
341         QNameModule prev = null;
342         for (QName qname : qnames) {
343             final QNameModule module = qname.getModule();
344             ret.add(module.equals(prev) ? qname.getLocalName() : qname);
345             prev = module;
346         }
347
348         return ret;
349     }
350 }