Merge "Introduce InstanceIdToNodes into yang-data-impl"
[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     /**
31      * An absolute schema node identifier.
32      */
33     public static final class Absolute extends SchemaNodeIdentifier {
34         private Absolute(final SchemaNodeIdentifier 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 SchemaNodeIdentifier createInstance(final SchemaNodeIdentifier parent, final QName qname) {
45             return new Absolute(parent, qname);
46         }
47     }
48
49     /**
50      * A relative schema node identifier.
51      */
52     public static class Relative extends SchemaNodeIdentifier {
53         private Relative(final SchemaNodeIdentifier 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 SchemaNodeIdentifier createInstance(final SchemaNodeIdentifier parent, final QName qname) {
64             return new Relative(parent, qname);
65         }
66     }
67
68     @SuppressWarnings("rawtypes")
69     private static final AtomicReferenceFieldUpdater<SchemaNodeIdentifier, ImmutableList> LEGACYPATH_UPDATER =
70             AtomicReferenceFieldUpdater.newUpdater(SchemaNodeIdentifier.class, ImmutableList.class, "legacyPath");
71
72     /**
73      * Shared instance of the conceptual root schema node.
74      */
75     public static final SchemaNodeIdentifier ROOT = new Absolute(null, null);
76
77     /**
78      * Shared instance of the "same" relative schema node.
79      */
80     public static final SchemaNodeIdentifier SAME = new Relative(null, null);
81
82     /**
83      * Parent path.
84      */
85     private final SchemaNodeIdentifier 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     protected SchemaNodeIdentifier(final SchemaNodeIdentifier parent, final QName qname) {
104         this.parent = parent;
105         this.qname = qname;
106
107         int h = parent == null ? 0 : parent.hashCode();
108         if (qname != null) {
109             h = h * 31 + qname.hashCode();
110         }
111
112         hash = h;
113     }
114
115     private ImmutableList<QName> getLegacyPath() {
116         ImmutableList<QName> ret = legacyPath;
117         if (ret == null) {
118             ret = ImmutableList.copyOf(getPathTowardsRoot()).reverse();
119             LEGACYPATH_UPDATER.lazySet(this, ret);
120         }
121
122         return ret;
123     }
124
125     /**
126      * Returns the complete path to schema node.
127      *
128      * @return list of <code>QName</code> instances which represents complete
129      *         path to schema node
130      *
131      * @deprecated Use {@link #getPathFromRoot()} instead.
132      */
133     @Deprecated
134     public List<QName> getPath() {
135         return getLegacyPath();
136     }
137
138     /**
139      * Constructs new instance of this class with the concrete path.
140      *
141      * @param path
142      *            list of QName instances which specifies exact path to the
143      *            module node
144      * @param absolute
145      *            boolean value which specifies if the path is absolute or
146      *            relative
147      *
148      * @return A SchemaPath instance.
149      */
150     public static SchemaNodeIdentifier create(final Iterable<QName> path, final boolean absolute) {
151         final SchemaNodeIdentifier parent = absolute ? ROOT : SAME;
152         return parent.createChild(path);
153     }
154
155     /**
156      * Constructs new instance of this class with the concrete path.
157      *
158      * @param absolute
159      *            boolean value which specifies if the path is absolute or
160      *            relative
161      * @param path
162      *            one or more QName instances which specifies exact path to the
163      *            module node
164      *
165      * @return A SchemaPath instance.
166      */
167     public static SchemaNodeIdentifier create(final boolean absolute, final QName... path) {
168         return create(Arrays.asList(path), absolute);
169     }
170
171     /**
172      * Create a new instance.
173      *
174      * @param parent Parent schema node identifier
175      * @param qname next path element
176      * @return A new SchemaPath instance
177      */
178     protected abstract SchemaNodeIdentifier createInstance(SchemaNodeIdentifier parent, QName qname);
179
180     /**
181      * Create a child path based on concatenation of this path and a relative path.
182      *
183      * @param relative Relative path
184      * @return A new child path
185      */
186     public SchemaNodeIdentifier createChild(final Iterable<QName> relative) {
187         if (Iterables.isEmpty(relative)) {
188             return this;
189         }
190
191         SchemaNodeIdentifier parentNode = this;
192         for (QName qname : relative) {
193             parentNode = parentNode.createInstance(parentNode, qname);
194         }
195
196         return parentNode;
197     }
198
199     /**
200      * Create a child path based on concatenation of this path and a relative path.
201      *
202      * @param relative Relative SchemaPath
203      * @return A new child path
204      */
205     public SchemaNodeIdentifier createChild(final SchemaNodeIdentifier relative) {
206         Preconditions.checkArgument(!relative.isAbsolute(), "Child creation requires relative path");
207
208         SchemaNodeIdentifier parentNode = this;
209         for (QName qname : relative.getPathFromRoot()) {
210             parentNode = parentNode.createInstance(parentNode, qname);
211         }
212
213         return parentNode;
214     }
215
216     /**
217      * Create a child path based on concatenation of this path and additional
218      * path elements.
219      *
220      * @param elements Relative SchemaPath elements
221      * @return A new child path
222      */
223     public SchemaNodeIdentifier createChild(final QName... elements) {
224         return createChild(Arrays.asList(elements));
225     }
226
227     /**
228      * Returns the list of nodes which need to be traversed to get from the
229      * starting point (root for absolute SchemaPaths) to the node represented
230      * by this object.
231      *
232      * @return list of <code>qname</code> instances which represents
233      *         path from the root to the schema node.
234      */
235     public Iterable<QName> getPathFromRoot() {
236         return getLegacyPath();
237     }
238
239     /**
240      * Returns the list of nodes which need to be traversed to get from this
241      * node to the starting point (root for absolute SchemaPaths).
242      *
243      * @return list of <code>qname</code> instances which represents
244      *         path from the schema node towards the root.
245      */
246     public Iterable<QName> getPathTowardsRoot() {
247         return new Iterable<QName>() {
248             @Override
249             public Iterator<QName> iterator() {
250                 return new Iterator<QName>() {
251                     private SchemaNodeIdentifier current = SchemaNodeIdentifier.this;
252
253                     @Override
254                     public boolean hasNext() {
255                         return current.parent != null;
256                     }
257
258                     @Override
259                     public QName next() {
260                         if (current.parent != null) {
261                             final QName ret = current.qname;
262                             current = current.parent;
263                             return ret;
264                         } else {
265                             throw new NoSuchElementException("No more elements available");
266                         }
267                     }
268
269                     @Override
270                     public void remove() {
271                         throw new UnsupportedOperationException("Component removal not supported");
272                     }
273                 };
274             }
275         };
276     }
277
278     /**
279      * Returns the immediate parent SchemaPath.
280      *
281      * @return Parent path, null if this SchemaPath is already toplevel.
282      */
283     public SchemaNodeIdentifier getParent() {
284         return parent;
285     }
286
287     /**
288      * Get the last component of this path.
289      *
290      * @return The last component of this path.
291      */
292     public final QName getLastComponent() {
293         return qname;
294     }
295
296     /**
297      * Describes whether schema node identifier is|isn't absolute.
298      *
299      * @return boolean value which is <code>true</code> if schema path is
300      *         absolute.
301      */
302     public abstract boolean isAbsolute();
303
304     @Override
305     public final int hashCode() {
306         return hash;
307     }
308
309     @Override
310     public boolean equals(final Object obj) {
311         if (this == obj) {
312             return true;
313         }
314         if (obj == null) {
315             return false;
316         }
317         if (getClass() != obj.getClass()) {
318             return false;
319         }
320         final SchemaNodeIdentifier other = (SchemaNodeIdentifier) obj;
321
322         if (qname != null) {
323             if (!qname.equals(other.qname)) {
324                 return false;
325             }
326         } else {
327             if (other.qname != null) {
328                 return false;
329             }
330         }
331
332         if (parent == null) {
333             return other.parent == null;
334         }
335         return parent.equals(other.parent);
336     }
337
338     @Override
339     public final String toString() {
340         return addToStringAttributes(Objects.toStringHelper(this)).toString();
341     }
342
343     protected ToStringHelper addToStringAttributes(final ToStringHelper toStringHelper) {
344         return toStringHelper.add("path", getPathFromRoot());
345     }
346 }