BUG-1092: rename data.api.InstanceIdentifier to YangInstanceIdentifier
[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 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 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 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 YangInstanceIdentifier(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 YangInstanceIdentifier 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 YangInstanceIdentifier(path, hash.toInstance());
142     }
143
144     public static final YangInstanceIdentifier 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 YangInstanceIdentifier 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         YangInstanceIdentifier other = (YangInstanceIdentifier) 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 YangInstanceIdentifier 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 YangInstanceIdentifier node(final PathArgument arg) {
205         return new YangInstanceIdentifier(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<YangInstanceIdentifier> relativeTo(final YangInstanceIdentifier 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 YangInstanceIdentifier 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 YangInstanceIdentifier 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<YangInstanceIdentifier> {
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          *
420          * Builds an {@link YangInstanceIdentifier} with path arguments from this builder
421          *
422          * @return {@link YangInstanceIdentifier}
423          */
424         YangInstanceIdentifier build();
425     }
426
427     /**
428      * Simple path argument identifying a {@link org.opendaylight.yangtools.yang.data.api.schema.ContainerNode} or
429      * {@link org.opendaylight.yangtools.yang.data.api.schema.LeafNode} leaf in particular subtree.
430      */
431     public static final class NodeIdentifier extends AbstractPathArgument {
432         private static final long serialVersionUID = -2255888212390871347L;
433
434         public NodeIdentifier(final QName node) {
435             super(node);
436         }
437     }
438
439     /**
440      * Composite path argument identifying a {@link org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode} leaf
441      * overall data tree.
442      */
443     public static final class NodeIdentifierWithPredicates extends AbstractPathArgument {
444         private static final long serialVersionUID = -4787195606494761540L;
445
446         private final Map<QName, Object> keyValues;
447
448         public NodeIdentifierWithPredicates(final QName node, final Map<QName, Object> keyValues) {
449             super(node);
450             this.keyValues = ImmutableMap.copyOf(keyValues);
451         }
452
453         public NodeIdentifierWithPredicates(final QName node, final QName key, final Object value) {
454             this(node, ImmutableMap.of(key, value));
455         }
456
457         public Map<QName, Object> getKeyValues() {
458             return keyValues;
459         }
460
461         @Override
462         public int hashCode() {
463             final int prime = 31;
464             int result = super.hashCode();
465             result = prime * result;
466
467             for (Entry<QName, Object> entry : keyValues.entrySet()) {
468                 result += Objects.hashCode(entry.getKey()) + YangInstanceIdentifier.hashCode(entry.getValue());
469             }
470             return result;
471         }
472
473         @Override
474         public boolean equals(final Object obj) {
475             if (!super.equals(obj)) {
476                 return false;
477             }
478
479             final Map<QName, Object> otherKeyValues = ((NodeIdentifierWithPredicates) obj).keyValues;
480             if (keyValues.size() != otherKeyValues.size()) {
481                 return false;
482             }
483
484             for (Entry<QName, Object> entry : keyValues.entrySet()) {
485                 if (!otherKeyValues.containsKey(entry.getKey())
486                         || !Objects.deepEquals(entry.getValue(), otherKeyValues.get(entry.getKey()))) {
487
488                     return false;
489                 }
490             }
491
492             return true;
493         }
494
495         @Override
496         public String toString() {
497             return super.toString() + '[' + keyValues + ']';
498         }
499     }
500
501     /**
502      * Simple path argument identifying a {@link LeafSetEntryNode} leaf
503      * overall data tree.
504      */
505     public static final class NodeWithValue extends AbstractPathArgument {
506         private static final long serialVersionUID = -3637456085341738431L;
507
508         private final Object value;
509
510         public NodeWithValue(final QName node, final Object value) {
511             super(node);
512             this.value = value;
513         }
514
515         public Object getValue() {
516             return value;
517         }
518
519         @Override
520         public int hashCode() {
521             final int prime = 31;
522             int result = super.hashCode();
523             result = prime * result + ((value == null) ? 0 : YangInstanceIdentifier.hashCode(value));
524             return result;
525         }
526
527         @Override
528         public boolean equals(final Object obj) {
529             if (!super.equals(obj)) {
530                 return false;
531             }
532             final NodeWithValue other = (NodeWithValue) obj;
533             return Objects.deepEquals(value, other.value);
534         }
535
536         @Override
537         public String toString() {
538             return super.toString() + '[' + value + ']';
539         }
540     }
541
542     /**
543      * Composite path argument identifying a {@link org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode} node in
544      * particular subtree.
545      *
546      * Augmentation is uniquely identified by set of all possible child nodes.
547      * This is possible
548      * to identify instance of augmentation,
549      * since RFC6020 states that <code>augment</code> that augment
550      * statement must not add multiple nodes from same namespace
551      * / module to the target node.
552      *
553      *
554      * @see http://tools.ietf.org/html/rfc6020#section-7.15
555      */
556     public static final class AugmentationIdentifier implements PathArgument {
557         private static final long serialVersionUID = -8122335594681936939L;
558         private final ImmutableSet<QName> childNames;
559
560         @Override
561         public QName getNodeType() {
562             // This should rather throw exception than return always null
563             throw new UnsupportedOperationException("Augmentation node has no QName");
564         }
565
566         /**
567          *
568          * Construct new augmentation identifier using supplied set of possible
569          * child nodes
570          *
571          * @param childNames
572          *            Set of possible child nodes.
573          */
574         public AugmentationIdentifier(final Set<QName> childNames) {
575             this.childNames = ImmutableSet.copyOf(childNames);
576         }
577
578         /**
579          * Augmentation node has no QName
580          *
581          * @deprecated Use
582          *             {@link AugmentationIdentifier#AugmentationIdentifier(Set)}
583          *             instead.
584          */
585         @Deprecated
586         public AugmentationIdentifier(final QName nodeType, final Set<QName> childNames) {
587             this(childNames);
588         }
589
590         /**
591          *
592          * Returns set of all possible child nodes
593          *
594          * @return set of all possible child nodes.
595          */
596         public Set<QName> getPossibleChildNames() {
597             return childNames;
598         }
599
600         @Override
601         public String toString() {
602             final StringBuffer sb = new StringBuffer("AugmentationIdentifier{");
603             sb.append("childNames=").append(childNames).append('}');
604             return sb.toString();
605         }
606
607         @Override
608         public boolean equals(final Object o) {
609             if (this == o) {
610                 return true;
611             }
612             if (!(o instanceof AugmentationIdentifier)) {
613                 return false;
614             }
615
616             AugmentationIdentifier that = (AugmentationIdentifier) o;
617             return childNames.equals(that.childNames);
618         }
619
620         @Override
621         public int hashCode() {
622             return childNames.hashCode();
623         }
624
625         @Override
626         public int compareTo(final PathArgument o) {
627             if (!(o instanceof AugmentationIdentifier)) {
628                 return -1;
629             }
630             AugmentationIdentifier other = (AugmentationIdentifier) o;
631             Set<QName> otherChildNames = other.getPossibleChildNames();
632             int thisSize = childNames.size();
633             int otherSize = otherChildNames.size();
634             if (thisSize == otherSize) {
635                 Iterator<QName> otherIterator = otherChildNames.iterator();
636                 for (QName name : childNames) {
637                     int c = name.compareTo(otherIterator.next());
638                     if (c != 0) {
639                         return c;
640                     }
641                 }
642                 return 0;
643             } else if (thisSize < otherSize) {
644                 return 1;
645             } else {
646                 return -1;
647             }
648         }
649     }
650
651     private static class BuilderImpl implements InstanceIdentifierBuilder {
652         private final HashCodeBuilder<PathArgument> hash;
653         private final List<PathArgument> path;
654
655         public BuilderImpl() {
656             this.hash = new HashCodeBuilder<>();
657             this.path = new ArrayList<>();
658         }
659
660         public BuilderImpl(final Iterable<PathArgument> prefix, final int hash) {
661             this.path = Lists.newArrayList(prefix);
662             this.hash = new HashCodeBuilder<>(hash);
663         }
664
665         @Override
666         public InstanceIdentifierBuilder node(final QName nodeType) {
667             final PathArgument arg = new NodeIdentifier(nodeType);
668             path.add(arg);
669             hash.addArgument(arg);
670             return this;
671         }
672
673         @Override
674         public InstanceIdentifierBuilder nodeWithKey(final QName nodeType, final QName key, final Object value) {
675             final PathArgument arg = new NodeIdentifierWithPredicates(nodeType, key, value);
676             path.add(arg);
677             hash.addArgument(arg);
678             return this;
679         }
680
681         @Override
682         public InstanceIdentifierBuilder nodeWithKey(final QName nodeType, final Map<QName, Object> keyValues) {
683             final PathArgument arg = new NodeIdentifierWithPredicates(nodeType, keyValues);
684             path.add(arg);
685             hash.addArgument(arg);
686             return this;
687         }
688
689         @Override
690         @Deprecated
691         public YangInstanceIdentifier toInstance() {
692             return build();
693         }
694
695         @Override
696         public YangInstanceIdentifier build() {
697             return new YangInstanceIdentifier(ImmutableList.copyOf(path), hash.toInstance());
698         }
699     }
700
701     @Override
702     public boolean contains(final YangInstanceIdentifier other) {
703         Preconditions.checkArgument(other != null, "other should not be null");
704
705         final Iterator<?> lit = pathArguments.iterator();
706         final Iterator<?> oit = other.pathArguments.iterator();
707
708         while (lit.hasNext()) {
709             if (!oit.hasNext()) {
710                 return false;
711             }
712
713             if (!lit.next().equals(oit.next())) {
714                 return false;
715             }
716         }
717
718         return true;
719     }
720
721     @Override
722     public String toString() {
723         /*
724          * The toStringCache is safe, since the object contract requires
725          * immutability of the object and all objects referenced from this
726          * object.
727          * Used lists, maps are immutable. Path Arguments (elements) are also
728          * immutable, since the PathArgument contract requires immutability.
729          * The cache is thread-safe - if multiple computations occurs at the
730          * same time, cache will be overwritten with same result.
731          */
732         if (toStringCache != null) {
733             return toStringCache;
734         }
735
736         final StringBuilder builder = new StringBuilder('/');
737         boolean first = true;
738         for (PathArgument argument : getPathArguments()) {
739             if (first) {
740                 first = false;
741             } else {
742                 builder.append('/');
743             }
744             builder.append(argument.toString());
745         }
746
747         toStringCache = builder.toString();
748         return toStringCache;
749     }
750 }