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