Enforce SchemaPath correctness
[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.Objects;
21 import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
22 import org.opendaylight.yangtools.concepts.Immutable;
23 import org.opendaylight.yangtools.yang.common.QName;
24
25 /**
26  * Represents unique path to the every node inside the module.
27  */
28 public abstract class SchemaPath implements Immutable {
29
30     /**
31      * An absolute SchemaPath.
32      */
33     private static final class AbsoluteSchemaPath extends SchemaPath {
34         private AbsoluteSchemaPath(final SchemaPath parent, final QName qname) {
35             super(parent, qname);
36         }
37
38         @Override
39         public boolean isAbsolute() {
40             return true;
41         }
42
43         @Override
44         protected SchemaPath createInstance(final SchemaPath parent, final QName qname) {
45             return new AbsoluteSchemaPath(parent, Preconditions.checkNotNull(qname));
46         }
47     }
48
49     /**
50      * A relative SchemaPath.
51      */
52     private static final class RelativeSchemaPath extends SchemaPath {
53         private RelativeSchemaPath(final SchemaPath parent, final QName qname) {
54             super(parent, qname);
55         }
56
57         @Override
58         public boolean isAbsolute() {
59             return false;
60         }
61
62         @Override
63         protected SchemaPath createInstance(final SchemaPath parent, final QName qname) {
64             return new RelativeSchemaPath(parent, Preconditions.checkNotNull(qname));
65         }
66     }
67
68     @SuppressWarnings("rawtypes")
69     private static final AtomicReferenceFieldUpdater<SchemaPath, ImmutableList> LEGACYPATH_UPDATER =
70             AtomicReferenceFieldUpdater.newUpdater(SchemaPath.class, ImmutableList.class, "legacyPath");
71
72     /**
73      * Shared instance of the conceptual root schema node.
74      */
75     public static final SchemaPath ROOT = new AbsoluteSchemaPath(null, null);
76
77     /**
78      * Shared instance of the "same" relative schema node.
79      */
80     public static final SchemaPath SAME = new RelativeSchemaPath(null, null);
81
82     /**
83      * Parent path.
84      */
85     private final SchemaPath parent;
86
87     /**
88      * This component.
89      */
90     private final QName qname;
91
92     /**
93      * Cached hash code. We can use this since we are immutable.
94      */
95     private final int hash;
96
97     /**
98      * Cached legacy path, filled-in when {@link #getPath()} or {@link #getPathTowardsRoot()}
99      * is invoked.
100      */
101     private volatile ImmutableList<QName> legacyPath;
102
103     /**
104      * @deprecated This constructor will be hidden in a future release.
105      * @param parent
106      * @param qname
107      */
108     @Deprecated
109     protected SchemaPath(final SchemaPath parent, final QName qname) {
110         this.parent = parent;
111         this.qname = qname;
112
113         int h = Objects.hashCode(parent);
114         if (qname != null) {
115             h = h * 31 + qname.hashCode();
116         }
117
118         hash = h;
119     }
120
121     private ImmutableList<QName> getLegacyPath() {
122         ImmutableList<QName> ret = legacyPath;
123         if (ret == null) {
124             ret = ImmutableList.copyOf(getPathTowardsRoot()).reverse();
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 }