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