Add SchemaNodeIdentifier.{first,last}Identifer()
[yangtools.git] / yang / 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 java.util.ArrayList;
18 import java.util.Arrays;
19 import java.util.Collection;
20 import java.util.List;
21 import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
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 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
27
28 /**
29  * Represents unique path to the every schema node inside the schema node identifier namespace. This concept is defined
30  * in <a href="https://tools.ietf.org/html/rfc7950#section-6.5">RFC7950</a>.
31  */
32 public abstract class SchemaNodeIdentifier implements Immutable {
33     /**
34      * An absolute schema node identifier.
35      */
36     public static final class Absolute extends SchemaNodeIdentifier {
37         private static final Interner<Absolute> INTERNER = Interners.newWeakInterner();
38
39         Absolute(final QName qname) {
40             super(qname);
41         }
42
43         Absolute(final Collection<QName> qnames) {
44             super(qnames);
45         }
46
47         public static @NonNull Absolute of(final QName nodeIdentifier) {
48             return new Absolute(nodeIdentifier);
49         }
50
51         public static @NonNull Absolute of(final QName... nodeIdentifiers) {
52             return new Absolute(Arrays.asList(nodeIdentifiers));
53         }
54
55         public static @NonNull Absolute of(final Collection<QName> nodeIdentifiers) {
56             return new Absolute(nodeIdentifiers);
57         }
58
59         public @NonNull Absolute intern() {
60             return INTERNER.intern(this);
61         }
62
63         @Override
64         SchemaPath implicitSchemaPathParent() {
65             return SchemaPath.ROOT;
66         }
67     }
68
69     /**
70      * A descendant schema node identifier.
71      */
72     public static final class Descendant extends SchemaNodeIdentifier {
73         Descendant(final QName qname) {
74             super(qname);
75         }
76
77         Descendant(final Collection<QName> qnames) {
78             super(qnames);
79         }
80
81         public static @NonNull Descendant of(final QName nodeIdentifier) {
82             return new Descendant(nodeIdentifier);
83         }
84
85         public static @NonNull Descendant of(final QName... nodeIdentifiers) {
86             return new Descendant(Arrays.asList(nodeIdentifiers));
87         }
88
89         public static @NonNull Descendant of(final Collection<QName> nodeIdentifiers) {
90             return new Descendant(nodeIdentifiers);
91         }
92
93         @Override
94         SchemaPath implicitSchemaPathParent() {
95             return SchemaPath.SAME;
96         }
97     }
98
99     private static final AtomicReferenceFieldUpdater<SchemaNodeIdentifier, SchemaPath> SCHEMAPATH_UPDATER =
100             AtomicReferenceFieldUpdater.newUpdater(SchemaNodeIdentifier.class, SchemaPath.class, "schemaPath");
101
102     private final @NonNull Object qnames;
103
104     // Cached SchemaPath.
105     private volatile SchemaPath schemaPath;
106     // Cached hashCode
107     private volatile int hash;
108
109     SchemaNodeIdentifier(final QName qname) {
110         this.qnames = requireNonNull(qname);
111     }
112
113     SchemaNodeIdentifier(final Collection<QName> qnames) {
114         final ImmutableList<QName> tmp = ImmutableList.copyOf(qnames);
115         checkArgument(!tmp.isEmpty(), "SchemaNodeIdentifier has to have at least one node identifier");
116         this.qnames = tmp.size() == 1 ? tmp.get(0) : tmp;
117     }
118
119     public final @NonNull List<QName> getNodeIdentifiers() {
120         return qnames instanceof QName ? ImmutableList.of((QName) qnames) : castQNames();
121     }
122
123     public final @NonNull QName firstNodeIdentifier() {
124         return qnames instanceof QName ? (QName) qnames : castQNames().get(0);
125     }
126
127     public final @NonNull QName lastNodeIdentifier() {
128         if (qnames instanceof QName) {
129             return (QName) qnames;
130         }
131         final ImmutableList<QName> cast = castQNames();
132         return cast.get(cast.size() - 1);
133     }
134
135     /**
136      * Create the {@link SchemaPath} equivalent of this identifier.
137      *
138      * @return SchemaPath equivalent.
139      */
140     public final @NonNull SchemaPath asSchemaPath() {
141         final SchemaPath ret = schemaPath;
142         return ret != null ? ret : loadSchemaPath();
143     }
144
145     private @NonNull SchemaPath loadSchemaPath() {
146         final SchemaPath newPath = implicitSchemaPathParent().createChild(getNodeIdentifiers());
147         return SCHEMAPATH_UPDATER.compareAndSet(this, null, newPath) ? newPath : schemaPath;
148     }
149
150     abstract SchemaPath implicitSchemaPathParent();
151
152     @Override
153     public final int hashCode() {
154         final int local;
155         return (local = hash) != 0 ? local : (hash = qnames.hashCode());
156     }
157
158     @Override
159     public final boolean equals(final Object obj) {
160         if (this == obj) {
161             return true;
162         }
163         if (obj == null || getClass() != obj.getClass()) {
164             return false;
165         }
166         return qnames.equals(((SchemaNodeIdentifier) obj).qnames);
167     }
168
169     @Override
170     public final String toString() {
171         return MoreObjects.toStringHelper(this).add("qnames", toStringQNames()).toString();
172     }
173
174     @SuppressWarnings("unchecked")
175     private @NonNull ImmutableList<QName> castQNames() {
176         return (ImmutableList<QName>) qnames;
177     }
178
179     private List<?> toStringQNames() {
180         final List<QName> ids = getNodeIdentifiers();
181         return ids.size() < 2 ? ids : simplifyQNames(ids);
182     }
183
184     private static List<?> simplifyQNames(final List<QName> qnames) {
185         final List<Object> ret = new ArrayList<>(qnames.size());
186
187         QNameModule prev = null;
188         for (QName qname : qnames) {
189             final QNameModule module = qname.getModule();
190             ret.add(module.equals(prev) ? qname.getLocalName() : qname);
191             prev = module;
192         }
193
194         return ret;
195     }
196 }