04975deefafc9a6936a2bd9bacd29610bace8980
[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, 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, 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     protected SchemaPath(final SchemaPath parent, final QName qname) {
104         this.parent = parent;
105         this.qname = qname;
106
107         int h = Objects.hashCode(parent);
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 SchemaPath create(final Iterable<QName> path, final boolean absolute) {
151         final SchemaPath 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 SchemaPath 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 SchemaPath
175      * @param qname next path element
176      * @return A new SchemaPath instance
177      */
178     protected abstract SchemaPath createInstance(SchemaPath 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 SchemaPath createChild(final Iterable<QName> relative) {
187         if (Iterables.isEmpty(relative)) {
188             return this;
189         }
190
191         SchemaPath parentPath = this;
192         for (QName qname : relative) {
193             parentPath = parentPath.createInstance(parentPath, qname);
194         }
195
196         return parentPath;
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 SchemaPath createChild(final SchemaPath relative) {
206         Preconditions.checkArgument(!relative.isAbsolute(), "Child creation requires relative path");
207
208         SchemaPath parentPath = this;
209         for (QName qname : relative.getPathFromRoot()) {
210             parentPath = parentPath.createInstance(parentPath, qname);
211         }
212
213         return parentPath;
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 SchemaPath 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 UnmodifiableIterator<QName>() {
251                     private SchemaPath current = SchemaPath.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             }
270         };
271     }
272
273     /**
274      * Returns the immediate parent SchemaPath.
275      *
276      * @return Parent path, null if this SchemaPath is already toplevel.
277      */
278     public SchemaPath getParent() {
279         return parent;
280     }
281
282     /**
283      * Get the last component of this path.
284      *
285      * @return The last component of this path.
286      */
287     public final QName getLastComponent() {
288         return qname;
289     }
290
291     /**
292      * Describes whether schema path is|isn't absolute.
293      *
294      * @return boolean value which is <code>true</code> if schema path is
295      *         absolute.
296      */
297     public abstract boolean isAbsolute();
298
299     @Override
300     public final int hashCode() {
301         return hash;
302     }
303
304     @Override
305     public boolean equals(final Object obj) {
306         if (this == obj) {
307             return true;
308         }
309         if (obj == null) {
310             return false;
311         }
312         if (getClass() != obj.getClass()) {
313             return false;
314         }
315         final SchemaPath other = (SchemaPath) obj;
316         return Objects.equals(qname, other.qname) && Objects.equals(parent, other.parent);
317     }
318
319     @Override
320     public final String toString() {
321         return addToStringAttributes(MoreObjects.toStringHelper(this)).toString();
322     }
323
324     protected ToStringHelper addToStringAttributes(final ToStringHelper toStringHelper) {
325         return toStringHelper.add("path", getPathFromRoot());
326     }
327 }