BUG-582: improve SchemaPath.hashCode()
[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.Preconditions;
11 import com.google.common.collect.ImmutableList;
12 import com.google.common.collect.Iterables;
13 import java.util.Arrays;
14 import java.util.Collections;
15 import java.util.List;
16 import org.opendaylight.yangtools.concepts.Immutable;
17 import org.opendaylight.yangtools.util.HashCodeBuilder;
18 import org.opendaylight.yangtools.yang.common.QName;
19
20 /**
21  *
22  * Represents unique path to the every node inside the module.
23  *
24  */
25 public class SchemaPath implements Immutable {
26     /**
27      * Shared instance of the conceptual root schema node.
28      */
29     public static final SchemaPath ROOT = new SchemaPath(Collections.<QName>emptyList(), true, Boolean.TRUE.hashCode());
30
31     /**
32      * Shared instance of the "same" relative schema node.
33      */
34     public static final SchemaPath SAME = new SchemaPath(Collections.<QName>emptyList(), false, Boolean.FALSE.hashCode());
35
36     /**
37      * List of QName instances which represents complete path to the node.
38      */
39     private final Iterable<QName> path;
40
41     /**
42      * Boolean value which represents type of schema path (relative or
43      * absolute).
44      */
45     private final boolean absolute;
46
47     /**
48      * Cached hash code. We can use this since we are immutable.
49      */
50     private final int hash;
51
52     /**
53      * Cached legacy path, filled-in when {@link #getPath()} or {@link #getPathTowardsRoot()}
54      * is invoked.
55      */
56     private ImmutableList<QName> legacyPath;
57
58     /**
59      * Constructs new instance of this class with the concrete path.
60      *
61      * @param path
62      *            list of QName instances which specifies exact path to the
63      *            module node
64      * @param absolute
65      *            boolean value which specifies if the path is absolute or
66      *            relative
67      *
68      * @deprecated Use {@link #create(Iterable, boolean)} instead.
69      */
70     @Deprecated
71     public SchemaPath(final List<QName> path, final boolean absolute) {
72         this(ImmutableList.copyOf(path), absolute, Boolean.valueOf(absolute).hashCode());
73     }
74
75     private ImmutableList<QName> getLegacyPath() {
76         if (legacyPath == null) {
77             legacyPath = ImmutableList.copyOf(path);
78         }
79
80         return legacyPath;
81     }
82
83     /**
84      * Returns the complete path to schema node.
85      *
86      * @return list of <code>QName</code> instances which represents complete
87      *         path to schema node
88      *
89      * @deprecated Use {@link #getPathFromRoot()} instead.
90      */
91     @Deprecated
92     public List<QName> getPath() {
93         return getLegacyPath();
94     }
95
96     private SchemaPath(final Iterable<QName> path, final boolean absolute, final int hash) {
97         this.path = Preconditions.checkNotNull(path);
98         this.absolute = absolute;
99         this.hash = hash;
100     }
101
102     /**
103      * Constructs new instance of this class with the concrete path.
104      *
105      * @param path
106      *            list of QName instances which specifies exact path to the
107      *            module node
108      * @param absolute
109      *            boolean value which specifies if the path is absolute or
110      *            relative
111      *
112      * @return A SchemaPath instance.
113      */
114     public static SchemaPath create(final Iterable<QName> path, final boolean absolute) {
115         final SchemaPath parent = absolute ? ROOT : SAME;
116         return parent.createChild(path);
117     }
118
119     /**
120      * Constructs new instance of this class with the concrete path.
121      *
122      * @param absolute
123      *            boolean value which specifies if the path is absolute or
124      *            relative
125      * @param path
126      *            one or more QName instances which specifies exact path to the
127      *            module node
128      *
129      * @return A SchemaPath instance.
130      */
131     public static SchemaPath create(final boolean absolute, final QName... path) {
132         return create(Arrays.asList(path), absolute);
133     }
134
135     private SchemaPath trustedCreateChild(final Iterable<QName> relative) {
136         if (Iterables.isEmpty(relative)) {
137             return this;
138         }
139
140         final HashCodeBuilder<QName> b = new HashCodeBuilder<>(hash);
141         for (QName p : relative) {
142             b.addArgument(p);
143         }
144
145         return new SchemaPath(Iterables.concat(path, relative), absolute, b.toInstance());
146     }
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 SchemaPath createChild(final Iterable<QName> relative) {
155         if (Iterables.isEmpty(relative)) {
156             return this;
157         }
158
159         return trustedCreateChild(ImmutableList.copyOf(relative));
160     }
161
162     /**
163      * Create a child path based on concatenation of this path and a relative path.
164      *
165      * @param relative Relative SchemaPath
166      * @return A new child path
167      */
168     public SchemaPath createChild(final SchemaPath relative) {
169         Preconditions.checkArgument(!relative.isAbsolute(), "Child creation requires relative path");
170         return trustedCreateChild(relative.path);
171     }
172
173     /**
174      * Create a child path based on concatenation of this path and additional
175      * path elements.
176      *
177      * @param elements Relative SchemaPath elements
178      * @return A new child path
179      */
180     public SchemaPath createChild(final QName... elements) {
181         return createChild(Arrays.asList(elements));
182     }
183
184     /**
185      * Returns the list of nodes which need to be traversed to get from the
186      * starting point (root for absolute SchemaPaths) to the node represented
187      * by this object.
188      *
189      * @return list of <code>qname</code> instances which represents
190      *         path from the root to the schema node.
191      */
192     public Iterable<QName> getPathFromRoot() {
193         return path;
194     }
195
196     /**
197      * Returns the list of nodes which need to be traversed to get from this
198      * node to the starting point (root for absolute SchemaPaths).
199      *
200      * @return list of <code>qname</code> instances which represents
201      *         path from the schema node towards the root.
202      */
203     public Iterable<QName> getPathTowardsRoot() {
204         return getLegacyPath().reverse();
205     }
206
207     /**
208      * Describes whether schema path is|isn't absolute.
209      *
210      * @return boolean value which is <code>true</code> if schema path is
211      *         absolute.
212      */
213     public boolean isAbsolute() {
214         return absolute;
215     }
216
217     @Override
218     public int hashCode() {
219         return hash;
220     }
221
222     @Override
223     public boolean equals(final Object obj) {
224         if (this == obj) {
225             return true;
226         }
227         if (obj == null) {
228             return false;
229         }
230         if (getClass() != obj.getClass()) {
231             return false;
232         }
233         SchemaPath other = (SchemaPath) obj;
234         if (absolute != other.absolute) {
235             return false;
236         }
237
238         return Iterables.elementsEqual(path, other.path);
239     }
240
241     @Override
242     public String toString() {
243         StringBuilder builder = new StringBuilder();
244         builder.append("SchemaPath [path=");
245         builder.append(path);
246         builder.append(", absolute=");
247         builder.append(absolute);
248         builder.append("]");
249         return builder.toString();
250     }
251 }