import static java.util.Objects.requireNonNull;
import com.google.common.annotations.Beta;
+import com.google.common.base.VerifyException;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.Serializable;
import java.lang.reflect.Array;
+import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import org.opendaylight.yangtools.concepts.Path;
import org.opendaylight.yangtools.util.HashCodeBuilder;
import org.opendaylight.yangtools.util.ImmutableOffsetMap;
-import org.opendaylight.yangtools.util.SharedSingletonMap;
+import org.opendaylight.yangtools.util.SingletonSet;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.common.QNameModule;
import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
* Unique identifier of a particular node instance in the data tree.
*
* <p>
- * Java representation of YANG Built-in type <code>instance-identifier</code>,
- * which conceptually is XPath expression minimized to uniquely identify element
- * in data tree which conforms to constraints maintained by YANG Model,
+ * Java representation of YANG Built-in type {@code instance-identifier}, 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.
+ * Constraints put in YANG specification on instance-identifier allowed it to be effectively represented in Java and its
+ * evaluation does not require a 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.
+ * <h2>Path Arguments</h2>
+ * 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.
*
* <ul>
- * <li>{@link NodeIdentifier} - Identifier of node, which has cardinality
- * <code>0..1</code> in particular subtree in data tree.</li>
- * <li>{@link NodeIdentifierWithPredicates} - Identifier of node (list item),
- * which has cardinality <code>0..n</code>.</li>
- * <li>{@link NodeWithValue} - Identifier of instance <code>leaf</code> node or
- * <code>leaf-list</code> node.</li>
- * <li>{@link AugmentationIdentifier} - Identifier of instance of
- * <code>augmentation</code> node.</li>
+ * <li>{@link NodeIdentifier} - Identifier of node, which has cardinality {@code 0..1} in particular subtree in data
+ * tree</li>
+ * <li>{@link NodeIdentifierWithPredicates} - Identifier of node (list item), which has cardinality {@code 0..n}</li>
+ * <li>{@link NodeWithValue} - Identifier of instance {@code leaf} node or {@code leaf-list} node</li>
+ * <li>{@link AugmentationIdentifier} - Identifier of instance of {@code augmentation} node</li>
* </ul>
*
* @see <a href="http://tools.ietf.org/html/rfc6020#section-9.13">RFC6020</a>
*/
-// FIXME: 4.0.0: this concept needs to be moved to yang-common, as parser components need the ability to refer
+// FIXME: 7.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.
-// FIXME: FixedYangInstanceIdentifier needs YangInstanceIdentifier initialized, but that includes initializing
-// this field. Figure out a way out of this pickle.
-@SuppressFBWarnings("IC_SUPERCLASS_USES_SUBCLASS_DURING_INITIALIZATION")
public abstract class YangInstanceIdentifier implements Path<YangInstanceIdentifier>, Immutable, Serializable {
- /**
- * An empty {@link YangInstanceIdentifier}. It corresponds to the path of the conceptual root of the YANG namespace.
- */
- public static final @NonNull YangInstanceIdentifier EMPTY = FixedYangInstanceIdentifier.EMPTY_INSTANCE;
-
private static final AtomicReferenceFieldUpdater<YangInstanceIdentifier, String> TOSTRINGCACHE_UPDATER =
AtomicReferenceFieldUpdater.newUpdater(YangInstanceIdentifier.class, String.class, "toStringCache");
private static final long serialVersionUID = 4L;
this.hash = hash;
}
+ /**
+ * Return An empty {@link YangInstanceIdentifier}. It corresponds to the path of the conceptual root of the YANG
+ * namespace.
+ *
+ * @return An empty YangInstanceIdentifier
+ */
+ public static @NonNull YangInstanceIdentifier empty() {
+ return FixedYangInstanceIdentifier.EMPTY_INSTANCE;
+ }
+
abstract @NonNull YangInstanceIdentifier createRelativeIdentifier(int skipFromRoot);
abstract @Nullable Collection<PathArgument> tryPathArguments();
/**
* Check if this instance identifier has empty path arguments, e.g. it is
- * empty and corresponds to {@link #EMPTY}.
+ * empty and corresponds to {@link #empty()}.
*
* @return True if this instance identifier is empty, false otherwise.
*/
*
* @return A optimized equivalent instance.
*/
- @Beta
public abstract @NonNull YangInstanceIdentifier toOptimized();
/**
* Return the conceptual parent {@link YangInstanceIdentifier}, which has
* one item less in {@link #getPathArguments()}.
*
- * @return Parent {@link YangInstanceIdentifier}, or null if this object is {@link #EMPTY}.
+ * @return Parent {@link YangInstanceIdentifier}, or null if this object is {@link #empty()}.
*/
public abstract @Nullable YangInstanceIdentifier getParent();
+ /**
+ * Return the conceptual parent {@link YangInstanceIdentifier}, which has one item less in
+ * {@link #getPathArguments()}.
+ *
+ * @return Parent {@link YangInstanceIdentifier}
+ * @throws VerifyException if this object is {@link #empty()}.
+ */
+ public abstract @NonNull YangInstanceIdentifier coerceParent();
+
/**
* Return the ancestor {@link YangInstanceIdentifier} with a particular depth, e.g. number of path arguments.
*
public static @NonNull YangInstanceIdentifier create(final Iterable<? extends PathArgument> path) {
if (Iterables.isEmpty(path)) {
- return EMPTY;
+ return empty();
}
final HashCodeBuilder<PathArgument> hash = new HashCodeBuilder<>();
return FixedYangInstanceIdentifier.create(path, hash.build());
}
+ public static @NonNull YangInstanceIdentifier create(final PathArgument pathArgument) {
+ return new FixedYangInstanceIdentifier(ImmutableList.of(pathArgument),
+ HashCodeBuilder.nextHashCode(1, pathArgument));
+ }
+
public static @NonNull YangInstanceIdentifier create(final PathArgument... path) {
// We are forcing a copy, since we cannot trust the user
return create(Arrays.asList(path));
*/
public Optional<YangInstanceIdentifier> relativeTo(final YangInstanceIdentifier ancestor) {
if (this == ancestor) {
- return Optional.of(EMPTY);
+ return Optional.of(empty());
}
if (ancestor.isEmpty()) {
return Optional.of(this);
return Optional.of(this);
}
if (!lit.hasNext()) {
- return Optional.of(EMPTY);
+ return Optional.of(empty());
}
return Optional.of(createRelativeIdentifier(common));
}
protected int hashCodeImpl() {
- return 31 + getNodeType().hashCode();
+ return nodeType.hashCode();
}
@Override
* Composite path argument identifying a {@link org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode} leaf
* overall data tree.
*/
- public static final class NodeIdentifierWithPredicates extends AbstractPathArgument {
- private static final long serialVersionUID = -4787195606494761540L;
+ public abstract static class NodeIdentifierWithPredicates extends AbstractPathArgument {
+ @Beta
+ public static final class Singleton extends NodeIdentifierWithPredicates {
+ private static final long serialVersionUID = 1L;
+
+ private final @NonNull QName key;
+ private final @NonNull Object value;
+
+ Singleton(final QName node, final QName key, final Object value) {
+ super(node);
+ this.key = requireNonNull(key);
+ this.value = requireNonNull(value);
+ }
+
+ @Override
+ public SingletonSet<Entry<QName, Object>> entrySet() {
+ return SingletonSet.of(singleEntry());
+ }
- private final @NonNull Map<QName, Object> keyValues;
+ @Override
+ public SingletonSet<QName> keySet() {
+ return SingletonSet.of(key);
+ }
+
+ @Override
+ public boolean containsKey(final QName qname) {
+ return key.equals(requireNonNull(qname));
+ }
+
+ @Override
+ public SingletonSet<Object> values() {
+ return SingletonSet.of(value);
+ }
+
+ @Override
+ public int size() {
+ return 1;
+ }
- // Exposed for NIPv1
- NodeIdentifierWithPredicates(final Map<QName, Object> keyValues, final QName node) {
+ @Override
+ public ImmutableMap<QName, Object> asMap() {
+ return ImmutableMap.of(key, value);
+ }
+
+ /**
+ * Return the single entry contained in this object. This is equivalent to
+ * {@code entrySet().iterator().next()}.
+ *
+ * @return A single entry.
+ */
+ public @NonNull Entry<QName, Object> singleEntry() {
+ return new SimpleImmutableEntry<>(key, value);
+ }
+
+ @Override
+ boolean equalMapping(final NodeIdentifierWithPredicates other) {
+ final Singleton single = (Singleton) other;
+ return key.equals(single.key) && Objects.deepEquals(value, single.value);
+ }
+
+ @Override
+ Object keyValue(final QName qname) {
+ return key.equals(qname) ? value : null;
+ }
+ }
+
+ private static final class Regular extends NodeIdentifierWithPredicates {
+ private static final long serialVersionUID = 1L;
+
+ private final @NonNull Map<QName, Object> keyValues;
+
+ Regular(final QName node, final Map<QName, Object> keyValues) {
+ super(node);
+ this.keyValues = requireNonNull(keyValues);
+ }
+
+ @Override
+ public Set<Entry<QName, Object>> entrySet() {
+ return keyValues.entrySet();
+ }
+
+ @Override
+ public Set<QName> keySet() {
+ return keyValues.keySet();
+ }
+
+ @Override
+ public boolean containsKey(final QName qname) {
+ return keyValues.containsKey(requireNonNull(qname));
+ }
+
+ @Override
+ public Collection<Object> values() {
+ return keyValues.values();
+ }
+
+ @Override
+ public int size() {
+ return keyValues.size();
+ }
+
+ @Override
+ public Map<QName, Object> asMap() {
+ return keyValues;
+ }
+
+ @Override
+ Object keyValue(final QName qname) {
+ return keyValues.get(qname);
+ }
+
+ @Override
+ boolean equalMapping(final NodeIdentifierWithPredicates other) {
+ final Map<QName, Object> otherKeyValues = ((Regular) other).keyValues;
+ // TODO: benchmark to see if just calling equals() on the two maps is not faster
+ if (keyValues == otherKeyValues) {
+ return true;
+ }
+ if (keyValues.size() != otherKeyValues.size()) {
+ return false;
+ }
+
+ for (Entry<QName, Object> entry : entrySet()) {
+ final Object otherValue = otherKeyValues.get(entry.getKey());
+ if (otherValue == null || !Objects.deepEquals(entry.getValue(), otherValue)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ }
+
+ private static final long serialVersionUID = -4787195606494761540L;
+
+ NodeIdentifierWithPredicates(final QName node) {
super(node);
- this.keyValues = requireNonNull(keyValues);
}
public static @NonNull NodeIdentifierWithPredicates of(final QName node) {
- return new NodeIdentifierWithPredicates(ImmutableMap.of(), node);
+ return new Regular(node, ImmutableMap.of());
}
- public static @NonNull NodeIdentifierWithPredicates of(final QName node, final Map<QName, Object> keyValues) {
- // Retains ImmutableMap for empty maps. For larger sizes uses a shared key set.
- return new NodeIdentifierWithPredicates(ImmutableOffsetMap.unorderedCopyOf(keyValues), node);
+ public static @NonNull NodeIdentifierWithPredicates of(final QName node, final QName key, final Object value) {
+ return new Singleton(node, key, value);
}
- public static @NonNull NodeIdentifierWithPredicates of(final QName node,
- final ImmutableOffsetMap<QName, Object> keyValues) {
- return new NodeIdentifierWithPredicates(keyValues, node);
+ public static @NonNull NodeIdentifierWithPredicates of(final QName node, final Entry<QName, Object> entry) {
+ return of(node, entry.getKey(), entry.getValue());
+ }
+
+ public static @NonNull NodeIdentifierWithPredicates of(final QName node, final Map<QName, Object> keyValues) {
+ return keyValues.size() == 1 ? of(keyValues, node)
+ // Retains ImmutableMap for empty maps. For larger sizes uses a shared key set.
+ : new Regular(node, ImmutableOffsetMap.unorderedCopyOf(keyValues));
}
public static @NonNull NodeIdentifierWithPredicates of(final QName node,
- final SharedSingletonMap<QName, Object> keyValues) {
- return new NodeIdentifierWithPredicates(keyValues, node);
+ final ImmutableOffsetMap<QName, Object> keyValues) {
+ return keyValues.size() == 1 ? of(keyValues, node) : new Regular(node, keyValues);
}
- public static @NonNull NodeIdentifierWithPredicates of(final QName node, final QName key, final Object value) {
- return of(node, SharedSingletonMap.unorderedOf(key, value));
+ private static @NonNull NodeIdentifierWithPredicates of(final Map<QName, Object> keyValues, final QName node) {
+ return of(node, keyValues.entrySet().iterator().next());
}
/**
*
* @return Predicate set.
*/
- @Beta
- public @NonNull Set<Entry<QName, Object>> entrySet() {
- return keyValues.entrySet();
- }
+ public abstract @NonNull Set<Entry<QName, Object>> entrySet();
/**
* Return the predicate key in the iteration order of {@link #entrySet()}.
*
* @return Predicate values.
*/
- @Beta
- public @NonNull Set<QName> keySet() {
- return keyValues.keySet();
- }
+ public abstract @NonNull Set<QName> keySet();
+
+ /**
+ * Determine whether a particular predicate key is present.
+ *
+ * @param key Predicate key
+ * @return True if the predicate is present, false otherwise
+ * @throws NullPointerException if {@code key} is null
+ */
+ public abstract boolean containsKey(QName key);
/**
* Return the predicate values in the iteration order of {@link #entrySet()}.
*
* @return Predicate values.
*/
- @Beta
- public @NonNull Collection<Object> values() {
- return keyValues.values();
- }
+ public abstract @NonNull Collection<Object> values();
@Beta
- public @Nullable Object getValue(final QName key) {
- return keyValues.get(requireNonNull(key));
+ public final @Nullable Object getValue(final QName key) {
+ return keyValue(requireNonNull(key));
}
@Beta
- public <T> @Nullable T getValue(final QName key, final Class<T> valueClass) {
+ public final <T> @Nullable T getValue(final QName key, final Class<T> valueClass) {
return valueClass.cast(getValue(key));
}
/**
* Return the number of predicates present.
*
- * @return Thee number of predicates present.
+ * @return The number of predicates present.
*/
- @Beta
- public int size() {
- return keyValues.size();
- }
+ public abstract int size();
/**
* A Map-like view of this identifier's predicates. The view is expected to be stable and effectively-immutable.
*
* @return Map of predicates.
- * @deprecated This method in a provisional one. It can be used in the code base, but users requiring it should
- * contact <a href="mailto:yangtools-dev@lists.opendaylight.org">yangtools-dev</a> for migration
- * guidelines. Callers are strongly encouraged to explore {@link #entrySet()}, {@link #size()},
- * {@link #values()} and {@link #keySet()} as an alternative.
*/
@Beta
- @Deprecated
- // FIXME: 4.0.0: evaluate the real usefulness of this. The problem here is Map.hashCode() and Map.equals(),
- // which limits our options.
- public @NonNull Map<QName, Object> asMap() {
- return keyValues;
- }
+ public abstract @NonNull Map<QName, Object> asMap();
@Override
- protected int hashCodeImpl() {
- final int prime = 31;
- int result = super.hashCodeImpl();
- result = prime * result;
-
- for (Entry<QName, Object> entry : keyValues.entrySet()) {
- // FIXME: 4.0.0: key and value expected to be non-null here
- result += Objects.hashCode(entry.getKey()) + YangInstanceIdentifier.hashCode(entry.getValue());
+ protected final int hashCodeImpl() {
+ int result = 31 * super.hashCodeImpl();
+ for (Entry<QName, Object> entry : entrySet()) {
+ result += entry.getKey().hashCode() + YangInstanceIdentifier.hashCode(entry.getValue());
}
return result;
}
@Override
@SuppressWarnings("checkstyle:equalsHashCode")
- public boolean equals(final Object obj) {
- if (!super.equals(obj)) {
- return false;
- }
-
- final Map<QName, Object> otherKeyValues = ((NodeIdentifierWithPredicates) obj).keyValues;
-
- // TODO: benchmark to see if just calling equals() on the two maps is not faster
- if (keyValues == otherKeyValues) {
- return true;
- }
- if (keyValues.size() != otherKeyValues.size()) {
- return false;
- }
+ public final boolean equals(final Object obj) {
+ return super.equals(obj) && equalMapping((NodeIdentifierWithPredicates) obj);
+ }
- for (Entry<QName, Object> entry : keyValues.entrySet()) {
- if (!otherKeyValues.containsKey(entry.getKey())
- || !Objects.deepEquals(entry.getValue(), otherKeyValues.get(entry.getKey()))) {
+ abstract boolean equalMapping(NodeIdentifierWithPredicates other);
- return false;
- }
- }
-
- return true;
- }
+ abstract @Nullable Object keyValue(@NonNull QName qname);
@Override
- public String toString() {
- return super.toString() + '[' + keyValues + ']';
+ public final String toString() {
+ return super.toString() + '[' + asMap() + ']';
}
@Override
- public String toRelativeString(final PathArgument previous) {
- return super.toRelativeString(previous) + '[' + keyValues + ']';
+ public final String toRelativeString(final PathArgument previous) {
+ return super.toRelativeString(previous) + '[' + asMap() + ']';
}
@Override
- Object writeReplace() {
+ final Object writeReplace() {
return new NIPv2(this);
}
}
public static final class NodeWithValue<T> extends AbstractPathArgument {
private static final long serialVersionUID = -3637456085341738431L;
- private final T value;
+ private final @NonNull T value;
public NodeWithValue(final QName node, final T value) {
super(node);
- this.value = value;
+ this.value = requireNonNull(value);
}
- public T getValue() {
+ public @NonNull T getValue() {
return value;
}
@Override
protected int hashCodeImpl() {
- final int prime = 31;
- int result = super.hashCodeImpl();
- result = prime * result + YangInstanceIdentifier.hashCode(value);
- return result;
+ return 31 * super.hashCodeImpl() + YangInstanceIdentifier.hashCode(value);
}
@Override
* @return this builder
* @throws NullPointerException if any of the arguments is null
*/
- @Beta
@NonNull InstanceIdentifierBuilder append(Collection<? extends PathArgument> args);
/**
* @return this builder
* @throws NullPointerException if any of the arguments is null
*/
- @Beta
default @NonNull InstanceIdentifierBuilder append(final PathArgument... args) {
return append(Arrays.asList(args));
}