2 * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.yangtools.yang.model.api.stmt;
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static java.util.Objects.requireNonNull;
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;
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>.
33 public abstract class SchemaNodeIdentifier implements Immutable {
35 * An absolute schema node identifier.
37 public abstract static class Absolute extends SchemaNodeIdentifier {
38 private static final Interner<Absolute> INTERNER = Interners.newWeakInterner();
45 * Create an absolute schema node identifier composed of a single node identifier.
47 * @param nodeIdentifier Single node identifier
48 * @return An absolute schema node identifier
49 * @throws NullPointerException if {@code nodeIdentifier} is null
51 public static @NonNull Absolute of(final QName nodeIdentifier) {
52 return new AbsoluteSingle(nodeIdentifier);
56 * Create an absolute schema node identifier composed of multiple node identifiers.
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
63 public static @NonNull Absolute of(final QName... nodeIdentifiers) {
64 return of(Arrays.asList(nodeIdentifiers));
68 * Create an absolute schema node identifier composed of multiple node identifiers.
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
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);
81 * Return an interned reference to an equivalent object.
83 * @return An interned reference, or this object if it was previously interned.
85 public final @NonNull Absolute intern() {
86 return INTERNER.intern(this);
90 final SchemaPath implicitSchemaPathParent() {
91 return SchemaPath.ROOT;
95 final String className() {
101 * A descendant schema node identifier.
103 public abstract static class Descendant extends SchemaNodeIdentifier {
109 * Create a descendant schema node identifier composed of a single node identifier.
111 * @param nodeIdentifier Single node identifier
112 * @return A descendant schema node identifier
113 * @throws NullPointerException if {@code nodeIdentifier} is null
115 public static @NonNull Descendant of(final QName nodeIdentifier) {
116 return new DescendantSingle(nodeIdentifier);
120 * Create a descendant schema node identifier composed of multiple node identifiers.
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
127 public static @NonNull Descendant of(final QName... nodeIdentifiers) {
128 return of(Arrays.asList(nodeIdentifiers));
132 * Create a descendant schema node identifier composed of multiple node identifiers.
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
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);
145 final SchemaPath implicitSchemaPathParent() {
146 return SchemaPath.SAME;
150 final String className() {
155 private static final class AbsoluteSingle extends Absolute {
156 private final @NonNull QName qname;
158 AbsoluteSingle(final QName qname) {
159 this.qname = requireNonNull(qname);
163 public ImmutableList<QName> getNodeIdentifiers() {
164 return ImmutableList.of(qname);
168 public QName firstNodeIdentifier() {
173 public QName lastNodeIdentifier() {
178 Object pathObject() {
183 private static final class AbsoluteMultiple extends Absolute {
184 private final @NonNull ImmutableList<QName> qnames;
186 AbsoluteMultiple(final ImmutableList<QName> qnames) {
187 this.qnames = requireNonNull(qnames);
191 public ImmutableList<QName> getNodeIdentifiers() {
196 Object pathObject() {
201 private static final class DescendantSingle extends Descendant {
202 private final @NonNull QName qname;
204 DescendantSingle(final QName qname) {
205 this.qname = requireNonNull(qname);
209 public ImmutableList<QName> getNodeIdentifiers() {
210 return ImmutableList.of(qname);
214 public QName firstNodeIdentifier() {
219 public QName lastNodeIdentifier() {
224 Object pathObject() {
229 private static final class DescandantMultiple extends Descendant {
230 private final @NonNull ImmutableList<QName> qnames;
232 DescandantMultiple(final ImmutableList<QName> qnames) {
233 this.qnames = requireNonNull(qnames);
237 public ImmutableList<QName> getNodeIdentifiers() {
242 Object pathObject() {
247 private static final AtomicReferenceFieldUpdater<SchemaNodeIdentifier, SchemaPath> SCHEMAPATH_UPDATER =
248 AtomicReferenceFieldUpdater.newUpdater(SchemaNodeIdentifier.class, SchemaPath.class, "schemaPath");
250 // Cached SchemaPath.
251 private volatile SchemaPath schemaPath;
253 private volatile int hash;
255 SchemaNodeIdentifier() {
260 * Return the non-empty sequence of node identifiers which constitute this schema node identifier.
262 * @return Non-empty sequence of node identifiers
264 public abstract @NonNull List<QName> getNodeIdentifiers();
267 * Return the first node identifier. This method is equivalent to {@code getNodeIdentifiers().get(0)}, but is
268 * potentially more efficient.
270 * @return The first node identifier
272 public @NonNull QName firstNodeIdentifier() {
273 return getNodeIdentifiers().get(0);
277 * Return the last node identifier. This method is equivalent to {@code getNodeIdentifiers().get(size - 1)}, but
278 * is potentially more efficient.
280 * @return The last node identifier
282 public @NonNull QName lastNodeIdentifier() {
283 final List<QName> local = getNodeIdentifiers();
284 return local.get(local.size() - 1);
288 * Create the {@link SchemaPath} equivalent of this identifier.
290 * @return SchemaPath equivalent.
292 public final @NonNull SchemaPath asSchemaPath() {
293 final SchemaPath ret = schemaPath;
294 return ret != null ? ret : loadSchemaPath();
298 public final int hashCode() {
300 return (local = hash) != 0 ? local : (hash = pathObject().hashCode());
304 public final boolean equals(final Object obj) {
305 return this == obj || obj != null && getClass() == obj.getClass()
306 && pathObject().equals(((SchemaNodeIdentifier) obj).pathObject());
310 public final String toString() {
311 return MoreObjects.toStringHelper(className()).add("qnames", toStringQNames()).toString();
314 abstract @NonNull SchemaPath implicitSchemaPathParent();
316 abstract @NonNull Object pathObject();
318 abstract @NonNull String className();
320 private @NonNull SchemaPath loadSchemaPath() {
321 final SchemaPath newPath = implicitSchemaPathParent().createChild(getNodeIdentifiers());
322 return SCHEMAPATH_UPDATER.compareAndSet(this, null, newPath) ? newPath : schemaPath;
325 private List<?> toStringQNames() {
326 final List<QName> ids = getNodeIdentifiers();
327 return ids.size() < 2 ? ids : simplifyQNames(ids);
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");
338 private static List<?> simplifyQNames(final List<QName> qnames) {
339 final List<Object> ret = new ArrayList<>(qnames.size());
341 QNameModule prev = null;
342 for (QName qname : qnames) {
343 final QNameModule module = qname.getModule();
344 ret.add(module.equals(prev) ? qname.getLocalName() : qname);