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