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