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.common;
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 org.eclipse.jdt.annotation.NonNull;
23 import org.opendaylight.yangtools.concepts.Immutable;
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>.
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 {
34 * An absolute schema node identifier.
36 public abstract static class Absolute extends SchemaNodeIdentifier {
37 private static final Interner<Absolute> INTERNER = Interners.newWeakInterner();
44 * Create an absolute schema node identifier composed of a single node identifier.
46 * @param nodeIdentifier Single node identifier
47 * @return An absolute schema node identifier
48 * @throws NullPointerException if {@code nodeIdentifier} is null
50 public static @NonNull Absolute of(final QName nodeIdentifier) {
51 return new AbsoluteSingle(nodeIdentifier);
55 * Create an absolute schema node identifier composed of multiple node identifiers.
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
62 public static @NonNull Absolute of(final QName... nodeIdentifiers) {
63 return of(Arrays.asList(nodeIdentifiers));
67 * Create an absolute schema node identifier composed of multiple node identifiers.
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
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);
80 * Return an interned reference to an equivalent object.
82 * @return An interned reference, or this object if it was previously interned.
84 public final @NonNull Absolute intern() {
85 return INTERNER.intern(this);
89 final String className() {
95 * A descendant schema node identifier.
97 public abstract static class Descendant extends SchemaNodeIdentifier {
103 * Create a descendant schema node identifier composed of a single node identifier.
105 * @param nodeIdentifier Single node identifier
106 * @return A descendant schema node identifier
107 * @throws NullPointerException if {@code nodeIdentifier} is null
109 public static @NonNull Descendant of(final QName nodeIdentifier) {
110 return new DescendantSingle(nodeIdentifier);
114 * Create a descendant schema node identifier composed of multiple node identifiers.
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
121 public static @NonNull Descendant of(final QName... nodeIdentifiers) {
122 return of(Arrays.asList(nodeIdentifiers));
126 * Create a descendant schema node identifier composed of multiple node identifiers.
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
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);
139 final String className() {
144 private static final class AbsoluteSingle extends Absolute {
145 private final @NonNull QName qname;
147 AbsoluteSingle(final QName qname) {
148 this.qname = requireNonNull(qname);
152 public ImmutableList<QName> getNodeIdentifiers() {
153 return ImmutableList.of(qname);
157 public QName firstNodeIdentifier() {
162 public QName lastNodeIdentifier() {
167 Object pathObject() {
172 private static final class AbsoluteMultiple extends Absolute {
173 private final @NonNull ImmutableList<QName> qnames;
175 AbsoluteMultiple(final ImmutableList<QName> qnames) {
176 this.qnames = requireNonNull(qnames);
180 public ImmutableList<QName> getNodeIdentifiers() {
185 Object pathObject() {
190 private static final class DescendantSingle extends Descendant {
191 private final @NonNull QName qname;
193 DescendantSingle(final QName qname) {
194 this.qname = requireNonNull(qname);
198 public ImmutableList<QName> getNodeIdentifiers() {
199 return ImmutableList.of(qname);
203 public QName firstNodeIdentifier() {
208 public QName lastNodeIdentifier() {
213 Object pathObject() {
218 private static final class DescandantMultiple extends Descendant {
219 private final @NonNull ImmutableList<QName> qnames;
221 DescandantMultiple(final ImmutableList<QName> qnames) {
222 this.qnames = requireNonNull(qnames);
226 public ImmutableList<QName> getNodeIdentifiers() {
231 Object pathObject() {
237 private volatile int hash;
239 SchemaNodeIdentifier() {
244 * Return the non-empty sequence of node identifiers which constitute this schema node identifier.
246 * @return Non-empty sequence of node identifiers
248 public abstract @NonNull List<QName> getNodeIdentifiers();
251 * Return the first node identifier. This method is equivalent to {@code getNodeIdentifiers().get(0)}, but is
252 * potentially more efficient.
254 * @return The first node identifier
256 public @NonNull QName firstNodeIdentifier() {
257 return getNodeIdentifiers().get(0);
261 * Return the last node identifier. This method is equivalent to {@code getNodeIdentifiers().get(size - 1)}, but
262 * is potentially more efficient.
264 * @return The last node identifier
266 public @NonNull QName lastNodeIdentifier() {
267 final List<QName> local = getNodeIdentifiers();
268 return local.get(local.size() - 1);
272 public final int hashCode() {
274 return (local = hash) != 0 ? local : (hash = pathObject().hashCode());
278 public final boolean equals(final Object obj) {
279 return this == obj || obj != null && getClass() == obj.getClass()
280 && pathObject().equals(((SchemaNodeIdentifier) obj).pathObject());
284 public final String toString() {
285 return MoreObjects.toStringHelper(className()).add("qnames", toStringQNames()).toString();
288 abstract @NonNull Object pathObject();
290 abstract @NonNull String className();
292 private List<?> toStringQNames() {
293 final List<QName> ids = getNodeIdentifiers();
294 return ids.size() < 2 ? ids : simplifyQNames(ids);
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");
305 private static List<?> simplifyQNames(final List<QName> qnames) {
306 final List<Object> ret = new ArrayList<>(qnames.size());
308 QNameModule prev = null;
309 for (QName qname : qnames) {
310 final QNameModule module = qname.getModule();
311 ret.add(module.equals(prev) ? qname.getLocalName() : qname);