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 * Return the first node identifier. This method is equivalent to {@code getNodeIdentifiers().get(0)}, but is
91 * potentially more efficient.
93 * @return The first node identifier
95 public abstract @NonNull QName firstNodeIdentifier();
98 * Return the last node identifier. This method is equivalent to {@code getNodeIdentifiers().get(size - 1)}, but
99 * is potentially more efficient.
101 * @return The last node identifier
103 public abstract @NonNull QName lastNodeIdentifier();
106 final SchemaPath implicitSchemaPathParent() {
107 return SchemaPath.ROOT;
111 final String className() {
117 * A descendant schema node identifier.
119 public abstract static class Descendant extends SchemaNodeIdentifier {
125 * Create a descendant schema node identifier composed of a single node identifier.
127 * @param nodeIdentifier Single node identifier
128 * @return A descendant schema node identifier
129 * @throws NullPointerException if {@code nodeIdentifier} is null
131 public static @NonNull Descendant of(final QName nodeIdentifier) {
132 return new DescendantSingle(nodeIdentifier);
136 * Create a descendant schema node identifier composed of multiple node identifiers.
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
143 public static @NonNull Descendant of(final QName... nodeIdentifiers) {
144 return of(Arrays.asList(nodeIdentifiers));
148 * Create a descendant schema node identifier composed of multiple node identifiers.
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
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);
161 final SchemaPath implicitSchemaPathParent() {
162 return SchemaPath.SAME;
166 final String className() {
171 private static final class AbsoluteSingle extends Absolute {
172 private final @NonNull QName qname;
174 AbsoluteSingle(final QName qname) {
175 this.qname = requireNonNull(qname);
179 public ImmutableList<QName> getNodeIdentifiers() {
180 return ImmutableList.of(qname);
184 public QName firstNodeIdentifier() {
189 public QName lastNodeIdentifier() {
194 Object pathObject() {
199 private static final class AbsoluteMultiple extends Absolute {
200 private final @NonNull ImmutableList<QName> qnames;
202 AbsoluteMultiple(final ImmutableList<QName> qnames) {
203 this.qnames = requireNonNull(qnames);
207 public ImmutableList<QName> getNodeIdentifiers() {
212 public @NonNull QName firstNodeIdentifier() {
213 return qnames.get(0);
217 public @NonNull QName lastNodeIdentifier() {
218 return qnames.get(qnames.size() - 1);
222 Object pathObject() {
227 private static final class DescendantSingle extends Descendant {
228 private final @NonNull QName qname;
230 DescendantSingle(final QName qname) {
231 this.qname = requireNonNull(qname);
235 public ImmutableList<QName> getNodeIdentifiers() {
236 return ImmutableList.of(qname);
240 Object pathObject() {
245 private static final class DescandantMultiple extends Descendant {
246 private final @NonNull ImmutableList<QName> qnames;
248 DescandantMultiple(final ImmutableList<QName> qnames) {
249 this.qnames = requireNonNull(qnames);
253 public ImmutableList<QName> getNodeIdentifiers() {
258 Object pathObject() {
263 private static final AtomicReferenceFieldUpdater<SchemaNodeIdentifier, SchemaPath> SCHEMAPATH_UPDATER =
264 AtomicReferenceFieldUpdater.newUpdater(SchemaNodeIdentifier.class, SchemaPath.class, "schemaPath");
266 // Cached SchemaPath.
267 private volatile SchemaPath schemaPath;
269 private volatile int hash;
271 SchemaNodeIdentifier() {
276 * Return the non-empty sequence of node identifiers which constitute this schema node identifier.
278 * @return Non-empty sequence of node identifiers
280 public abstract @NonNull List<QName> getNodeIdentifiers();
283 * Create the {@link SchemaPath} equivalent of this identifier.
285 * @return SchemaPath equivalent.
287 public final @NonNull SchemaPath asSchemaPath() {
288 final SchemaPath ret = schemaPath;
289 return ret != null ? ret : loadSchemaPath();
293 public final int hashCode() {
295 return (local = hash) != 0 ? local : (hash = pathObject().hashCode());
299 public final boolean equals(final Object obj) {
300 return this == obj || obj != null && getClass() == obj.getClass()
301 && pathObject().equals(((SchemaNodeIdentifier) obj).pathObject());
305 public final String toString() {
306 return MoreObjects.toStringHelper(className()).add("qnames", toStringQNames()).toString();
309 abstract @NonNull SchemaPath implicitSchemaPathParent();
311 abstract @NonNull Object pathObject();
313 abstract @NonNull String className();
315 private @NonNull SchemaPath loadSchemaPath() {
316 final SchemaPath newPath = implicitSchemaPathParent().createChild(getNodeIdentifiers());
317 return SCHEMAPATH_UPDATER.compareAndSet(this, null, newPath) ? newPath : schemaPath;
320 private List<?> toStringQNames() {
321 final List<QName> ids = getNodeIdentifiers();
322 return ids.size() < 2 ? ids : simplifyQNames(ids);
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");
333 private static List<?> simplifyQNames(final List<QName> qnames) {
334 final List<Object> ret = new ArrayList<>(qnames.size());
336 QNameModule prev = null;
337 for (QName qname : qnames) {
338 final QNameModule module = qname.getModule();
339 ret.add(module.equals(prev) ? qname.getLocalName() : qname);