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.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;
*
* @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
+// FIXME: 4.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
/**
* An empty {@link YangInstanceIdentifier}. It corresponds to the path of the conceptual root of the YANG namespace.
*/
- public static final YangInstanceIdentifier EMPTY = FixedYangInstanceIdentifier.EMPTY_INSTANCE;
+ public static final @NonNull YangInstanceIdentifier EMPTY = FixedYangInstanceIdentifier.EMPTY_INSTANCE;
private static final AtomicReferenceFieldUpdater<YangInstanceIdentifier, String> TOSTRINGCACHE_UPDATER =
AtomicReferenceFieldUpdater.newUpdater(YangInstanceIdentifier.class, String.class, "toStringCache");
* @return A optimized equivalent instance.
*/
@Beta
- public abstract YangInstanceIdentifier toOptimized();
+ public abstract @NonNull YangInstanceIdentifier toOptimized();
/**
* Return the conceptual parent {@link YangInstanceIdentifier}, which has
*
* @return Immutable iteration of path arguments.
*/
- public abstract List<PathArgument> getPathArguments();
+ public abstract @NonNull List<PathArgument> getPathArguments();
/**
* Returns an iterable of path arguments in reverse order. This is useful
*
* @return Immutable iterable of path arguments in reverse order.
*/
- public abstract List<PathArgument> getReversePathArguments();
+ public abstract @NonNull List<PathArgument> getReversePathArguments();
/**
* Returns the last PathArgument. This is equivalent of iterating
*/
public abstract PathArgument getLastPathArgument();
- public static YangInstanceIdentifier create(final Iterable<? extends PathArgument> path) {
+ public static @NonNull YangInstanceIdentifier create(final Iterable<? extends PathArgument> path) {
if (Iterables.isEmpty(path)) {
return EMPTY;
}
return FixedYangInstanceIdentifier.create(path, hash.build());
}
- public static YangInstanceIdentifier create(final PathArgument... path) {
+ public static @NonNull YangInstanceIdentifier create(final PathArgument... path) {
// We are forcing a copy, since we cannot trust the user
return create(Arrays.asList(path));
}
* @return A {@link YangInstanceIdentifier} instance
* @throws NullPointerException if {@code pathTowardsRoot} or any of its members is null
*/
- public static YangInstanceIdentifier createReverse(final Deque<PathArgument> pathTowardsRoot) {
+ public static @NonNull YangInstanceIdentifier createReverse(final Deque<PathArgument> pathTowardsRoot) {
final ImmutableList.Builder<PathArgument> builder = ImmutableList.builderWithExpectedSize(
pathTowardsRoot.size());
pathTowardsRoot.descendingIterator().forEachRemaining(builder::add);
* @return A {@link YangInstanceIdentifier} instance
* @throws NullPointerException if {@code pathTowardsRoot} is null
*/
- public static <T> YangInstanceIdentifier createReverse(final Deque<? extends T> stackTowardsRoot,
+ public static <T> @NonNull YangInstanceIdentifier createReverse(final Deque<? extends T> stackTowardsRoot,
final Function<T, PathArgument> function) {
final ImmutableList.Builder<PathArgument> builder = ImmutableList.builderWithExpectedSize(
stackTowardsRoot.size());
* @param name QName of {@link NodeIdentifier}
* @return Instance Identifier with additional path argument added to the end.
*/
- public final YangInstanceIdentifier node(final QName name) {
+ public final @NonNull YangInstanceIdentifier node(final QName name) {
return node(new NodeIdentifier(name));
}
* @param arg Path argument which should be added to the end
* @return Instance Identifier with additional path argument added to the end.
*/
- public final YangInstanceIdentifier node(final PathArgument arg) {
+ public final @NonNull YangInstanceIdentifier node(final PathArgument arg) {
return new StackedYangInstanceIdentifier(this, arg, HashCodeBuilder.nextHashCode(hash, arg));
}
return hash;
}
+ @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD",
+ justification = "https://github.com/spotbugs/spotbugs/issues/811")
private static int hashCode(final Object value) {
if (value == null) {
return 0;
return Objects.hashCode(value);
}
+ final Object writeReplace() {
+ return new YIDv1(this);
+ }
+
// Static factories & helpers
/**
* @param name QName of first node identifier
* @return Instance Identifier with only one path argument of type {@link NodeIdentifier}
*/
- public static YangInstanceIdentifier of(final QName name) {
+ public static @NonNull YangInstanceIdentifier of(final QName name) {
return create(new NodeIdentifier(name));
}
*
* @return new builder for InstanceIdentifier with empty path arguments.
*/
- public static InstanceIdentifierBuilder builder() {
+ public static @NonNull InstanceIdentifierBuilder builder() {
return new YangInstanceIdentifierBuilder();
}
* @param origin InstanceIdentifier from which path arguments are copied.
* @return new builder for InstanceIdentifier with path arguments copied from original instance identifier.
*/
- public static InstanceIdentifierBuilder builder(final YangInstanceIdentifier origin) {
+ public static @NonNull InstanceIdentifierBuilder builder(final YangInstanceIdentifier origin) {
return new YangInstanceIdentifierBuilder(origin.getPathArguments(), origin.hashCode());
}
*/
public interface PathArgument extends Comparable<PathArgument>, Immutable, Serializable {
/**
- * 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.
+ * Returns unique QName of data node as defined in YANG Schema, if available.
*
* @return Node type
+ * @throws UnsupportedOperationException if node type is not applicable, for example in case of an augmentation.
*/
- QName getNodeType();
+ @NonNull QName getNodeType();
/**
* Return the string representation of this object for use in context
* @param previous Previous path argument
* @return String representation
*/
- String toRelativeString(PathArgument previous);
+ @NonNull String toRelativeString(PathArgument previous);
}
private abstract static class AbstractPathArgument implements PathArgument {
private static final long serialVersionUID = -4546547994250849340L;
- private final QName nodeType;
- private transient int hashValue;
- private transient volatile boolean hashGuard = false;
+ private final @NonNull QName nodeType;
+ private transient volatile int hashValue;
protected AbstractPathArgument(final QName nodeType) {
this.nodeType = requireNonNull(nodeType);
@Override
public final int hashCode() {
- if (!hashGuard) {
- hashValue = hashCodeImpl();
- hashGuard = true;
- }
-
- return hashValue;
+ int local;
+ return (local = hashValue) != 0 ? local : (hashValue = hashCodeImpl());
}
@Override
return getNodeType().toString();
}
+
+ abstract Object writeReplace();
}
/**
* @param node Node's QName
* @return A {@link NodeIdentifier}
*/
- public static NodeIdentifier create(final QName node) {
+ public static @NonNull NodeIdentifier create(final QName node) {
return CACHE.getUnchecked(node);
}
+
+ @Override
+ Object writeReplace() {
+ return new NIv1(this);
+ }
}
/**
* 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 {
+ private static final class Singleton extends NodeIdentifierWithPredicates {
+ private static final long serialVersionUID = 1L;
- private final Map<QName, Object> keyValues;
+ private final @NonNull QName key;
+ private final @NonNull Object value;
- public NodeIdentifierWithPredicates(final QName node) {
- super(node);
- this.keyValues = ImmutableMap.of();
+ 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(new SimpleImmutableEntry<>(key, value));
+ }
+
+ @Override
+ public SingletonSet<QName> keySet() {
+ return SingletonSet.of(key);
+ }
+
+ @Override
+ public SingletonSet<Object> values() {
+ return SingletonSet.of(value);
+ }
+
+ @Override
+ public int size() {
+ return 1;
+ }
+
+ @Override
+ public ImmutableMap<QName, Object> asMap() {
+ return ImmutableMap.of(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;
+ }
}
- 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.unorderedCopyOf(keyValues);
+ 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 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;
+ }
}
- public NodeIdentifierWithPredicates(final QName node, final ImmutableOffsetMap<QName, Object> keyValues) {
+ private static final long serialVersionUID = -4787195606494761540L;
+
+ NodeIdentifierWithPredicates(final QName node) {
super(node);
- this.keyValues = requireNonNull(keyValues);
}
- public NodeIdentifierWithPredicates(final QName node, final SharedSingletonMap<QName, Object> keyValues) {
- super(node);
- this.keyValues = requireNonNull(keyValues);
+ public static @NonNull NodeIdentifierWithPredicates of(final QName node) {
+ return new Regular(node, ImmutableMap.of());
}
- public NodeIdentifierWithPredicates(final QName node, final QName key, final Object value) {
- this(node, SharedSingletonMap.unorderedOf(key, value));
+ public static @NonNull NodeIdentifierWithPredicates of(final QName node, final QName key, final Object value) {
+ return new Singleton(node, key, value);
}
- public Map<QName, Object> getKeyValues() {
- return keyValues;
+ public static @NonNull NodeIdentifierWithPredicates of(final QName node, final Entry<QName, Object> entry) {
+ return of(node, entry.getKey(), entry.getValue());
}
- @Override
- protected int hashCodeImpl() {
- final int prime = 31;
- int result = super.hashCodeImpl();
- result = prime * result;
+ 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 ImmutableOffsetMap<QName, Object> keyValues) {
+ return keyValues.size() == 1 ? of(keyValues, node) : new Regular(node, keyValues);
+ }
+
+ @Deprecated
+ public static @NonNull NodeIdentifierWithPredicates of(final QName node,
+ final SharedSingletonMap<QName, Object> keyValues) {
+ return of(node, keyValues.getEntry());
+ }
+
+ private static @NonNull NodeIdentifierWithPredicates of(final Map<QName, Object> keyValues, final QName node) {
+ return of(node, keyValues.entrySet().iterator().next());
+ }
+
+ /**
+ * Return the set of predicates keys and values. Keys are guaranteeed to be unique.
+ *
+ * @return Predicate set.
+ */
+ @Beta
+ public abstract @NonNull Set<Entry<QName, Object>> entrySet();
+
+ /**
+ * Return the predicate key in the iteration order of {@link #entrySet()}.
+ *
+ * @return Predicate values.
+ */
+ @Beta
+ public abstract @NonNull Set<QName> keySet();
+
+ /**
+ * Return the predicate values in the iteration order of {@link #entrySet()}.
+ *
+ * @return Predicate values.
+ */
+ @Beta
+ public abstract @NonNull Collection<Object> values();
- for (Entry<QName, Object> entry : keyValues.entrySet()) {
- result += Objects.hashCode(entry.getKey()) + YangInstanceIdentifier.hashCode(entry.getValue());
+ @Beta
+ public final @Nullable Object getValue(final QName key) {
+ return keyValue(requireNonNull(key));
+ }
+
+ @Beta
+ 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 The number of predicates present.
+ */
+ @Beta
+ 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 abstract @NonNull Map<QName, Object> asMap();
+
+ @Override
+ 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;
- }
+ public final boolean equals(final Object obj) {
+ return super.equals(obj) && equalMapping((NodeIdentifierWithPredicates) obj);
+ }
- final Map<QName, Object> otherKeyValues = ((NodeIdentifierWithPredicates) obj).keyValues;
+ abstract boolean equalMapping(NodeIdentifierWithPredicates other);
- // 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;
- }
+ abstract @Nullable Object keyValue(@NonNull QName qname);
- for (Entry<QName, Object> entry : keyValues.entrySet()) {
- if (!otherKeyValues.containsKey(entry.getKey())
- || !Objects.deepEquals(entry.getValue(), otherKeyValues.get(entry.getKey()))) {
-
- return false;
- }
- }
-
- return true;
+ @Override
+ public final String toString() {
+ return super.toString() + '[' + asMap() + ']';
}
@Override
- public String toString() {
- return super.toString() + '[' + keyValues + ']';
+ public final String toRelativeString(final PathArgument previous) {
+ return super.toRelativeString(previous) + '[' + asMap() + ']';
}
@Override
- public String toRelativeString(final PathArgument previous) {
- return super.toRelativeString(previous) + '[' + keyValues + ']';
+ final Object writeReplace() {
+ return new NIPv2(this);
}
}
public String toRelativeString(final PathArgument previous) {
return super.toRelativeString(previous) + '[' + value + ']';
}
+
+ @Override
+ Object writeReplace() {
+ return new NIVv1(this);
+ }
}
/**
*/
public static final class AugmentationIdentifier implements PathArgument {
private static final long serialVersionUID = -8122335594681936939L;
- private final ImmutableSet<QName> childNames;
+
+ private static final LoadingCache<ImmutableSet<QName>, AugmentationIdentifier> CACHE = CacheBuilder.newBuilder()
+ .weakValues().build(new CacheLoader<ImmutableSet<QName>, AugmentationIdentifier>() {
+ @Override
+ public AugmentationIdentifier load(final ImmutableSet<QName> key) {
+ return new AugmentationIdentifier(key);
+ }
+ });
+
+ private final @NonNull ImmutableSet<QName> childNames;
@Override
public QName getNodeType() {
throw new UnsupportedOperationException("Augmentation node has no QName");
}
+ /**
+ * Construct new augmentation identifier using supplied set of possible
+ * child nodes.
+ *
+ * @param childNames
+ * Set of possible child nodes.
+ */
+ public AugmentationIdentifier(final ImmutableSet<QName> childNames) {
+ this.childNames = requireNonNull(childNames);
+ }
+
/**
* Construct new augmentation identifier using supplied set of possible
* child nodes.
this.childNames = ImmutableSet.copyOf(childNames);
}
+ /**
+ * Return an AugmentationIdentifier for a particular set of QNames. Unlike the constructor, this factory method
+ * uses a global instance cache, resulting in object reuse for equal inputs.
+ *
+ * @param childNames Set of possible child nodes
+ * @return An {@link AugmentationIdentifier}
+ */
+ public static @NonNull AugmentationIdentifier create(final ImmutableSet<QName> childNames) {
+ return CACHE.getUnchecked(childNames);
+ }
+
+ /**
+ * Return an AugmentationIdentifier for a particular set of QNames. Unlike the constructor, this factory method
+ * uses a global instance cache, resulting in object reuse for equal inputs.
+ *
+ * @param childNames Set of possible child nodes
+ * @return An {@link AugmentationIdentifier}
+ */
+ public static @NonNull AugmentationIdentifier create(final Set<QName> childNames) {
+ final AugmentationIdentifier existing = CACHE.getIfPresent(childNames);
+ return existing != null ? existing : create(ImmutableSet.copyOf(childNames));
+ }
+
/**
* Returns set of all possible child nodes.
*
* @return set of all possible child nodes.
*/
- public Set<QName> getPossibleChildNames() {
+ public @NonNull Set<QName> getPossibleChildNames() {
return childNames;
}
return -1;
}
}
+
+ private Object writeReplace() {
+ return new AIv1(this);
+ }
}
/**
* @param arg A {@link PathArgument} to be added
* @return this builder
*/
- InstanceIdentifierBuilder node(PathArgument arg);
+ @NonNull InstanceIdentifierBuilder node(PathArgument arg);
/**
* Adds {@link NodeIdentifier} with supplied QName to path arguments of resulting instance identifier.
* @param nodeType QName of {@link NodeIdentifier} which will be added
* @return this builder
*/
- InstanceIdentifierBuilder node(QName nodeType);
+ @NonNull InstanceIdentifierBuilder node(QName nodeType);
/**
* Adds {@link NodeIdentifierWithPredicates} with supplied QName and key values to path arguments of resulting
* @param keyValues Map of key components and their respective values for {@link NodeIdentifierWithPredicates}
* @return this builder
*/
- InstanceIdentifierBuilder nodeWithKey(QName nodeType, Map<QName, Object> keyValues);
+ @NonNull InstanceIdentifierBuilder nodeWithKey(QName nodeType, Map<QName, Object> keyValues);
/**
* Adds {@link NodeIdentifierWithPredicates} with supplied QName and key, value.
* @param value value of key which will be added
* @return this builder
*/
- InstanceIdentifierBuilder nodeWithKey(QName nodeType, QName key, Object value);
+ @NonNull InstanceIdentifierBuilder nodeWithKey(QName nodeType, QName key, Object value);
/**
* Adds a collection of {@link PathArgument}s to path arguments of resulting instance identifier.
* @throws NullPointerException if any of the arguments is null
*/
@Beta
- InstanceIdentifierBuilder append(Collection<? extends PathArgument> args);
+ @NonNull InstanceIdentifierBuilder append(Collection<? extends PathArgument> args);
/**
* Adds a collection of {@link PathArgument}s to path arguments of resulting instance identifier.
* @throws NullPointerException if any of the arguments is null
*/
@Beta
- default InstanceIdentifierBuilder append(final PathArgument... args) {
+ default @NonNull InstanceIdentifierBuilder append(final PathArgument... args) {
return append(Arrays.asList(args));
}