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