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=e9f940a4555bad2af63797018916240a4209d3a3;hb=77b8e16d3a2abfb368d2974a473154fe255f903e;hp=60432b512faff22c47d38167c471a22ed17bbf14;hpb=5d50486aed75e0b94a06952add2cc5d9a62ba053;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 60432b512f..3926219368 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,56 +1,67 @@ /* * 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 static com.google.common.base.Verify.verify; + +import com.google.common.annotations.Beta; import com.google.common.base.Optional; import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; +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.Lists; - +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.Collections; +import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.Set; - +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import javax.annotation.Nonnull; +import javax.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.SharedSingletonMap; 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 partical node instance in the data tree. + * 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 minimised to uniquely identify element + * 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. - *

+ * *

* - * - * @see http://tools.ietf.org/html/rfc6020#section-9.13 + * @see RFC6020 */ -public final class YangInstanceIdentifier implements Path, Immutable, Serializable { - private static final YangInstanceIdentifier EMPTY = trustedCreate(Collections.emptyList()); +public abstract class YangInstanceIdentifier implements Path, Immutable, Serializable { + /** + * 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; + + private static final AtomicReferenceFieldUpdater TOSTRINGCACHE_UPDATER = + AtomicReferenceFieldUpdater.newUpdater(YangInstanceIdentifier.class, String.class, "toStringCache"); + private static final long serialVersionUID = 4L; - private static final long serialVersionUID = 2L; - private final Iterable pathArguments; private final int hash; + private transient volatile String toStringCache = null; - private transient ImmutableList legacyPath = null; - private transient String toStringCache = null; + // Package-private to prevent outside subclassing + YangInstanceIdentifier(final int hash) { + this.hash = hash; + } - private final ImmutableList getLegacyPath() { - if (legacyPath == null) { - synchronized (this) { - if (legacyPath == null) { - legacyPath = ImmutableList.copyOf(pathArguments); - } - } - } + @Nonnull abstract YangInstanceIdentifier createRelativeIdentifier(int skipFromRoot); - return legacyPath; - } + @Nonnull abstract Collection tryPathArguments(); + + @Nonnull abstract 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. + */ + @Beta + public abstract 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}. + */ + @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 Immutable iteration of path arguments. */ - public Iterable getPathArguments() { - return pathArguments; - } + public abstract List getPathArguments(); /** * Returns an iterable of path arguments in reverse order. This is useful @@ -113,9 +147,7 @@ public final class YangInstanceIdentifier implements Path getReversePathArguments() { - return getLegacyPath().reverse(); - } + public abstract List getReversePathArguments(); /** * Returns the last PathArgument. This is equivalent of iterating @@ -123,47 +155,28 @@ 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 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.toInstance()); - } - - public static final YangInstanceIdentifier create(final Iterable path) { - if (Iterables.isEmpty(path)) { - return EMPTY; - } - - return trustedCreate(ImmutableList.copyOf(path)); + return FixedYangInstanceIdentifier.create(path, hash.build()); } - public static final YangInstanceIdentifier create(final PathArgument... path) { + public static 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; + boolean pathArgumentsEqual(final YangInstanceIdentifier other) { + return Iterables.elementsEqual(getPathArguments(), other.getPathArguments()); } @Override @@ -171,38 +184,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()) { @@ -234,7 +251,72 @@ 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 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) { @@ -242,7 +324,7 @@ 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 @@ -331,17 +400,31 @@ public final class YangInstanceIdentifier implements Path * This method may return null, if the corresponding schema node, does * not have QName associated, such as in cases of augmentations. * * @return Node type */ QName getNodeType(); + + /** + * Return the string representation of this object for use in context + * provided by a previous object. This method can be implemented in + * terms of {@link #toString()}, but implementations are encourage to + * reuse any context already emitted by the previous object. + * + * @param previous Previous path argument + * @return String representation + */ + 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 transient volatile boolean hashGuard = false; protected AbstractPathArgument(final QName nodeType) { this.nodeType = Preconditions.checkNotNull(nodeType); @@ -353,15 +436,25 @@ 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); + @Override + public String toRelativeString(final PathArgument previous) { + if (previous instanceof AbstractPathArgument) { + final QNameModule mod = previous.getNodeType().getModule(); + if (getNodeType().getModule().equals(mod)) { + return getNodeType().getLocalName(); + } + } - /** - * - * Builds an {@link YangInstanceIdentifier} with path arguments from this builder - * - * @return {@link YangInstanceIdentifier} - */ - YangInstanceIdentifier build(); + return getNodeType().toString(); + } } /** @@ -430,10 +491,28 @@ public final class YangInstanceIdentifier implements Path CACHE = CacheBuilder.newBuilder().weakValues() + .build(new CacheLoader() { + @Override + public NodeIdentifier load(@Nonnull 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 NodeIdentifier create(final QName node) { + return CACHE.getUnchecked(node); + } } /** @@ -447,11 +526,13 @@ public final class YangInstanceIdentifier implements Path keyValues) { super(node); - this.keyValues = ImmutableMap.copyOf(keyValues); + // Retains ImmutableMap for empty maps. For larger sizes uses a shared key set. + this.keyValues = ImmutableOffsetMap.unorderedCopyOf(keyValues); } public NodeIdentifierWithPredicates(final QName node, final QName key, final Object value) { - this(node, ImmutableMap.of(key, value)); + super(node); + this.keyValues = SharedSingletonMap.unorderedOf(key, value); } public Map getKeyValues() { @@ -459,9 +540,9 @@ public final class YangInstanceIdentifier implements Path entry : keyValues.entrySet()) { @@ -471,12 +552,18 @@ public final class YangInstanceIdentifier implements Path 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; } @@ -496,40 +583,46 @@ public final class YangInstanceIdentifier implements Path 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; } @Override - public int hashCode() { + protected int hashCodeImpl() { final int prime = 31; - int result = super.hashCode(); - result = prime * result + ((value == null) ? 0 : YangInstanceIdentifier.hashCode(value)); + int result = super.hashCodeImpl(); + 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); } @@ -537,12 +630,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, @@ -550,8 +649,7 @@ public final class YangInstanceIdentifier implements PathRFC6020 */ public static final class AugmentationIdentifier implements PathArgument { private static final long serialVersionUID = -8122335594681936939L; @@ -564,9 +662,8 @@ public final class YangInstanceIdentifier implements Path childNames) { - this(childNames); - } - - /** - * - * Returns set of all possible child nodes + * Returns set of all possible child nodes. * * @return set of all possible child nodes. */ @@ -599,21 +683,24 @@ 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 { @@ -648,103 +741,74 @@ public final class YangInstanceIdentifier implements Path 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.toInstance()); - } - } - - @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(); + /** + * 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 + */ + InstanceIdentifierBuilder node(PathArgument arg); - while (lit.hasNext()) { - if (!oit.hasNext()) { - return false; - } + /** + * 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); - if (!lit.next().equals(oit.next())) { - return false; - } - } + /** + * 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); - return true; - } + /** + * 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); - @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 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 */ - if (toStringCache != null) { - return toStringCache; - } + @Beta + InstanceIdentifierBuilder append(Collection args); - final StringBuilder builder = new StringBuilder('/'); - boolean first = true; - for (PathArgument argument : getPathArguments()) { - if (first) { - first = false; - } else { - builder.append('/'); - } - builder.append(argument.toString()); + /** + * 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)); } - toStringCache = builder.toString(); - return toStringCache; + /** + * Builds an {@link YangInstanceIdentifier} with path arguments from this builder. + * + * @return {@link YangInstanceIdentifier} + */ + @Override + YangInstanceIdentifier build(); } }