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