Do not cache path from root
[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 java.util.Objects.requireNonNull;
12
13 import com.google.common.base.MoreObjects;
14 import com.google.common.base.MoreObjects.ToStringHelper;
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.NoSuchElementException;
20 import java.util.Objects;
21 import org.eclipse.jdt.annotation.NonNull;
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     // FIXME: 5.0.0: expose as a top-level construct (and use in APIs)
34     private static final class AbsoluteSchemaPath extends SchemaPath {
35         private AbsoluteSchemaPath(final SchemaPath parent, final QName qname) {
36             super(parent, qname);
37         }
38
39         @Override
40         public boolean isAbsolute() {
41             return true;
42         }
43
44         @Override
45         public AbsoluteSchemaPath createChild(final QName element) {
46             return new AbsoluteSchemaPath(this, requireNonNull(element));
47         }
48     }
49
50     /**
51      * A relative SchemaPath.
52      */
53     // FIXME: 5.0.0: expose as a top-level construct (and use in APIs)
54     private static final class RelativeSchemaPath extends SchemaPath {
55         private RelativeSchemaPath(final SchemaPath parent, final QName qname) {
56             super(parent, qname);
57         }
58
59         @Override
60         public boolean isAbsolute() {
61             return false;
62         }
63
64         @Override
65         public RelativeSchemaPath createChild(final QName element) {
66             return new RelativeSchemaPath(this, requireNonNull(element));
67         }
68     }
69
70     /**
71      * Shared instance of the conceptual root schema node.
72      */
73     public static final @NonNull SchemaPath ROOT = new AbsoluteSchemaPath(null, null);
74
75     /**
76      * Shared instance of the "same" relative schema node.
77      */
78     public static final @NonNull SchemaPath SAME = new RelativeSchemaPath(null, null);
79
80     /**
81      * Parent path.
82      */
83     private final SchemaPath parent;
84
85     /**
86      * This component.
87      */
88     private final QName qname;
89
90     /**
91      * Cached hash code. We can use this since we are immutable.
92      */
93     private final int hash;
94
95     SchemaPath(final SchemaPath parent, final QName qname) {
96         this.parent = parent;
97         this.qname = qname;
98
99         int tmp = Objects.hashCode(parent);
100         if (qname != null) {
101             tmp = tmp * 31 + qname.hashCode();
102         }
103
104         hash = tmp;
105     }
106
107     /**
108      * Constructs new instance of this class with the concrete path.
109      *
110      * @param path
111      *            list of QName instances which specifies exact path to the
112      *            module node
113      * @param absolute
114      *            boolean value which specifies if the path is absolute or
115      *            relative
116      *
117      * @return A SchemaPath instance.
118      */
119     public static @NonNull SchemaPath create(final Iterable<QName> path, final boolean absolute) {
120         return (absolute ? ROOT : SAME).createChild(path);
121     }
122
123     /**
124      * Constructs new instance of this class with the concrete path.
125      *
126      * @param absolute
127      *            boolean value which specifies if the path is absolute or
128      *            relative
129      * @param element
130      *            a single QName which specifies exact path to the
131      *            module node
132      *
133      * @return A SchemaPath instance.
134      */
135     public static @NonNull SchemaPath create(final boolean absolute, final QName element) {
136         return (absolute ? ROOT : SAME).createChild(element);
137     }
138
139     /**
140      * Constructs new instance of this class with the concrete path.
141      *
142      * @param absolute
143      *            boolean value which specifies if the path is absolute or
144      *            relative
145      * @param path
146      *            one or more QName instances which specifies exact path to the
147      *            module node
148      *
149      * @return A SchemaPath instance.
150      */
151     public static @NonNull SchemaPath create(final boolean absolute, final QName... path) {
152         return create(Arrays.asList(path), absolute);
153     }
154
155     /**
156      * Create a child path based on concatenation of this path and a relative path.
157      *
158      * @param relative Relative path
159      * @return A new child path
160      */
161     public @NonNull SchemaPath createChild(final Iterable<QName> relative) {
162         if (Iterables.isEmpty(relative)) {
163             return this;
164         }
165
166         SchemaPath parentPath = this;
167         for (QName item : relative) {
168             parentPath = parentPath.createChild(item);
169         }
170
171         return parentPath;
172     }
173
174     /**
175      * Create a child path based on concatenation of this path and a relative path.
176      *
177      * @param relative Relative SchemaPath
178      * @return A new child path
179      */
180     public @NonNull SchemaPath createChild(final SchemaPath relative) {
181         checkArgument(!relative.isAbsolute(), "Child creation requires relative path");
182         return createChild(relative.getPathFromRoot());
183     }
184
185     /**
186      * Create a child path based on concatenation of this path and an additional path element.
187      *
188      * @param element Relative SchemaPath elements
189      * @return A new child path
190      */
191     public abstract @NonNull SchemaPath createChild(QName element);
192
193     /**
194      * Create a child path based on concatenation of this path and additional
195      * path elements.
196      *
197      * @param elements Relative SchemaPath elements
198      * @return A new child path
199      */
200     public @NonNull SchemaPath createChild(final QName... elements) {
201         return createChild(Arrays.asList(elements));
202     }
203
204     /**
205      * Returns the list of nodes which need to be traversed to get from the
206      * starting point (root for absolute SchemaPaths) to the node represented
207      * by this object.
208      *
209      * @return list of <code>qname</code> instances which represents
210      *         path from the root to the schema node.
211      */
212     public Iterable<QName> getPathFromRoot() {
213         if (qname == null) {
214             return ImmutableList.of();
215         }
216         return parent == null ? ImmutableList.of(qname) : new PathFromRoot(this);
217     }
218
219     /**
220      * Returns the list of nodes which need to be traversed to get from this
221      * node to the starting point (root for absolute SchemaPaths).
222      *
223      * @return list of <code>qname</code> instances which represents
224      *         path from the schema node towards the root.
225      */
226     public Iterable<QName> getPathTowardsRoot() {
227         return () -> new UnmodifiableIterator<>() {
228             private SchemaPath current = SchemaPath.this;
229
230             @Override
231             public boolean hasNext() {
232                 return current.parent != null;
233             }
234
235             @Override
236             public QName next() {
237                 if (current.parent != null) {
238                     final QName ret = current.qname;
239                     current = current.parent;
240                     return ret;
241                 }
242
243                 throw new NoSuchElementException("No more elements available");
244             }
245         };
246     }
247
248     /**
249      * Returns the immediate parent SchemaPath.
250      *
251      * @return Parent path, null if this SchemaPath is already toplevel.
252      */
253     public SchemaPath getParent() {
254         return parent;
255     }
256
257     /**
258      * Get the last component of this path.
259      *
260      * @return The last component of this path.
261      */
262     public final QName getLastComponent() {
263         return qname;
264     }
265
266     /**
267      * Describes whether schema path is|isn't absolute.
268      *
269      * @return boolean value which is <code>true</code> if schema path is
270      *         absolute.
271      */
272     public abstract boolean isAbsolute();
273
274     @Override
275     public final int hashCode() {
276         return hash;
277     }
278
279     @Override
280     public boolean equals(final Object obj) {
281         if (this == obj) {
282             return true;
283         }
284         if (obj == null) {
285             return false;
286         }
287         if (getClass() != obj.getClass()) {
288             return false;
289         }
290         final SchemaPath other = (SchemaPath) obj;
291         return Objects.equals(qname, other.qname) && Objects.equals(parent, other.parent);
292     }
293
294     @Override
295     public final String toString() {
296         return addToStringAttributes(MoreObjects.toStringHelper(this)).toString();
297     }
298
299     protected ToStringHelper addToStringAttributes(final ToStringHelper toStringHelper) {
300         return toStringHelper.add("path", getPathFromRoot());
301     }
302 }