Remove SchemaPath.getPath()
[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      * Constructs new instance of this class with the concrete path.
146      *
147      * @param path
148      *            list of QName instances which specifies exact path to the
149      *            module node
150      * @param absolute
151      *            boolean value which specifies if the path is absolute or
152      *            relative
153      *
154      * @return A SchemaPath instance.
155      */
156     public static @NonNull SchemaPath create(final Iterable<QName> path, final boolean absolute) {
157         return (absolute ? ROOT : SAME).createChild(path);
158     }
159
160     /**
161      * Constructs new instance of this class with the concrete path.
162      *
163      * @param absolute
164      *            boolean value which specifies if the path is absolute or
165      *            relative
166      * @param element
167      *            a single QName which specifies exact path to the
168      *            module node
169      *
170      * @return A SchemaPath instance.
171      */
172     public static @NonNull SchemaPath create(final boolean absolute, final QName element) {
173         return (absolute ? ROOT : SAME).createChild(element);
174     }
175
176     /**
177      * Constructs new instance of this class with the concrete path.
178      *
179      * @param absolute
180      *            boolean value which specifies if the path is absolute or
181      *            relative
182      * @param path
183      *            one or more QName instances which specifies exact path to the
184      *            module node
185      *
186      * @return A SchemaPath instance.
187      */
188     public static @NonNull SchemaPath create(final boolean absolute, final QName... path) {
189         return create(Arrays.asList(path), absolute);
190     }
191
192     /**
193      * Create a child path based on concatenation of this path and a relative path.
194      *
195      * @param relative Relative path
196      * @return A new child path
197      */
198     public @NonNull SchemaPath createChild(final Iterable<QName> relative) {
199         if (Iterables.isEmpty(relative)) {
200             return this;
201         }
202
203         SchemaPath parentPath = this;
204         for (QName item : relative) {
205             parentPath = parentPath.createChild(item);
206         }
207
208         return parentPath;
209     }
210
211     /**
212      * Create a child path based on concatenation of this path and a relative path.
213      *
214      * @param relative Relative SchemaPath
215      * @return A new child path
216      */
217     public @NonNull SchemaPath createChild(final SchemaPath relative) {
218         checkArgument(!relative.isAbsolute(), "Child creation requires relative path");
219         return createChild(relative.getPathFromRoot());
220     }
221
222     /**
223      * Create a child path based on concatenation of this path and an additional path element.
224      *
225      * @param element Relative SchemaPath elements
226      * @return A new child path
227      */
228     public abstract @NonNull SchemaPath createChild(QName element);
229
230     /**
231      * Create a child path based on concatenation of this path and additional
232      * path elements.
233      *
234      * @param elements Relative SchemaPath elements
235      * @return A new child path
236      */
237     public @NonNull SchemaPath createChild(final QName... elements) {
238         return createChild(Arrays.asList(elements));
239     }
240
241     /**
242      * Returns the list of nodes which need to be traversed to get from the
243      * starting point (root for absolute SchemaPaths) to the node represented
244      * by this object.
245      *
246      * @return list of <code>qname</code> instances which represents
247      *         path from the root to the schema node.
248      */
249     public Iterable<QName> getPathFromRoot() {
250         return getLegacyPath();
251     }
252
253     /**
254      * Returns the list of nodes which need to be traversed to get from this
255      * node to the starting point (root for absolute SchemaPaths).
256      *
257      * @return list of <code>qname</code> instances which represents
258      *         path from the schema node towards the root.
259      */
260     public Iterable<QName> getPathTowardsRoot() {
261         return () -> new UnmodifiableIterator<>() {
262             private SchemaPath current = SchemaPath.this;
263
264             @Override
265             public boolean hasNext() {
266                 return current.parent != null;
267             }
268
269             @Override
270             public QName next() {
271                 if (current.parent != null) {
272                     final QName ret = current.qname;
273                     current = current.parent;
274                     return ret;
275                 }
276
277                 throw new NoSuchElementException("No more elements available");
278             }
279         };
280     }
281
282     /**
283      * Returns the immediate parent SchemaPath.
284      *
285      * @return Parent path, null if this SchemaPath is already toplevel.
286      */
287     public SchemaPath getParent() {
288         return parent;
289     }
290
291     /**
292      * Get the last component of this path.
293      *
294      * @return The last component of this path.
295      */
296     public final QName getLastComponent() {
297         return qname;
298     }
299
300     /**
301      * Describes whether schema path is|isn't absolute.
302      *
303      * @return boolean value which is <code>true</code> if schema path is
304      *         absolute.
305      */
306     public abstract boolean isAbsolute();
307
308     @Override
309     public final int hashCode() {
310         return hash;
311     }
312
313     @Override
314     public boolean equals(final Object obj) {
315         if (this == obj) {
316             return true;
317         }
318         if (obj == null) {
319             return false;
320         }
321         if (getClass() != obj.getClass()) {
322             return false;
323         }
324         final SchemaPath other = (SchemaPath) obj;
325         return Objects.equals(qname, other.qname) && Objects.equals(parent, other.parent);
326     }
327
328     @Override
329     public final String toString() {
330         return addToStringAttributes(MoreObjects.toStringHelper(this)).toString();
331     }
332
333     protected ToStringHelper addToStringAttributes(final ToStringHelper toStringHelper) {
334         return toStringHelper.add("path", getPathFromRoot());
335     }
336 }