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