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