Use VarHandle to cache path in SchemaPath
[yangtools.git] / yang / yang-model-api / src / main / java / org / opendaylight / yangtools / yang / model / api / SchemaPath.java
1 /*
2  * Copyright (c) 2013 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;
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.base.MoreObjects.ToStringHelper;
15 import com.google.common.collect.ImmutableList;
16 import com.google.common.collect.Iterables;
17 import com.google.common.collect.Lists;
18 import com.google.common.collect.UnmodifiableIterator;
19 import java.lang.invoke.MethodHandles;
20 import java.lang.invoke.VarHandle;
21 import java.util.ArrayList;
22 import java.util.Arrays;
23 import java.util.List;
24 import java.util.NoSuchElementException;
25 import java.util.Objects;
26 import org.eclipse.jdt.annotation.NonNull;
27 import org.opendaylight.yangtools.concepts.Immutable;
28 import org.opendaylight.yangtools.yang.common.QName;
29
30 /**
31  * Represents unique path to the every node inside the module.
32  */
33 public abstract class SchemaPath implements Immutable {
34
35     /**
36      * An absolute SchemaPath.
37      */
38     // FIXME: 5.0.0: expose as a top-level construct (and use in APIs)
39     private static final class AbsoluteSchemaPath extends SchemaPath {
40         private AbsoluteSchemaPath(final SchemaPath parent, final QName qname) {
41             super(parent, qname);
42         }
43
44         @Override
45         public boolean isAbsolute() {
46             return true;
47         }
48
49         @Override
50         public AbsoluteSchemaPath createChild(final QName element) {
51             return new AbsoluteSchemaPath(this, requireNonNull(element));
52         }
53     }
54
55     /**
56      * A relative SchemaPath.
57      */
58     // FIXME: 5.0.0: expose as a top-level construct (and use in APIs)
59     private static final class RelativeSchemaPath extends SchemaPath {
60         private RelativeSchemaPath(final SchemaPath parent, final QName qname) {
61             super(parent, qname);
62         }
63
64         @Override
65         public boolean isAbsolute() {
66             return false;
67         }
68
69         @Override
70         public RelativeSchemaPath createChild(final QName element) {
71             return new RelativeSchemaPath(this, requireNonNull(element));
72         }
73     }
74
75     private static final VarHandle LEGACY_PATH;
76
77     static {
78         try {
79             LEGACY_PATH = MethodHandles.lookup().findVarHandle(SchemaPath.class, "legacyPath", ImmutableList.class);
80         } catch (NoSuchFieldException | IllegalAccessException e) {
81             throw new ExceptionInInitializerError(e);
82         }
83     }
84
85     /**
86      * Shared instance of the conceptual root schema node.
87      */
88     public static final @NonNull SchemaPath ROOT = new AbsoluteSchemaPath(null, null);
89
90     /**
91      * Shared instance of the "same" relative schema node.
92      */
93     public static final @NonNull SchemaPath SAME = new RelativeSchemaPath(null, null);
94
95     /**
96      * Parent path.
97      */
98     private final SchemaPath parent;
99
100     /**
101      * This component.
102      */
103     private final QName qname;
104
105     /**
106      * Cached hash code. We can use this since we are immutable.
107      */
108     private final int hash;
109
110     /**
111      * Cached legacy path, filled-in when {@link #getPath()} or {@link #getPathTowardsRoot()} is invoked.
112      */
113     @SuppressWarnings("unused")
114     private volatile ImmutableList<QName> legacyPath;
115
116     SchemaPath(final SchemaPath parent, final QName qname) {
117         this.parent = parent;
118         this.qname = qname;
119
120         int tmp = Objects.hashCode(parent);
121         if (qname != null) {
122             tmp = tmp * 31 + qname.hashCode();
123         }
124
125         hash = tmp;
126     }
127
128     private ImmutableList<QName> getLegacyPath() {
129         final ImmutableList<QName> local = (ImmutableList<QName>) LEGACY_PATH.getAcquire(this);
130         return local != null ? local : loadLegacyPath();
131     }
132
133     @SuppressWarnings("unchecked")
134     private ImmutableList<QName> loadLegacyPath() {
135         final List<QName> tmp = new ArrayList<>();
136         for (QName item : getPathTowardsRoot()) {
137             tmp.add(item);
138         }
139         final ImmutableList<QName> ret = ImmutableList.copyOf(Lists.reverse(tmp));
140         final Object witness = LEGACY_PATH.compareAndExchangeRelease(this, null, ret);
141         return witness == null ? ret : (ImmutableList<QName>) witness;
142     }
143
144     /**
145      * Returns the complete path to schema node.
146      *
147      * @return list of <code>QName</code> instances which represents complete
148      *         path to schema node
149      *
150      * @deprecated Use {@link #getPathFromRoot()} instead.
151      */
152     @Deprecated(forRemoval = true)
153     public List<QName> getPath() {
154         return getLegacyPath();
155     }
156
157     /**
158      * Constructs new instance of this class with the concrete path.
159      *
160      * @param path
161      *            list of QName instances which specifies exact path to the
162      *            module node
163      * @param absolute
164      *            boolean value which specifies if the path is absolute or
165      *            relative
166      *
167      * @return A SchemaPath instance.
168      */
169     public static @NonNull SchemaPath create(final Iterable<QName> path, final boolean absolute) {
170         return (absolute ? ROOT : SAME).createChild(path);
171     }
172
173     /**
174      * Constructs new instance of this class with the concrete path.
175      *
176      * @param absolute
177      *            boolean value which specifies if the path is absolute or
178      *            relative
179      * @param element
180      *            a single QName which specifies exact path to the
181      *            module node
182      *
183      * @return A SchemaPath instance.
184      */
185     public static @NonNull SchemaPath create(final boolean absolute, final QName element) {
186         return (absolute ? ROOT : SAME).createChild(element);
187     }
188
189     /**
190      * Constructs new instance of this class with the concrete path.
191      *
192      * @param absolute
193      *            boolean value which specifies if the path is absolute or
194      *            relative
195      * @param path
196      *            one or more QName instances which specifies exact path to the
197      *            module node
198      *
199      * @return A SchemaPath instance.
200      */
201     public static @NonNull SchemaPath create(final boolean absolute, final QName... path) {
202         return create(Arrays.asList(path), absolute);
203     }
204
205     /**
206      * Create a child path based on concatenation of this path and a relative path.
207      *
208      * @param relative Relative path
209      * @return A new child path
210      */
211     public @NonNull SchemaPath createChild(final Iterable<QName> relative) {
212         if (Iterables.isEmpty(relative)) {
213             return this;
214         }
215
216         SchemaPath parentPath = this;
217         for (QName item : relative) {
218             parentPath = parentPath.createChild(item);
219         }
220
221         return parentPath;
222     }
223
224     /**
225      * Create a child path based on concatenation of this path and a relative path.
226      *
227      * @param relative Relative SchemaPath
228      * @return A new child path
229      */
230     public @NonNull SchemaPath createChild(final SchemaPath relative) {
231         checkArgument(!relative.isAbsolute(), "Child creation requires relative path");
232         return createChild(relative.getPathFromRoot());
233     }
234
235     /**
236      * Create a child path based on concatenation of this path and an additional path element.
237      *
238      * @param element Relative SchemaPath elements
239      * @return A new child path
240      */
241     public abstract @NonNull SchemaPath createChild(QName element);
242
243     /**
244      * Create a child path based on concatenation of this path and additional
245      * path elements.
246      *
247      * @param elements Relative SchemaPath elements
248      * @return A new child path
249      */
250     public @NonNull SchemaPath createChild(final QName... elements) {
251         return createChild(Arrays.asList(elements));
252     }
253
254     /**
255      * Returns the list of nodes which need to be traversed to get from the
256      * starting point (root for absolute SchemaPaths) to the node represented
257      * by this object.
258      *
259      * @return list of <code>qname</code> instances which represents
260      *         path from the root to the schema node.
261      */
262     public Iterable<QName> getPathFromRoot() {
263         return getLegacyPath();
264     }
265
266     /**
267      * Returns the list of nodes which need to be traversed to get from this
268      * node to the starting point (root for absolute SchemaPaths).
269      *
270      * @return list of <code>qname</code> instances which represents
271      *         path from the schema node towards the root.
272      */
273     public Iterable<QName> getPathTowardsRoot() {
274         return () -> new UnmodifiableIterator<>() {
275             private SchemaPath current = SchemaPath.this;
276
277             @Override
278             public boolean hasNext() {
279                 return current.parent != null;
280             }
281
282             @Override
283             public QName next() {
284                 if (current.parent != null) {
285                     final QName ret = current.qname;
286                     current = current.parent;
287                     return ret;
288                 }
289
290                 throw new NoSuchElementException("No more elements available");
291             }
292         };
293     }
294
295     /**
296      * Returns the immediate parent SchemaPath.
297      *
298      * @return Parent path, null if this SchemaPath is already toplevel.
299      */
300     public SchemaPath getParent() {
301         return parent;
302     }
303
304     /**
305      * Get the last component of this path.
306      *
307      * @return The last component of this path.
308      */
309     public final QName getLastComponent() {
310         return qname;
311     }
312
313     /**
314      * Describes whether schema path is|isn't absolute.
315      *
316      * @return boolean value which is <code>true</code> if schema path is
317      *         absolute.
318      */
319     public abstract boolean isAbsolute();
320
321     @Override
322     public final int hashCode() {
323         return hash;
324     }
325
326     @Override
327     public boolean equals(final Object obj) {
328         if (this == obj) {
329             return true;
330         }
331         if (obj == null) {
332             return false;
333         }
334         if (getClass() != obj.getClass()) {
335             return false;
336         }
337         final SchemaPath other = (SchemaPath) obj;
338         return Objects.equals(qname, other.qname) && Objects.equals(parent, other.parent);
339     }
340
341     @Override
342     public final String toString() {
343         return addToStringAttributes(MoreObjects.toStringHelper(this)).toString();
344     }
345
346     protected ToStringHelper addToStringAttributes(final ToStringHelper toStringHelper) {
347         return toStringHelper.add("path", getPathFromRoot());
348     }
349 }