Remove redundant type declarations
[mdsal.git] / binding2 / mdsal-binding2-spec / src / main / java / org / opendaylight / mdsal / binding / javav2 / spec / base / InstanceIdentifier.java
1 /*
2  * Copyright (c) 2017 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
9 package org.opendaylight.mdsal.binding.javav2.spec.base;
10
11 import com.google.common.annotations.Beta;
12 import com.google.common.base.MoreObjects;
13 import com.google.common.base.MoreObjects.ToStringHelper;
14 import com.google.common.base.Preconditions;
15 import com.google.common.collect.ImmutableCollection;
16 import com.google.common.collect.ImmutableList;
17 import com.google.common.collect.Iterables;
18 import java.io.IOException;
19 import java.io.Serializable;
20 import java.lang.reflect.Field;
21 import java.util.ArrayList;
22 import java.util.Collections;
23 import java.util.Iterator;
24 import java.util.List;
25 import org.opendaylight.mdsal.binding.javav2.spec.structural.Augmentation;
26 import org.opendaylight.mdsal.binding.javav2.spec.structural.TreeChildNode;
27 import org.opendaylight.yangtools.concepts.Identifiable;
28 import org.opendaylight.yangtools.concepts.Immutable;
29 import org.opendaylight.yangtools.concepts.Path;
30 import org.opendaylight.yangtools.util.HashCodeBuilder;
31
32 /**
33  *
34  * This instance identifier uniquely identifies a specific TreeNode in the data tree modeled by
35  * YANG.
36  *
37  * For Example let's say you were trying to refer to a node in inventory which was modeled in YANG
38  * as follows,
39  *
40  * <pre>
41  * module opendaylight-inventory {
42  *      ....
43  *
44  *      container nodes {
45  *        list node {
46  *            key "id";
47  *            ext:context-instance "node-context";
48  *
49  *            uses node;
50  *        }
51  *    }
52  *
53  * }
54  * </pre>
55  *
56  * You could create an instance identifier as follows to get to a node with id "openflow:1"
57  *
58  * InstanceIdentifierBuilder.builder(Nodes.class).child(Node.class, new NodeKey(new
59  * NodeId("openflow:1")).build();
60  *
61  * This would be the same as using a path like so, "/nodes/node/openflow:1" to refer to the
62  * openflow:1 node
63  *
64  */
65 @Beta
66 public class InstanceIdentifier<T extends TreeNode> implements Path<InstanceIdentifier<? extends TreeNode>>, Immutable,
67         Serializable {
68     private static final Field PATHARGUMENTS_FIELD;
69     private static final long serialVersionUID = 2L;
70     /*
71      * Protected to differentiate internal and external access. Internal
72      * access is required never to modify the contents. References passed
73      * to outside entities have to be wrapped in an unmodifiable view.
74      */
75     protected transient final Iterable<TreeArgument> pathArguments;
76     private final Class<T> targetType;
77     private final boolean wildcarded;
78     private final int hash;
79
80     static {
81         final Field f;
82         try {
83             f = InstanceIdentifier.class.getDeclaredField("pathArguments");
84         } catch (NoSuchFieldException | SecurityException e) {
85             throw new ExceptionInInitializerError(e);
86         }
87         f.setAccessible(true);
88         PATHARGUMENTS_FIELD = f;
89     }
90
91     InstanceIdentifier(final Class<T> type, final Iterable<TreeArgument> pathArguments, final boolean wildcarded, final int hash) {
92         this.pathArguments = Preconditions.checkNotNull(pathArguments);
93         this.targetType = Preconditions.checkNotNull(type);
94         this.wildcarded = wildcarded;
95         this.hash = hash;
96     }
97
98     /**
99      * Return the type of data which this InstanceIdentifier identifies.
100      *
101      * @return Target type
102      */
103     public final Class<T> getTargetType() {
104         return targetType;
105     }
106
107     /**
108      * Return the path argument chain which makes up this instance identifier.
109      *
110      * @return Path argument chain. Immutable and does not contain nulls.
111      */
112     public final Iterable<TreeArgument> getPathArguments() {
113         return Iterables.unmodifiableIterable(pathArguments);
114     }
115
116     /**
117      * Check whether an instance identifier contains any wildcards. A wildcard
118      * is an path argument which has a null key.
119      *
120      * @return true if any of the path arguments has a null key.
121      */
122     public final boolean isWildcarded() {
123         return wildcarded;
124     }
125
126     @Override
127     public final int hashCode() {
128         return hash;
129     }
130
131     @Override
132     public final boolean equals(final Object obj) {
133         if (this == obj) {
134             return true;
135         }
136         if (obj == null) {
137             return false;
138         }
139         if (getClass() != obj.getClass()) {
140             return false;
141         }
142
143         final InstanceIdentifier<?> other = (InstanceIdentifier<?>) obj;
144         if (pathArguments == other.pathArguments) {
145             return true;
146         }
147
148         /*
149          * We could now just go and compare the pathArguments, but that
150          * can be potentially expensive. Let's try to avoid that by
151          * checking various things that we have cached from pathArguments
152          * and trying to prove the identifiers are *not* equal.
153          */
154         if (hash != other.hash) {
155             return false;
156         }
157         if (wildcarded != other.wildcarded) {
158             return false;
159         }
160         if (targetType != other.targetType) {
161             return false;
162         }
163         if (fastNonEqual(other)) {
164             return false;
165         }
166
167         // Everything checks out so far, so we have to do a full equals
168         return Iterables.elementsEqual(pathArguments, other.pathArguments);
169     }
170
171     /**
172      * Perform class-specific fast checks for non-equality. This allows
173      * subclasses to avoid iterating over the pathArguments by performing
174      * quick checks on their specific fields.
175      *
176      * @param other The other identifier, guaranteed to be the same class
177      * @return true if the other identifier cannot be equal to this one.
178      */
179     protected boolean fastNonEqual(final InstanceIdentifier<?> other) {
180         return false;
181     }
182
183     @Override
184     public final String toString() {
185         return addToStringAttributes(MoreObjects.toStringHelper(this)).toString();
186     }
187
188     /**
189      * Add class-specific toString attributes.
190      *
191      * @param toStringHelper ToStringHelper instance
192      * @return ToStringHelper instance which was passed in
193      */
194     protected ToStringHelper addToStringAttributes(final ToStringHelper toStringHelper) {
195         return toStringHelper.add("targetType", targetType).add("path", Iterables.toString(pathArguments));
196     }
197
198     /**
199      * Return an instance identifier trimmed at the first occurrence of a
200      * specific component type.
201      *
202      * For example let's say an instance identifier was built like so,
203      * <pre>
204      *      identifier = InstanceIdentifierBuilder.builder(Nodes.class).child(Node.class, new NodeKey(new NodeId("openflow:1")).build();
205      * </pre>
206      *
207      * And you wanted to obtain the Instance identifier which represented Nodes you would do it like so,
208      *
209      * <pre>
210      *      identifier.firstIdentifierOf(Nodes.class)
211      * </pre>
212      *
213      * @param type component type
214      * @return trimmed instance identifier, or null if the component type
215      *         is not present.
216      */
217     public final <I extends TreeNode> InstanceIdentifier<I> firstIdentifierOf(final Class<I> type) {
218         int i = 1;
219         for (final TreeArgument a : pathArguments) {
220             if (type.equals(a.getType())) {
221                 @SuppressWarnings("unchecked")
222                 final InstanceIdentifier<I> ret = (InstanceIdentifier<I>) internalCreate(Iterables.limit(pathArguments, i));
223                 return ret;
224             }
225
226             ++i;
227         }
228
229         return null;
230     }
231
232     /**
233      * Return the key associated with the first component of specified type in
234      * an identifier.
235      *
236      * @param listItem component type
237      * @param listKey component key type
238      * @return key associated with the component, or null if the component type
239      *         is not present.
240      *
241      * @deprecated Use {@link #firstKeyOf(Class)} instead.
242      */
243     @Deprecated
244     public final <N extends TreeNode, K> K firstKeyOf(final Class<N> listItem,
245             final Class<K> listKey) {
246         return firstKeyOf(listItem);
247     }
248
249     /**
250      * Return the key associated with the first component of specified type in
251      * an identifier.
252      *
253      * @param listItem component type
254      * @return key associated with the component, or null if the component type
255      *         is not present.
256      */
257
258     public final <N extends TreeNode, K> K firstKeyOf(final Class<N> listItem) {
259         for (final TreeArgument i : pathArguments) {
260             if (listItem.equals(i.getType())) {
261                 @SuppressWarnings("unchecked")
262                 final K ret = ((IdentifiableItem<N, K>)i).getKey();
263                 return ret;
264             }
265         }
266
267         return null;
268     }
269
270     /**
271      * Check whether an identifier is contained in this identifier. This is a strict subtree check, which requires all
272      * PathArguments to match exactly, e.g.
273      *
274      *
275      * The contains method checks if the other identifier is fully contained within the current identifier. It does this
276      * by looking at only the types of the path arguments and not by comparing the path arguments themselves.
277      *
278      * To illustrate here is an example which explains the working of this API.
279      *
280      * Let's say you have two instance identifiers as follows,
281      *
282      * this = /nodes/node/openflow:1
283      * other = /nodes/node/openflow:2
284      *
285      * then this.contains(other) will return false.
286      *
287      * @param other
288      * @return
289      */
290     @Override
291     public final boolean contains(final InstanceIdentifier<? extends TreeNode> other) {
292         Preconditions.checkNotNull(other, "other should not be null");
293
294         final Iterator<?> lit = pathArguments.iterator();
295         final Iterator<?> oit = other.pathArguments.iterator();
296
297         while (lit.hasNext()) {
298             if (!oit.hasNext()) {
299                 return false;
300             }
301
302             if (!lit.next().equals(oit.next())) {
303                 return false;
304             }
305         }
306
307         return true;
308     }
309
310     /**
311      * Check whether this instance identifier contains the other identifier after wildcard expansion. This is similar
312      * to {@link #contains(InstanceIdentifier)}, with the exception that a wildcards are assumed to match the their
313      * non-wildcarded PathArgument counterpart.
314      *
315      * @param other Identifier which should be checked for inclusion.
316      * @return true if this identifier contains the other object
317      */
318     public final boolean containsWildcarded(final InstanceIdentifier<?> other) {
319         Preconditions.checkNotNull(other, "other should not be null");
320
321         final Iterator<TreeArgument> lit = pathArguments.iterator();
322         final Iterator<TreeArgument> oit = other.pathArguments.iterator();
323
324         while (lit.hasNext()) {
325             if (!oit.hasNext()) {
326                 return false;
327             }
328
329             final TreeArgument la = lit.next();
330             final TreeArgument oa = oit.next();
331
332             if (!la.getType().equals(oa.getType())) {
333                 return false;
334             }
335             if (la instanceof IdentifiableItem<?, ?> && oa instanceof IdentifiableItem<?, ?> && !la.equals(oa)) {
336                 return false;
337             }
338         }
339
340         return true;
341     }
342
343     /**
344      * Create a builder rooted at this key.
345      *
346      * @return A builder instance
347      */
348     public InstanceIdentifierBuilder<T> builder() {
349         return new InstanceIdentifierBuilderImpl<T>(new Item<T>(targetType), pathArguments, hash, isWildcarded());
350     }
351
352     private InstanceIdentifier<?> childIdentifier(final TreeArgument arg) {
353         return trustedCreate(arg, Iterables.concat(pathArguments, Collections.singleton(arg)), HashCodeBuilder.nextHashCode(hash, arg), isWildcarded());
354     }
355
356     @SuppressWarnings("unchecked")
357     public final <N extends TreeChildNode<? super T, ?>> InstanceIdentifier<N> child(final Class<N> container) {
358         final TreeArgument arg = new Item<>(container);
359         return (InstanceIdentifier<N>) childIdentifier(arg);
360     }
361
362     @SuppressWarnings("unchecked")
363     public final <N extends TreeChildNode<? super T, ?>, K> KeyedInstanceIdentifier<N, K> child(
364             final Class<N> listItem, final K listKey) {
365         final TreeArgument arg = new IdentifiableItem<>(listItem, listKey);
366         return (KeyedInstanceIdentifier<N, K>) childIdentifier(arg);
367     }
368
369     @SuppressWarnings("unchecked")
370     public final <N extends TreeNode & Augmentation<? super T>> InstanceIdentifier<N> augmentation(
371             final Class<N> container) {
372         final TreeArgument arg = new Item<>(container);
373         return (InstanceIdentifier<N>) childIdentifier(arg);
374     }
375
376     @Deprecated
377     private List<TreeArgument> legacyCache;
378
379     /**
380      * @deprecated Use {@link #getPathArguments()} instead.
381      */
382     @Deprecated
383     public final List<TreeArgument> getPath() {
384         if (legacyCache == null) {
385             legacyCache = ImmutableList.copyOf(pathArguments);
386         }
387
388         return legacyCache;
389     }
390
391     /**
392      * Create a new InstanceIdentifierBuilder given a base InstanceIdentifier
393      *
394      * @param base
395      * @param <T>
396      * @return
397      *
398      * @deprecated Use {@link #builder()} instead.
399      */
400     @Deprecated
401     public static <T extends TreeNode> InstanceIdentifierBuilder<T> builder(final InstanceIdentifier<T> base) {
402         return base.builder();
403     }
404
405     /**
406      * Create an InstanceIdentifierBuilder for a specific type of InstanceIdentifier as specified by container
407      *
408      * @param container
409      * @param <T>
410      * @return
411      */
412     public static <T extends TreeChildNode<? extends TreeRoot, ?>> InstanceIdentifierBuilder<T> builder(
413             final Class<T> container) {
414         return new InstanceIdentifierBuilderImpl<T>().addNode(container);
415     }
416
417     /**
418      * Create an InstanceIdentifierBuilder for a specific type of InstanceIdentifier which represents an IdentifiableItem
419      *
420      * @param listItem
421      * @param listKey
422      * @param <N>
423      * @param <K>
424      * @return
425      */
426
427     public static <N extends TreeChildNode<? extends TreeRoot, ?>, K> InstanceIdentifierBuilder<N> builder(
428             final Class<N> listItem, final K listKey) {
429         return new InstanceIdentifierBuilderImpl<N>().addNode(listItem, listKey);
430     }
431
432     /**
433      * Create an instance identifier for a very specific object type. This method
434      * implements {@link #create(Iterable)} semantics, except it is used by internal
435      * callers, which have assured that the argument is an immutable Iterable.
436      *
437      *
438      * @param pathArguments The path to a specific node in the data tree
439      * @return InstanceIdentifier instance
440      * @throws IllegalArgumentException if pathArguments is empty or
441      *         contains a null element.
442      */
443     private static InstanceIdentifier<?> internalCreate(final Iterable<TreeArgument> pathArguments) {
444         final Iterator<? extends TreeArgument> it = Preconditions.checkNotNull(pathArguments, "pathArguments may not be null").iterator();
445         final HashCodeBuilder<TreeArgument> hashBuilder = new HashCodeBuilder<>();
446         boolean wildcard = false;
447         TreeArgument a = null;
448
449         while (it.hasNext()) {
450             a = it.next();
451             Preconditions.checkArgument(a != null, "pathArguments may not contain null elements");
452
453             // TODO: sanity check ChildTreeNode<>;
454             hashBuilder.addArgument(a);
455
456             if (Identifiable.class.isAssignableFrom(a.getType()) && !(a instanceof IdentifiableItem<?, ?>)) {
457                 wildcard = true;
458             }
459         }
460         Preconditions.checkArgument(a != null, "pathArguments may not be empty");
461
462         return trustedCreate(a, pathArguments, hashBuilder.build(), wildcard);
463     }
464
465     /**
466      * Create an instance identifier for a very specific object type.
467      *
468      * Example
469      * <pre>
470      *  List&lt;PathArgument&gt; path = Arrays.asList(new Item(Nodes.class))
471      *  new InstanceIdentifier(path);
472      * </pre>
473      *
474      * @param pathArguments The path to a specific node in the data tree
475      * @return InstanceIdentifier instance
476      * @throws IllegalArgumentException if pathArguments is empty or
477      *         contains a null element.
478      */
479     public static InstanceIdentifier<?> create(final Iterable<? extends TreeArgument> pathArguments) {
480         if (pathArguments instanceof ImmutableCollection<?>) {
481             @SuppressWarnings("unchecked")
482             final Iterable<TreeArgument> immutableArguments = (Iterable<TreeArgument>) pathArguments;
483             return internalCreate(immutableArguments);
484         } else {
485             return internalCreate(ImmutableList.copyOf(pathArguments));
486         }
487     }
488
489     /**
490      * Create an instance identifier for a very specific object type.
491      *
492      * For example
493      * <pre>
494      *      new InstanceIdentifier(Nodes.class)
495      * </pre>
496      * would create an InstanceIdentifier for an object of type Nodes
497      *
498      * @param type The type of the object which this instance identifier represents
499      * @return InstanceIdentifier instance
500      */
501     @SuppressWarnings("unchecked")
502     public static <T extends TreeNode> InstanceIdentifier<T> create(final Class<T> type) {
503         return (InstanceIdentifier<T>) create(Collections.<TreeArgument> singletonList(new Item<>(type)));
504     }
505
506     /**
507      * Return the key associated with the last component of the specified identifier.
508      *
509      * @param id instance identifier
510      * @return key associated with the last component
511      * @throws IllegalArgumentException if the supplied identifier type cannot have a key.
512      * @throws NullPointerException if id is null.
513      */
514
515     public static <N extends TreeNode, K> K keyOf(
516             final InstanceIdentifier<N> id) {
517         Preconditions.checkNotNull(id);
518         Preconditions.checkArgument(id instanceof KeyedInstanceIdentifier, "%s does not have a key", id);
519
520         @SuppressWarnings("unchecked")
521         final K ret = ((KeyedInstanceIdentifier<N, K>)id).getKey();
522         return ret;
523     }
524
525     @SuppressWarnings({ "unchecked", "rawtypes" })
526     static InstanceIdentifier<?> trustedCreate(final TreeArgument arg, final Iterable<TreeArgument> pathArguments, final int hash, boolean wildcarded) {
527         if (Identifiable.class.isAssignableFrom(arg.getType()) && !(wildcarded)) {
528             Object key = null;
529             if (arg instanceof IdentifiableItem<?, ?>) {
530                 key = ((IdentifiableItem<?, ?>) arg).getKey();
531             } else {
532                 wildcarded = true;
533             }
534
535             return new KeyedInstanceIdentifier(arg.getType(), pathArguments, wildcarded, hash, key);
536         } else {
537             return new InstanceIdentifier(arg.getType(), pathArguments, wildcarded, hash);
538         }
539     }
540
541     private void writeObject(final java.io.ObjectOutputStream out) throws IOException {
542         out.defaultWriteObject();
543         out.writeInt(Iterables.size(pathArguments));
544         for (Object o : pathArguments) {
545             out.writeObject(o);
546         }
547     }
548
549     private void readObject(final java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
550         in.defaultReadObject();
551
552         final int size = in.readInt();
553         final List<TreeArgument> args = new ArrayList<>(size);
554         for (int i = 0; i < size; ++i) {
555             args.add((TreeArgument) in.readObject());
556         }
557
558         try {
559             PATHARGUMENTS_FIELD.set(this, ImmutableList.copyOf(args));
560         } catch (IllegalArgumentException | IllegalAccessException e) {
561             throw new IOException(e);
562         }
563     }
564 }