240b5036e44531c43a99502cc4146e790b496994
[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(Iterables.limit(pathArguments, i));
213                 return ret;
214             }
215
216             ++i;
217         }
218
219         return null;
220     }
221
222     /**
223      * Return the key associated with the first component of specified type in
224      * an identifier.
225      *
226      * @param listItem component type
227      * @param listKey component key type
228      * @return key associated with the component, or null if the component type
229      *         is not present.
230      *
231      * @deprecated Use {@link #firstKeyOf(Class)} instead.
232      */
233     @Deprecated
234     public final <N extends Identifiable<K> & DataObject, K extends Identifier<N>> K firstKeyOf(final Class<N> listItem, final Class<K> listKey) {
235         return firstKeyOf(listItem);
236     }
237
238     /**
239      * Return the key associated with the first component of specified type in
240      * an identifier.
241      *
242      * @param listItem component type
243      * @return key associated with the component, or null if the component type
244      *         is not present.
245      */
246     public final <N extends Identifiable<K> & DataObject, K extends Identifier<N>> K firstKeyOf(final Class<N> listItem) {
247         for (final PathArgument i : pathArguments) {
248             if (listItem.equals(i.getType())) {
249                 @SuppressWarnings("unchecked")
250                 final K ret = ((IdentifiableItem<N, K>)i).getKey();
251                 return ret;
252             }
253         }
254
255         return null;
256     }
257
258     /**
259      * Check whether an identifier is contained in this identifier. This is a strict subtree check, which requires all
260      * PathArguments to match exactly, e.g.
261      *
262      *
263      * The contains method checks if the other identifier is fully contained within the current identifier. It does this
264      * by looking at only the types of the path arguments and not by comparing the path arguments themselves.
265      *
266      * To illustrate here is an example which explains the working of this API.
267      *
268      * Let's say you have two instance identifiers as follows,
269      *
270      * this = /nodes/node/openflow:1
271      * other = /nodes/node/openflow:2
272      *
273      * then this.contains(other) will return false.
274      *
275      * @param other
276      * @return
277      */
278     @Override
279     public final boolean contains(final InstanceIdentifier<? extends DataObject> other) {
280         Preconditions.checkNotNull(other, "other should not be null");
281
282         final Iterator<?> lit = pathArguments.iterator();
283         final Iterator<?> oit = other.pathArguments.iterator();
284
285         while (lit.hasNext()) {
286             if (!oit.hasNext()) {
287                 return false;
288             }
289
290             if (!lit.next().equals(oit.next())) {
291                 return false;
292             }
293         }
294
295         return true;
296     }
297
298     /**
299      * Check whether this instance identifier contains the other identifier after wildcard expansion. This is similar
300      * to {@link #contains(InstanceIdentifier)}, with the exception that a wildcards are assumed to match the their
301      * non-wildcarded PathArgument counterpart.
302      *
303      * @param other Identifier which should be checked for inclusion.
304      * @return true if this identifier contains the other object
305      */
306     public final boolean containsWildcarded(final InstanceIdentifier<?> other) {
307         Preconditions.checkNotNull(other, "other should not be null");
308
309         final Iterator<PathArgument> lit = pathArguments.iterator();
310         final Iterator<PathArgument> oit = other.pathArguments.iterator();
311
312         while (lit.hasNext()) {
313             if (!oit.hasNext()) {
314                 return false;
315             }
316
317             final PathArgument la = lit.next();
318             final PathArgument oa = oit.next();
319
320             if (!la.getType().equals(oa.getType())) {
321                 return false;
322             }
323             if (la instanceof IdentifiableItem<?, ?> && oa instanceof IdentifiableItem<?, ?> && !la.equals(oa)) {
324                 return false;
325             }
326         }
327
328         return true;
329     }
330
331     /**
332      * Create a builder rooted at this key.
333      *
334      * @return A builder instance
335      */
336     public InstanceIdentifierBuilder<T> builder() {
337         return new InstanceIdentifierBuilderImpl<T>(new Item<T>(targetType), pathArguments, hash, isWildcarded());
338     }
339
340     private InstanceIdentifier<?> childIdentifier(final PathArgument arg) {
341         return trustedCreate(arg, Iterables.concat(pathArguments, Collections.singleton(arg)), HashCodeBuilder.nextHashCode(hash, arg), isWildcarded());
342     }
343
344     @SuppressWarnings("unchecked")
345     public final <N extends ChildOf<? super T>> InstanceIdentifier<N> child(final Class<N> container) {
346         final PathArgument arg = new Item<>(container);
347         return (InstanceIdentifier<N>) childIdentifier(arg);
348     }
349
350     @SuppressWarnings("unchecked")
351     public final <N extends Identifiable<K> & ChildOf<? super T>, K extends Identifier<N>> KeyedInstanceIdentifier<N, K> child(
352             final Class<N> listItem, final K listKey) {
353         final PathArgument arg = new IdentifiableItem<>(listItem, listKey);
354         return (KeyedInstanceIdentifier<N, K>) childIdentifier(arg);
355     }
356
357     @SuppressWarnings("unchecked")
358     public final <N extends DataObject & Augmentation<? super T>> InstanceIdentifier<N> augmentation(
359             final Class<N> container) {
360         final PathArgument arg = new Item<>(container);
361         return (InstanceIdentifier<N>) childIdentifier(arg);
362     }
363
364     @Deprecated
365     private List<PathArgument> legacyCache;
366
367     /**
368      * @deprecated Use {@link #getPathArguments()} instead.
369      */
370     @Deprecated
371     public final List<PathArgument> getPath() {
372         if (legacyCache == null) {
373             legacyCache = ImmutableList.<PathArgument>copyOf(pathArguments);
374         }
375
376         return legacyCache;
377     }
378
379     /**
380      * Create a new InstanceIdentifierBuilder given a base InstanceIdentifier
381      *
382      * @param base
383      * @param <T>
384      * @return
385      *
386      * @deprecated Use {@link #builder()} instead.
387      */
388     @Deprecated
389     public static <T extends DataObject> InstanceIdentifierBuilder<T> builder(final InstanceIdentifier<T> base) {
390         return base.builder();
391     }
392
393     /**
394      * Create an InstanceIdentifierBuilder for a specific type of InstanceIdentifier as specified by container
395      *
396      * @param container
397      * @param <T>
398      * @return
399      */
400     public static <T extends ChildOf<? extends DataRoot>> InstanceIdentifierBuilder<T> builder(final Class<T> container) {
401         return new InstanceIdentifierBuilderImpl<T>().addNode(container);
402     }
403
404     /**
405      * Create an InstanceIdentifierBuilder for a specific type of InstanceIdentifier which represents an IdentifiableItem
406      *
407      * @param listItem
408      * @param listKey
409      * @param <N>
410      * @param <K>
411      * @return
412      */
413     public static <N extends Identifiable<K> & ChildOf<? extends DataRoot>, K extends Identifier<N>> InstanceIdentifierBuilder<N> builder(
414             final Class<N> listItem, final K listKey) {
415         return new InstanceIdentifierBuilderImpl<N>().addNode(listItem, listKey);
416     }
417
418     /**
419      * Create an instance identifier for a very specific object type. This method
420      * implements {@link #create(Iterable)} semantics, except it is used by internal
421      * callers, which have assured that the argument is an immutable Iterable.
422      *
423      *
424      * @param pathArguments The path to a specific node in the data tree
425      * @return InstanceIdentifier instance
426      * @throws IllegalArgumentException if pathArguments is empty or
427      *         contains a null element.
428      */
429     private static InstanceIdentifier<?> internalCreate(final Iterable<PathArgument> pathArguments) {
430         final Iterator<? extends PathArgument> it = Preconditions.checkNotNull(pathArguments, "pathArguments may not be null").iterator();
431         final HashCodeBuilder<PathArgument> hashBuilder = new HashCodeBuilder<>();
432         boolean wildcard = false;
433         PathArgument a = null;
434
435         while (it.hasNext()) {
436             a = it.next();
437             Preconditions.checkArgument(a != null, "pathArguments may not contain null elements");
438
439             // TODO: sanity check ChildOf<>;
440             hashBuilder.addArgument(a);
441
442             if (Identifiable.class.isAssignableFrom(a.getType()) && !(a instanceof IdentifiableItem<?, ?>)) {
443                 wildcard = true;
444             }
445         }
446         Preconditions.checkArgument(a != null, "pathArguments may not be empty");
447
448         return trustedCreate(a, pathArguments, hashBuilder.build(), wildcard);
449     }
450
451     /**
452      * Create an instance identifier for a very specific object type.
453      *
454      * Example
455      * <pre>
456      *  List&lt;PathArgument&gt; path = Arrays.asList(new Item(Nodes.class))
457      *  new InstanceIdentifier(path);
458      * </pre>
459      *
460      * @param pathArguments The path to a specific node in the data tree
461      * @return InstanceIdentifier instance
462      * @throws IllegalArgumentException if pathArguments is empty or
463      *         contains a null element.
464      */
465     public static InstanceIdentifier<?> create(final Iterable<? extends PathArgument> pathArguments) {
466         if (pathArguments instanceof ImmutableCollection<?>) {
467             @SuppressWarnings("unchecked")
468             final Iterable<PathArgument> immutableArguments = (Iterable<PathArgument>) pathArguments;
469             return internalCreate(immutableArguments);
470         } else {
471             return internalCreate(ImmutableList.copyOf(pathArguments));
472         }
473     }
474
475     /**
476      * Create an instance identifier for a very specific object type.
477      *
478      * For example
479      * <pre>
480      *      new InstanceIdentifier(Nodes.class)
481      * </pre>
482      * would create an InstanceIdentifier for an object of type Nodes
483      *
484      * @param type The type of the object which this instance identifier represents
485      * @return InstanceIdentifier instance
486      */
487     @SuppressWarnings("unchecked")
488     public static <T extends DataObject> InstanceIdentifier<T> create(final Class<T> type) {
489         return (InstanceIdentifier<T>) create(Collections.<PathArgument> singletonList(new Item<>(type)));
490     }
491
492     /**
493      * Return the key associated with the last component of the specified identifier.
494      *
495      * @param id instance identifier
496      * @return key associated with the last component
497      * @throws IllegalArgumentException if the supplied identifier type cannot have a key.
498      * @throws NullPointerException if id is null.
499      */
500     public static <N extends Identifiable<K> & DataObject, K extends Identifier<N>> K keyOf(final InstanceIdentifier<N> id) {
501         Preconditions.checkNotNull(id);
502         Preconditions.checkArgument(id instanceof KeyedInstanceIdentifier, "%s does not have a key", id);
503
504         @SuppressWarnings("unchecked")
505         final K ret = ((KeyedInstanceIdentifier<N, K>)id).getKey();
506         return ret;
507     }
508
509     @SuppressWarnings({ "unchecked", "rawtypes" })
510     static InstanceIdentifier<?> trustedCreate(final PathArgument arg, final Iterable<PathArgument> pathArguments, final int hash, boolean wildcarded) {
511         if (Identifiable.class.isAssignableFrom(arg.getType()) && !(wildcarded)) {
512             Identifier<?> key = null;
513             if (arg instanceof IdentifiableItem<?, ?>) {
514                 key = ((IdentifiableItem<?, ?>)arg).key;
515             } else {
516                 wildcarded = true;
517             }
518
519             return new KeyedInstanceIdentifier(arg.getType(), pathArguments, wildcarded, hash, key);
520         } else {
521             return new InstanceIdentifier(arg.getType(), pathArguments, wildcarded, hash);
522         }
523     }
524
525     /**
526      * Path argument of {@link InstanceIdentifier}.
527      * <p>
528      * Interface which implementations are used as path components of the
529      * path in overall data tree.
530      */
531     public interface PathArgument extends Comparable<PathArgument> {
532         Class<? extends DataObject> getType();
533     }
534
535     private static abstract class AbstractPathArgument<T extends DataObject> implements PathArgument, Serializable {
536         private static final long serialVersionUID = 1L;
537         private final Class<T> type;
538
539         protected AbstractPathArgument(final Class<T> type) {
540             this.type = Preconditions.checkNotNull(type, "Type may not be null.");
541         }
542
543         @Override
544         public final Class<T> getType() {
545             return type;
546         }
547
548         @Override
549         public int hashCode() {
550             return type.hashCode();
551         }
552
553         @Override
554         public boolean equals(final Object obj) {
555             if (this == obj) {
556                 return true;
557             }
558             if (obj == null) {
559                 return false;
560             }
561             if (getClass() != obj.getClass()) {
562                 return false;
563             }
564             final AbstractPathArgument<?> other = (AbstractPathArgument<?>) obj;
565             return type.equals(other.type);
566         }
567
568         @Override
569         public int compareTo(final PathArgument arg) {
570             return type.getCanonicalName().compareTo(arg.getType().getCanonicalName());
571         }
572     }
573
574     /**
575      * An Item represents an object that probably is only one of it's kind. For example a Nodes object is only one of
576      * a kind. In YANG terms this would probably represent a container.
577      *
578      * @param <T>
579      */
580     public static final class Item<T extends DataObject> extends AbstractPathArgument<T> {
581         private static final long serialVersionUID = 1L;
582
583         public Item(final Class<T> type) {
584             super(type);
585         }
586
587         @Override
588         public String toString() {
589             return getType().getName();
590         }
591     }
592
593     /**
594      * An IdentifiableItem represents a object that is usually present in a collection and can be identified uniquely
595      * by a key. In YANG terms this would probably represent an item in a list.
596      *
597      * @param <I> An object that is identifiable by an identifier
598      * @param <T> The identifier of the object
599      */
600     public static final class IdentifiableItem<I extends Identifiable<T> & DataObject, T extends Identifier<I>> extends AbstractPathArgument<I> {
601         private static final long serialVersionUID = 1L;
602         private final T key;
603
604         public IdentifiableItem(final Class<I> type, final T key) {
605             super(type);
606             this.key = Preconditions.checkNotNull(key, "Key may not be null.");
607         }
608
609         public T getKey() {
610             return this.key;
611         }
612
613         @Override
614         public boolean equals(final Object obj) {
615             return super.equals(obj) && key.equals(((IdentifiableItem<?, ?>) obj).getKey());
616         }
617
618         @Override
619         public int hashCode() {
620             return super.hashCode() * 31 + key.hashCode();
621         }
622
623         @Override
624         public String toString() {
625             return getType().getName() + "[key=" + key + "]";
626         }
627     }
628
629
630     public interface InstanceIdentifierBuilder<T extends DataObject> extends Builder<InstanceIdentifier<T>> {
631         /**
632          * Append the specified container as a child of the current InstanceIdentifier referenced by the builder.
633          *
634          * This method should be used when you want to build an instance identifier by appending top-level
635          * elements
636          *
637          * Example,
638          * <pre>
639          *     InstanceIdentifier.builder().child(Nodes.class).build();
640          *
641          * </pre>
642          *
643          * NOTE :- The above example is only for illustration purposes InstanceIdentifier.builder() has been deprecated
644          * and should not be used. Use InstanceIdentifier.builder(Nodes.class) instead
645          *
646          * @param container
647          * @param <N>
648          * @return
649          */
650         <N extends ChildOf<? super T>> InstanceIdentifierBuilder<N> child(
651                 Class<N> container);
652
653         /**
654          * Append the specified listItem as a child of the current InstanceIdentifier referenced by the builder.
655          *
656          * This method should be used when you want to build an instance identifier by appending a specific list element
657          * to the identifier
658          *
659          * @param listItem
660          * @param listKey
661          * @param <N>
662          * @param <K>
663          * @return
664          */
665         <N extends Identifiable<K> & ChildOf<? super T>, K extends Identifier<N>> InstanceIdentifierBuilder<N> child(
666                 Class<N> listItem, K listKey);
667
668         /**
669          * Build an identifier which refers to a specific augmentation of the current InstanceIdentifier referenced by
670          * the builder
671          *
672          * @param container
673          * @param <N>
674          * @return
675          */
676         <N extends DataObject & Augmentation<? super T>> InstanceIdentifierBuilder<N> augmentation(
677                 Class<N> container);
678
679         /**
680          * Build the instance identifier.
681          *
682          * @return
683          */
684         @Override
685         InstanceIdentifier<T> build();
686
687         /*
688          * @deprecated use #build()
689          */
690         @Deprecated
691         InstanceIdentifier<T> toInstance();
692     }
693
694     private void writeObject(final java.io.ObjectOutputStream out) throws IOException {
695         out.defaultWriteObject();
696         out.writeInt(Iterables.size(pathArguments));
697         for (Object o : pathArguments) {
698             out.writeObject(o);
699         }
700     }
701
702     private void readObject(final java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
703         in.defaultReadObject();
704
705         final int size = in.readInt();
706         final List<PathArgument> args = new ArrayList<>(size);
707         for (int i = 0; i < size; ++i) {
708             args.add((PathArgument) in.readObject());
709         }
710
711         try {
712             PATHARGUMENTS_FIELD.set(this, ImmutableList.copyOf(args));
713         } catch (IllegalArgumentException | IllegalAccessException e) {
714             throw new IOException(e);
715         }
716     }
717 }