*/
package org.opendaylight.yangtools.yang.data.api;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Verify.verify;
+import static java.util.Objects.requireNonNull;
+
import com.google.common.annotations.Beta;
-import com.google.common.base.Optional;
-import com.google.common.base.Preconditions;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
import java.io.Serializable;
import java.lang.reflect.Array;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
+import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import javax.annotation.Nonnull;
* which conceptually is XPath expression minimized to uniquely identify element
* in data tree which conforms to constraints maintained by YANG Model,
* effectively this makes Instance Identifier a path to element in data tree.
+ *
* <p>
* Constraints put in YANG specification on instance-identifier allowed it to be
* effectively represented in Java and it's evaluation does not require
* full-blown XPath processor.
+ *
* <p>
* <h3>Path Arguments</h3>
* Path to the node represented in instance identifier consists of
* {@link PathArgument} which carries necessary information to uniquely identify
* node on particular level in the subtree.
- * <p>
+ *
* <ul>
* <li>{@link NodeIdentifier} - Identifier of node, which has cardinality
* <code>0..1</code> in particular subtree in data tree.</li>
* <code>augmentation</code> node.</li>
* </ul>
*
- *
* @see <a href="http://tools.ietf.org/html/rfc6020#section-9.13">RFC6020</a>
*/
+// FIXME: 3.0.0: this concept needs to be moved to yang-common, as parser components need the ability to refer
+// to data nodes -- most notably XPath expressions and {@code default} statement arguments need to be able
+// to represent these.
public abstract class YangInstanceIdentifier implements Path<YangInstanceIdentifier>, Immutable, Serializable {
/**
* An empty {@link YangInstanceIdentifier}. It corresponds to the path of the conceptual
}
@Nonnull abstract YangInstanceIdentifier createRelativeIdentifier(int skipFromRoot);
+
@Nonnull abstract Collection<PathArgument> tryPathArguments();
+
@Nonnull abstract Collection<PathArgument> tryReversePathArguments();
/**
* Return the conceptual parent {@link YangInstanceIdentifier}, which has
* one item less in {@link #getPathArguments()}.
*
- * @return Parent {@link YangInstanceIdentifier}, or null if this is object is {@link #EMPTY}.
+ * @return Parent {@link YangInstanceIdentifier}, or null if this object is {@link #EMPTY}.
*/
@Nullable public abstract YangInstanceIdentifier getParent();
+ /**
+ * Return the ancestor {@link YangInstanceIdentifier} with a particular depth, e.g. number of path arguments.
+ *
+ * @param depth Ancestor depth
+ * @return Ancestor {@link YangInstanceIdentifier}
+ * @throws IllegalArgumentException if the specified depth is negative or is greater than the depth of this object.
+ */
+ @Nonnull public abstract YangInstanceIdentifier getAncestor(int depth);
+
/**
* Returns an ordered iteration of path arguments.
*
return create(Arrays.asList(path));
}
- @Override
- public final int hashCode() {
- /*
- * The caching is safe, since the object contract requires
- * immutability of the object and all objects referenced from this
- * object.
- * Used lists, maps are immutable. Path Arguments (elements) are also
- * immutable, since the PathArgument contract requires immutability.
- */
- return hash;
- }
-
boolean pathArgumentsEqual(final YangInstanceIdentifier other) {
return Iterables.elementsEqual(getPathArguments(), other.getPathArguments());
}
}
/**
- * Constructs a new Instance Identifier with new {@link NodeIdentifier} added to the end of path arguments
+ * Constructs a new Instance Identifier with new {@link NodeIdentifier} added to the end of path arguments.
*
* @param name QName of {@link NodeIdentifier}
* @return Instance Identifier with additional path argument added to the end.
}
/**
- *
- * Constructs a new Instance Identifier with new {@link PathArgument} added to the end of path arguments
+ * Constructs a new Instance Identifier with new {@link PathArgument} added to the end of path arguments.
*
* @param arg Path argument which should be added to the end
* @return Instance Identifier with additional path argument added to the end.
* the specified parent is not in fact an ancestor of this object.
*/
public Optional<YangInstanceIdentifier> relativeTo(final YangInstanceIdentifier ancestor) {
- final Iterator<?> lit = getPathArguments().iterator();
- final Iterator<?> oit = ancestor.getPathArguments().iterator();
+ if (this == ancestor) {
+ return Optional.of(EMPTY);
+ }
+ if (ancestor.isEmpty()) {
+ return Optional.of(this);
+ }
+
+ final Iterator<PathArgument> lit = getPathArguments().iterator();
+ final Iterator<PathArgument> oit = ancestor.getPathArguments().iterator();
int common = 0;
while (oit.hasNext()) {
// Ancestor is not really an ancestor
if (!lit.hasNext() || !lit.next().equals(oit.next())) {
- return Optional.absent();
+ return Optional.empty();
}
++common;
}
@Override
- public final boolean contains(final YangInstanceIdentifier other) {
- Preconditions.checkArgument(other != null, "other should not be null");
+ public final boolean contains(@Nonnull final YangInstanceIdentifier other) {
+ if (this == other) {
+ return true;
+ }
- final Iterator<?> lit = getPathArguments().iterator();
- final Iterator<?> oit = other.getPathArguments().iterator();
+ checkArgument(other != null, "other should not be null");
+ final Iterator<PathArgument> lit = getPathArguments().iterator();
+ final Iterator<PathArgument> oit = other.getPathArguments().iterator();
while (lit.hasNext()) {
if (!oit.hasNext()) {
return ret;
}
+ @Override
+ public final int hashCode() {
+ /*
+ * The caching is safe, since the object contract requires
+ * immutability of the object and all objects referenced from this
+ * object.
+ * Used lists, maps are immutable. Path Arguments (elements) are also
+ * immutable, since the PathArgument contract requires immutability.
+ */
+ return hash;
+ }
+
private static int hashCode(final Object value) {
if (value == null) {
return 0;
// Static factories & helpers
/**
- * Returns a new InstanceIdentifier with only one path argument of type {@link NodeIdentifier} with supplied QName
+ * Returns a new InstanceIdentifier with only one path argument of type {@link NodeIdentifier} with supplied
+ * QName.
*
* @param name QName of first node identifier
* @return Instance Identifier with only one path argument of type {@link NodeIdentifier}
}
/**
- *
* Returns new builder for InstanceIdentifier with path arguments copied from original instance identifier.
*
* @param origin InstanceIdentifier from which path arguments are copied.
}
/**
- * Path argument / component of InstanceIdentifier
- *
+ * Path argument / component of InstanceIdentifier.
* Path argument uniquely identifies node in data tree on particular
* level.
+ *
* <p>
* This interface itself is used as common parent for actual
* path arguments types and should not be implemented by user code.
+ *
* <p>
* Path arguments SHOULD contain only minimum of information
* required to uniquely identify node on particular subtree level.
*
+ * <p>
* For actual path arguments types see:
* <ul>
* <li>{@link NodeIdentifier} - Identifier of container or leaf
* If applicable returns unique QName of data node as defined in YANG
* Schema.
*
+ * <p>
* This method may return null, if the corresponding schema node, does
* not have QName associated, such as in cases of augmentations.
*
String toRelativeString(PathArgument previous);
}
- private static abstract class AbstractPathArgument implements PathArgument {
+ private abstract static class AbstractPathArgument implements PathArgument {
private static final long serialVersionUID = -4546547994250849340L;
private final QName nodeType;
private transient int hashValue;
- private volatile transient boolean hashGuard = false;
+ private transient volatile boolean hashGuard = false;
protected AbstractPathArgument(final QName nodeType) {
- this.nodeType = Preconditions.checkNotNull(nodeType);
+ this.nodeType = requireNonNull(nodeType);
}
@Override
}
@Override
- public int compareTo(final PathArgument o) {
+ @SuppressWarnings("checkstyle:parameterName")
+ public int compareTo(@Nonnull final PathArgument o) {
return nodeType.compareTo(o.getNodeType());
}
@Override
public String toRelativeString(final PathArgument previous) {
if (previous instanceof AbstractPathArgument) {
- final QNameModule mod = ((AbstractPathArgument)previous).getNodeType().getModule();
+ final QNameModule mod = previous.getNodeType().getModule();
if (getNodeType().getModule().equals(mod)) {
return getNodeType().getLocalName();
}
private static final LoadingCache<QName, NodeIdentifier> CACHE = CacheBuilder.newBuilder().weakValues()
.build(new CacheLoader<QName, NodeIdentifier>() {
@Override
- public NodeIdentifier load(final QName key) {
+ public NodeIdentifier load(@Nonnull final QName key) {
return new NodeIdentifier(key);
}
});
public NodeIdentifierWithPredicates(final QName node, final Map<QName, Object> keyValues) {
super(node);
// Retains ImmutableMap for empty maps. For larger sizes uses a shared key set.
- this.keyValues = ImmutableOffsetMap.copyOf(keyValues);
+ this.keyValues = ImmutableOffsetMap.unorderedCopyOf(keyValues);
+ }
+
+ public NodeIdentifierWithPredicates(final QName node, final ImmutableOffsetMap<QName, Object> keyValues) {
+ super(node);
+ this.keyValues = requireNonNull(keyValues);
+ }
+
+ public NodeIdentifierWithPredicates(final QName node, final SharedSingletonMap<QName, Object> keyValues) {
+ super(node);
+ this.keyValues = requireNonNull(keyValues);
}
public NodeIdentifierWithPredicates(final QName node, final QName key, final Object value) {
- this(node, SharedSingletonMap.of(key, value));
+ this(node, SharedSingletonMap.unorderedOf(key, value));
}
public Map<QName, Object> getKeyValues() {
}
@Override
+ @SuppressWarnings("checkstyle:equalsHashCode")
public boolean equals(final Object obj) {
if (!super.equals(obj)) {
return false;
* Simple path argument identifying a {@link LeafSetEntryNode} leaf
* overall data tree.
*/
- public static final class NodeWithValue extends AbstractPathArgument {
+ public static final class NodeWithValue<T> extends AbstractPathArgument {
private static final long serialVersionUID = -3637456085341738431L;
- private final Object value;
+ private final T value;
- public NodeWithValue(final QName node, final Object value) {
+ public NodeWithValue(final QName node, final T value) {
super(node);
this.value = value;
}
- public Object getValue() {
+ public T getValue() {
return value;
}
protected int hashCodeImpl() {
final int prime = 31;
int result = super.hashCodeImpl();
- result = prime * result + ((value == null) ? 0 : YangInstanceIdentifier.hashCode(value));
+ result = prime * result + YangInstanceIdentifier.hashCode(value);
return result;
}
@Override
+ @SuppressWarnings("checkstyle:equalsHashCode")
public boolean equals(final Object obj) {
if (!super.equals(obj)) {
return false;
}
- final NodeWithValue other = (NodeWithValue) obj;
+ final NodeWithValue<?> other = (NodeWithValue<?>) obj;
return Objects.deepEquals(value, other.value);
}
}
/**
- * Composite path argument identifying a {@link org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode} node in
- * particular subtree.
+ * Composite path argument identifying a {@link org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode}
+ * node in particular subtree.
*
+ * <p>
* Augmentation is uniquely identified by set of all possible child nodes.
* This is possible
* to identify instance of augmentation,
* statement must not add multiple nodes from same namespace
* / module to the target node.
*
- *
* @see <a href="http://tools.ietf.org/html/rfc6020#section-7.15">RFC6020</a>
*/
public static final class AugmentationIdentifier implements PathArgument {
}
/**
- *
* Construct new augmentation identifier using supplied set of possible
- * child nodes
+ * child nodes.
*
* @param childNames
* Set of possible child nodes.
}
/**
- * Returns set of all possible child nodes
+ * Returns set of all possible child nodes.
*
* @return set of all possible child nodes.
*/
@Override
public String toString() {
- final StringBuilder sb = new StringBuilder("AugmentationIdentifier{");
- sb.append("childNames=").append(childNames).append('}');
- return sb.toString();
+ return "AugmentationIdentifier{" + "childNames=" + childNames + '}';
}
@Override
}
@Override
- public boolean equals(final Object o) {
- if (this == o) {
+ public boolean equals(final Object obj) {
+ if (this == obj) {
return true;
}
- if (!(o instanceof AugmentationIdentifier)) {
+ if (!(obj instanceof AugmentationIdentifier)) {
return false;
}
- AugmentationIdentifier that = (AugmentationIdentifier) o;
+ AugmentationIdentifier that = (AugmentationIdentifier) obj;
return childNames.equals(that.childNames);
}
}
@Override
- public int compareTo(final PathArgument o) {
+ @SuppressWarnings("checkstyle:parameterName")
+ public int compareTo(@Nonnull final PathArgument o) {
if (!(o instanceof AugmentationIdentifier)) {
return -1;
}
int thisSize = childNames.size();
int otherSize = otherChildNames.size();
if (thisSize == otherSize) {
- Iterator<QName> otherIterator = otherChildNames.iterator();
- for (QName name : childNames) {
- int c = name.compareTo(otherIterator.next());
- if (c != 0) {
- return c;
- }
+ // Quick Set-based comparison
+ if (childNames.equals(otherChildNames)) {
+ return 0;
}
- return 0;
+
+ // We already know the sets are not equal, but have equal size, hence the sets differ in their elements,
+ // but potentially share a common set of elements. The most consistent way of comparing them is using
+ // total ordering defined by QName's compareTo. Hence convert both sets to lists ordered
+ // by QName.compareTo() and decide on the first differing element.
+ final List<QName> diff = new ArrayList<>(Sets.symmetricDifference(childNames, otherChildNames));
+ verify(!diff.isEmpty(), "Augmentation identifiers %s and %s report no difference", this, o);
+ diff.sort(QName::compareTo);
+ return childNames.contains(diff.get(0)) ? -1 : 1;
} else if (thisSize < otherSize) {
return 1;
} else {
}
/**
- * Fluent Builder of Instance Identifier instances
+ * Fluent Builder of Instance Identifier instances.
*/
public interface InstanceIdentifierBuilder extends Builder<YangInstanceIdentifier> {
/**
- * Adds a {@link PathArgument} to to path arguments of resulting instance identifier.
+ * Adds a {@link PathArgument} to path arguments of resulting instance identifier.
*
* @param arg A {@link PathArgument} to be added
* @return this builder
InstanceIdentifierBuilder node(QName nodeType);
/**
- * Adds {@link NodeIdentifierWithPredicates} with supplied QName and key values to path arguments of resulting instance identifier.
+ * Adds {@link NodeIdentifierWithPredicates} with supplied QName and key values to path arguments of resulting
+ * instance identifier.
*
* @param nodeType QName of {@link NodeIdentifierWithPredicates} which will be added
* @param keyValues Map of key components and their respective values for {@link NodeIdentifierWithPredicates}
InstanceIdentifierBuilder nodeWithKey(QName nodeType, QName key, Object value);
/**
+ * Adds a collection of {@link PathArgument}s to path arguments of resulting instance identifier.
*
- * Builds an {@link YangInstanceIdentifier} with path arguments from this builder
+ * @param args {@link PathArgument}s to be added
+ * @return this builder
+ * @throws NullPointerException if any of the arguments is null
+ */
+ @Beta
+ InstanceIdentifierBuilder append(Collection<? extends PathArgument> args);
+
+ /**
+ * Adds a collection of {@link PathArgument}s to path arguments of resulting instance identifier.
+ *
+ * @param args {@link PathArgument}s to be added
+ * @return this builder
+ * @throws NullPointerException if any of the arguments is null
+ */
+ @Beta
+ default InstanceIdentifierBuilder append(final PathArgument... args) {
+ return append(Arrays.asList(args));
+ }
+
+ /**
+ * Builds an {@link YangInstanceIdentifier} with path arguments from this builder.
*
* @return {@link YangInstanceIdentifier}
*/
@Override
YangInstanceIdentifier build();
-
- /*
- * @deprecated use #build()
- */
- @Deprecated
- YangInstanceIdentifier toInstance();
}
}