Merge "Fix for Bug 589."
[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 java.util.Collections;
11 import java.util.List;
12
13 import org.opendaylight.yangtools.concepts.Builder;
14 import org.opendaylight.yangtools.concepts.Immutable;
15 import org.opendaylight.yangtools.concepts.Path;
16
17 import com.google.common.collect.ImmutableList;
18 import com.google.common.collect.Iterables;
19
20 /**
21  *
22  * This instance identifier uniquely identifies a specific DataObject in the data tree modeled by YANG.
23  *
24  * For Example let's say you were trying to refer to a node in inventory which was modeled in YANG as follows,
25  *
26  * <pre>
27  * module opendaylight-inventory {
28  *      ....
29  *
30  *      container nodes {
31  *        list node {
32  *            key "id";
33  *            ext:context-instance "node-context";
34  *
35  *            uses node;
36  *        }
37  *    }
38  *
39  * }
40  * </pre>
41  *
42  * You could create an instance identifier as follows to get to a node with id "openflow:1"
43  *
44  * InstanceIdentifierBuilder.builder(Nodes.class).child(Node.class, new NodeKey(new NodeId("openflow:1")).build();
45  *
46  * This would be the same as using a path like so, "/nodes/node/openflow:1" to refer to the openflow:1 node
47  *
48  */
49 public final class InstanceIdentifier<T extends DataObject> implements Path<InstanceIdentifier<? extends DataObject>>,Immutable {
50
51     private final List<PathArgument> path;
52     private final Class<T> targetType;
53
54     /**
55      * Create an instance identifier for a very specific object type.
56      *
57      * For example
58      * <pre>
59      *      new InstanceIdentifier(Nodes.class)
60      * </pre>
61      * would create an InstanceIdentifier for an object of type Nodes
62      *
63      * @param type The type of the object which this instance identifier represents
64      */
65     public InstanceIdentifier(Class<T> type) {
66         this(Collections.<PathArgument> singletonList(new Item<>(type)), type);
67     }
68
69     /**
70      * Create an instance identifier for a very specific object type.
71      *
72      * Example
73      * <pre>
74      *  List<PathArgument> path = Arrays.asList(new Item(Nodes.class))
75      *  new InstanceIdentifier(path, Nodes.class);
76      * </pre>
77      *
78      * @param path The path to a specific node in the data tree
79      * @param type The type of the object which this instance identifier represents
80      */
81     public InstanceIdentifier(List<PathArgument> path, Class<T> type) {
82         this.path = ImmutableList.copyOf(path);
83         this.targetType = type;
84     }
85
86     /**
87      *
88      * @return A list of the elements of the path
89      */
90     public List<PathArgument> getPath() {
91         return getPathArguments();
92     }
93
94     /**
95      *
96      * @return A list of the elements of the path
97      */
98
99     public List<PathArgument> getPathArguments() {
100         return this.path;
101     }
102
103     /**
104      *
105      * @return The target type of this instance identifier
106      */
107     public Class<T> getTargetType() {
108         return this.targetType;
109     }
110
111     @Override
112     public String toString() {
113         return "InstanceIdentifier [path=" + path + "]";
114     }
115
116     /**
117      * Return an instance identifier trimmed at the first occurrence of a
118      * specific component type.
119      *
120      * For example let's say an instance identifier was built like so,
121      * <pre>
122      *      identifier = InstanceIdentifierBuilder.builder(Nodes.class).child(Node.class, new NodeKey(new NodeId("openflow:1")).build();
123      * </pre>
124      *
125      * And you wanted to obtain the Instance identifier which represented Nodes you would do it like so,
126      *
127      * <pre>
128      *      identifier.firstIdentifierOf(Nodes.class)
129      * </pre>
130      *
131      * @param type component type
132      * @return trimmed instance identifier, or null if the component type
133      *         is not present.
134      */
135     @SuppressWarnings("hiding")
136     public <T extends DataObject> InstanceIdentifier<T> firstIdentifierOf(final Class<T> type) {
137         int i = 1;
138         for (final PathArgument a : path) {
139             if (type.equals(a.getType())) {
140                 return new InstanceIdentifier<>(path.subList(0, i), type);
141             }
142
143             ++i;
144         }
145
146         return null;
147     }
148
149     /**
150      * Return the key associated with the first component of specified type in
151      * an identifier.
152      *
153      * @param listItem component type
154      * @param listKey component key type
155      * @return key associated with the component, or null if the component type
156      *         is not present.
157      */
158     public <N extends Identifiable<K> & DataObject, K extends Identifier<N>> K firstKeyOf(final Class<N> listItem, final Class<K> listKey) {
159         for (PathArgument i : path) {
160             if (listItem.equals(i.getType())) {
161                 @SuppressWarnings("unchecked")
162                 final K ret = ((IdentifiableItem<N, K>)i).getKey();
163                 return ret;
164             }
165         }
166
167         return null;
168     }
169
170     /**
171      * Return the key associated with the last component of the specified identifier.
172      *
173      * @param id instance identifier
174      * @return key associated with the last component
175      */
176     public static <N extends Identifiable<K> & DataObject, K extends Identifier<N>> K keyOf(final InstanceIdentifier<N> id) {
177         @SuppressWarnings("unchecked")
178         final K ret = ((IdentifiableItem<N, K>)Iterables.getLast(id.getPath())).getKey();
179         return ret;
180     }
181
182     /**
183      * Path argument of {@link InstanceIdentifier}.
184      * <p>
185      * Interface which implementations are used as path components of the
186      * path in overall data tree.
187      *
188      */
189     public interface PathArgument {
190
191         Class<? extends DataObject> getType();
192     }
193
194
195     /**
196      * An Item represents an object that probably is only one of it's kind. For example a Nodes object is only one of
197      * a kind. In YANG terms this would probably represent a container.
198      *
199      * @param <T>
200      */
201     public static final class Item<T extends DataObject> implements PathArgument {
202         private final Class<T> type;
203
204         public Item(Class<T> type) {
205             this.type = type;
206         }
207
208         @Override
209         public Class<T> getType() {
210             return type;
211         }
212
213         @Override
214         public int hashCode() {
215             final int prime = 31;
216             int result = 1;
217             result = prime * result + ((type == null) ? 0 : type.hashCode());
218             return result;
219         }
220
221         @Override
222         public boolean equals(Object obj) {
223             if (this == obj)
224                 return true;
225             if (obj == null)
226                 return false;
227             if (getClass() != obj.getClass())
228                 return false;
229             Item<?> other = (Item<?>) obj;
230             if (type == null) {
231                 if (other.type != null)
232                     return false;
233             } else if (!type.equals(other.type))
234                 return false;
235             return true;
236         }
237
238         @Override
239         public String toString() {
240             return type.getName();
241         }
242     }
243
244     /**
245      * An IdentifiableItem represents a object that is usually present in a collection and can be identified uniquely
246      * by a key. In YANG terms this would probably represent an item in a list.
247      *
248      * @param <I> An object that is identifiable by an identifier
249      * @param <T> The identifier of the object
250      */
251     public static final class IdentifiableItem<I extends Identifiable<T> & DataObject, T extends Identifier<I>> implements
252             PathArgument {
253
254         private final T key;
255         private final Class<I> type;
256
257         public IdentifiableItem(Class<I> type, T key) {
258             if (type == null)
259                 throw new IllegalArgumentException("Type must not be null.");
260             if (key == null)
261                 throw new IllegalArgumentException("Key must not be null.");
262             this.type = type;
263             this.key = key;
264         }
265
266         public T getKey() {
267             return this.key;
268         }
269
270         @Override
271         public Class<I> getType() {
272             return this.type;
273         }
274
275         @Override
276         public boolean equals(Object obj) {
277             if (obj == null) {
278                 return false;
279             }
280             if (obj.hashCode() != hashCode()) {
281                 return false;
282             }
283             if (!(obj instanceof IdentifiableItem<?, ?>)) {
284                 return false;
285             }
286             IdentifiableItem<?, ?> foreign = (IdentifiableItem<?, ?>) obj;
287             return key.equals(foreign.getKey());
288         }
289
290         @Override
291         public int hashCode() {
292             return key.hashCode();
293         }
294
295         @Override
296         public String toString() {
297             return type.getName() + "[key=" + key + "]";
298         }
299     }
300
301     public interface InstanceIdentifierBuilder<T extends DataObject> extends Builder<InstanceIdentifier<T>> {
302         /**
303          * @deprecated use {@link child(Class)} or {@link augmentation(Class)} instead.
304          */
305         @Deprecated
306         <N extends DataObject> InstanceIdentifierBuilder<N> node(Class<N> container);
307
308         /**
309          * @deprecated use {@link child(Class,Identifier)} or {@link augmentation(Class,Identifier)} instead.
310          */
311         @Deprecated
312         <N extends Identifiable<K> & DataObject, K extends Identifier<N>> InstanceIdentifierBuilder<N> node(
313                 Class<N> listItem, K listKey);
314
315         /**
316          * Append the specified container as a child of the current InstanceIdentifier referenced by the builder.
317          *
318          * This method should be used when you want to build an instance identifier by appending top-level
319          * elements
320          *
321          * Example,
322          * <pre>
323          *     InstanceIdentifier.builder().child(Nodes.class).build();
324          *
325          * </pre>
326          *
327          * NOTE :- The above example is only for illustration purposes InstanceIdentifier.builder() has been deprecated
328          * and should not be used. Use InstanceIdentifier.builder(Nodes.class) instead
329          *
330          * @param container
331          * @param <N>
332          * @return
333          */
334         <N extends ChildOf<? super T>> InstanceIdentifierBuilder<N> child(Class<N> container);
335
336         /**
337          * Append the specified listItem as a child of the current InstanceIdentifier referenced by the builder.
338          *
339          * This method should be used when you want to build an instance identifier by appending a specific list element
340          * to the identifier
341          *
342          * @param listItem
343          * @param listKey
344          * @param <N>
345          * @param <K>
346          * @return
347          */
348         <N extends Identifiable<K> & ChildOf<? super T>, K extends Identifier<N>> InstanceIdentifierBuilder<N> child(
349                 Class<N> listItem, K listKey);
350
351         /**
352          * Build an identifier which refers to a specific augmentation of the current InstanceIdentifier referenced by
353          * the builder
354          *
355          * @param container
356          * @param <N>
357          * @return
358          */
359         <N extends DataObject & Augmentation<? super T>> InstanceIdentifierBuilder<N> augmentation(Class<N> container);
360
361         /**
362          * Build the instance identifier.
363          *
364          * @return
365          */
366         InstanceIdentifier<T> build();
367
368     }
369
370     /**
371      * @deprecated use {@link builder(Class)} or {@link builder(Class,Identifier)} instead.
372      */
373     @Deprecated
374     @SuppressWarnings("rawtypes")
375     public static InstanceIdentifierBuilder<?> builder() {
376         return new BuilderImpl();
377     }
378
379     /**
380      * Create an InstanceIdentifierBuilder for a specific type of InstanceIdentifier as specified by container
381      *
382      * @param container
383      * @param <T>
384      * @return
385      */
386     public static <T extends ChildOf<? extends DataRoot>> InstanceIdentifierBuilder<T> builder(Class<T> container) {
387         return new BuilderImpl<T>().addNode(container);
388     }
389
390     /**
391      * Create an InstanceIdentifierBuilder for a specific type of InstanceIdentifier which represents an IdentifiableItem
392      *
393      * @param listItem
394      * @param listKey
395      * @param <N>
396      * @param <K>
397      * @return
398      */
399     public static <N extends Identifiable<K> & ChildOf<? extends DataRoot>, K extends Identifier<N>> InstanceIdentifierBuilder<N> builder(
400             Class<N> listItem, K listKey) {
401         return new BuilderImpl<N>().addNode(listItem, listKey);
402     }
403
404     /**
405      * Create a new InstanceIdentifierBuilder given a base InstanceIdentifier
406      *
407      * @param basePath
408      * @param <T>
409      * @return
410      */
411     public static <T extends DataObject> InstanceIdentifierBuilder<T> builder(InstanceIdentifier<T> basePath) {
412         return new BuilderImpl<T>(basePath.path,basePath.targetType);
413     }
414
415     private static final class BuilderImpl<T extends DataObject> implements InstanceIdentifierBuilder<T> {
416
417         private final ImmutableList.Builder<PathArgument> path;
418         private Class<? extends DataObject> target = null;
419
420         public BuilderImpl() {
421             this.path = ImmutableList.builder();
422         }
423
424         public BuilderImpl(List<? extends PathArgument> prefix,Class<? extends DataObject> target) {
425             this.path = ImmutableList.<PathArgument>builder().addAll(prefix);
426             this.target = target;
427         }
428
429         @SuppressWarnings("unchecked")
430         private <N extends DataObject> InstanceIdentifierBuilder<N> addNode(Class<N> container) {
431             target = container;
432             path.add(new Item<N>(container));
433             return (InstanceIdentifierBuilder<N>) this;
434         }
435
436         @SuppressWarnings("unchecked")
437         private <N extends DataObject & Identifiable<K> , K extends Identifier<N>> InstanceIdentifierBuilder<N> addNode(
438                 Class<N> listItem, K listKey) {
439             target = listItem;
440             path.add(new IdentifiableItem<N, K>(listItem, listKey));
441             return (InstanceIdentifierBuilder<N>) this;
442         }
443
444         @SuppressWarnings({ "unchecked", "rawtypes" })
445         @Override
446         public InstanceIdentifier<T> toInstance() {
447             return new InstanceIdentifier(path.build(), target);
448         }
449
450         @Override
451         public InstanceIdentifier<T> build() {
452             return toInstance();
453         }
454
455         @Override
456         public <N extends DataObject> InstanceIdentifierBuilder<N> node(Class<N> container) {
457             return addNode(container);
458         }
459
460         @Override
461         public <N extends DataObject & Identifiable<K> , K extends Identifier<N>> InstanceIdentifierBuilder<N> node(
462                 Class<N> listItem, K listKey) {
463             return addNode(listItem, listKey);
464         }
465
466         @Override
467         public <N extends ChildOf<? super T>> InstanceIdentifierBuilder<N> child(Class<N> container) {
468             return addNode(container);
469         }
470
471         @Override
472         public <N extends Identifiable<K> & ChildOf<? super T>, K extends Identifier<N>> InstanceIdentifierBuilder<N> child(
473                 Class<N> listItem, K listKey) {
474             return addNode(listItem,listKey);
475         }
476
477         @Override
478         public <N extends DataObject & Augmentation<? super T>> InstanceIdentifierBuilder<N> augmentation(
479                 Class<N> container) {
480             return addNode(container);
481         }
482
483         @Override
484         public int hashCode() {
485             final int prime = 31;
486             int result = 1;
487             result = prime * result + ((path == null) ? 0 : path.hashCode());
488             return result;
489         }
490     }
491
492     @Override
493     public int hashCode() {
494         final int prime = 31;
495         int result = 1;
496         result = prime * result + ((path == null) ? 0 : path.hashCode());
497         return result;
498     }
499
500     @Override
501     public boolean equals(Object obj) {
502         if (this == obj) {
503             return true;
504         }
505         if (obj == null) {
506             return false;
507         }
508         if (getClass() != obj.getClass()) {
509             return false;
510         }
511         InstanceIdentifier<?> other = (InstanceIdentifier<?>) obj;
512         if (path == null) {
513             if (other.path != null) {
514                 return false;
515             }
516         } else if (!path.equals(other.path)) {
517             return false;
518         }
519         return true;
520     }
521
522     /**
523      * The contains method checks if the other identifier is fully contained within the current identifier. It does this
524      * by looking at only the types of the path arguments and not by comparing the path arguments themselse.
525      * If you want to compare path arguments you must use containsWildcarded
526      *
527      * To illustrate here is an example which explains the working of this api.
528      *
529      * Let's say you have two instance identifiers as follows,
530      *
531      * this = /nodes/node/openflow:1
532      * other = /nodes/node/openflow:2
533      *
534      * then this.contains(other) will return true. To ensure that this and other are compared properly you must use
535      * containsWildcarded
536      *
537      * @param other
538      * @return
539      */
540     @Override
541     public boolean contains(final InstanceIdentifier<?> other) {
542         if(other == null) {
543             throw new IllegalArgumentException("other should not be null");
544         }
545         final int localSize = this.path.size();
546         final List<PathArgument> otherPath = other.getPath();
547         if(localSize > other.path.size()) {
548             return false;
549         }
550         for(int i = 0;i<localSize;i++ ) {
551             if(!path.get(i).equals(otherPath.get(i))) {
552                 return false;
553             }
554         }
555         return true;
556     }
557
558     /**
559      * The containsWildcarded method checks if the other identifier is fully contained within the current identifier.
560      * It does this by looking at both the type and identity of the path arguments.
561      *
562      * @param other
563      * @return
564      */
565     public boolean containsWildcarded(final InstanceIdentifier<?> other) {
566         if(other == null) {
567             throw new IllegalArgumentException("other should not be null");
568         }
569         final int localSize = this.path.size();
570         final List<PathArgument> otherPath = other.getPath();
571         if(localSize > other.path.size()) {
572             return false;
573         }
574         for(int i = 0;i<localSize;i++ ) {
575             final PathArgument localArgument = path.get(i);
576             final PathArgument otherArgument = otherPath.get(i);
577             if(!localArgument.getType().equals(otherArgument.getType())) {
578                 return false;
579             }
580             if(localArgument instanceof IdentifiableItem<?, ?> && otherArgument instanceof IdentifiableItem<?, ?> && !localArgument.equals(otherPath.get(i))) {
581                 return false;
582             }
583         }
584         return true;
585     }
586
587     public boolean isWildcarded() {
588         for(PathArgument pathArgument : path) {
589             if(Identifiable.class.isAssignableFrom(pathArgument.getType()) && !(pathArgument instanceof IdentifiableItem<?, ?>)) {
590                 return true;
591             }
592         }
593         return false;
594     }
595 }