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