Add DataObject{Identifier,Reference}.Builder(.WithKey)
[yangtools.git] / binding / binding-spec / src / main / java / org / opendaylight / yangtools / yang / binding / InstanceIdentifier.java
index 68262cb103a03e5bb24f27ca40bcb6c59ae5af88..fb943cb3470255309c6025e7e66e2124276e1e2b 100644 (file)
@@ -12,8 +12,6 @@ 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.ToStringHelper;
 import com.google.common.base.VerifyException;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
@@ -22,7 +20,6 @@ import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 import java.io.ObjectStreamException;
 import java.io.Serializable;
-import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 import org.eclipse.jdt.annotation.NonNull;
@@ -32,6 +29,8 @@ import org.opendaylight.yangtools.binding.Augmentation;
 import org.opendaylight.yangtools.binding.ChildOf;
 import org.opendaylight.yangtools.binding.ChoiceIn;
 import org.opendaylight.yangtools.binding.DataObject;
+import org.opendaylight.yangtools.binding.DataObjectIdentifier;
+import org.opendaylight.yangtools.binding.DataObjectReference;
 import org.opendaylight.yangtools.binding.DataObjectStep;
 import org.opendaylight.yangtools.binding.DataRoot;
 import org.opendaylight.yangtools.binding.ExactDataObjectStep;
@@ -41,8 +40,8 @@ import org.opendaylight.yangtools.binding.KeyStep;
 import org.opendaylight.yangtools.binding.KeylessStep;
 import org.opendaylight.yangtools.binding.NodeStep;
 import org.opendaylight.yangtools.binding.impl.AbstractDataObjectReference;
+import org.opendaylight.yangtools.binding.impl.AbstractDataObjectReferenceBuilder;
 import org.opendaylight.yangtools.concepts.HierarchicalIdentifier;
-import org.opendaylight.yangtools.util.HashCodeBuilder;
 
 /**
  * This instance identifier uniquely identifies a specific DataObject in the data tree modeled by YANG.
@@ -78,22 +77,11 @@ public sealed class InstanceIdentifier<T extends DataObject> extends AbstractDat
     @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<DataObjectStep<?>> pathArguments;
-
-    private final @NonNull Class<T> targetType;
     private final boolean wildcarded;
-    private final int hash;
 
-    InstanceIdentifier(final Class<T> type, final Iterable<DataObjectStep<?>> pathArguments, final boolean wildcarded,
-            final int hash) {
-        this.pathArguments = requireNonNull(pathArguments);
-        targetType = requireNonNull(type);
+    InstanceIdentifier(final Iterable<? extends @NonNull DataObjectStep<?>> steps, final boolean wildcarded) {
+        super(steps);
         this.wildcarded = wildcarded;
-        this.hash = hash;
     }
 
     /**
@@ -102,7 +90,7 @@ public sealed class InstanceIdentifier<T extends DataObject> extends AbstractDat
      * @return Target type
      */
     public final @NonNull Class<T> getTargetType() {
-        return targetType;
+        return lastStep().type();
     }
 
     /**
@@ -115,15 +103,10 @@ public sealed class InstanceIdentifier<T extends DataObject> extends AbstractDat
      */
     @SuppressWarnings("unchecked")
     public final <N extends DataObject> @NonNull InstanceIdentifier<N> verifyTarget(final Class<@NonNull N> target) {
-        verify(target.equals(targetType), "Cannot adapt %s to %s", this, target);
+        verify(target.equals(getTargetType()), "Cannot adapt %s to %s", this, target);
         return (InstanceIdentifier<N>) this;
     }
 
-    @Override
-    public final @NonNull Iterable<@NonNull DataObjectStep<?>> steps() {
-        return Iterables.unmodifiableIterable(pathArguments);
-    }
-
     @Override
     public final boolean isExact() {
         return !wildcarded;
@@ -136,50 +119,13 @@ public sealed class InstanceIdentifier<T extends DataObject> extends AbstractDat
     }
 
     @Override
