Rework data.api.InstanceIdentifier to work on Iterables
[yangtools.git] / yang / yang-data-api / src / main / java / org / opendaylight / yangtools / yang / data / api / InstanceIdentifier.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
3  * This program and the accompanying materials are made available under the
4  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
5  * and is available at http://www.eclipse.org/legal/epl-v10.html
6  */
7 package org.opendaylight.yangtools.yang.data.api;
8
9 import com.google.common.base.Optional;
10 import com.google.common.base.Preconditions;
11 import com.google.common.collect.ImmutableList;
12 import com.google.common.collect.ImmutableMap;
13 import com.google.common.collect.ImmutableSet;
14 import com.google.common.collect.Iterables;
15 import com.google.common.collect.Lists;
16
17 import java.io.Serializable;
18 import java.lang.reflect.Array;
19 import java.util.ArrayList;
20 import java.util.Arrays;
21 import java.util.Collections;
22 import java.util.Iterator;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Map.Entry;
26 import java.util.Objects;
27 import java.util.Set;
28
29 import org.opendaylight.yangtools.concepts.Builder;
30 import org.opendaylight.yangtools.concepts.Immutable;
31 import org.opendaylight.yangtools.concepts.Path;
32 import org.opendaylight.yangtools.util.HashCodeBuilder;
33 import org.opendaylight.yangtools.yang.common.QName;
34 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
35
36 /**
37  * Unique identifier of a partical node instance in the data tree.
38  *
39  * <p>
40  * Java representation of YANG Built-in type <code>instance-identifier</code>,
41  * which conceptually is XPath expression minimised to uniquely identify element
42  * in data tree which conforms to constraints maintained by YANG Model,
43  * effectively this makes Instance Identifier a path to element in data tree.
44  * <p>
45  * Constraints put in YANG specification on instance-identifier allowed it to be
46  * effectively represented in Java and it's evaluation does not require
47  * full-blown XPath processor.
48  * <p>
49  * <h3>Path Arguments</h3>
50  * Path to the node represented in instance identifier consists of
51  * {@link PathArgument} which carries necessary information to uniquely identify
52  * node on particular level in the subtree.
53  * <p>
54  * <ul>
55  * <li>{@link NodeIdentifier} - Identifier of node, which has cardinality
56  * <code>0..1</code> in particular subtree in data tree.</li>
57  * <li>{@link NodeIdentifierWithPredicates} - Identifier of node (list item),
58  * which has cardinality <code>0..n</code>.</li>
59  * <li>{@link NodeWithValue} - Identifier of instance <code>leaf</code> node or
60  * <code>leaf-list</code> node.</li>
61  * <li>{@link AugmentationIdentifier} - Identifier of instance of
62  * <code>augmentation</code> node.</li>
63  * </ul>
64  *
65  *
66  * @see http://tools.ietf.org/html/rfc6020#section-9.13
67  */
68 public final class InstanceIdentifier implements Path<InstanceIdentifier>, Immutable, Serializable {
69     private static final InstanceIdentifier EMPTY = trustedCreate(Collections.<PathArgument>emptyList());
70
71     private static final long serialVersionUID = 2L;
72     private final Iterable<PathArgument> pathArguments;
73     private final int hash;
74
75     private transient ImmutableList<PathArgument> legacyPath = null;
76     private transient String toStringCache = null;
77
78     private final ImmutableList<PathArgument> getLegacyPath() {
79         if (legacyPath == null) {
80             synchronized (this) {
81                 if (legacyPath == null) {
82                     legacyPath = ImmutableList.copyOf(pathArguments);
83                 }
84             }
85         }
86
87         return legacyPath;
88     }
89
90     /**
91      * Returns a list of path arguments.
92      *
93      * @deprecated Use {@link #getPathArguments()} instead.
94      * @return Immutable list of path arguments.
95      */
96     @Deprecated
97     public List<PathArgument> getPath() {
98         return getLegacyPath();
99     }
100
101     /**
102      * Returns an ordered iteration of path arguments.
103      *
104      * @return Immutable iteration of path arguments.
105      */
106     public Iterable<PathArgument> getPathArguments() {
107         return pathArguments;
108     }
109
110     /**
111      * Returns an iterable of path arguments in reverse order. This is useful
112      * when walking up a tree organized this way.
113      *
114      * @return Immutable iterable of path arguments in reverse order.
115      */
116     public Iterable<PathArgument> getReversePathArguments() {
117         return getLegacyPath().reverse();
118     }
119
120     /**
121      * Returns the last PathArgument. This is equivalent of iterating
122      * to the last element of the iterable returned by {@link #getPathArguments()}.
123      *
124      * @return The last past argument, or null if there are no PathArguments.
125      */
126     public PathArgument getLastPathArgument() {
127         return Iterables.getFirst(getReversePathArguments(), null);
128     }
129
130     private InstanceIdentifier(final Iterable<PathArgument> path, final int hash) {
131         this.pathArguments = Preconditions.checkNotNull(path, "path must not be null.");
132         this.hash = hash;
133     }
134
135     private static final InstanceIdentifier trustedCreate(final Iterable<PathArgument> path) {
136         final HashCodeBuilder<PathArgument> hash = new HashCodeBuilder<>();
137         for (PathArgument a : path) {
138             hash.addArgument(a);
139         }
140
141         return new InstanceIdentifier(path, hash.toInstance());
142     }
143
144     public static final InstanceIdentifier create(final Iterable<? extends PathArgument> path) {
145         if (Iterables.isEmpty(path)) {
146             return EMPTY;
147         }
148
149         return trustedCreate(ImmutableList.copyOf(path));
150     }
151
152     public static final InstanceIdentifier create(final PathArgument... path) {
153         // We are forcing a copy, since we cannot trust the user
154         return create(Arrays.asList(path));
155     }
156
157     @Override
158     public int hashCode() {
159         /*
160          * The caching is safe, since the object contract requires
161          * immutability of the object and all objects referenced from this
162          * object.
163          * Used lists, maps are immutable. Path Arguments (elements) are also
164          * immutable, since the PathArgument contract requires immutability.
165          */
166         return hash;
167     }
168
169     @Override
170     public boolean equals(final Object obj) {
171         if (this == obj) {
172             return true;
173         }
174         if (obj == null) {
175             return false;
176         }
177         if (getClass() != obj.getClass()) {
178             return false;
179         }
180         InstanceIdentifier other = (InstanceIdentifier) obj;
181         if (this.hashCode() != obj.hashCode()) {
182             return false;
183         }
184         return Iterables.elementsEqual(pathArguments, other.pathArguments);
185     }
186
187     /**
188      * Constructs a new Instance Identifier with new {@link NodeIdentifier} added to the end of path arguments
189      *
190      * @param name QName of {@link NodeIdentifier}
191      * @return Instance Identifier with additional path argument added to the end.
192      */
193     public InstanceIdentifier node(final QName name) {
194         return node(new NodeIdentifier(name));
195     }
196
197     /**
198      *
199      * Constructs a new Instance Identifier with new {@link PathArgument} added to the end of path arguments
200      *
201      * @param arg Path argument which should be added to the end
202      * @return Instance Identifier with additional path argument added to the end.
203      */
204     public InstanceIdentifier node(final PathArgument arg) {
205         return new InstanceIdentifier(Iterables.concat(pathArguments, Collections.singleton(arg)), HashCodeBuilder.nextHashCode(hash, arg));
206     }
207
208     /**
209      * Get the relative path from an ancestor. This method attempts to perform
210      * the reverse of concatenating a base (ancestor) and a path.
211      *
212      * @param ancestor
213      *            Ancestor against which the relative path should be calculated
214      * @return This object's relative path from parent, or Optional.absent() if
215      *         the specified parent is not in fact an ancestor of this object.
216      */
217     public Optional<InstanceIdentifier> relativeTo(final InstanceIdentifier ancestor) {
218         final Iterator<?> lit = pathArguments.iterator();
219         final Iterator<?> oit = ancestor.pathArguments.iterator();
220         int common = 0;
221
222         while (oit.hasNext()) {
223             // Ancestor is not really an ancestor
224             if (!lit.hasNext() || !lit.next().equals(oit.next())) {
225                 return Optional.absent();
226             }
227
228             ++common;
229         }
230
231         if (common == 0) {
232             return Optional.of(this);
233         }
234         if (!lit.hasNext()) {
235             return Optional.of(EMPTY);
236         }
237         return Optional.of(trustedCreate(Iterables.skip(pathArguments, common)));
238     }
239
240     private static int hashCode(final Object value) {
241         if (value == null) {
242             return 0;
243         }
244
245         if (value.getClass().equals(byte[].class)) {
246             return Arrays.hashCode((byte[]) value);
247         }
248
249         if (value.getClass().isArray()) {
250             int hash = 0;
251             int length = Array.getLength(value);
252             for (int i = 0; i < length; i++) {
253                 hash += Objects.hashCode(Array.get(value, i));
254             }
255
256             return hash;
257         }
258
259         return Objects.hashCode(value);
260     }
261
262     // Static factories & helpers
263
264     /**
265      *
266      * Returns a new InstanceIdentifier with only one path argument of type {@link NodeIdentifier} with supplied QName
267      *
268      * @param name QName of first node identifier
269      * @return Instance Identifier with only one path argument of type {@link NodeIdentifier}
270      */
271     public static InstanceIdentifier of(final QName name) {
272         return create(new NodeIdentifier(name));
273     }
274
275     /**
276      *
277      * Returns new builder for InstanceIdentifier with empty path arguments.
278      *
279      * @return new builder for InstanceIdentifier with empty path arguments.
280      */
281     static public InstanceIdentifierBuilder builder() {
282         return new BuilderImpl();
283     }
284
285     /**
286      *
287      * Returns new builder for InstanceIdentifier with path arguments copied from original instance identifier.
288      *
289      * @param origin Instace Identifier from which path arguments are copied.
290      * @return new builder for InstanceIdentifier with path arguments copied from original instance identifier.
291      */
292     static public InstanceIdentifierBuilder builder(final InstanceIdentifier origin) {
293         return new BuilderImpl(origin.getPathArguments(), origin.hashCode());
294     }
295
296     /**
297      * Returns new builder for InstanceIdentifier with first path argument set to {@link NodeIdentifier}.
298      *
299      * @param node QName of first {@link NodeIdentifier} path argument.
300      * @return  new builder for InstanceIdentifier with first path argument set to {@link NodeIdentifier}.
301      *
302      * @deprecated Either use {@link #node(QName)} or instantiate an intermediate builder.
303      */
304     @Deprecated
305     public static InstanceIdentifierBuilder builder(final QName node) {
306         return builder().node(node);
307     }
308
309     /**
310      * Path argument / component of InstanceIdentifier
311      *
312      * Path argument uniquelly identifies node in data tree on particular
313      * level.
314      * <p>
315      * This interface itself is used as common parent for actual
316      * path arguments types and should not be implemented by user code.
317      * <p>
318      * Path arguments SHOULD contain only minimum of information
319      * required to uniquely identify node on particular subtree level.
320      *
321      * For actual path arguments types see:
322      * <ul>
323      * <li>{@link NodeIdentifier} - Identifier of container or leaf
324      * <li>{@link NodeIdentifierWithPredicates} - Identifier of list entries, which have key defined
325      * <li>{@link AugmentationIdentifier} - Identifier of augmentation
326      * <li>{@link NodeWithValue} - Identifier of leaf-list entry
327      * </ul>
328      */
329     public interface PathArgument extends Comparable<PathArgument>, Immutable, Serializable {
330         /**
331          * If applicable returns unique QName of data node as defined in YANG
332          * Schema.
333          *
334          * This method may return null, if the corresponding schema node, does
335          * not have QName associated, such as in cases of augmentations.
336          *
337          * @return Node type
338          */
339         QName getNodeType();
340     }
341
342     private static abstract class AbstractPathArgument implements PathArgument {
343         private static final long serialVersionUID = -4546547994250849340L;
344         private final QName nodeType;
345
346         protected AbstractPathArgument(final QName nodeType) {
347             this.nodeType = Preconditions.checkNotNull(nodeType);
348         }
349
350         @Override
351         public final QName getNodeType() {
352             return nodeType;
353         }
354
355         @Override
356         public int compareTo(final PathArgument o) {
357             return nodeType.compareTo(o.getNodeType());
358         }
359
360         @Override
361         public int hashCode() {
362             return 31 + getNodeType().hashCode();
363         }
364
365         @Override
366         public boolean equals(final Object obj) {
367             if (this == obj) {
368                 return true;
369             }
370             if (obj == null || this.getClass() != obj.getClass()) {
371                 return false;
372             }
373
374             return getNodeType().equals(((AbstractPathArgument)obj).getNodeType());
375         }
376
377         @Override
378         public String toString() {
379             return getNodeType().toString();
380         }
381     }
382
383     /**
384      *
385      * Fluent Builder of Instance Identifier instances
386      *
387      * @
388      *
389      */
390     public interface InstanceIdentifierBuilder extends Builder<InstanceIdentifier> {
391         /**
392          * Adds {@link NodeIdentifier} with supplied QName to path arguments of resulting instance identifier.
393          *
394          * @param nodeType QName of {@link NodeIdentifier} which will be added
395          * @return this builder
396          */
397         InstanceIdentifierBuilder node(QName nodeType);
398
399         /**
400          * Adds {@link NodeIdentifierWithPredicates} with supplied QName and key values to path arguments of resulting instance identifier.
401          *
402          * @param nodeType QName of {@link NodeIdentifierWithPredicates} which will be added
403          * @param keyValues Map of key components and their respective values for {@link NodeIdentifierWithPredicates}
404          * @return this builder
405          */
406         InstanceIdentifierBuilder nodeWithKey(QName nodeType, Map<QName, Object> keyValues);
407
408         /**
409          * Adds {@link NodeIdentifierWithPredicates} with supplied QName and key, value.
410          *
411          * @param nodeType QName of {@link NodeIdentifierWithPredicates} which will be added
412          * @param key QName of key which will be added
413          * @param value value of key which will be added
414          * @return this builder
415          */
416         InstanceIdentifierBuilder nodeWithKey(QName nodeType, QName key, Object value);
417
418         /**
419          * @return
420          * @deprecated use {@link #build()}
421          *
422          */
423         @Deprecated
424         InstanceIdentifier getIdentifier();
425
426         /**
427          *
428          * Builds an {@link InstanceIdentifier} with path arguments from this builder
429          *
430          * @return {@link InstanceIdentifier}
431          */
432         InstanceIdentifier build();
433     }
434
435     /**
436      * Simple path argument identifying a {@link org.opendaylight.yangtools.yang.data.api.schema.ContainerNode} or
437      * {@link org.opendaylight.yangtools.yang.data.api.schema.LeafNode} leaf in particular subtree.
438      */
439     public static final class NodeIdentifier extends AbstractPathArgument {
440         private static final long serialVersionUID = -2255888212390871347L;
441
442         public NodeIdentifier(final QName node) {
443             super(node);
444         }
445     }
446
447     /**
448      * Composite path argument identifying a {@link org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode} leaf
449      * overall data tree.
450      */
451     public static final class NodeIdentifierWithPredicates extends AbstractPathArgument {
452         private static final long serialVersionUID = -4787195606494761540L;
453
454         private final Map<QName, Object> keyValues;
455
456         public NodeIdentifierWithPredicates(final QName node, final Map<QName, Object> keyValues) {
457             super(node);
458             this.keyValues = ImmutableMap.copyOf(keyValues);
459         }
460
461         public NodeIdentifierWithPredicates(final QName node, final QName key, final Object value) {
462             this(node, ImmutableMap.of(key, value));
463         }
464
465         public Map<QName, Object> getKeyValues() {
466             return keyValues;
467         }
468
469         @Override
470         public int hashCode() {
471             final int prime = 31;
472             int result = super.hashCode();
473             result = prime * result;
474
475             for (Entry<QName, Object> entry : keyValues.entrySet()) {
476                 result += Objects.hashCode(entry.getKey()) + InstanceIdentifier.hashCode(entry.getValue());
477             }
478             return result;
479         }
480
481         @Override
482         public boolean equals(final Object obj) {
483             if (!super.equals(obj)) {
484                 return false;
485             }
486
487             final Map<QName, Object> otherKeyValues = ((NodeIdentifierWithPredicates) obj).keyValues;
488             if (keyValues.size() != otherKeyValues.size()) {
489                 return false;
490             }
491
492             for (Entry<QName, Object> entry : keyValues.entrySet()) {
493                 if (!otherKeyValues.containsKey(entry.getKey())
494                         || !Objects.deepEquals(entry.getValue(), otherKeyValues.get(entry.getKey()))) {
495
496                     return false;
497                 }
498             }
499
500             return true;
501         }
502
503         @Override
504         public String toString() {
505             return super.toString() + '[' + keyValues + ']';
506         }
507     }
508
509     /**
510      * Simple path argument identifying a {@link LeafSetEntryNode} leaf
511      * overall data tree.
512      */
513     public static final class NodeWithValue extends AbstractPathArgument {
514         private static final long serialVersionUID = -3637456085341738431L;
515
516         private final Object value;
517
518         public NodeWithValue(final QName node, final Object value) {
519             super(node);
520             this.value = value;
521         }
522
523         public Object getValue() {
524             return value;
525         }
526
527         @Override
528         public int hashCode() {
529             final int prime = 31;
530             int result = super.hashCode();
531             result = prime * result + ((value == null) ? 0 : InstanceIdentifier.hashCode(value));
532             return result;
533         }
534
535         @Override
536         public boolean equals(final Object obj) {
537             if (!super.equals(obj)) {
538                 return false;
539             }
540             final NodeWithValue other = (NodeWithValue) obj;
541             return Objects.deepEquals(value, other.value);
542         }
543
544         @Override
545         public String toString() {
546             return super.toString() + '[' + value + ']';
547         }
548     }
549
550     /**
551      * Composite path argument identifying a {@link org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode} node in
552      * particular subtree.
553      *
554      * Augmentation is uniquely identified by set of all possible child nodes.
555      * This is possible
556      * to identify instance of augmentation,
557      * since RFC6020 states that <code>augment</code> that augment
558      * statement must not add multiple nodes from same namespace
559      * / module to the target node.
560      *
561      *
562      * @see http://tools.ietf.org/html/rfc6020#section-7.15
563      */
564     public static final class AugmentationIdentifier implements PathArgument {
565         private static final long serialVersionUID = -8122335594681936939L;
566         private final ImmutableSet<QName> childNames;
567
568         @Override
569         public QName getNodeType() {
570             // This should rather throw exception than return always null
571             throw new UnsupportedOperationException("Augmentation node has no QName");
572         }
573
574         /**
575          *
576          * Construct new augmentation identifier using supplied set of possible
577          * child nodes
578          *
579          * @param childNames
580          *            Set of possible child nodes.
581          */
582         public AugmentationIdentifier(final Set<QName> childNames) {
583             this.childNames = ImmutableSet.copyOf(childNames);
584         }
585
586         /**
587          * Augmentation node has no QName
588          *
589          * @deprecated Use
590          *             {@link AugmentationIdentifier#AugmentationIdentifier(Set)}
591          *             instead.
592          */
593         @Deprecated
594         public AugmentationIdentifier(final QName nodeType, final Set<QName> childNames) {
595             this(childNames);
596         }
597
598         /**
599          *
600          * Returns set of all possible child nodes
601          *
602          * @return set of all possible child nodes.
603          */
604         public Set<QName> getPossibleChildNames() {
605             return childNames;
606         }
607
608         @Override
609         public String toString() {
610             final StringBuffer sb = new StringBuffer("AugmentationIdentifier{");
611             sb.append("childNames=").append(childNames).append('}');
612             return sb.toString();
613         }
614
615         @Override
616         public boolean equals(final Object o) {
617             if (this == o) {
618                 return true;
619             }
620             if (!(o instanceof AugmentationIdentifier)) {
621                 return false;
622             }
623
624             AugmentationIdentifier that = (AugmentationIdentifier) o;
625             return childNames.equals(that.childNames);
626         }
627
628         @Override
629         public int hashCode() {
630             return childNames.hashCode();
631         }
632
633         @Override
634         public int compareTo(final PathArgument o) {
635             if (!(o instanceof AugmentationIdentifier)) {
636                 return -1;
637             }
638             AugmentationIdentifier other = (AugmentationIdentifier) o;
639             Set<QName> otherChildNames = other.getPossibleChildNames();
640             int thisSize = childNames.size();
641             int otherSize = otherChildNames.size();
642             if (thisSize == otherSize) {
643                 Iterator<QName> otherIterator = otherChildNames.iterator();
644                 for (QName name : childNames) {
645                     int c = name.compareTo(otherIterator.next());
646                     if (c != 0) {
647                         return c;
648                     }
649                 }
650                 return 0;
651             } else if (thisSize < otherSize) {
652                 return 1;
653             } else {
654                 return -1;
655             }
656         }
657     }
658
659     private static class BuilderImpl implements InstanceIdentifierBuilder {
660         private final HashCodeBuilder<PathArgument> hash;
661         private final List<PathArgument> path;
662
663         public BuilderImpl() {
664             this.hash = new HashCodeBuilder<>();
665             this.path = new ArrayList<>();
666         }
667
668         public BuilderImpl(final Iterable<PathArgument> prefix, final int hash) {
669             this.path = Lists.newArrayList(prefix);
670             this.hash = new HashCodeBuilder<>(hash);
671         }
672
673         @Override
674         public InstanceIdentifierBuilder node(final QName nodeType) {
675             final PathArgument arg = new NodeIdentifier(nodeType);
676             path.add(arg);
677             hash.addArgument(arg);
678             return this;
679         }
680
681         @Override
682         public InstanceIdentifierBuilder nodeWithKey(final QName nodeType, final QName key, final Object value) {
683             final PathArgument arg = new NodeIdentifierWithPredicates(nodeType, key, value);
684             path.add(arg);
685             hash.addArgument(arg);
686             return this;
687         }
688
689         @Override
690         public InstanceIdentifierBuilder nodeWithKey(final QName nodeType, final Map<QName, Object> keyValues) {
691             final PathArgument arg = new NodeIdentifierWithPredicates(nodeType, keyValues);
692             path.add(arg);
693             hash.addArgument(arg);
694             return this;
695         }
696
697         @Override
698         @Deprecated
699         public InstanceIdentifier toInstance() {
700             return build();
701         }
702
703         @Override
704         public InstanceIdentifier build() {
705             return new InstanceIdentifier(ImmutableList.copyOf(path), hash.toInstance());
706         }
707
708         @Override
709         @Deprecated
710         public InstanceIdentifier getIdentifier() {
711             return build();
712         }
713     }
714
715     @Override
716     public boolean contains(final InstanceIdentifier other) {
717         Preconditions.checkArgument(other != null, "other should not be null");
718
719         final Iterator<?> lit = pathArguments.iterator();
720         final Iterator<?> oit = other.pathArguments.iterator();
721
722         while (lit.hasNext()) {
723             if (!oit.hasNext()) {
724                 return false;
725             }
726
727             if (!lit.next().equals(oit.next())) {
728                 return false;
729             }
730         }
731
732         return true;
733     }
734
735     @Override
736     public String toString() {
737         /*
738          * The toStringCache is safe, since the object contract requires
739          * immutability of the object and all objects referenced from this
740          * object.
741          * Used lists, maps are immutable. Path Arguments (elements) are also
742          * immutable, since the PathArgument contract requires immutability.
743          * The cache is thread-safe - if multiple computations occurs at the
744          * same time, cache will be overwritten with same result.
745          */
746         if (toStringCache != null) {
747             return toStringCache;
748         }
749
750         final StringBuilder builder = new StringBuilder('/');
751         boolean first = true;
752         for (PathArgument argument : getPathArguments()) {
753             if (first) {
754                 first = false;
755             } else {
756                 builder.append('/');
757             }
758             builder.append(argument.toString());
759         }
760
761         toStringCache = builder.toString();
762         return toStringCache;
763     }
764 }