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