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