Refactor AbstractDataObjectReference.toString()
[yangtools.git] / binding / binding-spec / src / main / java / org / opendaylight / yangtools / yang / binding / InstanceIdentifier.java
1 /*
2  * Copyright (c) 2013 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.binding;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static com.google.common.base.Verify.verify;
12 import static com.google.common.base.Verify.verifyNotNull;
13 import static java.util.Objects.requireNonNull;
14
15 import com.google.common.annotations.Beta;
16 import com.google.common.base.VerifyException;
17 import com.google.common.collect.ImmutableList;
18 import com.google.common.collect.Iterables;
19 import java.io.IOException;
20 import java.io.ObjectInputStream;
21 import java.io.ObjectOutputStream;
22 import java.io.ObjectStreamException;
23 import java.io.Serializable;
24 import java.util.ArrayList;
25 import java.util.List;
26 import java.util.Objects;
27 import org.eclipse.jdt.annotation.NonNull;
28 import org.eclipse.jdt.annotation.Nullable;
29 import org.opendaylight.yangtools.binding.Augmentable;
30 import org.opendaylight.yangtools.binding.Augmentation;
31 import org.opendaylight.yangtools.binding.ChildOf;
32 import org.opendaylight.yangtools.binding.ChoiceIn;
33 import org.opendaylight.yangtools.binding.DataObject;
34 import org.opendaylight.yangtools.binding.DataObjectIdentifier;
35 import org.opendaylight.yangtools.binding.DataObjectStep;
36 import org.opendaylight.yangtools.binding.DataRoot;
37 import org.opendaylight.yangtools.binding.ExactDataObjectStep;
38 import org.opendaylight.yangtools.binding.Key;
39 import org.opendaylight.yangtools.binding.KeyAware;
40 import org.opendaylight.yangtools.binding.KeyStep;
41 import org.opendaylight.yangtools.binding.KeylessStep;
42 import org.opendaylight.yangtools.binding.NodeStep;
43 import org.opendaylight.yangtools.binding.impl.AbstractDataObjectReference;
44 import org.opendaylight.yangtools.concepts.HierarchicalIdentifier;
45
46 /**
47  * This instance identifier uniquely identifies a specific DataObject in the data tree modeled by YANG.
48  *
49  * <p>
50  * For Example let's say you were trying to refer to a node in inventory which was modeled in YANG as follows,
51  * <pre>code{
52  *   module opendaylight-inventory {
53  *     ....
54  *
55  *     container nodes {
56  *       list node {
57  *         key "id";
58  *         ext:context-instance "node-context";
59  *
60  *         uses node;
61  *       }
62  *     }
63  *   }
64  * }</pre>
65  *
66  * <p>
67  * You can create an instance identifier as follows to get to a node with id "openflow:1": {@code
68  * InstanceIdentifier.builder(Nodes.class).child(Node.class, new NodeKey(new NodeId("openflow:1")).build();
69  * }
70  *
71  * <p>
72  * This would be the same as using a path like so, "/nodes/node/openflow:1" to refer to the openflow:1 node
73  */
74 public sealed class InstanceIdentifier<T extends DataObject> extends AbstractDataObjectReference<T, DataObjectStep<?>>
75         implements HierarchicalIdentifier<InstanceIdentifier<? extends DataObject>>
76         permits KeyedInstanceIdentifier {
77     @java.io.Serial
78     private static final long serialVersionUID = 3L;
79
80     private final boolean wildcarded;
81
82     InstanceIdentifier(final Iterable<? extends @NonNull DataObjectStep<?>> steps, final boolean wildcarded) {
83         super(steps);
84         this.wildcarded = wildcarded;
85     }
86
87     /**
88      * Return the type of data which this InstanceIdentifier identifies.
89      *
90      * @return Target type
91      */
92     public final @NonNull Class<T> getTargetType() {
93         return lastStep().type();
94     }
95
96     /**
97      * Perform a safe target type adaptation of this instance identifier to target type. This method is useful when
98      * dealing with type-squashed instances.
99      *
100      * @return Path argument with target type
101      * @throws VerifyException if this instance identifier cannot be adapted to target type
102      * @throws NullPointerException if {@code target} is null
103      */
104     @SuppressWarnings("unchecked")
105     public final <N extends DataObject> @NonNull InstanceIdentifier<N> verifyTarget(final Class<@NonNull N> target) {
106         verify(target.equals(getTargetType()), "Cannot adapt %s to %s", this, target);
107         return (InstanceIdentifier<N>) this;
108     }
109
110     @Override
111     public final boolean isExact() {
112         return !wildcarded;
113     }
114
115     @Override
116     @Deprecated(since = "14.0.0", forRemoval = true)
117     public final boolean isWildcarded() {
118         return wildcarded;
119     }
120
121     @Override
122     public final InstanceIdentifier<T> toLegacy() {
123         return this;
124     }
125
126     @Override
127     protected final Class<?> contract() {
128         return wildcarded ? super.contract() : DataObjectIdentifier.class;
129     }
130
131     /**
132      * Return an instance identifier trimmed at the first occurrence of a specific component type.
133      *
134      * <p>
135      * For example let's say an instance identifier was built like so,
136      * <pre>
137      *      identifier = InstanceIdentifier.builder(Nodes.class).child(Node.class,
138      *                   new NodeKey(new NodeId("openflow:1")).build();
139      * </pre>
140      *
141      * <p>
142      * And you wanted to obtain the Instance identifier which represented Nodes you would do it like so,
143      * <pre>
144      *      identifier.firstIdentifierOf(Nodes.class)
145      * </pre>
146      *
147      * @param type component type
148      * @return trimmed instance identifier, or null if the component type
149      *         is not present.
150      */
151     public final <I extends DataObject> @Nullable InstanceIdentifier<I> firstIdentifierOf(
152             final Class<@NonNull I> type) {
153         int count = 1;
154         for (var step : steps()) {
155             if (type.equals(step.type())) {
156                 @SuppressWarnings("unchecked")
157                 final var ret = (InstanceIdentifier<I>) internalCreate(Iterables.limit(steps(), count));
158                 return ret;
159             }
160
161             ++count;
162         }
163
164         return null;
165     }
166
167     /**
168      * Return the key associated with the first component of specified type in
169      * an identifier.
170      *
171      * @param listItem component type
172      * @return key associated with the component, or null if the component type
173      *         is not present.
174      */
175     public final <N extends KeyAware<K> & DataObject, K extends Key<N>> @Nullable K firstKeyOf(
176             final Class<@NonNull N> listItem) {
177         for (var step : steps()) {
178             if (step instanceof KeyStep<?, ?> keyPredicate && listItem.equals(step.type())) {
179                 @SuppressWarnings("unchecked")
180                 final var ret = (K) keyPredicate.key();
181                 return ret;
182             }
183         }
184         return null;
185     }
186
187     /**
188      * Check whether an identifier is contained in this identifier. This is a strict subtree check, which requires all
189      * PathArguments to match exactly.
190      *
191      * <p>
192      * The contains method checks if the other identifier is fully contained within the current identifier. It does this
193      * by looking at only the types of the path arguments and not by comparing the path arguments themselves.
194      *
195      * <p>
196      * To illustrate here is an example which explains the working of this API. Let's say you have two instance
197      * identifiers as follows:
198      * {@code
199      * this = /nodes/node/openflow:1
200      * other = /nodes/node/openflow:2
201      * }
202      * then this.contains(other) will return false.
203      *
204      * @param other Potentially-container instance identifier
205      * @return True if the specified identifier is contained in this identifier.
206      */
207     @Override
208     public final boolean contains(final InstanceIdentifier<? extends DataObject> other) {
209         requireNonNull(other, "other should not be null");
210
211         final var oit = other.steps().iterator();
212         for (var step : steps()) {
213             if (!oit.hasNext()) {
214                 return false;
215             }
216             if (!step.equals(oit.next())) {
217                 return false;
218             }
219         }
220         return true;
221     }
222
223     /**
224      * Check whether this instance identifier contains the other identifier after wildcard expansion. This is similar
225      * to {@link #contains(InstanceIdentifier)}, with the exception that a wildcards are assumed to match the their
226      * non-wildcarded PathArgument counterpart.
227      *
228      * @param other Identifier which should be checked for inclusion.
229      * @return true if this identifier contains the other object
230      */
231     public final boolean containsWildcarded(final InstanceIdentifier<?> other) {
232         requireNonNull(other, "other should not be null");
233
234         final var otherSteps = other.steps().iterator();
235         for (var step : steps()) {
236             if (!otherSteps.hasNext()) {
237                 return false;
238             }
239
240             final var otherStep = otherSteps.next();
241             if (step instanceof ExactDataObjectStep) {
242                 if (!step.equals(otherStep)) {
243                     return false;
244                 }
245             } else if (step instanceof KeylessStep<?> keyless) {
246                 if (!keyless.matches(otherStep)) {
247                     return false;
248                 }
249             } else {
250                 throw new IllegalStateException("Unhandled step " + step);
251             }
252         }
253
254         return true;
255     }
256
257     private <N extends DataObject> @NonNull InstanceIdentifier<N> childIdentifier(final DataObjectStep<N> arg) {
258         return trustedCreate(arg, concat(steps(), arg), wildcarded);
259     }
260
261     /**
262      * Create an InstanceIdentifier for a child container. This method is a more efficient equivalent to
263      * {@code builder().child(container).build()}.
264      *
265      * @param container Container to append
266      * @param <N> Container type
267      * @return An InstanceIdentifier.
268      * @throws NullPointerException if {@code container} is null
269      */
270     public final <N extends ChildOf<? super T>> @NonNull InstanceIdentifier<N> child(
271             final Class<@NonNull N> container) {
272         return childIdentifier(createStep(container));
273     }
274
275     /**
276      * Create an InstanceIdentifier for a child list item. This method is a more efficient equivalent to
277      * {@code builder().child(listItem, listKey).build()}.
278      *
279      * @param listItem List to append
280      * @param listKey List key
281      * @param <N> List type
282      * @param <K> Key type
283      * @return An InstanceIdentifier.
284      * @throws NullPointerException if any argument is null
285      */
286     @SuppressWarnings("unchecked")
287     public final <N extends KeyAware<K> & ChildOf<? super T>, K extends Key<N>>
288             @NonNull KeyedInstanceIdentifier<N, K> child(final Class<@NonNull N> listItem, final K listKey) {
289         return (KeyedInstanceIdentifier<N, K>) childIdentifier(new KeyStep<>(listItem, listKey));
290     }
291
292     /**
293      * Create an InstanceIdentifier for a child container. This method is a more efficient equivalent to
294      * {@code builder().child(caze, container).build()}.
295      *
296      * @param caze Choice case class
297      * @param container Container to append
298      * @param <C> Case type
299      * @param <N> Container type
300      * @return An InstanceIdentifier.
301      * @throws NullPointerException if any argument is null
302      */
303     // FIXME: add a proper caller
304     public final <C extends ChoiceIn<? super T> & DataObject, N extends ChildOf<? super C>>
305             @NonNull InstanceIdentifier<N> child(final Class<@NonNull C> caze, final Class<@NonNull N> container) {
306         return childIdentifier(createStep(caze, container));
307     }
308
309     /**
310      * Create an InstanceIdentifier for a child list item. This method is a more efficient equivalent to
311      * {@code builder().child(caze, listItem, listKey).build()}.
312      *
313      * @param caze Choice case class
314      * @param listItem List to append
315      * @param listKey List key
316      * @param <C> Case type
317      * @param <N> List type
318      * @param <K> Key type
319      * @return An InstanceIdentifier.
320      * @throws NullPointerException if any argument is null
321      */
322     // FIXME: add a proper caller
323     @SuppressWarnings("unchecked")
324     public final <C extends ChoiceIn<? super T> & DataObject, K extends Key<N>,
325         N extends KeyAware<K> & ChildOf<? super C>> @NonNull KeyedInstanceIdentifier<N, K> child(
326                 final Class<@NonNull C> caze, final Class<@NonNull N> listItem, final K listKey) {
327         return (KeyedInstanceIdentifier<N, K>) childIdentifier(new KeyStep<>(listItem, requireNonNull(caze), listKey));
328     }
329
330     /**
331      * Create an InstanceIdentifier for a child augmentation. This method is a more efficient equivalent to
332      * {@code builder().augmentation(container).build()}.
333      *
334      * @param container Container to append
335      * @param <N> Container type
336      * @return An InstanceIdentifier.
337      * @throws NullPointerException if {@code container} is null
338      */
339     public final <N extends DataObject & Augmentation<? super T>> @NonNull InstanceIdentifier<N> augmentation(
340             final Class<@NonNull N> container) {
341         return childIdentifier(new NodeStep<>(container));
342     }
343
344     @Override
345     protected Object toSerialForm() {
346         return new IIv5(this);
347     }
348
349     @java.io.Serial
350     private void readObject(final ObjectInputStream stream) throws IOException, ClassNotFoundException {
351         throwNSE();
352     }
353
354     @java.io.Serial
355     private void readObjectNoData() throws ObjectStreamException {
356         throwNSE();
357     }
358
359     @java.io.Serial
360     private void writeObject(final ObjectOutputStream stream) throws IOException {
361         throwNSE();
362     }
363
364     @Override
365     public Builder<T> toBuilder() {
366         return new RegularBuilder<>(this);
367     }
368
369     /**
370      * Create a {@link Builder} for a specific type of InstanceIdentifier as specified by container.
371      *
372      * @param container Base container
373      * @param <T> Type of the container
374      * @return A new {@link Builder}
375      * @throws NullPointerException if {@code container} is null
376      */
377     public static <T extends ChildOf<? extends DataRoot>> @NonNull Builder<T> builder(
378             final Class<T> container) {
379         return new RegularBuilder<>(createStep(container));
380     }
381
382     /**
383      * Create a {@link Builder} for a specific type of InstanceIdentifier as specified by container in
384      * a {@code grouping} used in the {@code case} statement.
385      *
386      * @param caze Choice case class
387      * @param container Base container
388      * @param <C> Case type
389      * @param <T> Type of the container
390      * @return A new {@link Builder}
391      * @throws NullPointerException if any argument is null
392      */
393     public static <C extends ChoiceIn<? extends DataRoot> & DataObject, T extends ChildOf<? super C>>
394             @NonNull Builder<T> builder(final Class<C> caze, final Class<T> container) {
395         return new RegularBuilder<>(createStep(caze, container));
396     }
397
398     /**
399      * Create a {@link Builder} for a specific type of InstanceIdentifier which represents an {@link IdentifiableItem}.
400      *
401      * @param listItem list item class
402      * @param listKey key value
403      * @param <N> List type
404      * @param <K> List key
405      * @return A new {@link Builder}
406      * @throws NullPointerException if any argument is null
407      */
408     public static <N extends KeyAware<K> & ChildOf<? extends DataRoot>,
409             K extends Key<N>> @NonNull KeyedBuilder<N, K> builder(final Class<N> listItem,
410                     final K listKey) {
411         return new KeyedBuilder<>(new KeyStep<>(listItem, listKey));
412     }
413
414     /**
415      * Create a {@link Builder} for a specific type of InstanceIdentifier which represents an {@link IdentifiableItem}
416      *  in a {@code grouping} used in the {@code case} statement.
417      *
418      * @param caze Choice case class
419      * @param listItem list item class
420      * @param listKey key value
421      * @param <C> Case type
422      * @param <N> List type
423      * @param <K> List key
424      * @return A new {@link Builder}
425      * @throws NullPointerException if any argument is null
426      */
427     public static <C extends ChoiceIn<? extends DataRoot> & DataObject,
428             N extends KeyAware<K> & ChildOf<? super C>, K extends Key<N>>
429             @NonNull KeyedBuilder<N, K> builder(final Class<C> caze, final Class<N> listItem,
430                     final K listKey) {
431         return new KeyedBuilder<>(new KeyStep<>(listItem, requireNonNull(caze), listKey));
432     }
433
434     public static <R extends DataRoot & DataObject, T extends ChildOf<? super R>>
435             @NonNull Builder<T> builderOfInherited(final Class<R> root, final Class<T> container) {
436         // FIXME: we are losing root identity, hence namespaces may not work correctly
437         return new RegularBuilder<>(createStep(container));
438     }
439
440     public static <R extends DataRoot & DataObject, C extends ChoiceIn<? super R> & DataObject,
441             T extends ChildOf<? super C>>
442             @NonNull Builder<T> builderOfInherited(final Class<R> root,
443                 final Class<C> caze, final Class<T> container) {
444         // FIXME: we are losing root identity, hence namespaces may not work correctly
445         return new RegularBuilder<>(createStep(caze, container));
446     }
447
448     public static <R extends DataRoot & DataObject, N extends KeyAware<K> & ChildOf<? super R>,
449             K extends Key<N>>
450             @NonNull KeyedBuilder<N, K> builderOfInherited(final Class<R> root,
451                 final Class<N> listItem, final K listKey) {
452         // FIXME: we are losing root identity, hence namespaces may not work correctly
453         return new KeyedBuilder<>(new KeyStep<>(listItem, listKey));
454     }
455
456     public static <R extends DataRoot & DataObject, C extends ChoiceIn<? super R> & DataObject,
457             N extends KeyAware<K> & ChildOf<? super C>, K extends Key<N>>
458             @NonNull KeyedBuilder<N, K> builderOfInherited(final Class<R> root,
459                 final Class<C> caze, final Class<N> listItem, final K listKey) {
460         // FIXME: we are losing root identity, hence namespaces may not work correctly
461         return new KeyedBuilder<>(new KeyStep<>(listItem, requireNonNull(caze), listKey));
462     }
463
464     @Beta
465     @SuppressWarnings({ "rawtypes", "unchecked" })
466     public static <T extends DataObject, C extends ChoiceIn<?> & DataObject> @NonNull DataObjectStep<T> createStep(
467             final Class<C> caze, final Class<T> type) {
468         return KeyAware.class.isAssignableFrom(type) ? new KeylessStep(type, caze) : new NodeStep<>(type, caze);
469     }
470
471     @Beta
472     public static <T extends DataObject> @NonNull DataObjectStep<T> createStep(final Class<T> type) {
473         return createStep(null, type);
474     }
475
476     /**
477      * Create an instance identifier for a very specific object type. This method implements {@link #create(Iterable)}
478      * semantics, except it is used by internal callers, which have assured that the argument is an immutable Iterable.
479      *
480      * @param pathArguments The path to a specific node in the data tree
481      * @return InstanceIdentifier instance
482      * @throws IllegalArgumentException if pathArguments is empty or contains a null element.
483      * @throws NullPointerException if {@code pathArguments} is null
484      */
485     private static @NonNull InstanceIdentifier<?> internalCreate(
486             final Iterable<? extends DataObjectStep<?>> pathArguments) {
487         final var it = requireNonNull(pathArguments, "pathArguments may not be null").iterator();
488         checkArgument(it.hasNext(), "pathArguments may not be empty");
489
490         boolean wildcard = false;
491         DataObjectStep<?> arg;
492
493         do {
494             arg = it.next();
495             // Non-null is implied by our callers
496             final var type = verifyNotNull(arg).type();
497             checkArgument(ChildOf.class.isAssignableFrom(type) || Augmentation.class.isAssignableFrom(type),
498                 "%s is not a valid path argument", type);
499
500             if (!(arg instanceof ExactDataObjectStep)) {
501                 wildcard = true;
502             }
503         } while (it.hasNext());
504
505         return trustedCreate(arg, pathArguments, wildcard);
506     }
507
508     /**
509      * Create an instance identifier for a sequence of {@link DataObjectStep} steps. The steps are required to be formed
510      * of classes extending either {@link ChildOf} or {@link Augmentation} contracts. This method does not check whether
511      * or not the sequence is structurally sound, for example that an {@link Augmentation} follows an
512      * {@link Augmentable} step. Furthermore the compile-time indicated generic type of the returned object does not
513      * necessarily match the contained state.
514      *
515      * <p>
516      * Failure to observe precautions to validate the list's contents may yield an object which mey be rejected at
517      * run-time or lead to undefined behaviour.
518      *
519      * @param pathArguments The path to a specific node in the data tree
520      * @return InstanceIdentifier instance
521      * @throws NullPointerException if {@code pathArguments} is, or contains an item which is, {@code null}
522      * @throws IllegalArgumentException if {@code pathArguments} is empty or contains an item which does not represent
523      *                                  a valid addressing step.
524      */
525     @SuppressWarnings("unchecked")
526     public static <T extends DataObject> @NonNull InstanceIdentifier<T> unsafeOf(
527             final List<? extends DataObjectStep<?>> pathArguments) {
528         return (InstanceIdentifier<T>) internalCreate(ImmutableList.copyOf(pathArguments));
529     }
530
531     /**
532      * Create an instance identifier for a very specific object type.
533      *
534      * <p>
535      * For example
536      * <pre>
537      *      new InstanceIdentifier(Nodes.class)
538      * </pre>
539      * would create an InstanceIdentifier for an object of type Nodes
540      *
541      * @param type The type of the object which this instance identifier represents
542      * @return InstanceIdentifier instance
543      */
544     // FIXME: considering removing in favor of always going through a builder
545     @SuppressWarnings("unchecked")
546     public static <T extends ChildOf<? extends DataRoot>> @NonNull InstanceIdentifier<T> create(
547             final Class<@NonNull T> type) {
548         return (InstanceIdentifier<T>) internalCreate(ImmutableList.of(createStep(type)));
549     }
550
551     /**
552      * Return the key associated with the last component of the specified identifier.
553      *
554      * @param id instance identifier
555      * @return key associated with the last component
556      * @throws IllegalArgumentException if the supplied identifier type cannot have a key.
557      * @throws NullPointerException if id is null.
558      */
559     // FIXME: reconsider naming and design of this method
560     public static <N extends KeyAware<K> & DataObject, K extends Key<N>> K keyOf(
561             final InstanceIdentifier<N> id) {
562         requireNonNull(id);
563         checkArgument(id instanceof KeyedInstanceIdentifier, "%s does not have a key", id);
564
565         @SuppressWarnings("unchecked")
566         final K ret = ((KeyedInstanceIdentifier<N, K>)id).key();
567         return ret;
568     }
569
570     @SuppressWarnings({ "unchecked", "rawtypes" })
571     static <N extends DataObject> @NonNull InstanceIdentifier<N> trustedCreate(final DataObjectStep<?> lastStep,
572             final Iterable<? extends DataObjectStep<?>> pathArguments, final boolean wildcarded) {
573         return switch (lastStep) {
574             case NodeStep<?> cast -> new InstanceIdentifier(pathArguments, wildcarded);
575             case KeyStep<?, ?> cast -> new KeyedInstanceIdentifier(pathArguments, wildcarded);
576             case KeylessStep<?> cast -> new InstanceIdentifier(pathArguments, true);
577         };
578     }
579
580     @Deprecated(since = "13.0.0", forRemoval = true)
581     private abstract static sealed class AbstractPathArgument<T extends DataObject>
582             implements Comparable<AbstractPathArgument<?>>, Serializable {
583         @java.io.Serial
584         private static final long serialVersionUID = 1L;
585
586         private final @NonNull Class<T> type;
587
588         AbstractPathArgument(final Class<T> type) {
589             this.type = requireNonNull(type, "Type may not be null.");
590         }
591
592         /**
593          * Return the data object type backing this PathArgument.
594          *
595          * @return Data object type.
596          */
597         final @NonNull Class<T> type() {
598             return type;
599         }
600
601         /**
602          * Return an optional enclosing case type. This is used only when {@link #type()} references a node defined
603          * in a {@code grouping} which is reference inside a {@code case} statement in order to safely reference the
604          * node.
605          *
606          * @return case class or {@code null}
607          */
608         Class<? extends DataObject> caseType() {
609             return null;
610         }
611
612         @Nullable Object key() {
613             return null;
614         }
615
616         @Override
617         public final int hashCode() {
618             return Objects.hash(type, caseType(), key());
619         }
620
621         @Override
622         public final boolean equals(final Object obj) {
623             return this == obj || obj instanceof AbstractPathArgument<?> other && type.equals(other.type)
624                 && Objects.equals(key(), other.key()) && Objects.equals(caseType(), other.caseType());
625         }
626
627         @Override
628         public final int compareTo(final AbstractPathArgument<?> arg) {
629             final int cmp = compareClasses(type, arg.type());
630             if (cmp != 0) {
631                 return cmp;
632             }
633             final var caseType = caseType();
634             final var argCaseType = arg.caseType();
635             if (caseType == null) {
636                 return argCaseType == null ? 1 : -1;
637             }
638             return argCaseType == null ? 1 : compareClasses(caseType, argCaseType);
639         }
640
641         private static int compareClasses(final Class<?> first, final Class<?> second) {
642             return first.getCanonicalName().compareTo(second.getCanonicalName());
643         }
644
645         @java.io.Serial
646         final Object readResolve() throws ObjectStreamException {
647             return toStep();
648         }
649
650         abstract DataObjectStep<?> toStep();
651     }
652
653     /**
654      * An Item represents an object that probably is only one of it's kind. For example a Nodes object is only one of
655      * a kind. In YANG terms this would probably represent a container.
656      *
657      * @param <T> Item type
658      */
659     @Deprecated(since = "13.0.0", forRemoval = true)
660     private static sealed class Item<T extends DataObject> extends AbstractPathArgument<T> {
661         @java.io.Serial
662         private static final long serialVersionUID = 1L;
663
664         Item(final Class<T> type) {
665             super(type);
666         }
667
668         @Override
669         @SuppressWarnings({ "rawtypes", "unchecked" })
670         final DataObjectStep<?> toStep() {
671             return createStep((Class) caseType(), type());
672         }
673
674         @Override
675         public String toString() {
676             return type().getName();
677         }
678     }
679
680     /**
681      * An IdentifiableItem represents a object that is usually present in a collection and can be identified uniquely
682      * by a key. In YANG terms this would probably represent an item in a list.
683      *
684      * @param <I> An object that is identifiable by an identifier
685      * @param <T> The identifier of the object
686      */
687     @Deprecated(since = "13.0.0", forRemoval = true)
688     private static sealed class IdentifiableItem<I extends KeyAware<T> & DataObject, T extends Key<I>>
689             extends AbstractPathArgument<I> {
690         @java.io.Serial
691         private static final long serialVersionUID = 1L;
692
693         private final @NonNull T key;
694
695         IdentifiableItem(final Class<I> type, final T key) {
696             super(type);
697             this.key = requireNonNull(key, "Key may not be null.");
698         }
699
700         /**
701          * Return the data object type backing this PathArgument.
702          *
703          * @return Data object type.
704          */
705         @Override
706         final @NonNull T key() {
707             return key;
708         }
709
710         @Override
711         final KeyStep<?, ?> toStep() {
712             return new KeyStep<>(type(), caseType(), key);
713         }
714
715         @Override
716         public String toString() {
717             return type().getName() + "[key=" + key + "]";
718         }
719     }
720
721     @Deprecated(since = "13.0.0", forRemoval = true)
722     private static final class CaseItem<C extends ChoiceIn<?> & DataObject, T extends ChildOf<? super C>>
723             extends Item<T> {
724         @java.io.Serial
725         private static final long serialVersionUID = 1L;
726
727         private final Class<C> caseType;
728
729         CaseItem(final Class<C> caseType, final Class<T> type) {
730             super(type);
731             this.caseType = requireNonNull(caseType);
732         }
733
734         @Override
735         Class<C> caseType() {
736             return caseType;
737         }
738     }
739
740     @Deprecated(since = "13.0.0", forRemoval = true)
741     private static final class CaseIdentifiableItem<C extends ChoiceIn<?> & DataObject,
742             T extends ChildOf<? super C> & KeyAware<K>, K extends Key<T>> extends IdentifiableItem<T, K> {
743         @java.io.Serial
744         private static final long serialVersionUID = 1L;
745
746         private final Class<C> caseType;
747
748         CaseIdentifiableItem(final Class<C> caseType, final Class<T> type, final K key) {
749             super(type, key);
750             this.caseType = requireNonNull(caseType);
751         }
752
753         @Override
754         Class<C> caseType() {
755             return caseType;
756         }
757     }
758
759     /**
760      * A builder of {@link InstanceIdentifier} objects.
761      *
762      * @param <T> Instance identifier target type
763      */
764     public abstract static sealed class Builder<T extends DataObject> {
765         private final ArrayList<@NonNull DataObjectStep<?>> pathBuilder;
766         private final Iterable<? extends @NonNull DataObjectStep<?>> basePath;
767
768         private boolean wildcard;
769
770         Builder(final Builder<?> prev, final DataObjectStep<?> item) {
771             pathBuilder = prev.pathBuilder;
772             basePath = prev.basePath;
773             wildcard = prev.wildcard;
774             appendItem(item);
775         }
776
777         Builder(final InstanceIdentifier<T> identifier) {
778             pathBuilder = new ArrayList<>(4);
779             wildcard = identifier.isWildcarded();
780             basePath = identifier.steps();
781         }
782
783         Builder(final DataObjectStep<?> item, final boolean wildcard) {
784             pathBuilder = new ArrayList<>(4);
785             basePath = null;
786             this.wildcard = wildcard;
787             appendItem(item);
788         }
789
790         final boolean wildcard() {
791             return wildcard;
792         }
793
794         /**
795          * Build an identifier which refers to a specific augmentation of the current InstanceIdentifier referenced by
796          * the builder.
797          *
798          * @param container augmentation class
799          * @param <N> augmentation type
800          * @return this builder
801          * @throws NullPointerException if {@code container} is null
802          */
803         public final <N extends DataObject & Augmentation<? super T>> Builder<N> augmentation(
804                 final Class<N> container) {
805             return append(new NodeStep<>(container));
806         }
807
808         /**
809          * Append the specified container as a child of the current InstanceIdentifier referenced by the builder. This
810          * method should be used when you want to build an instance identifier by appending top-level elements, for
811          * example
812          * <pre>
813          *     InstanceIdentifier.builder().child(Nodes.class).build();
814          * </pre>
815          *
816          * <p>
817          * NOTE :- The above example is only for illustration purposes InstanceIdentifier.builder() has been deprecated
818          * and should not be used. Use InstanceIdentifier.builder(Nodes.class) instead
819          *
820          * @param container Container to append
821          * @param <N> Container type
822          * @return this builder
823          * @throws NullPointerException if {@code container} is null
824          */
825         public final <N extends ChildOf<? super T>> Builder<N> child(final Class<N> container) {
826             return append(createStep(container));
827         }
828
829         /**
830          * Append the specified container as a child of the current InstanceIdentifier referenced by the builder. This
831          * method should be used when you want to build an instance identifier by appending a container node to the
832          * identifier and the {@code container} is defined in a {@code grouping} used in a {@code case} statement.
833          *
834          * @param caze Choice case class
835          * @param container Container to append
836          * @param <C> Case type
837          * @param <N> Container type
838          * @return this builder
839          * @throws NullPointerException if {@code container} is null
840          */
841         public final <C extends ChoiceIn<? super T> & DataObject, N extends ChildOf<? super C>> Builder<N> child(
842                 final Class<C> caze, final Class<N> container) {
843             return append(createStep(caze, container));
844         }
845
846         /**
847          * Append the specified listItem as a child of the current InstanceIdentifier referenced by the builder. This
848          * method should be used when you want to build an instance identifier by appending a specific list element to
849          * the identifier.
850          *
851          * @param listItem List to append
852          * @param listKey List key
853          * @param <N> List type
854          * @param <K> Key type
855          * @return this builder
856          * @throws NullPointerException if any argument is null
857          */
858         public final <N extends KeyAware<K> & ChildOf<? super T>, K extends Key<N>> KeyedBuilder<N, K> child(
859                 final Class<@NonNull N> listItem, final K listKey) {
860             return append(new KeyStep<>(listItem, listKey));
861         }
862
863         /**
864          * Append the specified listItem as a child of the current InstanceIdentifier referenced by the builder. This
865          * method should be used when you want to build an instance identifier by appending a specific list element to
866          * the identifier and the {@code list} is defined in a {@code grouping} used in a {@code case} statement.
867          *
868          * @param caze Choice case class
869          * @param listItem List to append
870          * @param listKey List key
871          * @param <C> Case type
872          * @param <N> List type
873          * @param <K> Key type
874          * @return this builder
875          * @throws NullPointerException if any argument is null
876          */
877         public final <C extends ChoiceIn<? super T> & DataObject, K extends Key<N>,
878                 N extends KeyAware<K> & ChildOf<? super C>> KeyedBuilder<N, K> child(final Class<C> caze,
879                     final Class<N> listItem, final K listKey) {
880             return append(new KeyStep<>(listItem, requireNonNull(caze), listKey));
881         }
882
883         /**
884          * Build the instance identifier.
885          *
886          * @return Resulting {@link InstanceIdentifier}.
887          */
888         public abstract @NonNull InstanceIdentifier<T> build();
889
890         final Iterable<? extends DataObjectStep<?>> buildSteps() {
891             final var prefix = basePath;
892             if (prefix == null) {
893                 return pathBuilder.isEmpty() ? ImmutableList.of() : ImmutableList.copyOf(pathBuilder);
894             }
895
896             return switch (pathBuilder.size()) {
897                 case 0 -> prefix;
898                 case 1 -> concat(prefix, pathBuilder.getFirst());
899                 default -> ImmutableList.<DataObjectStep<?>>builder().addAll(prefix).addAll(pathBuilder).build();
900             };
901         }
902
903         @Override
904         public final int hashCode() {
905             int hash = 1;
906             for (var step : pathArguments()) {
907                 hash = 31 * hash + step.hashCode();
908             }
909             return hash;
910         }
911
912         @Override
913         public final boolean equals(final Object obj) {
914             return this == obj || obj instanceof Builder<?> other
915                 && wildcard == other.wildcard && Iterables.elementsEqual(pathArguments(), other.pathArguments());
916         }
917
918         // Note: not suitable for use in result
919         private Iterable<? extends DataObjectStep<?>> pathArguments() {
920             return basePath == null ? pathBuilder : Iterables.concat(basePath, pathBuilder);
921         }
922
923         final void appendItem(final DataObjectStep<?> item) {
924             pathBuilder.add(requireNonNull(item));
925             if (!(item instanceof ExactDataObjectStep)) {
926                 wildcard = true;
927             }
928         }
929
930         abstract <X extends DataObject> @NonNull RegularBuilder<X> append(DataObjectStep<X> step);
931
932         abstract <X extends DataObject & KeyAware<Y>, Y extends Key<X>> @NonNull KeyedBuilder<X, Y> append(
933             KeyStep<Y, X> step);
934     }
935
936     public static final class KeyedBuilder<T extends DataObject & KeyAware<K>, K extends Key<T>>
937             extends Builder<T> {
938         KeyedBuilder(final KeyStep<K, T> firstStep) {
939             super(firstStep, false);
940         }
941
942         KeyedBuilder(final KeyedInstanceIdentifier<T, K> identifier) {
943             super(identifier);
944         }
945
946         private KeyedBuilder(final RegularBuilder<?> prev, final KeyStep<K, T> lastStep) {
947             super(prev, lastStep);
948         }
949
950         /**
951          * Build the instance identifier.
952          *
953          * @return Resulting {@link KeyedInstanceIdentifier}.
954          */
955         @Override
956         public @NonNull KeyedInstanceIdentifier<T, K> build() {
957             return new KeyedInstanceIdentifier<>(buildSteps(), wildcard());
958         }
959
960         @Override
961         <X extends DataObject> @NonNull RegularBuilder<X> append(final DataObjectStep<X> step) {
962             return new RegularBuilder<>(this, step);
963         }
964
965         @Override
966         @SuppressWarnings("unchecked")
967         <X extends DataObject & KeyAware<Y>, Y extends Key<X>> KeyedBuilder<X, Y> append(final KeyStep<Y, X> step) {
968             appendItem(step);
969             return (KeyedBuilder<X, Y>) this;
970         }
971     }
972
973     private static final class RegularBuilder<T extends DataObject> extends Builder<T> {
974         RegularBuilder(final DataObjectStep<T> item) {
975             super(item, !(item instanceof ExactDataObjectStep));
976         }
977
978         RegularBuilder(final InstanceIdentifier<T> identifier) {
979             super(identifier);
980         }
981
982         private RegularBuilder(final KeyedBuilder<?, ?> prev, final DataObjectStep<T> item) {
983             super(prev, item);
984         }
985
986         @Override
987         public InstanceIdentifier<T> build() {
988             return new InstanceIdentifier<>(buildSteps(), wildcard());
989         }
990
991         @Override
992         @SuppressWarnings("unchecked")
993         <X extends DataObject> RegularBuilder<X> append(final DataObjectStep<X> step) {
994             appendItem(step);
995             return (RegularBuilder<X>) this;
996         }
997
998         @Override
999         <X extends DataObject & KeyAware<Y>, Y extends Key<X>> KeyedBuilder<X, Y> append(
1000                 final KeyStep<Y, X> item) {
1001             return new KeyedBuilder<>(this, item);
1002         }
1003     }
1004 }