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