Bug 2444: Added concrete API definitions for YANG statements.
[yangtools.git] / yang / yang-model-api / src / main / java / org / opendaylight / yangtools / yang / model / api / stmt / SchemaNodeIdentifier.java
1 /*
2  * Copyright (c) 2015 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.stmt;
9
10 import com.google.common.base.Objects;
11 import com.google.common.base.Objects.ToStringHelper;
12 import com.google.common.base.Preconditions;
13 import com.google.common.collect.ImmutableList;
14 import com.google.common.collect.Iterables;
15 import java.util.Arrays;
16 import java.util.Iterator;
17 import java.util.List;
18 import java.util.NoSuchElementException;
19 import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
20 import org.opendaylight.yangtools.concepts.Immutable;
21 import org.opendaylight.yangtools.yang.common.QName;
22
23 /**
24  * Represents unique path to the every schema node inside the schema node identifier
25  * namespace.
26  *
27  */
28 public abstract class SchemaNodeIdentifier implements Immutable {
29     /**
30      * An absolute schema node identifier.
31      */
32     public static final class Absolute extends SchemaNodeIdentifier {
33         private Absolute(final SchemaNodeIdentifier 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 SchemaNodeIdentifier createInstance(final SchemaNodeIdentifier parent, final QName qname) {
44             return new Absolute(parent, qname);
45         }
46     }
47
48     /**
49      * A relative schema node identifier.
50      */
51     public static class Relative extends SchemaNodeIdentifier {
52         private Relative(final SchemaNodeIdentifier 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 SchemaNodeIdentifier createInstance(final SchemaNodeIdentifier parent, final QName qname) {
63             return new Relative(parent, qname);
64         }
65     }
66
67     @SuppressWarnings("rawtypes")
68     private static final AtomicReferenceFieldUpdater<SchemaNodeIdentifier, ImmutableList> LEGACYPATH_UPDATER =
69             AtomicReferenceFieldUpdater.newUpdater(SchemaNodeIdentifier.class, ImmutableList.class, "legacyPath");
70
71     /**
72      * Shared instance of the conceptual root schema node.
73      */
74     public static final SchemaNodeIdentifier ROOT = new Absolute(null, null);
75
76     /**
77      * Shared instance of the "same" relative schema node.
78      */
79     public static final SchemaNodeIdentifier SAME = new Relative(null, null);
80
81     /**
82      * Parent path.
83      */
84     private final SchemaNodeIdentifier 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     private ImmutableList<QName> getLegacyPath() {
103         ImmutableList<QName> ret = legacyPath;
104         if (ret == null) {
105             ret = ImmutableList.copyOf(getPathTowardsRoot()).reverse();
106             LEGACYPATH_UPDATER.lazySet(this, ret);
107         }
108
109         return ret;
110     }
111
112     /**
113      * Returns the complete path to schema node.
114      *
115      * @return list of <code>QName</code> instances which represents complete
116      *         path to schema node
117      *
118      * @deprecated Use {@link #getPathFromRoot()} instead.
119      */
120     @Deprecated
121     public List<QName> getPath() {
122         return getLegacyPath();
123     }
124
125     protected SchemaNodeIdentifier(final SchemaNodeIdentifier parent, final QName qname) {
126         this.parent = parent;
127         this.qname = qname;
128
129         int h = parent == null ? 0 : parent.hashCode();
130         if (qname != null) {
131             h = h * 31 + qname.hashCode();
132         }
133
134         hash = h;
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 SchemaNodeIdentifier create(final Iterable<QName> path, final boolean absolute) {
150         final SchemaNodeIdentifier 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 SchemaNodeIdentifier 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 schema node identifier
174      * @param qname next path element
175      * @return A new SchemaPath instance
176      */
177     protected abstract SchemaNodeIdentifier createInstance(SchemaNodeIdentifier 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 SchemaNodeIdentifier createChild(final Iterable<QName> relative) {
186         if (Iterables.isEmpty(relative)) {
187             return this;
188         }
189
190         SchemaNodeIdentifier parent = this;
191         for (QName qname : relative) {
192             parent = parent.createInstance(parent, qname);
193         }
194
195         return parent;
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 SchemaNodeIdentifier createChild(final SchemaNodeIdentifier relative) {
205         Preconditions.checkArgument(!relative.isAbsolute(), "Child creation requires relative path");
206
207         SchemaNodeIdentifier parent = this;
208         for (QName qname : relative.getPathFromRoot()) {
209             parent = parent.createInstance(parent, qname);
210         }
211
212         return parent;
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 SchemaNodeIdentifier 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 Iterator<QName>() {
250                     private SchemaNodeIdentifier current = SchemaNodeIdentifier.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                     @Override
269                     public void remove() {
270                         throw new UnsupportedOperationException("Component removal not supported");
271                     }
272                 };
273             }
274         };
275     }
276
277     /**
278      * Returns the immediate parent SchemaPath.
279      *
280      * @return Parent path, null if this SchemaPath is already toplevel.
281      */
282     public SchemaNodeIdentifier getParent() {
283         return parent;
284     }
285
286     /**
287      * Get the last component of this path.
288      *
289      * @return The last component of this path.
290      */
291     public final QName getLastComponent() {
292         return qname;
293     }
294
295     /**
296      * Describes whether schema node identifier is|isn't absolute.
297      *
298      * @return boolean value which is <code>true</code> if schema path is
299      *         absolute.
300      */
301     public abstract boolean isAbsolute();
302
303     @Override
304     public final int hashCode() {
305         return hash;
306     }
307
308     @Override
309     public boolean equals(final Object obj) {
310         if (this == obj) {
311             return true;
312         }
313         if (obj == null) {
314             return false;
315         }
316         if (getClass() != obj.getClass()) {
317             return false;
318         }
319         final SchemaNodeIdentifier other = (SchemaNodeIdentifier) obj;
320
321         if (qname != null) {
322             if (!qname.equals(other.qname)) {
323                 return false;
324             }
325         } else {
326             if (other.qname != null) {
327                 return false;
328             }
329         }
330
331         if (parent == null) {
332             return other.parent == null;
333         }
334         return parent.equals(other.parent);
335     }
336
337     @Override
338     public final String toString() {
339         return addToStringAttributes(Objects.toStringHelper(this)).toString();
340     }
341
342     protected ToStringHelper addToStringAttributes(final ToStringHelper toStringHelper) {
343         return toStringHelper.add("path", getPathFromRoot());
344     }
345 }