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