e01c3224a8886327b34172005f535a3668a0749d
[yangtools.git] / data / yang-data-tree-ri / src / main / java / org / opendaylight / yangtools / yang / data / tree / leafref / LeafRefPath.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.data.tree.leafref;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11
12 import com.google.common.collect.ImmutableList;
13 import com.google.common.collect.Iterables;
14 import java.lang.invoke.MethodHandles;
15 import java.lang.invoke.VarHandle;
16 import java.util.Arrays;
17 import java.util.Iterator;
18 import java.util.NoSuchElementException;
19 import java.util.Objects;
20 import org.opendaylight.yangtools.concepts.Immutable;
21
22 public abstract class LeafRefPath implements Immutable {
23     /**
24      * An absolute LeafRefPath.
25      */
26     private static final class AbsoluteLeafRefPath extends LeafRefPath {
27         private AbsoluteLeafRefPath(final LeafRefPath parent, final QNameWithPredicate qname) {
28             super(parent, qname);
29         }
30
31         @Override
32         public boolean isAbsolute() {
33             return true;
34         }
35
36         @Override
37         protected LeafRefPath createInstance(final LeafRefPath newParent, final QNameWithPredicate newQname) {
38             return new AbsoluteLeafRefPath(newParent, newQname);
39         }
40     }
41
42     /**
43      * A relative LeafRefPath.
44      */
45     private static final class RelativeLeafRefPath extends LeafRefPath {
46         private RelativeLeafRefPath(final LeafRefPath parent, final QNameWithPredicate qname) {
47             super(parent, qname);
48         }
49
50         @Override
51         public boolean isAbsolute() {
52             return false;
53         }
54
55         @Override
56         protected LeafRefPath createInstance(final LeafRefPath newParent, final QNameWithPredicate newQname) {
57             return new RelativeLeafRefPath(newParent, newQname);
58         }
59     }
60
61     private static final VarHandle LEGACYPATH;
62
63     static {
64         try {
65             LEGACYPATH = MethodHandles.lookup().findVarHandle(LeafRefPath.class, "legacyPath", ImmutableList.class);
66         } catch (NoSuchFieldException | IllegalAccessException e) {
67             throw new ExceptionInInitializerError(e);
68         }
69     }
70
71     /**
72      * Shared instance of the conceptual root schema node.
73      */
74     public static final LeafRefPath ROOT = new AbsoluteLeafRefPath(null, null);
75
76     /**
77      * Shared instance of the "same" relative schema node.
78      */
79     public static final LeafRefPath SAME = new RelativeLeafRefPath(null, null);
80
81     /**
82      * Parent path.
83      */
84     private final LeafRefPath parent;
85
86     /**
87      * This component.
88      */
89     private final QNameWithPredicate 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 #getPathFromRoot()} is invoked.
98      */
99     @SuppressWarnings("unused")
100     private volatile ImmutableList<QNameWithPredicate> legacyPath;
101
102     protected LeafRefPath(final LeafRefPath parent, final QNameWithPredicate qname) {
103         this.parent = parent;
104         this.qname = qname;
105
106         int hc = Objects.hashCode(parent);
107         if (qname != null) {
108             hc = hc * 31 + qname.hashCode();
109         }
110
111         hash = hc;
112     }
113
114     /**
115      * Constructs new instance of this class with the concrete path.
116      *
117      * @param path list of QNameWithPredicate instances which specifies exact path to the module node
118      * @param absolute boolean value which specifies if the path is absolute or relative
119      * @return A LeafRefPath instance.
120      */
121     public static LeafRefPath create(final Iterable<QNameWithPredicate> path, final boolean absolute) {
122         final LeafRefPath parent = absolute ? ROOT : SAME;
123         return parent.createChild(path);
124     }
125
126     /**
127      * Constructs new instance of this class with the concrete path.
128      *
129      * @param absolute boolean value which specifies if the path is absolute or relative
130      * @param path one or more QNameWithPredicate instances which specifies exact path to the module node
131      * @return A LeafRefPath instance.
132      */
133     public static LeafRefPath create(final boolean absolute, final QNameWithPredicate... path) {
134         return create(Arrays.asList(path), absolute);
135     }
136
137     /**
138      * Create a new instance.
139      *
140      * @param newParent Parent LeafRefPath
141      * @param newQname next path element
142      * @return A new LeafRefPath instance
143      */
144     protected abstract LeafRefPath createInstance(LeafRefPath newParent, QNameWithPredicate newQname);
145
146     /**
147      * Create a child path based on concatenation of this path and a relative path.
148      *
149      * @param relative Relative path
150      * @return A new child path
151      */
152     public LeafRefPath createChild(final Iterable<QNameWithPredicate> relative) {
153         if (Iterables.isEmpty(relative)) {
154             return this;
155         }
156
157         LeafRefPath newParent = this;
158         for (QNameWithPredicate relativeQname : relative) {
159             newParent = newParent.createInstance(newParent, relativeQname);
160         }
161
162         return newParent;
163     }
164
165     /**
166      * Create a child path based on concatenation of this path and a relative path.
167      *
168      * @param relative Relative LeafRefPath
169      * @return A new child path
170      */
171     public LeafRefPath createChild(final LeafRefPath relative) {
172         checkArgument(!relative.isAbsolute(), "Child creation requires relative path");
173
174         LeafRefPath newParent = this;
175         for (QNameWithPredicate relativeQname : relative.getPathFromRoot()) {
176             newParent = newParent.createInstance(newParent, relativeQname);
177         }
178
179         return newParent;
180     }
181
182     /**
183      * Create a child path based on concatenation of this path and additional path elements.
184      *
185      * @param elements Relative LeafRefPath elements
186      * @return A new child path
187      */
188     public LeafRefPath createChild(final QNameWithPredicate... elements) {
189         return createChild(Arrays.asList(elements));
190     }
191
192     /**
193      * Returns the list of nodes which need to be traversed to get from the starting point (root for absolute
194      * LeafRefPaths) to the node represented by this object.
195      *
196      * @return list of {@code qname} instances which represents path from the root to the schema node.
197      */
198     public Iterable<QNameWithPredicate> getPathFromRoot() {
199         final var local = (ImmutableList<QNameWithPredicate>) LEGACYPATH.getAcquire(this);
200         return local != null ? local : loadLegacyPath();
201     }
202
203     private ImmutableList<QNameWithPredicate> loadLegacyPath() {
204         final var ret = ImmutableList.copyOf(getPathTowardsRoot()).reverse();
205         final var witness = (ImmutableList<QNameWithPredicate>) LEGACYPATH.compareAndExchangeRelease(this, null, ret);
206         return witness != null ? witness : ret;
207     }
208
209     /**
210      * Returns the list of nodes which need to be traversed to get from this node to the starting point (root
211      * for absolute LeafRefPaths).
212      *
213      * @return list of {@code qname} instances which represents path from the schema node towards the root.
214      */
215     public Iterable<QNameWithPredicate> getPathTowardsRoot() {
216         return () -> new Iterator<>() {
217             private LeafRefPath current = LeafRefPath.this;
218
219             @Override
220             public boolean hasNext() {
221                 return current.parent != null;
222             }
223
224             @Override
225             public QNameWithPredicate next() {
226                 if (current.parent == null) {
227                     throw new NoSuchElementException("No more elements available");
228                 }
229
230                 final QNameWithPredicate ret = current.qname;
231                 current = current.parent;
232                 return ret;
233             }
234         };
235     }
236
237     /**
238      * Returns the immediate parent LeafRefPath.
239      *
240      * @return Parent path, null if this LeafRefPath is already toplevel.
241      */
242     public LeafRefPath getParent() {
243         return parent;
244     }
245
246     /**
247      * Get the last component of this path.
248      *
249      * @return The last component of this path.
250      */
251     public final QNameWithPredicate getLastComponent() {
252         return qname;
253     }
254
255     /**
256      * Describes whether schema path is|isn't absolute.
257      *
258      * @return boolean value which is {@code true} if schema path is  absolute.
259      */
260     public abstract boolean isAbsolute();
261
262     @Override
263     public final int hashCode() {
264         return hash;
265     }
266
267     @Override
268     public boolean equals(final Object obj) {
269         if (this == obj) {
270             return true;
271         }
272         if (obj == null || getClass() != obj.getClass()) {
273             return false;
274         }
275         final LeafRefPath other = (LeafRefPath) obj;
276         return Objects.equals(qname, other.qname) && Objects.equals(parent, other.parent);
277     }
278
279     @Override
280     public String toString() {
281         StringBuilder sb = new StringBuilder();
282
283         sb.append(isAbsolute() ? "Absolute path:" : "Relative path:");
284
285         for (QNameWithPredicate qnameWithPredicate : getPathFromRoot()) {
286             sb.append('/').append(qnameWithPredicate);
287         }
288
289         return sb.toString();
290     }
291 }