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