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