X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=binding%2Fyang-binding%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fyangtools%2Fyang%2Fbinding%2FInstanceIdentifier.java;h=22681a4d0f13e789f541c444d117e22d76b08df2;hb=106cf943e6837e6c30d7628b7c597ee57f0bfbd3;hp=0c89142533d5c337fc8a503337f7b5b949fc8177;hpb=19a1a7c742416955cb09f6d58915b9aaa944101a;p=yangtools.git diff --git a/binding/yang-binding/src/main/java/org/opendaylight/yangtools/yang/binding/InstanceIdentifier.java b/binding/yang-binding/src/main/java/org/opendaylight/yangtools/yang/binding/InstanceIdentifier.java index 0c89142533..22681a4d0f 100644 --- a/binding/yang-binding/src/main/java/org/opendaylight/yangtools/yang/binding/InstanceIdentifier.java +++ b/binding/yang-binding/src/main/java/org/opendaylight/yangtools/yang/binding/InstanceIdentifier.java @@ -12,18 +12,21 @@ import static com.google.common.base.Verify.verify; import static com.google.common.base.Verify.verifyNotNull; import static java.util.Objects.requireNonNull; +import com.google.common.annotations.Beta; import com.google.common.base.MoreObjects; import com.google.common.base.MoreObjects.ToStringHelper; import com.google.common.base.VerifyException; -import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; +import java.io.IOException; +import java.io.NotSerializableException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.io.ObjectStreamException; import java.io.Serializable; import java.util.Collections; -import java.util.Iterator; +import java.util.List; import java.util.Objects; -import java.util.Optional; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import org.opendaylight.yangtools.concepts.HierarchicalIdentifier; @@ -34,50 +37,49 @@ import org.opendaylight.yangtools.util.HashCodeBuilder; * *

* For Example let's say you were trying to refer to a node in inventory which was modeled in YANG as follows, + *

code{
+ *   module opendaylight-inventory {
+ *     ....
  *
- * 

- *

- * module opendaylight-inventory {
- *      ....
- *
- *      container nodes {
- *        list node {
- *            key "id";
- *            ext:context-instance "node-context";
- *
- *            uses node;
- *        }
- *    }
+ *     container nodes {
+ *       list node {
+ *         key "id";
+ *         ext:context-instance "node-context";
  *
- * }
- * 
+ * uses node; + * } + * } + * } + * }
* *

