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