X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=yang%2Fyang-data-api%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fyangtools%2Fyang%2Fdata%2Fapi%2FYangInstanceIdentifier.java;h=a7539e9167f08cb0917ba2b532e12f22e925194e;hb=ce2dacd877878d47df97c524f2307f0d2b393163;hp=62c876b530495e8cdf151e4585f45a7216a4a577;hpb=2f0d8e2c8b438422d2e7a4aef04a9ae1fbd57b41;p=yangtools.git diff --git a/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/YangInstanceIdentifier.java b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/YangInstanceIdentifier.java index 62c876b530..a7539e9167 100644 --- a/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/YangInstanceIdentifier.java +++ b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/YangInstanceIdentifier.java @@ -1,39 +1,51 @@ /* * Copyright (c) 2014 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.data.api; -import com.google.common.base.Optional; -import com.google.common.base.Preconditions; +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.VerifyException; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.ObjectStreamException; +import com.google.common.collect.Sets; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.Serializable; import java.lang.reflect.Array; -import java.lang.reflect.Field; +import java.util.AbstractMap.SimpleImmutableEntry; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; +import java.util.Collection; +import java.util.Deque; import java.util.Iterator; import java.util.List; 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 java.util.function.Function; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; import org.opendaylight.yangtools.concepts.Builder; import org.opendaylight.yangtools.concepts.Immutable; import org.opendaylight.yangtools.concepts.Path; import org.opendaylight.yangtools.util.HashCodeBuilder; +import org.opendaylight.yangtools.util.ImmutableOffsetMap; +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; @@ -42,94 +54,108 @@ import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode; * Unique identifier of a particular node instance in the data tree. * *

- * Java representation of YANG Built-in type instance-identifier, - * 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. + * *

- * 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. - *

- *

Path Arguments

- * 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. - *

+ * 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. + * + *

Path Arguments

+ * 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. + * * * - * * @see RFC6020 */ -public final class YangInstanceIdentifier implements Path, Immutable, Serializable { - @SuppressWarnings("rawtypes") - private static final AtomicReferenceFieldUpdater LEGACYPATH_UPDATER = - AtomicReferenceFieldUpdater.newUpdater(YangInstanceIdentifier.class, ImmutableList.class, "legacyPath"); +// 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. +public abstract class YangInstanceIdentifier implements Path, Immutable, Serializable { private static final AtomicReferenceFieldUpdater TOSTRINGCACHE_UPDATER = AtomicReferenceFieldUpdater.newUpdater(YangInstanceIdentifier.class, String.class, "toStringCache"); - private static final YangInstanceIdentifier EMPTY = trustedCreate(Collections.emptyList()); - private static final Field PATHARGUMENTS_FIELD; + private static final long serialVersionUID = 4L; - private static final long serialVersionUID = 3L; - private transient final Iterable pathArguments; private final int hash; - - private volatile ImmutableList legacyPath = null; private transient volatile String toStringCache = null; - static { - final Field f; - try { - f = YangInstanceIdentifier.class.getDeclaredField("pathArguments"); - } catch (NoSuchFieldException | SecurityException e) { - throw new ExceptionInInitializerError(e); - } - f.setAccessible(true); + // Package-private to prevent outside subclassing + YangInstanceIdentifier(final int hash) { + this.hash = hash; + } - PATHARGUMENTS_FIELD = f; + /** + * 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; } - private final ImmutableList getLegacyPath() { - // Temporary variable saves a volatile read - ImmutableList ret = legacyPath; - if (ret == null) { - // We could have used a synchronized block, but the window is quite - // small and worst that can happen is duplicate object construction. - ret = ImmutableList.copyOf(pathArguments); - LEGACYPATH_UPDATER.lazySet(this, ret); - } + abstract @NonNull YangInstanceIdentifier createRelativeIdentifier(int skipFromRoot); - return ret; - } + abstract @Nullable Collection tryPathArguments(); + + abstract @Nullable Collection tryReversePathArguments(); /** - * Returns a list of path arguments. + * Check if this instance identifier has empty path arguments, e.g. it is + * empty and corresponds to {@link #empty()}. * - * @deprecated Use {@link #getPathArguments()} instead. - * @return Immutable list of path arguments. + * @return True if this instance identifier is empty, false otherwise. */ - @Deprecated - public List getPath() { - return getLegacyPath(); - } + public abstract boolean isEmpty(); + + /** + * Return an optimized version of this identifier, useful when the identifier + * will be used very frequently. + * + * @return A optimized equivalent instance. + */ + 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()}. + */ + 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. + * + * @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. + */ + public abstract @NonNull YangInstanceIdentifier getAncestor(int depth); /** * Returns an ordered iteration of path arguments. * * @return Immutable iteration of path arguments. */ - public Iterable getPathArguments() { - return pathArguments; - } + public abstract @NonNull List getPathArguments(); /** * Returns an iterable of path arguments in reverse order. This is useful @@ -137,9 +163,7 @@ public final class YangInstanceIdentifier implements Path getReversePathArguments() { - return getLegacyPath().reverse(); - } + public abstract @NonNull List getReversePathArguments(); /** * Returns the last PathArgument. This is equivalent of iterating @@ -147,47 +171,66 @@ public final class YangInstanceIdentifier implements Path path, final int hash) { - this.pathArguments = Preconditions.checkNotNull(path, "path must not be null."); - this.hash = hash; - } + public static @NonNull YangInstanceIdentifier create(final Iterable path) { + if (Iterables.isEmpty(path)) { + return empty(); + } - private static final YangInstanceIdentifier trustedCreate(final Iterable path) { final HashCodeBuilder hash = new HashCodeBuilder<>(); for (PathArgument a : path) { hash.addArgument(a); } - return new YangInstanceIdentifier(path, hash.build()); + return FixedYangInstanceIdentifier.create(path, hash.build()); } - public static final YangInstanceIdentifier create(final Iterable path) { - if (Iterables.isEmpty(path)) { - return EMPTY; - } - - return trustedCreate(ImmutableList.copyOf(path)); + public static @NonNull YangInstanceIdentifier create(final PathArgument pathArgument) { + return new FixedYangInstanceIdentifier(ImmutableList.of(pathArgument), + HashCodeBuilder.nextHashCode(1, pathArgument)); } - public static final 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)); } - @Override - public 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; + /** + * Create a {@link YangInstanceIdentifier} by taking a snapshot of provided path and iterating it backwards. + * + * @param pathTowardsRoot Path towards root + * @return A {@link YangInstanceIdentifier} instance + * @throws NullPointerException if {@code pathTowardsRoot} or any of its members is null + */ + public static @NonNull YangInstanceIdentifier createReverse(final Deque pathTowardsRoot) { + final ImmutableList.Builder builder = ImmutableList.builderWithExpectedSize( + pathTowardsRoot.size()); + pathTowardsRoot.descendingIterator().forEachRemaining(builder::add); + return YangInstanceIdentifier.create(builder.build()); + } + + /** + * Create a {@link YangInstanceIdentifier} by walking specified stack backwards and extracting path components + * from it. + * + * @param stackTowardsRoot Stack towards root, + * @return A {@link YangInstanceIdentifier} instance + * @throws NullPointerException if {@code pathTowardsRoot} is null + */ + public static @NonNull YangInstanceIdentifier createReverse(final Deque stackTowardsRoot, + final Function function) { + final ImmutableList.Builder builder = ImmutableList.builderWithExpectedSize( + stackTowardsRoot.size()); + final Iterator it = stackTowardsRoot.descendingIterator(); + while (it.hasNext()) { + builder.add(function.apply(it.next())); + } + return YangInstanceIdentifier.create(builder.build()); + } + + boolean pathArgumentsEqual(final YangInstanceIdentifier other) { + return Iterables.elementsEqual(getPathArguments(), other.getPathArguments()); } @Override @@ -195,38 +238,35 @@ public final class YangInstanceIdentifier implements Path relativeTo(final YangInstanceIdentifier ancestor) { - final Iterator lit = pathArguments.iterator(); - final Iterator oit = ancestor.pathArguments.iterator(); + if (this == ancestor) { + return Optional.of(empty()); + } + if (ancestor.isEmpty()) { + return Optional.of(this); + } + + final Iterator lit = getPathArguments().iterator(); + final Iterator 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; @@ -256,17 +303,84 @@ public final class YangInstanceIdentifier implements Path lit = getPathArguments().iterator(); + final Iterator oit = other.getPathArguments().iterator(); + + while (lit.hasNext()) { + if (!oit.hasNext()) { + return false; + } + + if (!lit.next().equals(oit.next())) { + return false; + } + } + + return true; + } + + @Override + public final String toString() { + /* + * The toStringCache 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. + * The cache is thread-safe - if multiple computations occurs at the + * same time, cache will be overwritten with same result. + */ + String ret = toStringCache; + if (ret == null) { + final StringBuilder builder = new StringBuilder("/"); + PathArgument prev = null; + for (PathArgument argument : getPathArguments()) { + if (prev != null) { + builder.append('/'); + } + builder.append(argument.toRelativeString(prev)); + prev = argument; + } + + ret = builder.toString(); + TOSTRINGCACHE_UPDATER.lazySet(this, ret); } - return Optional.of(trustedCreate(Iterables.skip(pathArguments, common))); + 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; + } + + @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; } - if (value.getClass().equals(byte[].class)) { + if (byte[].class.equals(value.getClass())) { return Arrays.hashCode((byte[]) value); } @@ -283,52 +397,56 @@ public final class YangInstanceIdentifier implements Path * This interface itself is used as common parent for actual * path arguments types and should not be implemented by user code. + * *

* Path arguments SHOULD contain only minimum of information * required to uniquely identify node on particular subtree level. * + *

* For actual path arguments types see: *

    *
  • {@link NodeIdentifier} - Identifier of container or leaf @@ -339,15 +457,12 @@ public final class YangInstanceIdentifier implements Path, Immutable, Serializable { /** - * If applicable returns unique QName of data node as defined in YANG - * Schema. - * - * 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 @@ -358,18 +473,16 @@ public final class YangInstanceIdentifier implements Path HASH_UPDATER = - AtomicReferenceFieldUpdater.newUpdater(AbstractPathArgument.class, Integer.class, "hash"); + private abstract static class AbstractPathArgument implements PathArgument { private static final long serialVersionUID = -4546547994250849340L; - private final QName nodeType; - private volatile transient Integer hash = null; + private final @NonNull QName nodeType; + private transient volatile int hashValue; protected AbstractPathArgument(final QName nodeType) { - this.nodeType = Preconditions.checkNotNull(nodeType); + this.nodeType = requireNonNull(nodeType); } @Override @@ -378,23 +491,19 @@ public final class YangInstanceIdentifier implements Path { - /** - * 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); - - /** - * 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} - * @return this builder - */ - InstanceIdentifierBuilder nodeWithKey(QName nodeType, Map keyValues); - - /** - * Adds {@link NodeIdentifierWithPredicates} with supplied QName and key, value. - * - * @param nodeType QName of {@link NodeIdentifierWithPredicates} which will be added - * @param key QName of key which will be added - * @param value value of key which will be added - * @return this builder - */ - InstanceIdentifierBuilder nodeWithKey(QName nodeType, QName key, Object value); - - /** - * - * Builds an {@link YangInstanceIdentifier} with path arguments from this builder - * - * @return {@link YangInstanceIdentifier} - */ - @Override - YangInstanceIdentifier build(); - - /* - * @deprecated use #build() - */ - @Deprecated - YangInstanceIdentifier toInstance(); + abstract Object writeReplace(); } /** @@ -480,76 +544,292 @@ public final class YangInstanceIdentifier implements Path CACHE = CacheBuilder.newBuilder().weakValues() + .build(new CacheLoader() { + @Override + public NodeIdentifier load(final QName key) { + return new NodeIdentifier(key); + } + }); public NodeIdentifier(final QName node) { super(node); } + + /** + * Return a NodeIdentifier for a particular QName. Unlike the constructor, this factory method uses a global + * instance cache, resulting in object reuse for equal inputs. + * + * @param node Node's QName + * @return A {@link NodeIdentifier} + */ + 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 { + @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); + } - private final Map keyValues; + @Override + public SingletonSet> entrySet() { + return SingletonSet.of(singleEntry()); + } - public NodeIdentifierWithPredicates(final QName node, final Map keyValues) { - super(node); - this.keyValues = ImmutableMap.copyOf(keyValues); - } + @Override + public SingletonSet keySet() { + return SingletonSet.of(key); + } - public NodeIdentifierWithPredicates(final QName node, final QName key, final Object value) { - this(node, ImmutableMap.of(key, value)); - } + @Override + public boolean containsKey(final QName qname) { + return key.equals(requireNonNull(qname)); + } - public Map getKeyValues() { - return keyValues; - } + @Override + public SingletonSet values() { + return SingletonSet.of(value); + } - @Override - protected int hashCodeImpl() { - final int prime = 31; - int result = super.hashCodeImpl(); - result = prime * result; + @Override + public int size() { + return 1; + } - for (Entry entry : keyValues.entrySet()) { - result += Objects.hashCode(entry.getKey()) + YangInstanceIdentifier.hashCode(entry.getValue()); + @Override + public ImmutableMap 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 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; } - return result; } - @Override - public boolean equals(final Object obj) { - if (!super.equals(obj)) { - return false; + private static final class Regular extends NodeIdentifierWithPredicates { + private static final long serialVersionUID = 1L; + + private final @NonNull Map keyValues; + + Regular(final QName node, final Map keyValues) { + super(node); + this.keyValues = requireNonNull(keyValues); } - final Map otherKeyValues = ((NodeIdentifierWithPredicates) obj).keyValues; - if (keyValues.size() != otherKeyValues.size()) { - return false; + @Override + public Set> entrySet() { + return keyValues.entrySet(); + } + + @Override + public Set keySet() { + return keyValues.keySet(); } - for (Entry entry : keyValues.entrySet()) { - if (!otherKeyValues.containsKey(entry.getKey()) - || !Objects.deepEquals(entry.getValue(), otherKeyValues.get(entry.getKey()))) { + @Override + public boolean containsKey(final QName qname) { + return keyValues.containsKey(requireNonNull(qname)); + } + + @Override + public Collection values() { + return keyValues.values(); + } + + @Override + public int size() { + return keyValues.size(); + } + + @Override + public Map asMap() { + return keyValues; + } + @Override + Object keyValue(final QName qname) { + return keyValues.get(qname); + } + + @Override + boolean equalMapping(final NodeIdentifierWithPredicates other) { + final Map 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 entry : entrySet()) { + final Object otherValue = otherKeyValues.get(entry.getKey()); + if (otherValue == null || !Objects.deepEquals(entry.getValue(), otherValue)) { + return false; + } + } + + return true; } + } - return true; + private static final long serialVersionUID = -4787195606494761540L; + + NodeIdentifierWithPredicates(final QName node) { + super(node); } + public static @NonNull NodeIdentifierWithPredicates of(final QName node) { + return new Regular(node, ImmutableMap.of()); + } + + 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 Entry entry) { + return of(node, entry.getKey(), entry.getValue()); + } + + public static @NonNull NodeIdentifierWithPredicates of(final QName node, final Map 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 keyValues) { + return keyValues.size() == 1 ? of(keyValues, node) : new Regular(node, keyValues); + } + + private static @NonNull NodeIdentifierWithPredicates of(final Map 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. + */ + public abstract @NonNull Set> entrySet(); + + /** + * Return the predicate key in the iteration order of {@link #entrySet()}. + * + * @return Predicate values. + */ + public abstract @NonNull Set 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. + */ + public abstract @NonNull Collection values(); + + @Beta + public final @Nullable Object getValue(final QName key) { + return keyValue(requireNonNull(key)); + } + + @Beta + public final @Nullable T getValue(final QName key, final Class valueClass) { + return valueClass.cast(getValue(key)); + } + + /** + * Return the number of predicates present. + * + * @return The number of predicates present. + */ + 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. + */ + @Beta + public abstract @NonNull Map asMap(); + @Override - public String toString() { - return super.toString() + '[' + keyValues + ']'; + protected final int hashCodeImpl() { + int result = 31 * super.hashCodeImpl(); + for (Entry entry : entrySet()) { + result += entry.getKey().hashCode() + YangInstanceIdentifier.hashCode(entry.getValue()); + } + return result; } @Override - public String toRelativeString(final PathArgument previous) { - return super.toRelativeString(previous) + '[' + keyValues + ']'; + @SuppressWarnings("checkstyle:equalsHashCode") + public final boolean equals(final Object obj) { + return super.equals(obj) && equalMapping((NodeIdentifierWithPredicates) obj); + } + + abstract boolean equalMapping(NodeIdentifierWithPredicates other); + + abstract @Nullable Object keyValue(@NonNull QName qname); + + @Override + public final String toString() { + return super.toString() + '[' + asMap() + ']'; + } + + @Override + public final String toRelativeString(final PathArgument previous) { + return super.toRelativeString(previous) + '[' + asMap() + ']'; + } + + @Override + final Object writeReplace() { + return new NIPv2(this); } } @@ -557,34 +837,32 @@ public final class YangInstanceIdentifier implements Path extends AbstractPathArgument { private static final long serialVersionUID = -3637456085341738431L; - private final Object value; + private final @NonNull T value; - public NodeWithValue(final QName node, final Object value) { + public NodeWithValue(final QName node, final T value) { super(node); - this.value = value; + this.value = requireNonNull(value); } - public Object getValue() { + public @NonNull T getValue() { return value; } @Override protected int hashCodeImpl() { - final int prime = 31; - int result = super.hashCodeImpl(); - result = prime * result + ((value == null) ? 0 : YangInstanceIdentifier.hashCode(value)); - return result; + return 31 * super.hashCodeImpl() + YangInstanceIdentifier.hashCode(value); } @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); } @@ -597,12 +875,18 @@ public final class YangInstanceIdentifier implements Path * Augmentation is uniquely identified by set of all possible child nodes. * This is possible * to identify instance of augmentation, @@ -610,12 +894,20 @@ public final class YangInstanceIdentifier implements PathRFC6020 */ public static final class AugmentationIdentifier implements PathArgument { private static final long serialVersionUID = -8122335594681936939L; - private final ImmutableSet childNames; + + private static final LoadingCache, AugmentationIdentifier> CACHE = CacheBuilder.newBuilder() + .weakValues().build(new CacheLoader, AugmentationIdentifier>() { + @Override + public AugmentationIdentifier load(final ImmutableSet key) { + return new AugmentationIdentifier(key); + } + }); + + private final @NonNull ImmutableSet childNames; @Override public QName getNodeType() { @@ -624,9 +916,19 @@ public final class YangInstanceIdentifier implements Path childNames) { + this.childNames = requireNonNull(childNames); + } + + /** * Construct new augmentation identifier using supplied set of possible - * child nodes + * child nodes. * * @param childNames * Set of possible child nodes. @@ -636,19 +938,40 @@ public final class YangInstanceIdentifier implements Path 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 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 getPossibleChildNames() { + public @NonNull Set getPossibleChildNames() { return childNames; } @Override public String toString() { - final StringBuffer sb = new StringBuffer("AugmentationIdentifier{"); - sb.append("childNames=").append(childNames).append('}'); - return sb.toString(); + return "AugmentationIdentifier{" + "childNames=" + childNames + '}'; } @Override @@ -657,15 +980,15 @@ public final class YangInstanceIdentifier implements Path 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 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 { return -1; } } - } - - private static class BuilderImpl implements InstanceIdentifierBuilder { - private final HashCodeBuilder hash; - private final List path; - - public BuilderImpl() { - this.hash = new HashCodeBuilder<>(); - this.path = new ArrayList<>(); - } - public BuilderImpl(final Iterable prefix, final int hash) { - this.path = Lists.newArrayList(prefix); - this.hash = new HashCodeBuilder<>(hash); - } - - @Override - public InstanceIdentifierBuilder node(final QName nodeType) { - final PathArgument arg = new NodeIdentifier(nodeType); - path.add(arg); - hash.addArgument(arg); - return this; - } - - @Override - public InstanceIdentifierBuilder nodeWithKey(final QName nodeType, final QName key, final Object value) { - final PathArgument arg = new NodeIdentifierWithPredicates(nodeType, key, value); - path.add(arg); - hash.addArgument(arg); - return this; - } - - @Override - public InstanceIdentifierBuilder nodeWithKey(final QName nodeType, final Map keyValues) { - final PathArgument arg = new NodeIdentifierWithPredicates(nodeType, keyValues); - path.add(arg); - hash.addArgument(arg); - return this; - } - - @Override - @Deprecated - public YangInstanceIdentifier toInstance() { - return build(); - } - - @Override - public YangInstanceIdentifier build() { - return new YangInstanceIdentifier(ImmutableList.copyOf(path), hash.build()); + private Object writeReplace() { + return new AIv1(this); } } - @Override - public boolean contains(final YangInstanceIdentifier other) { - Preconditions.checkArgument(other != null, "other should not be null"); - - final Iterator lit = pathArguments.iterator(); - final Iterator oit = other.pathArguments.iterator(); - - while (lit.hasNext()) { - if (!oit.hasNext()) { - return false; - } - - if (!lit.next().equals(oit.next())) { - return false; - } - } + /** + * Fluent Builder of Instance Identifier instances. + */ + public interface InstanceIdentifierBuilder extends Builder { + /** + * Adds a {@link PathArgument} to path arguments of resulting instance identifier. + * + * @param arg A {@link PathArgument} to be added + * @return this builder + */ + @NonNull InstanceIdentifierBuilder node(PathArgument arg); - return true; - } + /** + * 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 + */ + @NonNull InstanceIdentifierBuilder node(QName nodeType); - @Override - public String toString() { - /* - * The toStringCache 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. - * The cache is thread-safe - if multiple computations occurs at the - * same time, cache will be overwritten with same result. + /** + * 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} + * @return this builder */ - String ret = toStringCache; - if (ret == null) { - final StringBuilder builder = new StringBuilder("/"); - PathArgument prev = null; - for (PathArgument argument : getPathArguments()) { - if (prev != null) { - builder.append('/'); - } - builder.append(argument.toRelativeString(prev)); - prev = argument; - } + @NonNull InstanceIdentifierBuilder nodeWithKey(QName nodeType, Map keyValues); - ret = builder.toString(); - TOSTRINGCACHE_UPDATER.lazySet(this, ret); - } - return ret; - } + /** + * Adds {@link NodeIdentifierWithPredicates} with supplied QName and key, value. + * + * @param nodeType QName of {@link NodeIdentifierWithPredicates} which will be added + * @param key QName of key which will be added + * @param value value of key which will be added + * @return this builder + */ + @NonNull InstanceIdentifierBuilder nodeWithKey(QName nodeType, QName key, Object value); - private void readObject(final ObjectInputStream inputStream) throws IOException, ClassNotFoundException { - inputStream.defaultReadObject(); + /** + * 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 + */ + @NonNull InstanceIdentifierBuilder append(Collection args); - try { - PATHARGUMENTS_FIELD.set(this, legacyPath); - } catch (IllegalArgumentException | IllegalAccessException e) { - throw new IOException(e); + /** + * 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 + */ + default @NonNull InstanceIdentifierBuilder append(final PathArgument... args) { + return append(Arrays.asList(args)); } - } - - private Object readResolve() throws ObjectStreamException { - return legacyPath.isEmpty() ? EMPTY : this; - } - private void writeObject(final ObjectOutputStream outputStream) throws IOException { - /* - * This may look strange, but what we are doing here is side-stepping the fact - * that pathArguments is not generally serializable. We are forcing instantiation - * of the legacy path, which is an ImmutableList (thus Serializable) and write - * it out. The read path does the opposite -- it reads the legacyPath and then - * uses invocation API to set the field. + /** + * Builds an {@link YangInstanceIdentifier} with path arguments from this builder. + * + * @return {@link YangInstanceIdentifier} */ - getLegacyPath(); - outputStream.defaultWriteObject(); + @Override + YangInstanceIdentifier build(); } }