-    public final int hashCode() {
-        return hash;
-    }
-
-    @Override
-    public final boolean equals(final Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (obj == null) {
-            return false;
-        }
-        if (getClass() != obj.getClass()) {
-            return false;
-        }
-
-        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.
-         */
-        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);
-    }
-
-    boolean keyEquals(final InstanceIdentifier<?> other) {
-        return true;
+    public final InstanceIdentifier<T> toLegacy() {
+        return this;
     }
 
     @Override
     protected final Class<?> contract() {
-        return getClass();
-    }
-
-    @Override
-    protected ToStringHelper addToStringAttributes(final ToStringHelper toStringHelper) {
-        return toStringHelper.add("targetType", targetType).add("path", Iterables.toString(pathArguments));
+        return wildcarded ? super.contract() : DataObjectIdentifier.class;
     }
 
     /**
@@ -205,10 +151,10 @@ public sealed class InstanceIdentifier<T extends DataObject> extends AbstractDat
     public final <I extends DataObject> @Nullable InstanceIdentifier<I> firstIdentifierOf(
             final Class<@NonNull I> type) {
         int count = 1;
-        for (var step : pathArguments) {
+        for (var step : steps()) {
             if (type.equals(step.type())) {
                 @SuppressWarnings("unchecked")
-                final var ret = (InstanceIdentifier<I>) internalCreate(Iterables.limit(pathArguments, count));
+                final var ret = (InstanceIdentifier<I>) internalCreate(Iterables.limit(steps(), count));
                 return ret;
             }
 
@@ -228,7 +174,7 @@ public sealed class InstanceIdentifier<T extends DataObject> extends AbstractDat
      */
     public final <N extends KeyAware<K> & DataObject, K extends Key<N>> @Nullable K firstKeyOf(
             final Class<@NonNull N> listItem) {
-        for (var step : pathArguments) {
+        for (var step : steps()) {
             if (step instanceof KeyStep<?, ?> keyPredicate && listItem.equals(step.type())) {
                 @SuppressWarnings("unchecked")
                 final var ret = (K) keyPredicate.key();
@@ -262,8 +208,8 @@ public sealed class InstanceIdentifier<T extends DataObject> extends AbstractDat
     public final boolean contains(final InstanceIdentifier<? extends DataObject> other) {
         requireNonNull(other, "other should not be null");
 
-        final var oit = other.pathArguments.iterator();
-        for (var step : pathArguments) {
+        final var oit = other.steps().iterator();
+        for (var step : steps()) {
             if (!oit.hasNext()) {
                 return false;
             }
@@ -285,8 +231,8 @@ public sealed class InstanceIdentifier<T extends DataObject> extends AbstractDat
     public final boolean containsWildcarded(final InstanceIdentifier<?> other) {
         requireNonNull(other, "other should not be null");
 
-        final var otherSteps = other.pathArguments.iterator();
-        for (var step : pathArguments) {
+        final var otherSteps = other.steps().iterator();
+        for (var step : steps()) {
             if (!otherSteps.hasNext()) {
                 return false;
             }
@@ -309,8 +255,7 @@ public sealed class InstanceIdentifier<T extends DataObject> extends AbstractDat
     }
 
     private <N extends DataObject> @NonNull InstanceIdentifier<N> childIdentifier(final DataObjectStep<N> arg) {
-        return trustedCreate(arg, Iterables.concat(pathArguments, Collections.singleton(arg)),
-            HashCodeBuilder.nextHashCode(hash, arg), wildcarded);
+        return trustedCreate(arg, concat(steps(), arg), wildcarded);
     }
 
     /**
@@ -324,7 +269,7 @@ public sealed class InstanceIdentifier<T extends DataObject> extends AbstractDat
      */
     public final <N extends ChildOf<? super T>> @NonNull InstanceIdentifier<N> child(
             final Class<@NonNull N> container) {
-        return childIdentifier(createStep(container));
+        return childIdentifier(DataObjectStep.of(container));
     }
 
     /**
@@ -358,7 +303,7 @@ public sealed class InstanceIdentifier<T extends DataObject> extends AbstractDat
     // FIXME: add a proper caller
     public final <C extends ChoiceIn<? super T> & DataObject, N extends ChildOf<? super C>>
             @NonNull InstanceIdentifier<N> child(final Class<@NonNull C> caze, final Class<@NonNull N> container) {
-        return childIdentifier(createStep(caze, container));
+        return childIdentifier(DataObjectStep.of(caze, container));
     }
 
     /**
@@ -398,7 +343,7 @@ public sealed class InstanceIdentifier<T extends DataObject> extends AbstractDat
 
     @Override
     protected Object toSerialForm() {
-        return new IIv4<>(this);
+        return new IIv5(this);
     }
 
     @java.io.Serial
@@ -430,8 +375,8 @@ public sealed class InstanceIdentifier<T extends DataObject> extends AbstractDat
      * @throws NullPointerException if {@code container} is null
      */
     public static <T extends ChildOf<? extends DataRoot>> @NonNull Builder<T> builder(
-            final Class<T> container) {
-        return new RegularBuilder<>(createStep(container));
+            final @NonNull Class<T> container) {
+        return new RegularBuilder<>(DataObjectStep.of(container));
     }
 
     /**
@@ -446,8 +391,8 @@ public sealed class InstanceIdentifier<T extends DataObject> extends AbstractDat
      * @throws NullPointerException if any argument is null
      */
     public static <C extends ChoiceIn<? extends DataRoot> & DataObject, T extends ChildOf<? super C>>
-            @NonNull Builder<T> builder(final Class<C> caze, final Class<T> container) {
-        return new RegularBuilder<>(createStep(caze, container));
+            @NonNull Builder<T> builder(final @NonNull Class<C> caze, final @NonNull Class<T> container) {
+        return new RegularBuilder<>(DataObjectStep.of(caze, container));
     }
 
     /**
@@ -481,15 +426,15 @@ public sealed class InstanceIdentifier<T extends DataObject> extends AbstractDat
      */
     public static <C extends ChoiceIn<? extends DataRoot> & DataObject,
             N extends KeyAware<K> & ChildOf<? super C>, K extends Key<N>>
-            @NonNull KeyedBuilder<N, K> builder(final Class<C> caze, final Class<N> listItem,
-                    final K listKey) {
+            @NonNull KeyedBuilder<N, K> builder(final @NonNull Class<C> caze, final @NonNull Class<N> listItem,
+                    final @NonNull K listKey) {
         return new KeyedBuilder<>(new KeyStep<>(listItem, requireNonNull(caze), listKey));
     }
 
     public static <R extends DataRoot & DataObject, T extends ChildOf<? super R>>
-            @NonNull Builder<T> builderOfInherited(final Class<R> root, final Class<T> container) {
+            @NonNull Builder<T> builderOfInherited(final @NonNull Class<R> root, final @NonNull Class<T> container) {
         // FIXME: we are losing root identity, hence namespaces may not work correctly
-        return new RegularBuilder<>(createStep(container));
+        return new RegularBuilder<>(DataObjectStep.of(container));
     }
 
     public static <R extends DataRoot & DataObject, C extends ChoiceIn<? super R> & DataObject,
@@ -497,13 +442,13 @@ public sealed class InstanceIdentifier<T extends DataObject> extends AbstractDat
             @NonNull Builder<T> builderOfInherited(final Class<R> root,
                 final Class<C> caze, final Class<T> container) {
         // FIXME: we are losing root identity, hence namespaces may not work correctly
-        return new RegularBuilder<>(createStep(caze, container));
+        return new RegularBuilder<>(DataObjectStep.of(caze, container));
     }
 
     public static <R extends DataRoot & DataObject, N extends KeyAware<K> & ChildOf<? super R>,
             K extends Key<N>>
-            @NonNull KeyedBuilder<N, K> builderOfInherited(final Class<R> root,
-                final Class<N> listItem, final K listKey) {
+            @NonNull KeyedBuilder<N, K> builderOfInherited(final @NonNull Class<R> root,
+                final @NonNull Class<N> listItem, final @NonNull K listKey) {
         // FIXME: we are losing root identity, hence namespaces may not work correctly
         return new KeyedBuilder<>(new KeyStep<>(listItem, listKey));
     }
@@ -516,18 +461,6 @@ public sealed class InstanceIdentifier<T extends DataObject> extends AbstractDat
         return new KeyedBuilder<>(new KeyStep<>(listItem, requireNonNull(caze), listKey));
     }
 
-    @Beta
-    @SuppressWarnings({ "rawtypes", "unchecked" })
-    public static <T extends DataObject, C extends ChoiceIn<?> & DataObject> @NonNull DataObjectStep<T> createStep(
-            final Class<C> caze, final Class<T> type) {
-        return KeyAware.class.isAssignableFrom(type) ? new KeylessStep(type, caze) : new NodeStep<>(type, caze);
-    }
-
-    @Beta
-    public static <T extends DataObject> @NonNull DataObjectStep<T> createStep(final Class<T> type) {
-        return createStep(null, type);
-    }
-
     /**
      * Create an instance identifier for a very specific object type. This method implements {@link #create(Iterable)}
      * semantics, except it is used by internal callers, which have assured that the argument is an immutable Iterable.
@@ -537,11 +470,11 @@ public sealed class InstanceIdentifier<T extends DataObject> extends AbstractDat
      * @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<DataObjectStep<?>> pathArguments) {
+    private static @NonNull InstanceIdentifier<?> internalCreate(
+            final Iterable<? extends DataObjectStep<?>> pathArguments) {
         final var it = requireNonNull(pathArguments, "pathArguments may not be null").iterator();
         checkArgument(it.hasNext(), "pathArguments may not be empty");
 
-        final var hashBuilder = new HashCodeBuilder<DataObjectStep<?>>();
         boolean wildcard = false;
         DataObjectStep<?> arg;
 
@@ -552,14 +485,12 @@ public sealed class InstanceIdentifier<T extends DataObject> extends AbstractDat
             checkArgument(ChildOf.class.isAssignableFrom(type) || Augmentation.class.isAssignableFrom(type),
                 "%s is not a valid path argument", type);
 
-            hashBuilder.addArgument(arg);
-
             if (!(arg instanceof ExactDataObjectStep)) {
                 wildcard = true;
             }
         } while (it.hasNext());
 
-        return trustedCreate(arg, pathArguments, hashBuilder.build(), wildcard);
+        return trustedCreate(arg, pathArguments, wildcard);
     }
 
     /**
@@ -602,7 +533,7 @@ public sealed class InstanceIdentifier<T extends DataObject> extends AbstractDat
     @SuppressWarnings("unchecked")
     public static <T extends ChildOf<? extends DataRoot>> @NonNull InstanceIdentifier<T> create(
             final Class<@NonNull T> type) {
-        return (InstanceIdentifier<T>) internalCreate(ImmutableList.of(createStep(type)));
+        return (InstanceIdentifier<T>) internalCreate(ImmutableList.of(DataObjectStep.of(type)));
     }
 
     /**
@@ -626,17 +557,12 @@ public sealed class InstanceIdentifier<T extends DataObject> extends AbstractDat
 
     @SuppressWarnings({ "unchecked", "rawtypes" })
     static <N extends DataObject> @NonNull InstanceIdentifier<N> trustedCreate(final DataObjectStep<?> lastStep,
-            final Iterable<DataObjectStep<?>> pathArguments, final int hash, final boolean wildcarded) {
-        // FIXME: use a switch expression
-        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);
-        }
+            final Iterable<? extends DataObjectStep<?>> pathArguments, final boolean wildcarded) {
+        return switch (lastStep) {
+            case NodeStep<?> cast -> new InstanceIdentifier(pathArguments, wildcarded);
+            case KeyStep<?, ?> cast -> new KeyedInstanceIdentifier(pathArguments, wildcarded);
+            case KeylessStep<?> cast -> new InstanceIdentifier(pathArguments, true);
+        };
     }
 
     @Deprecated(since = "13.0.0", forRemoval = true)
@@ -730,7 +656,7 @@ public sealed class InstanceIdentifier<T extends DataObject> extends AbstractDat
         @Override
         @SuppressWarnings({ "rawtypes", "unchecked" })
         final DataObjectStep<?> toStep() {
-            return createStep((Class) caseType(), type());
+            return DataObjectStep.of((Class) caseType(), type());
         }
 
         @Override
@@ -823,185 +749,72 @@ public sealed class InstanceIdentifier<T extends DataObject> extends AbstractDat
      *
      * @param <T> Instance identifier target type
      */
-    public abstract static sealed class Builder<T extends DataObject> {
-        private final ImmutableList.Builder<DataObjectStep<?>> pathBuilder;
-        private final HashCodeBuilder<DataObjectStep<?>> hashBuilder;
-        private final Iterable<? extends DataObjectStep<?>> basePath;
-
-        private boolean wildcard;
-
+    public abstract static sealed class Builder<T extends DataObject> extends AbstractDataObjectReferenceBuilder<T> {
         Builder(final Builder<?> prev, final DataObjectStep<?> item) {
-            pathBuilder = prev.pathBuilder;
-            hashBuilder = prev.hashBuilder;
-            basePath = prev.basePath;
-            wildcard = prev.wildcard;
-            appendItem(item);
+            super(prev, item);
         }
 
         Builder(final InstanceIdentifier<T> identifier) {
-            pathBuilder = ImmutableList.builder();
-            hashBuilder = new HashCodeBuilder<>(identifier.hashCode());
-            wildcard = identifier.isWildcarded();
-            basePath = identifier.pathArguments;
+            super(identifier);
         }
 
         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;
+            super(item, wildcard);
         }
 
-        /**
-         * Build an identifier which refers to a specific augmentation of the current InstanceIdentifier referenced by
-         * the builder.
-         *
-         * @param container augmentation class
-         * @param <N> augmentation type
-         * @return this builder
-         * @throws NullPointerException if {@code container} is null
-         */
+        @Override
         public final <N extends DataObject & Augmentation<? super T>> Builder<N> augmentation(
-                final Class<N> container) {
-            return append(new NodeStep<>(container));
+                final Class<N> augmentation) {
+            return append(new NodeStep<>(augmentation));
         }
 
-        /**
-         * 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
-         * example
-         * <pre>
-         *     InstanceIdentifier.builder().child(Nodes.class).build();
-         * </pre>
-         *
-         * <p>
-         * NOTE :- The above example is only for illustration purposes InstanceIdentifier.builder() has been deprecated
-         * and should not be used. Use InstanceIdentifier.builder(Nodes.class) instead
-         *
-         * @param container Container to append
-         * @param <N> Container type
-         * @return this builder
-         * @throws NullPointerException if {@code container} is null
-         */
+        @Override
         public final <N extends ChildOf<? super T>> Builder<N> child(final Class<N> container) {
-            return append(createStep(container));
+            return append(DataObjectStep.of(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 a container node to the
-         * identifier and the {@code container} is defined in a {@code grouping} used in a {@code case} statement.
-         *
-         * @param caze Choice case class
-         * @param container Container to append
-         * @param <C> Case type
-         * @param <N> Container type
-         * @return this builder
-         * @throws NullPointerException if {@code container} is null
-         */
+        @Override
         public final <C extends ChoiceIn<? super T> & DataObject, N extends ChildOf<? super C>> Builder<N> child(
                 final Class<C> caze, final Class<N> container) {
-            return append(createStep(caze, container));
+            return append(DataObjectStep.of(caze, container));
         }
 
-        /**
-         * Append the specified listItem 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 a specific list element to
-         * the identifier.
-         *
-         * @param listItem List to append
-         * @param listKey List key
-         * @param <N> List type
-         * @param <K> Key type
-         * @return this builder
-         * @throws NullPointerException if any argument is null
-         */
+        @Override
         public final <N extends KeyAware<K> & ChildOf<? super T>, K extends Key<N>> KeyedBuilder<N, K> 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
-         * method should be used when you want to build an instance identifier by appending a specific list element to
-         * the identifier and the {@code list} is defined in a {@code grouping} used in a {@code case} statement.
-         *
-         * @param caze Choice case class
-         * @param listItem List to append
-         * @param listKey List key
-         * @param <C> Case type
-         * @param <N> List type
-         * @param <K> Key type
-         * @return this builder
-         * @throws NullPointerException if any argument is null
-         */
+        @Override
         public final <C extends ChoiceIn<? super T> & DataObject, K extends Key<N>,
                 N extends KeyAware<K> & ChildOf<? super C>> KeyedBuilder<N, K> child(final Class<C> caze,
                     final Class<N> listItem, final K listKey) {
             return append(new KeyStep<>(listItem, requireNonNull(caze), listKey));
         }
 
-        /**
-         * Build the instance identifier.
-         *
-         * @return Resulting {@link InstanceIdentifier}.
-         */
+        @Override
         public abstract @NonNull InstanceIdentifier<T> build();
 
         @Override
-        public final int hashCode() {
-            return hashBuilder.build();
-        }
+        protected abstract <X extends DataObject> @NonNull RegularBuilder<X> append(DataObjectStep<X> step);
 
         @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<DataObjectStep<?>> 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 <X extends DataObject> @NonNull RegularBuilder<X> append(DataObjectStep<X> step);
-
-        abstract <X extends DataObject & KeyAware<Y>, Y extends Key<X>> @NonNull KeyedBuilder<X, Y> append(
+        protected abstract <X extends DataObject & KeyAware<Y>, Y extends Key<X>> @NonNull KeyedBuilder<X, Y> append(
             KeyStep<Y, X> step);
     }
 
     public static final class KeyedBuilder<T extends DataObject & KeyAware<K>, K extends Key<T>>
-            extends Builder<T> {
-        private @NonNull KeyStep<K, T> lastStep;
-
+            extends Builder<T> implements DataObjectReference.Builder.WithKey<T, K> {
         KeyedBuilder(final KeyStep<K, T> firstStep) {
             super(firstStep, false);
-            lastStep = requireNonNull(firstStep);
         }
 
         KeyedBuilder(final KeyedInstanceIdentifier<T, K> identifier) {
             super(identifier);
-            lastStep = identifier.lastStep();
         }
 
         private KeyedBuilder(final RegularBuilder<?> prev, final KeyStep<K, T> lastStep) {
             super(prev, lastStep);
-            this.lastStep = requireNonNull(lastStep);
         }
 
         /**
@@ -1011,56 +824,50 @@ public sealed class InstanceIdentifier<T extends DataObject> extends AbstractDat
          */
         @Override
         public @NonNull KeyedInstanceIdentifier<T, K> build() {
-            return new KeyedInstanceIdentifier<>(lastStep, pathArguments(), wildcard(), hashCode());
+            return new KeyedInstanceIdentifier<>(buildSteps(), wildcard());
         }
 
         @Override
-        <X extends DataObject> @NonNull RegularBuilder<X> append(final DataObjectStep<X> step) {
+        protected <X extends DataObject> @NonNull RegularBuilder<X> append(final DataObjectStep<X> step) {
             return new RegularBuilder<>(this, step);
         }
 
         @Override
         @SuppressWarnings("unchecked")
-        <X extends DataObject & KeyAware<Y>, Y extends Key<X>> KeyedBuilder<X, Y> append(final KeyStep<Y, X> step) {
+        protected <X extends DataObject & KeyAware<Y>, Y extends Key<X>> KeyedBuilder<X, Y> append(
+                final KeyStep<Y, X> step) {
             appendItem(step);
-            lastStep = (KeyStep<K, T>) requireNonNull(step);
             return (KeyedBuilder<X, Y>) this;
         }
     }
 
     private static final class RegularBuilder<T extends DataObject> extends Builder<T> {
-        private @NonNull Class<T> type;
-
         RegularBuilder(final DataObjectStep<T> item) {
             super(item, !(item instanceof ExactDataObjectStep));
-            type = item.type();
         }
 
         RegularBuilder(final InstanceIdentifier<T> identifier) {
             super(identifier);
-            type = identifier.getTargetType();
         }
 
         private RegularBuilder(final KeyedBuilder<?, ?> prev, final DataObjectStep<T> item) {
             super(prev, item);
-            type = item.type();
         }
 
         @Override
         public InstanceIdentifier<T> build() {
-            return new InstanceIdentifier<>(type, pathArguments(), wildcard(), hashCode());
+            return new InstanceIdentifier<>(buildSteps(), wildcard());
         }
 
         @Override
-        @SuppressWarnings({ "rawtypes", "unchecked" })
-        <X extends DataObject> RegularBuilder<X> append(final DataObjectStep<X> step) {
+        @SuppressWarnings("unchecked")
+        protected <X extends DataObject> RegularBuilder<X> append(final DataObjectStep<X> step) {
             appendItem(step);
-            type = (Class) step.type();
             return (RegularBuilder<X>) this;
         }
 
         @Override
-        <X extends DataObject & KeyAware<Y>, Y extends Key<X>> KeyedBuilder<X, Y> append(
+        protected <X extends DataObject & KeyAware<Y>, Y extends Key<X>> KeyedBuilder<X, Y> append(
                 final KeyStep<Y, X> item) {
             return new KeyedBuilder<>(this, item);
         }