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