Eliminate AbstractPathArgument
[yangtools.git] / data / 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 java.util.Objects.requireNonNull;
12
13 import com.google.common.annotations.Beta;
14 import com.google.common.base.VerifyException;
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.ImmutableList;
19 import com.google.common.collect.ImmutableMap;
20 import com.google.common.collect.Iterables;
21 import java.io.Serializable;
22 import java.lang.invoke.MethodHandles;
23 import java.lang.invoke.VarHandle;
24 import java.lang.reflect.Array;
25 import java.util.AbstractMap.SimpleImmutableEntry;
26 import java.util.Arrays;
27 import java.util.Collection;
28 import java.util.Deque;
29 import java.util.Iterator;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Map.Entry;
33 import java.util.Objects;
34 import java.util.Optional;
35 import java.util.Set;
36 import java.util.function.Function;
37 import org.eclipse.jdt.annotation.NonNull;
38 import org.eclipse.jdt.annotation.Nullable;
39 import org.opendaylight.yangtools.concepts.HierarchicalIdentifier;
40 import org.opendaylight.yangtools.concepts.Immutable;
41 import org.opendaylight.yangtools.concepts.Mutable;
42 import org.opendaylight.yangtools.util.ImmutableOffsetMap;
43 import org.opendaylight.yangtools.util.SingletonSet;
44 import org.opendaylight.yangtools.yang.common.QName;
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}, which conceptually is XPath expression
52  * minimized to uniquely identify element in data tree which conforms to constraints maintained by YANG Model,
53  * effectively this makes Instance Identifier a path to element in data tree.
54  *
55  * <p>
56  * Constraints put in YANG specification on instance-identifier allowed it to be effectively represented in Java and its
57  * evaluation does not require a full-blown XPath processor.
58  *
59  * <h2>Path Arguments</h2>
60  * Path to the node represented in instance identifier consists of {@link PathArgument} which carries necessary
61  * information to uniquely identify node on particular level in the subtree.
62  *
63  * <ul>
64  *   <li>{@link NodeIdentifier} - Identifier of node, which has cardinality {@code 0..1} in particular subtree in data
65  *       tree</li>
66  *   <li>{@link NodeIdentifierWithPredicates} - Identifier of node (list item), which has cardinality {@code 0..n}</li>
67  *   <li>{@link NodeWithValue} - Identifier of instance {@code leaf} node or {@code leaf-list} node</li>
68  * </ul>
69  *
70  * @see <a href="http://tools.ietf.org/html/rfc6020#section-9.13">RFC6020</a>
71  */
72 public abstract sealed class YangInstanceIdentifier implements HierarchicalIdentifier<YangInstanceIdentifier>
73         permits FixedYangInstanceIdentifier, StackedYangInstanceIdentifier {
74     @java.io.Serial
75     private static final long serialVersionUID = 4L;
76     private static final VarHandle TO_STRING_CACHE;
77     private static final VarHandle HASH;
78
79     static {
80         final var lookup = MethodHandles.lookup();
81         try {
82             HASH = lookup.findVarHandle(YangInstanceIdentifier.class, "hash", int.class);
83             TO_STRING_CACHE = lookup.findVarHandle(YangInstanceIdentifier.class, "toStringCache", String.class);
84         } catch (NoSuchFieldException | IllegalAccessException e) {
85             throw new ExceptionInInitializerError(e);
86         }
87     }
88
89     @SuppressWarnings("unused")
90     private int hash;
91     @SuppressWarnings("unused")
92     private transient String toStringCache = null;
93
94     YangInstanceIdentifier() {
95         // Package-private to prevent outside subclassing
96     }
97
98     /**
99      * Return An empty {@link YangInstanceIdentifier}. It corresponds to the path of the conceptual root of the YANG
100      * namespace.
101      *
102      * @return An empty YangInstanceIdentifier
103      */
104     public static @NonNull YangInstanceIdentifier empty() {
105         return FixedYangInstanceIdentifier.EMPTY_INSTANCE;
106     }
107
108     abstract @NonNull YangInstanceIdentifier createRelativeIdentifier(int skipFromRoot);
109
110     abstract @Nullable List<PathArgument> tryPathArguments();
111
112     abstract @Nullable List<PathArgument> tryReversePathArguments();
113
114     /**
115      * Check if this instance identifier has empty path arguments, e.g. it is
116      * empty and corresponds to {@link #empty()}.
117      *
118      * @return True if this instance identifier is empty, false otherwise.
119      */
120     public abstract boolean isEmpty();
121
122     /**
123      * Return an optimized version of this identifier, useful when the identifier
124      * will be used very frequently.
125      *
126      * @return A optimized equivalent instance.
127      */
128     public abstract @NonNull YangInstanceIdentifier toOptimized();
129
130     /**
131      * Return the conceptual parent {@link YangInstanceIdentifier}, which has
132      * one item less in {@link #getPathArguments()}.
133      *
134      * @return Parent {@link YangInstanceIdentifier}, or null if this object is {@link #empty()}.
135      */
136     public abstract @Nullable YangInstanceIdentifier getParent();
137
138     /**
139      * Return the conceptual parent {@link YangInstanceIdentifier}, which has one item less in
140      * {@link #getPathArguments()}.
141      *
142      * @return Parent {@link YangInstanceIdentifier}
143      * @throws VerifyException if this object is {@link #empty()}.
144      */
145     public abstract @NonNull YangInstanceIdentifier coerceParent();
146
147     /**
148      * Return the ancestor {@link YangInstanceIdentifier} with a particular depth, e.g. number of path arguments.
149      *
150      * @param depth Ancestor depth
151      * @return Ancestor {@link YangInstanceIdentifier}
152      * @throws IllegalArgumentException if the specified depth is negative or is greater than the depth of this object.
153      */
154     public abstract @NonNull YangInstanceIdentifier getAncestor(int depth);
155
156     /**
157      * Returns an ordered iteration of path arguments.
158      *
159      * @return Immutable iteration of path arguments.
160      */
161     public abstract @NonNull List<PathArgument> getPathArguments();
162
163     /**
164      * Returns an iterable of path arguments in reverse order. This is useful
165      * when walking up a tree organized this way.
166      *
167      * @return Immutable iterable of path arguments in reverse order.
168      */
169     public abstract @NonNull List<PathArgument> getReversePathArguments();
170
171     /**
172      * Returns the last PathArgument. This is equivalent of iterating
173      * to the last element of the iterable returned by {@link #getPathArguments()}.
174      *
175      * @return The last past argument, or null if there are no PathArguments.
176      */
177     public abstract PathArgument getLastPathArgument();
178
179     public static @NonNull YangInstanceIdentifier create(final Iterable<? extends PathArgument> path) {
180         return Iterables.isEmpty(path) ? empty() : new FixedYangInstanceIdentifier(ImmutableList.copyOf(path));
181     }
182
183     public static @NonNull YangInstanceIdentifier create(final PathArgument pathArgument) {
184         return new FixedYangInstanceIdentifier(ImmutableList.of(pathArgument));
185     }
186
187     public static @NonNull YangInstanceIdentifier create(final PathArgument... path) {
188         // We are forcing a copy, since we cannot trust the user
189         return create(Arrays.asList(path));
190     }
191
192     /**
193      * Create a {@link YangInstanceIdentifier} by taking a snapshot of provided path and iterating it backwards.
194      *
195      * @param pathTowardsRoot Path towards root
196      * @return A {@link YangInstanceIdentifier} instance
197      * @throws NullPointerException if {@code pathTowardsRoot} or any of its members is null
198      */
199     public static @NonNull YangInstanceIdentifier createReverse(final Deque<PathArgument> pathTowardsRoot) {
200         final ImmutableList.Builder<PathArgument> builder = ImmutableList.builderWithExpectedSize(
201             pathTowardsRoot.size());
202         pathTowardsRoot.descendingIterator().forEachRemaining(builder::add);
203         return YangInstanceIdentifier.create(builder.build());
204     }
205
206     /**
207      * Create a {@link YangInstanceIdentifier} by walking specified stack backwards and extracting path components
208      * from it.
209      *
210      * @param stackTowardsRoot Stack towards root,
211      * @return A {@link YangInstanceIdentifier} instance
212      * @throws NullPointerException if {@code pathTowardsRoot} is null
213      */
214     public static <T> @NonNull YangInstanceIdentifier createReverse(final Deque<? extends T> stackTowardsRoot,
215             final Function<T, PathArgument> function) {
216         final ImmutableList.Builder<PathArgument> builder = ImmutableList.builderWithExpectedSize(
217             stackTowardsRoot.size());
218         final Iterator<? extends T> it = stackTowardsRoot.descendingIterator();
219         while (it.hasNext()) {
220             builder.add(function.apply(it.next()));
221         }
222         return YangInstanceIdentifier.create(builder.build());
223     }
224
225     boolean pathArgumentsEqual(final YangInstanceIdentifier other) {
226         return getPathArguments().equals(other.getPathArguments());
227     }
228
229     @Override
230     public final boolean equals(final Object obj) {
231         return this == obj || obj instanceof YangInstanceIdentifier other && pathArgumentsEqual(other);
232     }
233
234     /**
235      * Constructs a new Instance Identifier with new {@link NodeIdentifier} added to the end of path arguments.
236      *
237      * @param name QName of {@link NodeIdentifier}
238      * @return Instance Identifier with additional path argument added to the end.
239      */
240     public final @NonNull YangInstanceIdentifier node(final QName name) {
241         return node(new NodeIdentifier(name));
242     }
243
244     /**
245      * Constructs a new Instance Identifier with new {@link PathArgument} added to the end of path arguments.
246      *
247      * @param arg Path argument which should be added to the end
248      * @return Instance Identifier with additional path argument added to the end.
249      */
250     public final @NonNull YangInstanceIdentifier node(final PathArgument arg) {
251         return new StackedYangInstanceIdentifier(this, arg);
252     }
253
254     /**
255      * Get the relative path from an ancestor. This method attempts to perform
256      * the reverse of concatenating a base (ancestor) and a path.
257      *
258      * @param ancestor
259      *            Ancestor against which the relative path should be calculated
260      * @return This object's relative path from parent, or Optional.absent() if
261      *         the specified parent is not in fact an ancestor of this object.
262      */
263     public Optional<YangInstanceIdentifier> relativeTo(final YangInstanceIdentifier ancestor) {
264         if (this == ancestor) {
265             return Optional.of(empty());
266         }
267         if (ancestor.isEmpty()) {
268             return Optional.of(this);
269         }
270
271         final Iterator<PathArgument> lit = getPathArguments().iterator();
272         final Iterator<PathArgument> oit = ancestor.getPathArguments().iterator();
273         int common = 0;
274
275         while (oit.hasNext()) {
276             // Ancestor is not really an ancestor
277             if (!lit.hasNext() || !lit.next().equals(oit.next())) {
278                 return Optional.empty();
279             }
280
281             ++common;
282         }
283
284         if (common == 0) {
285             return Optional.of(this);
286         }
287         if (!lit.hasNext()) {
288             return Optional.of(empty());
289         }
290
291         return Optional.of(createRelativeIdentifier(common));
292     }
293
294     @Override
295     public final boolean contains(final YangInstanceIdentifier other) {
296         if (this == other) {
297             return true;
298         }
299
300         checkArgument(other != null, "other should not be null");
301         final Iterator<PathArgument> lit = getPathArguments().iterator();
302         final Iterator<PathArgument> oit = other.getPathArguments().iterator();
303
304         while (lit.hasNext()) {
305             if (!oit.hasNext()) {
306                 return false;
307             }
308
309             if (!lit.next().equals(oit.next())) {
310                 return false;
311             }
312         }
313
314         return true;
315     }
316
317     @Override
318     public final String toString() {
319         /*
320          * The toStringCache 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          * The cache is thread-safe - if multiple computations occurs at the
326          * same time, cache will be overwritten with same result.
327          */
328         final String ret = (String) TO_STRING_CACHE.getAcquire(this);
329         return ret != null ? ret : loadToString();
330     }
331
332     private String loadToString() {
333         final StringBuilder builder = new StringBuilder("/");
334         PathArgument prev = null;
335         for (PathArgument argument : getPathArguments()) {
336             if (prev != null) {
337                 builder.append('/');
338             }
339             builder.append(argument.toRelativeString(prev));
340             prev = argument;
341         }
342
343         final String ret = builder.toString();
344         final String witness = (String) TO_STRING_CACHE.compareAndExchangeRelease(this, null, ret);
345         return witness == null ? ret : witness;
346     }
347
348     @Override
349     public final int hashCode() {
350         /*
351          * The caching is safe, since the object contract requires
352          * immutability of the object and all objects referenced from this
353          * object.
354          * Used lists, maps are immutable. Path Arguments (elements) are also
355          * immutable, since the PathArgument contract requires immutability.
356          */
357         final int local = (int) HASH.getAcquire(this);
358         return local != 0 ? local : loadHashCode();
359     }
360
361     private static int hashCode(final Object value) {
362         if (value == null) {
363             return 0;
364         }
365
366         if (byte[].class.equals(value.getClass())) {
367             return Arrays.hashCode((byte[]) value);
368         }
369
370         if (value.getClass().isArray()) {
371             int hash = 0;
372             int length = Array.getLength(value);
373             for (int i = 0; i < length; i++) {
374                 hash += Objects.hashCode(Array.get(value, i));
375             }
376
377             return hash;
378         }
379
380         return Objects.hashCode(value);
381     }
382
383     private int loadHashCode() {
384         final int computed = computeHashCode();
385         HASH.setRelease(this, computed);
386         return computed;
387     }
388
389     abstract int computeHashCode();
390
391     @java.io.Serial
392     final Object writeReplace() {
393         return new YIDv1(this);
394     }
395
396     // Static factories & helpers
397
398     /**
399      * Returns a new InstanceIdentifier with only one path argument of type {@link NodeIdentifier} with supplied
400      * QName.
401      *
402      * @param name QName of first node identifier
403      * @return Instance Identifier with only one path argument of type {@link NodeIdentifier}
404      */
405     public static @NonNull YangInstanceIdentifier of(final QName name) {
406         return create(new NodeIdentifier(name));
407     }
408
409     /**
410      * Returns new builder for InstanceIdentifier with empty path arguments.
411      *
412      * @return new builder for InstanceIdentifier with empty path arguments.
413      */
414     public static @NonNull InstanceIdentifierBuilder builder() {
415         return new YangInstanceIdentifierBuilder();
416     }
417
418     /**
419      * Returns new builder for InstanceIdentifier with path arguments copied from original instance identifier.
420      *
421      * @param origin InstanceIdentifier from which path arguments are copied.
422      * @return new builder for InstanceIdentifier with path arguments copied from original instance identifier.
423      */
424     public static @NonNull InstanceIdentifierBuilder builder(final YangInstanceIdentifier origin) {
425         return new YangInstanceIdentifierBuilder(origin.getPathArguments());
426     }
427
428     /**
429      * Path argument / component of InstanceIdentifier.
430      * Path argument uniquely identifies node in data tree on particular
431      * level.
432      *
433      * <p>
434      * This interface itself is used as common parent for actual
435      * path arguments types and should not be implemented by user code.
436      *
437      * <p>
438      * Path arguments SHOULD contain only minimum of information
439      * required to uniquely identify node on particular subtree level.
440      *
441      * <p>
442      * For actual path arguments types see:
443      * <ul>
444      * <li>{@link NodeIdentifier} - Identifier of container or leaf
445      * <li>{@link NodeIdentifierWithPredicates} - Identifier of list entries, which have key defined
446      * <li>{@link NodeWithValue} - Identifier of leaf-list entry
447      * </ul>
448      */
449     public abstract static sealed class PathArgument implements Comparable<PathArgument>, Immutable, Serializable {
450         @java.io.Serial
451         private static final long serialVersionUID = -4546547994250849340L;
452
453         private final @NonNull QName nodeType;
454         private transient volatile int hashValue;
455
456         protected PathArgument(final QName nodeType) {
457             this.nodeType = requireNonNull(nodeType);
458         }
459
460         /**
461          * Returns unique QName of data node as defined in YANG Schema, if available.
462          *
463          * @return Node type
464          */
465         public final @NonNull QName getNodeType() {
466             return nodeType;
467         }
468
469         /**
470          * Return the string representation of this object for use in context
471          * provided by a previous object. This method can be implemented in
472          * terms of {@link #toString()}, but implementations are encourage to
473          * reuse any context already emitted by the previous object.
474          *
475          * @param previous Previous path argument
476          * @return String representation
477          */
478         public @NonNull String toRelativeString(final PathArgument previous) {
479             if (previous != null && nodeType.getModule().equals(previous.nodeType.getModule())) {
480                 return nodeType.getLocalName();
481             }
482             return nodeType.toString();
483         }
484
485         @Override
486         @SuppressWarnings("checkstyle:parameterName")
487         public int compareTo(final PathArgument o) {
488             return nodeType.compareTo(o.nodeType);
489         }
490
491         protected int hashCodeImpl() {
492             return nodeType.hashCode();
493         }
494
495         @Override
496         public final int hashCode() {
497             int local;
498             return (local = hashValue) != 0 ? local : (hashValue = hashCodeImpl());
499         }
500
501         @Override
502         public boolean equals(final Object obj) {
503             if (this == obj) {
504                 return true;
505             }
506             if (obj == null || this.getClass() != obj.getClass()) {
507                 return false;
508             }
509
510             return nodeType.equals(((PathArgument) obj).nodeType);
511         }
512
513         @Override
514         public String toString() {
515             return nodeType.toString();
516         }
517
518         @java.io.Serial
519         abstract Object writeReplace();
520     }
521
522     /**
523      * Simple path argument identifying a {@link org.opendaylight.yangtools.yang.data.api.schema.ContainerNode} or
524      * {@link org.opendaylight.yangtools.yang.data.api.schema.LeafNode} leaf in particular subtree.
525      */
526     public static final class NodeIdentifier extends PathArgument {
527         @java.io.Serial
528         private static final long serialVersionUID = -2255888212390871347L;
529         private static final LoadingCache<QName, NodeIdentifier> CACHE = CacheBuilder.newBuilder().weakValues()
530                 .build(new CacheLoader<QName, NodeIdentifier>() {
531                     @Override
532                     public NodeIdentifier load(final QName key) {
533                         return new NodeIdentifier(key);
534                     }
535                 });
536
537         public NodeIdentifier(final QName node) {
538             super(node);
539         }
540
541         /**
542          * Return a NodeIdentifier for a particular QName. Unlike the constructor, this factory method uses a global
543          * instance cache, resulting in object reuse for equal inputs.
544          *
545          * @param node Node's QName
546          * @return A {@link NodeIdentifier}
547          */
548         public static @NonNull NodeIdentifier create(final QName node) {
549             return CACHE.getUnchecked(node);
550         }
551
552         @Override
553         Object writeReplace() {
554             return new NIv1(this);
555         }
556     }
557
558     /**
559      * Composite path argument identifying a {@link org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode} leaf
560      * overall data tree.
561      */
562     public abstract static sealed class NodeIdentifierWithPredicates extends PathArgument {
563         @Beta
564         public static final class Singleton extends NodeIdentifierWithPredicates {
565             @java.io.Serial
566             private static final long serialVersionUID = 1L;
567
568             private final @NonNull QName key;
569             private final @NonNull Object value;
570
571             Singleton(final QName node, final QName key, final Object value) {
572                 super(node);
573                 this.key = requireNonNull(key);
574                 this.value = requireNonNull(value);
575             }
576
577             @Override
578             public SingletonSet<Entry<QName, Object>> entrySet() {
579                 return SingletonSet.of(singleEntry());
580             }
581
582             @Override
583             public SingletonSet<QName> keySet() {
584                 return SingletonSet.of(key);
585             }
586
587             @Override
588             public boolean containsKey(final QName qname) {
589                 return key.equals(requireNonNull(qname));
590             }
591
592             @Override
593             public SingletonSet<Object> values() {
594                 return SingletonSet.of(value);
595             }
596
597             @Override
598             public int size() {
599                 return 1;
600             }
601
602             @Override
603             public ImmutableMap<QName, Object> asMap() {
604                 return ImmutableMap.of(key, value);
605             }
606
607             /**
608              * Return the single entry contained in this object. This is equivalent to
609              * {@code entrySet().iterator().next()}.
610              *
611              * @return A single entry.
612              */
613             public @NonNull Entry<QName, Object> singleEntry() {
614                 return new SimpleImmutableEntry<>(key, value);
615             }
616
617             @Override
618             boolean equalMapping(final NodeIdentifierWithPredicates other) {
619                 final Singleton single = (Singleton) other;
620                 return key.equals(single.key) && Objects.deepEquals(value, single.value);
621             }
622
623             @Override
624             Object keyValue(final QName qname) {
625                 return key.equals(qname) ? value : null;
626             }
627         }
628
629         private static final class Regular extends NodeIdentifierWithPredicates {
630             @java.io.Serial
631             private static final long serialVersionUID = 1L;
632
633             private final @NonNull Map<QName, Object> keyValues;
634
635             Regular(final QName node, final Map<QName, Object> keyValues) {
636                 super(node);
637                 this.keyValues = requireNonNull(keyValues);
638             }
639
640             @Override
641             public Set<Entry<QName, Object>> entrySet() {
642                 return keyValues.entrySet();
643             }
644
645             @Override
646             public Set<QName> keySet() {
647                 return keyValues.keySet();
648             }
649
650             @Override
651             public boolean containsKey(final QName qname) {
652                 return keyValues.containsKey(requireNonNull(qname));
653             }
654
655             @Override
656             public Collection<Object> values() {
657                 return keyValues.values();
658             }
659
660             @Override
661             public int size() {
662                 return keyValues.size();
663             }
664
665             @Override
666             public Map<QName, Object> asMap() {
667                 return keyValues;
668             }
669
670             @Override
671             Object keyValue(final QName qname) {
672                 return keyValues.get(qname);
673             }
674
675             @Override
676             boolean equalMapping(final NodeIdentifierWithPredicates other) {
677                 final Map<QName, Object> otherKeyValues = ((Regular) other).keyValues;
678                 // TODO: benchmark to see if just calling equals() on the two maps is not faster
679                 if (keyValues == otherKeyValues) {
680                     return true;
681                 }
682                 if (keyValues.size() != otherKeyValues.size()) {
683                     return false;
684                 }
685
686                 for (Entry<QName, Object> entry : entrySet()) {
687                     final Object otherValue = otherKeyValues.get(entry.getKey());
688                     if (otherValue == null || !Objects.deepEquals(entry.getValue(), otherValue)) {
689                         return false;
690                     }
691                 }
692
693                 return true;
694             }
695         }
696
697         @java.io.Serial
698         private static final long serialVersionUID = -4787195606494761540L;
699
700         NodeIdentifierWithPredicates(final QName node) {
701             super(node);
702         }
703
704         public static @NonNull NodeIdentifierWithPredicates of(final QName node) {
705             return new Regular(node, ImmutableMap.of());
706         }
707
708         public static @NonNull NodeIdentifierWithPredicates of(final QName node, final QName key, final Object value) {
709             return new Singleton(node, key, value);
710         }
711
712         public static @NonNull NodeIdentifierWithPredicates of(final QName node, final Entry<QName, Object> entry) {
713             return of(node, entry.getKey(), entry.getValue());
714         }
715
716         public static @NonNull NodeIdentifierWithPredicates of(final QName node, final Map<QName, Object> keyValues) {
717             return keyValues.size() == 1 ? of(keyValues, node)
718                     // Retains ImmutableMap for empty maps. For larger sizes uses a shared key set.
719                     : new Regular(node, ImmutableOffsetMap.unorderedCopyOf(keyValues));
720         }
721
722         public static @NonNull NodeIdentifierWithPredicates of(final QName node,
723                 final ImmutableOffsetMap<QName, Object> keyValues) {
724             return keyValues.size() == 1 ? of(keyValues, node) : new Regular(node, keyValues);
725         }
726
727         private static @NonNull NodeIdentifierWithPredicates of(final Map<QName, Object> keyValues, final QName node) {
728             return of(node, keyValues.entrySet().iterator().next());
729         }
730
731         /**
732          * Return the set of predicates keys and values. Keys are guaranteeed to be unique.
733          *
734          * @return Predicate set.
735          */
736         public abstract @NonNull Set<Entry<QName, Object>> entrySet();
737
738         /**
739          * Return the predicate key in the iteration order of {@link #entrySet()}.
740          *
741          * @return Predicate values.
742          */
743         public abstract @NonNull Set<QName> keySet();
744
745         /**
746          * Determine whether a particular predicate key is present.
747          *
748          * @param key Predicate key
749          * @return True if the predicate is present, false otherwise
750          * @throws NullPointerException if {@code key} is null
751          */
752         public abstract boolean containsKey(QName key);
753
754         /**
755          * Return the predicate values in the iteration order of {@link #entrySet()}.
756          *
757          * @return Predicate values.
758          */
759         public abstract @NonNull Collection<Object> values();
760
761         @Beta
762         public final @Nullable Object getValue(final QName key) {
763             return keyValue(requireNonNull(key));
764         }
765
766         @Beta
767         public final <T> @Nullable T getValue(final QName key, final Class<T> valueClass) {
768             return valueClass.cast(getValue(key));
769         }
770
771         /**
772          * Return the number of predicates present.
773          *
774          * @return The number of predicates present.
775          */
776         public abstract int size();
777
778         /**
779          * A Map-like view of this identifier's predicates. The view is expected to be stable and effectively-immutable.
780          *
781          * @return Map of predicates.
782          */
783         @Beta
784         public abstract @NonNull Map<QName, Object> asMap();
785
786         @Override
787         protected final int hashCodeImpl() {
788             int result = 31 * super.hashCodeImpl();
789             for (Entry<QName, Object> entry : entrySet()) {
790                 result += entry.getKey().hashCode() + YangInstanceIdentifier.hashCode(entry.getValue());
791             }
792             return result;
793         }
794
795         @Override
796         @SuppressWarnings("checkstyle:equalsHashCode")
797         public final boolean equals(final Object obj) {
798             return super.equals(obj) && equalMapping((NodeIdentifierWithPredicates) obj);
799         }
800
801         abstract boolean equalMapping(NodeIdentifierWithPredicates other);
802
803         abstract @Nullable Object keyValue(@NonNull QName qname);
804
805         @Override
806         public final String toString() {
807             return super.toString() + '[' + asMap() + ']';
808         }
809
810         @Override
811         public final String toRelativeString(final PathArgument previous) {
812             return super.toRelativeString(previous) + '[' + asMap() + ']';
813         }
814
815         @Override
816         final Object writeReplace() {
817             return new NIPv2(this);
818         }
819     }
820
821     /**
822      * Simple path argument identifying a {@link LeafSetEntryNode} leaf
823      * overall data tree.
824      */
825     public static final class NodeWithValue<T> extends PathArgument {
826         @java.io.Serial
827         private static final long serialVersionUID = -3637456085341738431L;
828
829         private final @NonNull T value;
830
831         public NodeWithValue(final QName node, final T value) {
832             super(node);
833             this.value = requireNonNull(value);
834         }
835
836         public @NonNull T getValue() {
837             return value;
838         }
839
840         @Override
841         protected int hashCodeImpl() {
842             return 31 * super.hashCodeImpl() + YangInstanceIdentifier.hashCode(value);
843         }
844
845         @Override
846         @SuppressWarnings("checkstyle:equalsHashCode")
847         public boolean equals(final Object obj) {
848             if (!super.equals(obj)) {
849                 return false;
850             }
851             final NodeWithValue<?> other = (NodeWithValue<?>) obj;
852             return Objects.deepEquals(value, other.value);
853         }
854
855         @Override
856         public String toString() {
857             return super.toString() + '[' + value + ']';
858         }
859
860         @Override
861         public String toRelativeString(final PathArgument previous) {
862             return super.toRelativeString(previous) + '[' + value + ']';
863         }
864
865         @Override
866         Object writeReplace() {
867             return new NIVv1(this);
868         }
869     }
870
871     /**
872      * Fluent Builder of Instance Identifier instances.
873      */
874     public interface InstanceIdentifierBuilder extends Mutable {
875         /**
876          * Adds a {@link PathArgument} to path arguments of resulting instance identifier.
877          *
878          * @param arg A {@link PathArgument} to be added
879          * @return this builder
880          */
881         @NonNull InstanceIdentifierBuilder node(PathArgument arg);
882
883         /**
884          * Adds {@link NodeIdentifier} with supplied QName to path arguments of resulting instance identifier.
885          *
886          * @param nodeType QName of {@link NodeIdentifier} which will be added
887          * @return this builder
888          */
889         @NonNull InstanceIdentifierBuilder node(QName nodeType);
890
891         /**
892          * Adds {@link NodeIdentifierWithPredicates} with supplied QName and key values to path arguments of resulting
893          * instance identifier.
894          *
895          * @param nodeType QName of {@link NodeIdentifierWithPredicates} which will be added
896          * @param keyValues Map of key components and their respective values for {@link NodeIdentifierWithPredicates}
897          * @return this builder
898          */
899         @NonNull InstanceIdentifierBuilder nodeWithKey(QName nodeType, Map<QName, Object> keyValues);
900
901         /**
902          * Adds {@link NodeIdentifierWithPredicates} with supplied QName and key, value.
903          *
904          * @param nodeType QName of {@link NodeIdentifierWithPredicates} which will be added
905          * @param key QName of key which will be added
906          * @param value value of key which will be added
907          * @return this builder
908          */
909         @NonNull InstanceIdentifierBuilder nodeWithKey(QName nodeType, QName key, Object value);
910
911         /**
912          * Adds a collection of {@link PathArgument}s to path arguments of resulting instance identifier.
913          *
914          * @param args {@link PathArgument}s to be added
915          * @return this builder
916          * @throws NullPointerException if any of the arguments is null
917          */
918         @NonNull InstanceIdentifierBuilder append(Collection<? extends PathArgument> args);
919
920         /**
921          * Adds a collection of {@link PathArgument}s to path arguments of resulting instance identifier.
922          *
923          * @param args {@link PathArgument}s to be added
924          * @return this builder
925          * @throws NullPointerException if any of the arguments is null
926          */
927         default @NonNull InstanceIdentifierBuilder append(final PathArgument... args) {
928             return append(Arrays.asList(args));
929         }
930
931         /**
932          * Builds an {@link YangInstanceIdentifier} with path arguments from this builder.
933          *
934          * @return {@link YangInstanceIdentifier}
935          */
936         @NonNull YangInstanceIdentifier build();
937     }
938 }