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