/*
* Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*/
package org.opendaylight.yangtools.yang.binding;
import static com.google.common.base.Preconditions.checkArgument;
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.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.List;
import java.util.Objects;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
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.
*
*
* 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 {
* ....
*
* 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
* 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 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;
private final @NonNull Class targetType;
private final boolean wildcarded;
private final int hash;
InstanceIdentifier(final Class type, final Iterable> pathArguments, final boolean wildcarded,
final int hash) {
this.pathArguments = requireNonNull(pathArguments);
targetType = requireNonNull(type);
this.wildcarded = wildcarded;
this.hash = hash;
}
/**
* Return the type of data which this InstanceIdentifier identifies.
*
* @return Target type
*/
public final @NonNull Class getTargetType() {
return targetType;
}
/**
* Perform a safe target type adaptation of this instance identifier to target type. This method is useful when
* dealing with type-squashed instances.
*
* @return Path argument with target type
* @throws VerifyException if this instance identifier cannot be adapted to target type
* @throws NullPointerException if {@code target} is null
*/
@SuppressWarnings("unchecked")
public final @NonNull InstanceIdentifier verifyTarget(final Class<@NonNull N> target) {
verify(target.equals(targetType), "Cannot adapt %s to %s", this, target);
return (InstanceIdentifier) this;
}
/**
* Return the path argument chain which makes up this instance identifier.
*
* @return Path argument chain. Immutable and does not contain nulls.
*/
public final @NonNull Iterable> getPathArguments() {
return Iterables.unmodifiableIterable(pathArguments);
}
/**
* Check whether an instance identifier contains any wildcards. A wildcard is an path argument which has a null key.
*
* @return true if any of the path arguments has a null key.
*/
public final boolean isWildcarded() {
return wildcarded;
}
@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;
}
@Override
public final String toString() {
return addToStringAttributes(MoreObjects.toStringHelper(this)).toString();
}
/**
* Add class-specific toString attributes.
*
* @param toStringHelper ToStringHelper instance
* @return ToStringHelper instance which was passed in
*/
protected ToStringHelper addToStringAttributes(final ToStringHelper toStringHelper) {
return toStringHelper.add("targetType", targetType).add("path", Iterables.toString(pathArguments));
}
/**
* Return an instance identifier trimmed at the first occurrence of a specific component type.
*
*
* For example let's say an instance identifier was built like so,
*
* identifier = InstanceIdentifier.builder(Nodes.class).child(Node.class,
* new NodeKey(new NodeId("openflow:1")).build();
*
*
*
* And you wanted to obtain the Instance identifier which represented Nodes you would do it like so,
*
*
*
* identifier.firstIdentifierOf(Nodes.class)
*
*
* @param type component type
* @return trimmed instance identifier, or null if the component type
* is not present.
*/
public final @Nullable InstanceIdentifier firstIdentifierOf(
final Class<@NonNull I> type) {
int count = 1;
for (var step : pathArguments) {
if (type.equals(step.type())) {
@SuppressWarnings("unchecked")
final var ret = (InstanceIdentifier) internalCreate(Iterables.limit(pathArguments, count));
return ret;
}
++count;
}
return null;
}
/**
* Return the key associated with the first component of specified type in
* an identifier.
*
* @param listItem component type
* @return key associated with the component, or null if the component type
* is not present.
*/
public final & DataObject, K extends Key> @Nullable K firstKeyOf(
final Class<@NonNull N> listItem) {
for (var step : pathArguments) {
if (step instanceof KeyStep, ?> keyPredicate && listItem.equals(step.type())) {
@SuppressWarnings("unchecked")
final var ret = (K) keyPredicate.key();
return ret;
}
}
return null;
}
/**
* Check whether an identifier is contained in this identifier. This is a strict subtree check, which requires all
* PathArguments to match exactly.
*
*
* The contains method checks if the other identifier is fully contained within the current identifier. It does this
* by looking at only the types of the path arguments and not by comparing the path arguments themselves.
*
*
* To illustrate here is an example which explains the working of this API. Let's say you have two instance
* identifiers as follows:
* {@code
* this = /nodes/node/openflow:1
* other = /nodes/node/openflow:2
* }
* then this.contains(other) will return false.
*
* @param other Potentially-container instance identifier
* @return True if the specified identifier is contained in this identifier.
*/
@Override
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) {
if (!oit.hasNext()) {
return false;
}
if (!step.equals(oit.next())) {
return false;
}
}
return true;
}
/**
* Check whether this instance identifier contains the other identifier after wildcard expansion. This is similar
* to {@link #contains(InstanceIdentifier)}, with the exception that a wildcards are assumed to match the their
* non-wildcarded PathArgument counterpart.
*
* @param other Identifier which should be checked for inclusion.
* @return true if this identifier contains the other object
*/
public final boolean containsWildcarded(final InstanceIdentifier> other) {
requireNonNull(other, "other should not be null");
final var otherSteps = other.pathArguments.iterator();
for (var step : pathArguments) {
if (!otherSteps.hasNext()) {
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 DataObjectStep arg) {
return trustedCreate(arg, Iterables.concat(pathArguments, Collections.singleton(arg)),
HashCodeBuilder.nextHashCode(hash, arg), wildcarded);
}
/**
* Create an InstanceIdentifier for a child container. This method is a more efficient equivalent to
* {@code builder().child(container).build()}.
*
* @param container Container to append
* @param Container type
* @return An InstanceIdentifier.
* @throws NullPointerException if {@code container} is null
*/
public final > @NonNull InstanceIdentifier child(
final Class<@NonNull N> container) {
return childIdentifier(createStep(container));
}
/**
* Create an InstanceIdentifier for a child list item. This method is a more efficient equivalent to
* {@code builder().child(listItem, listKey).build()}.
*
* @param listItem List to append
* @param listKey List key
* @param List type
* @param Key type
* @return An InstanceIdentifier.
* @throws NullPointerException if any argument is null
*/
@SuppressWarnings("unchecked")
public final & ChildOf super T>, K extends Key>
@NonNull KeyedInstanceIdentifier child(final Class<@NonNull N> listItem, final K listKey) {
return (KeyedInstanceIdentifier) childIdentifier(new KeyStep<>(listItem, listKey));
}
/**
* Create an InstanceIdentifier for a child container. This method is a more efficient equivalent to
* {@code builder().child(caze, container).build()}.
*
* @param caze Choice case class
* @param container Container to append
* @param Case type
* @param Container type
* @return An InstanceIdentifier.
* @throws NullPointerException if any argument is null
*/
// FIXME: add a proper caller
public final & DataObject, N extends ChildOf super C>>
@NonNull InstanceIdentifier child(final Class<@NonNull C> caze, final Class<@NonNull N> container) {
return childIdentifier(createStep(caze, container));
}
/**
* Create an InstanceIdentifier for a child list item. This method is a more efficient equivalent to
* {@code builder().child(caze, listItem, listKey).build()}.
*
* @param caze Choice case class
* @param listItem List to append
* @param listKey List key
* @param Case type
* @param List type
* @param Key type
* @return An InstanceIdentifier.
* @throws NullPointerException if any argument is null
*/
// FIXME: add a proper caller
@SuppressWarnings("unchecked")
public final & DataObject, K extends Key,
N extends KeyAware & ChildOf super C>> @NonNull KeyedInstanceIdentifier child(
final Class<@NonNull C> caze, final Class<@NonNull N> listItem, final K listKey) {
return (KeyedInstanceIdentifier) childIdentifier(new KeyStep<>(listItem, requireNonNull(caze), listKey));
}
/**
* Create an InstanceIdentifier for a child augmentation. This method is a more efficient equivalent to
* {@code builder().augmentation(container).build()}.
*
* @param container Container to append
* @param Container type
* @return An InstanceIdentifier.
* @throws NullPointerException if {@code container} is null
*/
public final > @NonNull InstanceIdentifier augmentation(
final Class<@NonNull N> 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());
}
/**
* Create a builder rooted at this key.
*
* @return A builder instance
*/
// FIXME: rename this method to 'toBuilder()'
public @NonNull Builder builder() {
return new RegularBuilder<>(this);
}
/**
* 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 Builder}
* @throws NullPointerException if {@code container} is null
*/
public static > @NonNull Builder builder(
final Class container) {
return new RegularBuilder<>(createStep(container));
}
/**
* 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 Builder}
* @throws NullPointerException if any argument is null
*/
public static & DataObject, T extends ChildOf super C>>
@NonNull Builder builder(final Class caze, final Class container) {
return new RegularBuilder<>(createStep(caze, container));
}
/**
* 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 Builder}
* @throws NullPointerException if any argument is null
*/
public static & ChildOf extends DataRoot>,
K extends Key> @NonNull KeyedBuilder builder(final Class listItem,
final K listKey) {
return new KeyedBuilder<>(new KeyStep<>(listItem, listKey));
}
/**
* 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
* @param listKey key value
* @param Case type
* @param List type
* @param List key
* @return A new {@link Builder}
* @throws NullPointerException if any argument is null
*/
public static & DataObject,
N extends KeyAware & ChildOf super C>, K extends Key>
@NonNull KeyedBuilder builder(final Class caze, final Class listItem,
final K listKey) {
return new KeyedBuilder<>(new KeyStep<>(listItem, requireNonNull(caze), listKey));
}
public static >
@NonNull Builder builderOfInherited(final Class root, final Class container) {
// FIXME: we are losing root identity, hence namespaces may not work correctly
return new RegularBuilder<>(createStep(container));
}
public static & DataObject,
T extends ChildOf super C>>
@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 RegularBuilder<>(createStep(caze, container));
}
public static & ChildOf super R>,
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 KeyedBuilder<>(new KeyStep<>(listItem, listKey));
}
public static & DataObject,
N extends KeyAware & ChildOf super C>, 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 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);
}
/**
* 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.
*
* @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 null
*/
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 var hashBuilder = new HashCodeBuilder>();
boolean wildcard = false;
DataObjectStep> arg;
do {
arg = it.next();
// Non-null is implied by our callers
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 (!(arg instanceof ExactDataObjectStep)) {
wildcard = true;
}
} while (it.hasNext());
return trustedCreate(arg, pathArguments, hashBuilder.build(), wildcard);
}
/**
* 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.
*
*
* 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 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.
*/
@SuppressWarnings("unchecked")
public static @NonNull InstanceIdentifier unsafeOf(
final List extends DataObjectStep>> pathArguments) {
return (InstanceIdentifier) internalCreate(ImmutableList.copyOf(pathArguments));
}
/**
* Create an instance identifier for a very specific object type.
*
*
* For example
*
* new InstanceIdentifier(Nodes.class)
*
* would create an InstanceIdentifier for an object of type Nodes
*
* @param type The type of the object which this instance identifier represents
* @return InstanceIdentifier instance
*/
// FIXME: considering removing in favor of always going through a builder
@SuppressWarnings("unchecked")
public static > @NonNull InstanceIdentifier create(
final Class<@NonNull T> type) {
return (InstanceIdentifier) internalCreate(ImmutableList.of(createStep(type)));
}
/**
* Return the key associated with the last component of the specified identifier.
*
* @param id instance identifier
* @return key associated with the last component
* @throws IllegalArgumentException if the supplied identifier type cannot have a key.
* @throws NullPointerException if id is null.
*/
// FIXME: reconsider naming and design of this method
public static & DataObject, K extends Key> K keyOf(
final InstanceIdentifier id) {
requireNonNull(id);
checkArgument(id instanceof KeyedInstanceIdentifier, "%s does not have a key", id);
@SuppressWarnings("unchecked")
final K ret = ((KeyedInstanceIdentifier)id).getKey();
return ret;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
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);
}
}
@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.
*/
final @NonNull Class type() {
return type;
}
/**
* 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 case class or {@code null}
*/
Class extends DataObject> caseType() {
return null;
}
@Nullable Object key() {
return null;
}
@Override
public final int hashCode() {
return Objects.hash(type, caseType(), key());
}
@Override
public final boolean equals(final Object obj) {
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 AbstractPathArgument> arg) {
final int cmp = compareClasses(type, arg.type());
if (cmp != 0) {
return cmp;
}
final var caseType = caseType();
final var argCaseType = arg.caseType();
if (caseType == null) {
return argCaseType == null ? 1 : -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();
}
/**
* An Item represents an object that probably is only one of it's kind. For example a Nodes object is only one of
* a kind. In YANG terms this would probably represent a container.
*
* @param Item type
*/
@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);
}
@Override
@SuppressWarnings({ "rawtypes", "unchecked" })
final DataObjectStep> toStep() {
return createStep((Class) caseType(), type());
}
@Override
public String toString() {
return type().getName();
}
}
/**
* An IdentifiableItem represents a object that is usually present in a collection and can be identified uniquely
* by a key. In YANG terms this would probably represent an item in a list.
*
* @param An object that is identifiable by an identifier
* @param The identifier of the object
*/
@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;
IdentifiableItem(final Class type, final T key) {
super(type);
this.key = requireNonNull(key, "Key may not be null.");
}
/**
* Return the data object type backing this PathArgument.
*
* @return Data object type.
*/
@Override
final @NonNull T key() {
return key;
}
@Override
final KeyStep, ?> toStep() {
return new KeyStep<>(type(), caseType(), key);
}
@Override
public String toString() {
return type().getName() + "[key=" + key + "]";
}
}
@Deprecated(since = "13.0.0", forRemoval = true)
private static final class CaseItem & DataObject, T extends ChildOf super C>>
extends Item {
@java.io.Serial
private static final long serialVersionUID = 1L;
private final Class caseType;
CaseItem(final Class caseType, final Class type) {
super(type);
this.caseType = requireNonNull(caseType);
}
@Override
Class caseType() {
return caseType;
}
}
@Deprecated(since = "13.0.0", forRemoval = true)
private static final class CaseIdentifiableItem & DataObject,
T extends ChildOf super C> & KeyAware, K extends Key> extends IdentifiableItem {
@java.io.Serial
private static final long serialVersionUID = 1L;
private final Class caseType;
CaseIdentifiableItem(final Class caseType, final Class type, final K key) {
super(type, key);
this.caseType = requireNonNull(caseType);
}
@Override
Class caseType() {
return caseType;
}
}
/**
* 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 extends DataObjectStep>> 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
* example
*
* InstanceIdentifier.builder().child(Nodes.class).build();
*
*
*
* 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 Container type
* @return this builder
* @throws NullPointerException if {@code container} is null
*/
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
* 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 Case type
* @param Container type
* @return this builder
* @throws NullPointerException if {@code container} is null
*/
public final & DataObject, N extends ChildOf super C>> 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
* 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 List type
* @param Key type
* @return this builder
* @throws NullPointerException if any argument is null
*/
public final & ChildOf super T>, 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
* 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 Case type
* @param List type
* @param Key type
* @return this builder
* @throws NullPointerException if any argument is null
*/
public final & DataObject, K extends Key,
N extends KeyAware & ChildOf super C>> KeyedBuilder child(final Class caze,
final Class listItem, final K listKey) {
return append(new KeyStep<>(listItem, requireNonNull(caze), listKey));
}
/**
* Build the instance identifier.
*
* @return Resulting {@link InstanceIdentifier}.
*/
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 {@link KeyedInstanceIdentifier}.
*/
@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 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);
}
}
}