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