* You can create an instance identifier as follows to get to a node with id "openflow:1": {@code - * InstanceIdentifierBuilder.builder(Nodes.class).child(Node.class, new NodeKey(new NodeId("openflow:1")).build(); + * InstanceIdentifier.builder(Nodes.class).child(Node.class, new NodeKey(new NodeId("openflow:1")).build(); * } * *

* This would be the same as using a path like so, "/nodes/node/openflow:1" to refer to the openflow:1 node */ -public class InstanceIdentifier - implements HierarchicalIdentifier> { +public sealed class InstanceIdentifier + implements HierarchicalIdentifier> + permits KeyedInstanceIdentifier { + @java.io.Serial private static final long serialVersionUID = 3L; /* * Protected to differentiate internal and external access. Internal access is required never to modify * the contents. References passed to outside entities have to be wrapped in an unmodifiable view. */ - final Iterable pathArguments; + final Iterable> pathArguments; private final @NonNull Class targetType; private final boolean wildcarded; private final int hash; - InstanceIdentifier(final Class type, final Iterable pathArguments, final boolean wildcarded, + InstanceIdentifier(final Class type, final Iterable> pathArguments, final boolean wildcarded, final int hash) { this.pathArguments = requireNonNull(pathArguments); - this.targetType = requireNonNull(type); + targetType = requireNonNull(type); this.wildcarded = wildcarded; this.hash = hash; } @@ -110,7 +112,7 @@ public class InstanceIdentifier * * @return Path argument chain. Immutable and does not contain nulls. */ - public final @NonNull Iterable getPathArguments() { + public final @NonNull Iterable> getPathArguments() { return Iterables.unmodifiableIterable(pathArguments); } @@ -140,43 +142,24 @@ public class InstanceIdentifier return false; } - final InstanceIdentifier other = (InstanceIdentifier) obj; + final var other = (InstanceIdentifier) obj; if (pathArguments == other.pathArguments) { return true; } /* - * We could now just go and compare the pathArguments, but that - * can be potentially expensive. Let's try to avoid that by - * checking various things that we have cached from pathArguments - * and trying to prove the identifiers are *not* equal. + * We could now just go and compare the pathArguments, but that can be potentially expensive. Let's try to avoid + * that by checking various things that we have cached from pathArguments and trying to prove the identifiers + * are *not* equal. */ - if (hash != other.hash) { - return false; - } - if (wildcarded != other.wildcarded) { - return false; - } - if (targetType != other.targetType) { - return false; - } - if (fastNonEqual(other)) { - return false; - } - - // Everything checks out so far, so we have to do a full equals - return Iterables.elementsEqual(pathArguments, other.pathArguments); + return hash == other.hash && wildcarded == other.wildcarded && targetType == other.targetType + && keyEquals(other) + // Everything checks out so far, so we have to do a full equals + && Iterables.elementsEqual(pathArguments, other.pathArguments); } - /** - * Perform class-specific fast checks for non-equality. This allows subclasses to avoid iterating over the - * pathArguments by performing quick checks on their specific fields. - * - * @param other The other identifier, guaranteed to be the same class - * @return true if the other identifier cannot be equal to this one. - */ - protected boolean fastNonEqual(final InstanceIdentifier other) { - return false; + boolean keyEquals(final InstanceIdentifier other) { + return true; } @Override @@ -200,7 +183,7 @@ public class InstanceIdentifier *

* For example let's say an instance identifier was built like so, *

-     *      identifier = InstanceIdentifierBuilder.builder(Nodes.class).child(Node.class,
+     *      identifier = InstanceIdentifier.builder(Nodes.class).child(Node.class,
      *                   new NodeKey(new NodeId("openflow:1")).build();
      * 
* @@ -219,11 +202,10 @@ public class InstanceIdentifier public final @Nullable InstanceIdentifier firstIdentifierOf( final Class<@NonNull I> type) { int count = 1; - for (final PathArgument a : pathArguments) { - if (type.equals(a.getType())) { + for (var step : pathArguments) { + if (type.equals(step.type())) { @SuppressWarnings("unchecked") - final InstanceIdentifier ret = (InstanceIdentifier) internalCreate( - Iterables.limit(pathArguments, count)); + final var ret = (InstanceIdentifier) internalCreate(Iterables.limit(pathArguments, count)); return ret; } @@ -241,16 +223,15 @@ public class InstanceIdentifier * @return key associated with the component, or null if the component type * is not present. */ - public final & DataObject, K extends Identifier> @Nullable K firstKeyOf( + public final & DataObject, K extends Key> @Nullable K firstKeyOf( final Class<@NonNull N> listItem) { - for (final PathArgument i : pathArguments) { - if (listItem.equals(i.getType())) { + for (var step : pathArguments) { + if (step instanceof KeyStep keyPredicate && listItem.equals(step.type())) { @SuppressWarnings("unchecked") - final K ret = ((IdentifiableItem)i).getKey(); + final var ret = (K) keyPredicate.key(); return ret; } } - return null; } @@ -278,19 +259,15 @@ public class InstanceIdentifier public final boolean contains(final InstanceIdentifier other) { requireNonNull(other, "other should not be null"); - final Iterator lit = pathArguments.iterator(); - final Iterator oit = other.pathArguments.iterator(); - - while (lit.hasNext()) { + final var oit = other.pathArguments.iterator(); + for (var step : pathArguments) { if (!oit.hasNext()) { return false; } - - if (!lit.next().equals(oit.next())) { + if (!step.equals(oit.next())) { return false; } } - return true; } @@ -305,31 +282,32 @@ public class InstanceIdentifier public final boolean containsWildcarded(final InstanceIdentifier other) { requireNonNull(other, "other should not be null"); - final Iterator lit = pathArguments.iterator(); - final Iterator oit = other.pathArguments.iterator(); - - while (lit.hasNext()) { - if (!oit.hasNext()) { + final var otherSteps = other.pathArguments.iterator(); + for (var step : pathArguments) { + if (!otherSteps.hasNext()) { return false; } - final PathArgument la = lit.next(); - final PathArgument oa = oit.next(); - - if (!la.getType().equals(oa.getType())) { - return false; - } - if (la instanceof IdentifiableItem && oa instanceof IdentifiableItem && !la.equals(oa)) { - return false; + final var otherStep = otherSteps.next(); + if (step instanceof ExactDataObjectStep) { + if (!step.equals(otherStep)) { + return false; + } + } else if (step instanceof KeylessStep keyless) { + if (!keyless.matches(otherStep)) { + return false; + } + } else { + throw new IllegalStateException("Unhandled step " + step); } } return true; } - private @NonNull InstanceIdentifier childIdentifier(final AbstractPathArgument arg) { + private @NonNull InstanceIdentifier childIdentifier(final DataObjectStep arg) { return trustedCreate(arg, Iterables.concat(pathArguments, Collections.singleton(arg)), - HashCodeBuilder.nextHashCode(hash, arg), isWildcarded()); + HashCodeBuilder.nextHashCode(hash, arg), wildcarded); } /** @@ -343,7 +321,7 @@ public class InstanceIdentifier */ public final > @NonNull InstanceIdentifier child( final Class<@NonNull N> container) { - return childIdentifier(Item.of(container)); + return childIdentifier(createStep(container)); } /** @@ -358,9 +336,9 @@ public class InstanceIdentifier * @throws NullPointerException if any argument is null */ @SuppressWarnings("unchecked") - public final & ChildOf, K extends Identifier> + public final & ChildOf, K extends Key> @NonNull KeyedInstanceIdentifier child(final Class<@NonNull N> listItem, final K listKey) { - return (KeyedInstanceIdentifier) childIdentifier(IdentifiableItem.of(listItem, listKey)); + return (KeyedInstanceIdentifier) childIdentifier(new KeyStep<>(listItem, listKey)); } /** @@ -377,7 +355,7 @@ public class InstanceIdentifier // FIXME: add a proper caller public final & DataObject, N extends ChildOf> @NonNull InstanceIdentifier child(final Class<@NonNull C> caze, final Class<@NonNull N> container) { - return childIdentifier(Item.of(caze, container)); + return childIdentifier(createStep(caze, container)); } /** @@ -395,10 +373,10 @@ public class InstanceIdentifier */ // FIXME: add a proper caller @SuppressWarnings("unchecked") - public final & DataObject, K extends Identifier, - N extends Identifiable & ChildOf> @NonNull KeyedInstanceIdentifier child( + public final & DataObject, K extends Key, + N extends KeyAware & ChildOf> @NonNull KeyedInstanceIdentifier child( final Class<@NonNull C> caze, final Class<@NonNull N> listItem, final K listKey) { - return (KeyedInstanceIdentifier) childIdentifier(IdentifiableItem.of(caze, listItem, listKey)); + return (KeyedInstanceIdentifier) childIdentifier(new KeyStep<>(listItem, requireNonNull(caze), listKey)); } /** @@ -412,7 +390,31 @@ public class InstanceIdentifier */ public final > @NonNull InstanceIdentifier augmentation( final Class<@NonNull N> container) { - return childIdentifier(Item.of(container)); + return childIdentifier(new NodeStep<>(container)); + } + + @java.io.Serial + Object writeReplace() throws ObjectStreamException { + return new IIv4<>(this); + } + + @java.io.Serial + private void readObject(final ObjectInputStream stream) throws IOException, ClassNotFoundException { + throwNSE(); + } + + @java.io.Serial + private void readObjectNoData() throws ObjectStreamException { + throwNSE(); + } + + @java.io.Serial + private void writeObject(final ObjectOutputStream stream) throws IOException { + throwNSE(); + } + + private void throwNSE() throws NotSerializableException { + throw new NotSerializableException(getClass().getName()); } /** @@ -421,59 +423,58 @@ public class InstanceIdentifier * @return A builder instance */ // FIXME: rename this method to 'toBuilder()' - public @NonNull InstanceIdentifierBuilder builder() { - return new InstanceIdentifierBuilderImpl<>(Item.of(targetType), pathArguments, hash, isWildcarded()); + public @NonNull Builder builder() { + return new RegularBuilder<>(this); } /** - * Create an InstanceIdentifierBuilder for a specific type of InstanceIdentifier as specified by container. + * Create a {@link Builder} for a specific type of InstanceIdentifier as specified by container. * * @param container Base container * @param Type of the container - * @return A new {@link InstanceIdentifierBuilder} + * @return A new {@link Builder} * @throws NullPointerException if {@code container} is null */ - public static > @NonNull InstanceIdentifierBuilder builder( + public static > @NonNull Builder builder( final Class container) { - return new InstanceIdentifierBuilderImpl().addWildNode(Item.of(container)); + return new RegularBuilder<>(createStep(container)); } /** - * Create an InstanceIdentifierBuilder for a specific type of InstanceIdentifier as specified by container in + * Create a {@link Builder} for a specific type of InstanceIdentifier as specified by container in * a {@code grouping} used in the {@code case} statement. * * @param caze Choice case class * @param container Base container * @param Case type * @param Type of the container - * @return A new {@link InstanceIdentifierBuilder} + * @return A new {@link Builder} * @throws NullPointerException if any argument is null */ public static & DataObject, T extends ChildOf> - @NonNull InstanceIdentifierBuilder builder(final Class caze, final Class container) { - return new InstanceIdentifierBuilderImpl().addWildNode(Item.of(caze, container)); + @NonNull Builder builder(final Class caze, final Class container) { + return new RegularBuilder<>(createStep(caze, container)); } /** - * Create an InstanceIdentifierBuilder for a specific type of InstanceIdentifier which represents an - * {@link IdentifiableItem}. + * Create a {@link Builder} for a specific type of InstanceIdentifier which represents an {@link IdentifiableItem}. * * @param listItem list item class * @param listKey key value * @param List type * @param List key - * @return A new {@link InstanceIdentifierBuilder} + * @return A new {@link Builder} * @throws NullPointerException if any argument is null */ - public static & ChildOf, - K extends Identifier> @NonNull InstanceIdentifierBuilder builder(final Class listItem, + public static & ChildOf, + K extends Key> @NonNull KeyedBuilder builder(final Class listItem, final K listKey) { - return new InstanceIdentifierBuilderImpl().addNode(IdentifiableItem.of(listItem, listKey)); + return new KeyedBuilder<>(new KeyStep<>(listItem, listKey)); } /** - * Create an InstanceIdentifierBuilder for a specific type of InstanceIdentifier which represents an - * {@link IdentifiableItem} in a {@code grouping} used in the {@code case} statement. + * Create a {@link Builder} for a specific type of InstanceIdentifier which represents an {@link IdentifiableItem} + * in a {@code grouping} used in the {@code case} statement. * * @param caze Choice case class * @param listItem list item class @@ -481,44 +482,56 @@ public class InstanceIdentifier * @param Case type * @param List type * @param List key - * @return A new {@link InstanceIdentifierBuilder} + * @return A new {@link Builder} * @throws NullPointerException if any argument is null */ public static & DataObject, - N extends Identifiable & ChildOf, K extends Identifier> - @NonNull InstanceIdentifierBuilder builder(final Class caze, final Class listItem, + N extends KeyAware & ChildOf, K extends Key> + @NonNull KeyedBuilder builder(final Class caze, final Class listItem, final K listKey) { - return new InstanceIdentifierBuilderImpl().addNode(IdentifiableItem.of(caze, listItem, listKey)); + return new KeyedBuilder<>(new KeyStep<>(listItem, requireNonNull(caze), listKey)); } public static > - @NonNull InstanceIdentifierBuilder builderOfInherited(final Class root, final Class container) { + @NonNull Builder builderOfInherited(final Class root, final Class container) { // FIXME: we are losing root identity, hence namespaces may not work correctly - return new InstanceIdentifierBuilderImpl().addWildNode(Item.of(container)); + return new RegularBuilder<>(createStep(container)); } public static & DataObject, T extends ChildOf> - @NonNull InstanceIdentifierBuilder builderOfInherited(final Class root, + @NonNull Builder builderOfInherited(final Class root, final Class caze, final Class container) { // FIXME: we are losing root identity, hence namespaces may not work correctly - return new InstanceIdentifierBuilderImpl().addWildNode(Item.of(caze, container)); + return new RegularBuilder<>(createStep(caze, container)); } - public static & ChildOf, - K extends Identifier> - @NonNull InstanceIdentifierBuilder builderOfInherited(final Class root, + public static & ChildOf, + K extends Key> + @NonNull KeyedBuilder builderOfInherited(final Class root, final Class listItem, final K listKey) { // FIXME: we are losing root identity, hence namespaces may not work correctly - return new InstanceIdentifierBuilderImpl().addNode(IdentifiableItem.of(listItem, listKey)); + return new KeyedBuilder<>(new KeyStep<>(listItem, listKey)); } public static & DataObject, - N extends Identifiable & ChildOf, K extends Identifier> - @NonNull InstanceIdentifierBuilder builderOfInherited(final Class root, + N extends KeyAware & ChildOf, K extends Key> + @NonNull KeyedBuilder builderOfInherited(final Class root, final Class caze, final Class listItem, final K listKey) { // FIXME: we are losing root identity, hence namespaces may not work correctly - return new InstanceIdentifierBuilderImpl().addNode(IdentifiableItem.of(caze, listItem, listKey)); + return new KeyedBuilder<>(new KeyStep<>(listItem, requireNonNull(caze), listKey)); + } + + @Beta + @SuppressWarnings({ "rawtypes", "unchecked" }) + public static & DataObject> @NonNull DataObjectStep createStep( + final Class caze, final Class type) { + return KeyAware.class.isAssignableFrom(type) ? new KeylessStep(type, caze) : new NodeStep<>(type, caze); + } + + @Beta + public static @NonNull DataObjectStep createStep(final Class type) { + return createStep(null, type); } /** @@ -530,24 +543,24 @@ public class InstanceIdentifier * @throws IllegalArgumentException if pathArguments is empty or contains a null element. * @throws NullPointerException if {@code pathArguments} is null */ - private static @NonNull InstanceIdentifier internalCreate(final Iterable pathArguments) { + private static @NonNull InstanceIdentifier internalCreate(final Iterable> pathArguments) { final var it = requireNonNull(pathArguments, "pathArguments may not be null").iterator(); checkArgument(it.hasNext(), "pathArguments may not be empty"); - final HashCodeBuilder hashBuilder = new HashCodeBuilder<>(); + final var hashBuilder = new HashCodeBuilder>(); boolean wildcard = false; - PathArgument arg; + DataObjectStep arg; do { arg = it.next(); // Non-null is implied by our callers - final var type = verifyNotNull(arg).getType(); + final var type = verifyNotNull(arg).type(); checkArgument(ChildOf.class.isAssignableFrom(type) || Augmentation.class.isAssignableFrom(type), "%s is not a valid path argument", type); hashBuilder.addArgument(arg); - if (Identifiable.class.isAssignableFrom(type) && !(arg instanceof IdentifiableItem)) { + if (!(arg instanceof ExactDataObjectStep)) { wildcard = true; } } while (it.hasNext()); @@ -556,29 +569,26 @@ public class InstanceIdentifier } /** - * Create an instance identifier for a very specific object type. + * Create an instance identifier for a sequence of {@link DataObjectStep} steps. The steps are required to be formed + * of classes extending either {@link ChildOf} or {@link Augmentation} contracts. This method does not check whether + * or not the sequence is structurally sound, for example that an {@link Augmentation} follows an + * {@link Augmentable} step. Furthermore the compile-time indicated generic type of the returned object does not + * necessarily match the contained state. * *

- * Example: - *

-     *  List<PathArgument> path = Arrays.asList(new Item(Nodes.class))
-     *  new InstanceIdentifier(path);
-     * 
+ * Failure to observe precautions to validate the list's contents may yield an object which mey be rejected at + * run-time or lead to undefined behaviour. * * @param pathArguments The path to a specific node in the data tree * @return InstanceIdentifier instance - * @throws IllegalArgumentException if pathArguments is empty or - * contains a null element. + * @throws NullPointerException if {@code pathArguments} is, or contains an item which is, {@code null} + * @throws IllegalArgumentException if {@code pathArguments} is empty or contains an item which does not represent + * a valid addressing step. */ - // FIXME: rename to 'unsafeOf()' - public static @NonNull InstanceIdentifier create(final Iterable pathArguments) { - if (pathArguments instanceof ImmutableCollection) { - @SuppressWarnings("unchecked") - final var immutableArguments = (ImmutableCollection) pathArguments; - return internalCreate(immutableArguments); - } - - return internalCreate(ImmutableList.copyOf(pathArguments)); + @SuppressWarnings("unchecked") + public static @NonNull InstanceIdentifier unsafeOf( + final List> pathArguments) { + return (InstanceIdentifier) internalCreate(ImmutableList.copyOf(pathArguments)); } /** @@ -598,7 +608,7 @@ public class InstanceIdentifier @SuppressWarnings("unchecked") public static > @NonNull InstanceIdentifier create( final Class<@NonNull T> type) { - return (InstanceIdentifier) internalCreate(ImmutableList.of(Item.of(type))); + return (InstanceIdentifier) internalCreate(ImmutableList.of(createStep(type))); } /** @@ -610,7 +620,7 @@ public class InstanceIdentifier * @throws NullPointerException if id is null. */ // FIXME: reconsider naming and design of this method - public static & DataObject, K extends Identifier> K keyOf( + public static & DataObject, K extends Key> K keyOf( final InstanceIdentifier id) { requireNonNull(id); checkArgument(id instanceof KeyedInstanceIdentifier, "%s does not have a key", id); @@ -621,99 +631,90 @@ public class InstanceIdentifier } @SuppressWarnings({ "unchecked", "rawtypes" }) - static @NonNull InstanceIdentifier trustedCreate(final PathArgument arg, - final Iterable pathArguments, final int hash, boolean wildcarded) { - if (Identifiable.class.isAssignableFrom(arg.getType()) && !wildcarded) { - Identifier key = null; - if (arg instanceof IdentifiableItem) { - key = ((IdentifiableItem)arg).getKey(); - } else { - wildcarded = true; - } - - return new KeyedInstanceIdentifier(arg.getType(), pathArguments, wildcarded, hash, key); + static @NonNull InstanceIdentifier trustedCreate(final DataObjectStep lastStep, + final Iterable> pathArguments, final int hash, final boolean wildcarded) { + if (lastStep instanceof NodeStep) { + return new InstanceIdentifier(lastStep.type(), pathArguments, wildcarded, hash); + } else if (lastStep instanceof KeyStep predicate) { + return new KeyedInstanceIdentifier(predicate, pathArguments, wildcarded, hash); + } else if (lastStep instanceof KeylessStep) { + return new InstanceIdentifier(lastStep.type(), pathArguments, true, hash); + } else { + throw new IllegalStateException("Unhandled step " + lastStep); } - - return new InstanceIdentifier(arg.getType(), pathArguments, wildcarded, hash); } - /** - * Path argument of {@link InstanceIdentifier}. Interface which implementations are used as path components of the - * path in overall data tree. - */ - public interface PathArgument extends Comparable { + @Deprecated(since = "13.0.0", forRemoval = true) + private abstract static sealed class AbstractPathArgument + implements Comparable>, Serializable { + @java.io.Serial + private static final long serialVersionUID = 1L; + + private final @NonNull Class type; + + AbstractPathArgument(final Class type) { + this.type = requireNonNull(type, "Type may not be null."); + } + /** * Return the data object type backing this PathArgument. * * @return Data object type. */ - @NonNull Class getType(); + final @NonNull Class type() { + return type; + } /** - * Return an optional enclosing case type. This is used only when {@link #getType()} references a node defined + * Return an optional enclosing case type. This is used only when {@link #type()} references a node defined * in a {@code grouping} which is reference inside a {@code case} statement in order to safely reference the * node. * - * @return Optional case class. + * @return case class or {@code null} */ - default Optional> getCaseType() { - return Optional.empty(); - } - } - - private abstract static class AbstractPathArgument implements PathArgument, Serializable { - private static final long serialVersionUID = 1L; - - private final @NonNull Class type; - - AbstractPathArgument(final Class type) { - this.type = requireNonNull(type, "Type may not be null."); - } - - @Override - public final Class getType() { - return type; + Class caseType() { + return null; } - Object getKey() { + @Nullable Object key() { return null; } @Override public final int hashCode() { - return Objects.hash(type, getCaseType(), getKey()); + return Objects.hash(type, caseType(), key()); } @Override public final boolean equals(final Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof AbstractPathArgument)) { - return false; - } - final AbstractPathArgument other = (AbstractPathArgument) obj; - return type.equals(other.type) && Objects.equals(getKey(), other.getKey()) - && getCaseType().equals(other.getCaseType()); + return this == obj || obj instanceof AbstractPathArgument other && type.equals(other.type) + && Objects.equals(key(), other.key()) && Objects.equals(caseType(), other.caseType()); } @Override - public final int compareTo(final PathArgument arg) { - final int cmp = compareClasses(type, arg.getType()); + public final int compareTo(final AbstractPathArgument arg) { + final int cmp = compareClasses(type, arg.type()); if (cmp != 0) { return cmp; } - final Optional> caseType = getCaseType(); - if (!caseType.isPresent()) { - return arg.getCaseType().isPresent() ? -1 : 1; + final var caseType = caseType(); + final var argCaseType = arg.caseType(); + if (caseType == null) { + return argCaseType == null ? 1 : -1; } - final Optional> argCaseType = getCaseType(); - return argCaseType.isPresent() ? compareClasses(caseType.get(), argCaseType.get()) : 1; + return argCaseType == null ? 1 : compareClasses(caseType, argCaseType); } private static int compareClasses(final Class first, final Class second) { return first.getCanonicalName().compareTo(second.getCanonicalName()); } + + @java.io.Serial + final Object readResolve() throws ObjectStreamException { + return toStep(); + } + + abstract DataObjectStep toStep(); } /** @@ -722,44 +723,24 @@ public class InstanceIdentifier * * @param Item type */ - public static class Item extends AbstractPathArgument { + @Deprecated(since = "13.0.0", forRemoval = true) + private static sealed class Item extends AbstractPathArgument { + @java.io.Serial private static final long serialVersionUID = 1L; Item(final Class type) { super(type); } - /** - * Return a PathArgument instance backed by the specified class. - * - * @param type Backing class - * @param Item type - * @return A new PathArgument - * @throws NullPointerException if {@code} is null. - */ - public static @NonNull Item of(final Class type) { - return new Item<>(type); - } - - /** - * Return a PathArgument instance backed by the specified class, which in turn is defined in a {@code grouping} - * used in a corresponding {@code case} statement. - * - * @param caseType defining case class - * @param type Backing class - * @param Case type - * @param Item type - * @return A new PathArgument - * @throws NullPointerException if any argument is null. - */ - public static & DataObject, T extends ChildOf> @NonNull Item of( - final Class caseType, final Class type) { - return new CaseItem<>(caseType, type); + @Override + @SuppressWarnings({ "rawtypes", "unchecked" }) + final DataObjectStep toStep() { + return createStep((Class) caseType(), type()); } @Override public String toString() { - return getType().getName(); + return type().getName(); } } @@ -770,8 +751,10 @@ public class InstanceIdentifier * @param An object that is identifiable by an identifier * @param The identifier of the object */ - public static class IdentifiableItem & DataObject, T extends Identifier> + @Deprecated(since = "13.0.0", forRemoval = true) + private static sealed class IdentifiableItem & DataObject, T extends Key> extends AbstractPathArgument { + @java.io.Serial private static final long serialVersionUID = 1L; private final @NonNull T key; @@ -781,57 +764,31 @@ public class InstanceIdentifier this.key = requireNonNull(key, "Key may not be null."); } - /** - * Return an IdentifiableItem instance backed by the specified class with specified key. - * - * @param type Backing class - * @param key Key - * @param List type - * @param Key type - * @return An IdentifiableItem - * @throws NullPointerException if any argument is null. - */ - public static & DataObject, I extends Identifier> - @NonNull IdentifiableItem of(final Class type, final I key) { - return new IdentifiableItem<>(type, key); - } - - /** - * Return an IdentifiableItem instance backed by the specified class with specified key. The class is in turn - * defined in a {@code grouping} used in a corresponding {@code case} statement. - * - * @param caseType defining case class - * @param type Backing class - * @param Case type - * @param List type - * @param Key type - * @return A new PathArgument - * @throws NullPointerException if any argument is null. - */ - public static & DataObject, T extends ChildOf & Identifiable, - I extends Identifier> @NonNull IdentifiableItem of(final Class caseType, - final Class type, final I key) { - return new CaseIdentifiableItem<>(caseType, type, key); - } - /** * Return the data object type backing this PathArgument. * * @return Data object type. */ @Override - public final @NonNull T getKey() { + final @NonNull T key() { return key; } + @Override + final KeyStep toStep() { + return new KeyStep<>(type(), caseType(), key); + } + @Override public String toString() { - return getType().getName() + "[key=" + key + "]"; + return type().getName() + "[key=" + key + "]"; } } + @Deprecated(since = "13.0.0", forRemoval = true) private static final class CaseItem & DataObject, T extends ChildOf> extends Item { + @java.io.Serial private static final long serialVersionUID = 1L; private final Class caseType; @@ -842,13 +799,15 @@ public class InstanceIdentifier } @Override - public Optional> getCaseType() { - return Optional.of(caseType); + Class caseType() { + return caseType; } } + @Deprecated(since = "13.0.0", forRemoval = true) private static final class CaseIdentifiableItem & DataObject, - T extends ChildOf & Identifiable, K extends Identifier> extends IdentifiableItem { + T extends ChildOf & KeyAware, K extends Key> extends IdentifiableItem { + @java.io.Serial private static final long serialVersionUID = 1L; private final Class caseType; @@ -859,14 +818,65 @@ public class InstanceIdentifier } @Override - public Optional> getCaseType() { - return Optional.of(caseType); + Class caseType() { + return caseType; } } - // FIXME: rename to 'Builder' - // FIXME: introduce KeyedBuilder with specialized build() method - public interface InstanceIdentifierBuilder { + /** + * A builder of {@link InstanceIdentifier} objects. + * + * @param Instance identifier target type + */ + public abstract static sealed class Builder { + private final ImmutableList.Builder> pathBuilder; + private final HashCodeBuilder> hashBuilder; + private final Iterable> basePath; + + private boolean wildcard; + + Builder(final Builder prev, final DataObjectStep item) { + pathBuilder = prev.pathBuilder; + hashBuilder = prev.hashBuilder; + basePath = prev.basePath; + wildcard = prev.wildcard; + appendItem(item); + } + + Builder(final InstanceIdentifier identifier) { + pathBuilder = ImmutableList.builder(); + hashBuilder = new HashCodeBuilder<>(identifier.hashCode()); + wildcard = identifier.isWildcarded(); + basePath = identifier.pathArguments; + } + + Builder(final DataObjectStep item, final boolean wildcard) { + pathBuilder = ImmutableList.builder(); + hashBuilder = new HashCodeBuilder<>(); + basePath = null; + hashBuilder.addArgument(item); + pathBuilder.add(item); + this.wildcard = wildcard; + } + + final boolean wildcard() { + return wildcard; + } + + /** + * Build an identifier which refers to a specific augmentation of the current InstanceIdentifier referenced by + * the builder. + * + * @param container augmentation class + * @param augmentation type + * @return this builder + * @throws NullPointerException if {@code container} is null + */ + public final > Builder augmentation( + final Class container) { + return append(new NodeStep<>(container)); + } + /** * Append the specified container as a child of the current InstanceIdentifier referenced by the builder. This * method should be used when you want to build an instance identifier by appending top-level elements, for @@ -884,7 +894,9 @@ public class InstanceIdentifier * @return this builder * @throws NullPointerException if {@code container} is null */ - > @NonNull InstanceIdentifierBuilder child(Class container); + public final > Builder child(final Class container) { + return append(createStep(container)); + } /** * Append the specified container as a child of the current InstanceIdentifier referenced by the builder. This @@ -898,8 +910,10 @@ public class InstanceIdentifier * @return this builder * @throws NullPointerException if {@code container} is null */ - & DataObject, N extends ChildOf> - @NonNull InstanceIdentifierBuilder child(Class caze, Class container); + public final & DataObject, N extends ChildOf> Builder child( + final Class caze, final Class container) { + return append(createStep(caze, container)); + } /** * Append the specified listItem as a child of the current InstanceIdentifier referenced by the builder. This @@ -913,8 +927,10 @@ public class InstanceIdentifier * @return this builder * @throws NullPointerException if any argument is null */ - & ChildOf, K extends Identifier> - @NonNull InstanceIdentifierBuilder child(Class<@NonNull N> listItem, K listKey); + public final & ChildOf, K extends Key> KeyedBuilder child( + final Class<@NonNull N> listItem, final K listKey) { + return append(new KeyStep<>(listItem, listKey)); + } /** * Append the specified listItem as a child of the current InstanceIdentifier referenced by the builder. This @@ -930,31 +946,128 @@ public class InstanceIdentifier * @return this builder * @throws NullPointerException if any argument is null */ - & DataObject, K extends Identifier, - N extends Identifiable & ChildOf> @NonNull InstanceIdentifierBuilder child( - Class caze, Class listItem, K listKey); + public final & DataObject, K extends Key, + N extends KeyAware & ChildOf> KeyedBuilder child(final Class caze, + final Class listItem, final K listKey) { + return append(new KeyStep<>(listItem, requireNonNull(caze), listKey)); + } /** - * Build an identifier which refers to a specific augmentation of the current InstanceIdentifier referenced by - * the builder. + * Build the instance identifier. * - * @param container augmentation class - * @param augmentation type - * @return this builder - * @throws NullPointerException if {@code container} is null + * @return Resulting {@link InstanceIdentifier}. */ - > @NonNull InstanceIdentifierBuilder augmentation( - Class container); + public abstract @NonNull InstanceIdentifier build(); + + @Override + public final int hashCode() { + return hashBuilder.build(); + } + + @Override + public final boolean equals(final Object obj) { + return this == obj || obj instanceof Builder other + && wildcard == other.wildcard && hashCode() == other.hashCode() + && Iterables.elementsEqual(pathArguments(), other.pathArguments()); + } + + final Iterable> pathArguments() { + final var args = pathBuilder.build(); + return basePath == null ? args : Iterables.concat(basePath, args); + } + + final void appendItem(final DataObjectStep item) { + hashBuilder.addArgument(item); + pathBuilder.add(item); + if (!(item instanceof ExactDataObjectStep)) { + wildcard = true; + } + } + + abstract @NonNull RegularBuilder append(DataObjectStep step); + + abstract , Y extends Key> @NonNull KeyedBuilder append( + KeyStep step); + } + + public static final class KeyedBuilder, K extends Key> + extends Builder { + private @NonNull KeyStep lastStep; + + KeyedBuilder(final KeyStep firstStep) { + super(firstStep, false); + lastStep = requireNonNull(firstStep); + } + + KeyedBuilder(final KeyedInstanceIdentifier identifier) { + super(identifier); + lastStep = identifier.lastStep(); + } + + private KeyedBuilder(final RegularBuilder prev, final KeyStep lastStep) { + super(prev, lastStep); + this.lastStep = requireNonNull(lastStep); + } /** * Build the instance identifier. * - * @return Resulting instance identifier. + * @return Resulting {@link KeyedInstanceIdentifier}. */ - @NonNull InstanceIdentifier build(); + @Override + public @NonNull KeyedInstanceIdentifier build() { + return new KeyedInstanceIdentifier<>(lastStep, pathArguments(), wildcard(), hashCode()); + } + + @Override + @NonNull RegularBuilder append(final DataObjectStep step) { + return new RegularBuilder<>(this, step); + } + + @Override + @SuppressWarnings("unchecked") + , Y extends Key> KeyedBuilder append(final KeyStep step) { + appendItem(step); + lastStep = (KeyStep) requireNonNull(step); + return (KeyedBuilder) this; + } } - private Object writeReplace() throws ObjectStreamException { - return new InstanceIdentifierV3<>(this); + private static final class RegularBuilder extends Builder { + private @NonNull Class type; + + RegularBuilder(final DataObjectStep item) { + super(item, !(item instanceof ExactDataObjectStep)); + type = item.type(); + } + + RegularBuilder(final InstanceIdentifier identifier) { + super(identifier); + type = identifier.getTargetType(); + } + + private RegularBuilder(final KeyedBuilder prev, final DataObjectStep item) { + super(prev, item); + type = item.type(); + } + + @Override + public InstanceIdentifier build() { + return new InstanceIdentifier<>(type, pathArguments(), wildcard(), hashCode()); + } + + @Override + @SuppressWarnings({ "rawtypes", "unchecked" }) + RegularBuilder append(final DataObjectStep step) { + appendItem(step); + type = (Class) step.type(); + return (RegularBuilder) this; + } + + @Override + , Y extends Key> KeyedBuilder append( + final KeyStep item) { + return new KeyedBuilder<>(this, item); + } } }