X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=yang%2Fyang-binding%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fyangtools%2Fyang%2Fbinding%2FInstanceIdentifier.java;h=b135a86234bdf8136f393cd7eb5b62f4e48e23ee;hb=71913183c53b27c73fcac08c2cf8c58c3acf4e2e;hp=b49ff9ded56081ee30296166c016a6159748dd85;hpb=b9d3dd0e55e1b3926586e2acfdf9e64e7017c737;p=mdsal.git diff --git a/yang/yang-binding/src/main/java/org/opendaylight/yangtools/yang/binding/InstanceIdentifier.java b/yang/yang-binding/src/main/java/org/opendaylight/yangtools/yang/binding/InstanceIdentifier.java index b49ff9ded5..b135a86234 100644 --- a/yang/yang-binding/src/main/java/org/opendaylight/yangtools/yang/binding/InstanceIdentifier.java +++ b/yang/yang-binding/src/main/java/org/opendaylight/yangtools/yang/binding/InstanceIdentifier.java @@ -7,278 +7,638 @@ */ package org.opendaylight.yangtools.yang.binding; -import java.util.ArrayList; +import com.google.common.base.Objects; +import com.google.common.base.Objects.ToStringHelper; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; import java.util.Collections; +import java.util.Iterator; import java.util.List; - import org.opendaylight.yangtools.concepts.Builder; import org.opendaylight.yangtools.concepts.Immutable; import org.opendaylight.yangtools.concepts.Path; /** - * Uniquely identifies data location in the overall of data tree - * modeled by YANG. - * - * + * + * This instance identifier uniquely identifies a specific DataObject in the data tree modeled by YANG. + * + * For Example let's say you were trying to refer to a node in inventory which was modeled in YANG as follows, + * + *
+ * module opendaylight-inventory {
+ *      ....
+ *
+ *      container nodes {
+ *        list node {
+ *            key "id";
+ *            ext:context-instance "node-context";
+ *
+ *            uses node;
+ *        }
+ *    }
+ *
+ * }
+ * 
+ * + * You could create an instance identifier as follows to get to a node with id "openflow:1" + * + * InstanceIdentifierBuilder.builder(Nodes.class).child(Node.class, new NodeKey(new NodeId("openflow:1")).build(); + * + * This would be the same as using a path like so, "/nodes/node/openflow:1" to refer to the openflow:1 node + * */ -public final class InstanceIdentifier implements Path>,Immutable { - - private final List path; +public class InstanceIdentifier implements Path>, Immutable { + /* + * Protected to differentiate internal and external access. Internal + * access is required never to modify the contents. References passed + * to outside entities have to be wrapped in an unmodifiable view. + */ + protected final Iterable pathArguments; private final Class targetType; - - public InstanceIdentifier(Class type) { - path = Collections. singletonList(new Item<>(type)); - this.targetType = type; + private final boolean wildcarded; + private final int hash; + + InstanceIdentifier(final Class type, final Iterable pathArguments, final boolean wildcarded, final int hash) { + this.pathArguments = Preconditions.checkNotNull(pathArguments); + this.targetType = Preconditions.checkNotNull(type); + this.wildcarded = wildcarded; + this.hash = hash; + } + + /** + * Return the type of data which this InstanceIdentifier identifies. + * + * @return Target type + */ + public final Class getTargetType() { + return targetType; } - public InstanceIdentifier(List path, Class type) { - this.path = Collections. unmodifiableList(new ArrayList<>(path)); - this.targetType = type; + /** + * Return the path argument chain which makes up this instance identifier. + * + * @return Path argument chain. Immutable and does not contain nulls. + */ + public final Iterable getPathArguments() { + return Iterables.unmodifiableIterable(pathArguments); } /** - * - * @return path + * Check whether an instance identifier contains any wildcards. A wildcard + * is an path argument which has a null key. + * + * @return @true if any of the path arguments has a null key. */ - public List getPath() { - return this.path; + public final boolean isWildcarded() { + return wildcarded; } - public Class getTargetType() { - return this.targetType; + @Override + public final int hashCode() { + return hash; } @Override - public String toString() { - return "InstanceIdentifier [path=" + path + "]"; + public final boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + + final InstanceIdentifier other = (InstanceIdentifier) obj; + if (pathArguments == other.pathArguments) { + return true; + } + + /* + * We could now just go and compare the pathArguments, but that + * can be potentially expensive. Let's try to avoid that by + * checking various things that we have cached from pathArguments + * and trying to prove the identifiers are *not* equal. + */ + if (hash != other.hash) { + return false; + } + if (wildcarded != other.wildcarded) { + return false; + } + if (targetType != other.targetType) { + return false; + } + if (fastNonEqual(other)) { + return false; + } + + // Everything checks out so far, so we have to do a full equals + return Iterables.elementsEqual(pathArguments, other.pathArguments); } /** - * Path argument of {@link InstanceIdentifier}. - *

- * Interface which implementations are used as path components of the - * path in overall data tree. + * Perform class-specific fast checks for non-equality. This allows + * subclasses to avoid iterating over the pathArguments by performing + * quick checks on their specific fields. * + * @param other The other identifier, guaranteed to be the same class + * @return @true if the other identifier cannot be equal to this one. */ - public interface PathArgument { + protected boolean fastNonEqual(final InstanceIdentifier other) { + return false; + } - Class getType(); + @Override + public final String toString() { + return addToStringAttributes(Objects.toStringHelper(this)).toString(); + } + /** + * Add class-specific toString attributes. + * + * @param toStringHelper ToStringHelper instance + * @return ToStringHelper instance which was passed in + */ + protected ToStringHelper addToStringAttributes(final ToStringHelper toStringHelper) { + return toStringHelper.add("targetType", targetType).add("path", Iterables.toString(getPathArguments())); } - public static final class Item implements PathArgument { - private final Class type; + /** + * Return an instance identifier trimmed at the first occurrence of a + * specific component type. + * + * For example let's say an instance identifier was built like so, + *

+     *      identifier = InstanceIdentifierBuilder.builder(Nodes.class).child(Node.class, new NodeKey(new NodeId("openflow:1")).build();
+     * 
+ * + * And you wanted to obtain the Instance identifier which represented Nodes you would do it like so, + * + *
+     *      identifier.firstIdentifierOf(Nodes.class)
+     * 
+ * + * @param type component type + * @return trimmed instance identifier, or null if the component type + * is not present. + */ + public final InstanceIdentifier firstIdentifierOf(final Class type) { + int i = 1; + for (final PathArgument a : getPathArguments()) { + if (type.equals(a.getType())) { + @SuppressWarnings("unchecked") + final InstanceIdentifier ret = (InstanceIdentifier) internalCreate(Iterables.limit(getPathArguments(), i)); + return ret; + } - public Item(Class type) { - this.type = type; + ++i; } - public Class getType() { - return type; - } + return null; + } - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((type == null) ? 0 : type.hashCode()); - return result; + /** + * Return the key associated with the first component of specified type in + * an identifier. + * + * @param listItem component type + * @param listKey component key type + * @return key associated with the component, or null if the component type + * is not present. + */ + public final & DataObject, K extends Identifier> K firstKeyOf(final Class listItem, final Class listKey) { + for (final PathArgument i : getPathArguments()) { + if (listItem.equals(i.getType())) { + @SuppressWarnings("unchecked") + final K ret = ((IdentifiableItem)i).getKey(); + return ret; + } } - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - Item other = (Item) obj; - if (type == null) { - if (other.type != null) - return false; - } else if (!type.equals(other.type)) - return false; - return true; - } - - @Override - public String toString() { - return type.getName(); - } + return null; } - public static final class IdentifiableItem & DataObject, T extends Identifier> implements - PathArgument { + /** + * Check whether an identifier is contained in this identifier. This is a strict subtree check, which requires all + * PathArguments to match exactly, e.g. + * + * + * The contains method checks if the other identifier is fully contained within the current identifier. It does this + * by looking at only the types of the path arguments and not by comparing the path arguments themselves. + * + * To illustrate here is an example which explains the working of this API. + * + * Let's say you have two instance identifiers as follows, + * + * this = /nodes/node/openflow:1 + * other = /nodes/node/openflow:2 + * + * then this.contains(other) will return false. + * + * @param other + * @return + */ + @Override + public final boolean contains(final InstanceIdentifier other) { + Preconditions.checkNotNull(other, "other should not be null"); - private final T key; - private final Class type; - - public IdentifiableItem(Class type, T key) { - if (type == null) - throw new IllegalArgumentException("Type must not be null."); - if (key == null) - throw new IllegalArgumentException("Key must not be null."); - this.type = type; - this.key = key; - } + final Iterator lit = pathArguments.iterator(); + final Iterator oit = other.pathArguments.iterator(); - public T getKey() { - return this.key; - } + while (lit.hasNext()) { + if (!oit.hasNext()) { + return false; + } - @Override - public Class getType() { - return this.type; + if (!lit.next().equals(oit.next())) { + return false; + } } - @Override - public boolean equals(Object obj) { - if (obj == null) { + return true; + } + + /** + * Check whether this instance identifier contains the other identifier after wildcard expansion. This is similar + * to {@link #contains(InstanceIdentifier)}, with the exception that a wildcards are assumed to match the their + * non-wildcarded PathArgument counterpart. + * + * @param other Identifier which should be checked for inclusion. + * @return @true if this identifier contains the other object + */ + public final boolean containsWildcarded(final InstanceIdentifier other) { + Preconditions.checkNotNull(other, "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 (obj.hashCode() != hashCode()) { + + final PathArgument la = lit.next(); + final PathArgument oa = oit.next(); + + if (!la.getType().equals(oa.getType())) { return false; } - if (!(obj instanceof IdentifiableItem)) { + if (la instanceof IdentifiableItem && oa instanceof IdentifiableItem && !la.equals(oa)) { return false; } - IdentifiableItem foreign = (IdentifiableItem) obj; - return key.equals(foreign.getKey()); } - @Override - public int hashCode() { - return key.hashCode(); - } + return true; + } - @Override - public String toString() { - return type.getName() + "[key=" + key + "]"; + /** + * Create a builder rooted at this key. + * + * @return A builder instance + */ + public InstanceIdentifierBuilder builder() { + return new InstanceIdentifierBuilderImpl(new Item(targetType), pathArguments, hash, isWildcarded()); + } + + private InstanceIdentifier childIdentifier(final PathArgument arg) { + return trustedCreate(arg, Iterables.concat(pathArguments, Collections.singleton(arg)), HashCodeBuilder.nextHashCode(hash, arg), isWildcarded()); + } + + @SuppressWarnings("unchecked") + public final > InstanceIdentifier child(final Class container) { + final PathArgument arg = new Item<>(container); + return (InstanceIdentifier) childIdentifier(arg); + } + + @SuppressWarnings("unchecked") + public final & ChildOf, K extends Identifier> InstanceIdentifier child( + final Class listItem, final K listKey) { + final PathArgument arg = new IdentifiableItem<>(listItem, listKey); + return (InstanceIdentifier) childIdentifier(arg); + } + + @SuppressWarnings("unchecked") + public final > InstanceIdentifier augmentation( + final Class container) { + final PathArgument arg = new Item<>(container); + return (InstanceIdentifier) childIdentifier(arg); + } + + @Deprecated + private List legacyCache; + + /** + * @deprecated Use {@link #getPathArguments()} instead. + */ + @Deprecated + public final List getPath() { + if (legacyCache == null) { + legacyCache = ImmutableList.copyOf(getPathArguments()); } + + return legacyCache; } - public interface InstanceIdentifierBuilder extends Builder> { + /** + * Create a new InstanceIdentifierBuilder given a base InstanceIdentifier + * + * @param basePath + * @param + * @return + * + * @deprecated Use {@link #builder()} instead. + */ + @Deprecated + public static InstanceIdentifierBuilder builder(final InstanceIdentifier base) { + return base.builder(); + } - InstanceIdentifierBuilder node(Class container); + /** + * Create an InstanceIdentifierBuilder for a specific type of InstanceIdentifier as specified by container + * + * @param container + * @param + * @return + */ + public static > InstanceIdentifierBuilder builder(final Class container) { + return new InstanceIdentifierBuilderImpl().addNode(container); + } - & DataObject, K extends Identifier> InstanceIdentifierBuilder node( - Class listItem, K listKey); + /** + * Create an InstanceIdentifierBuilder for a specific type of InstanceIdentifier which represents an IdentifiableItem + * + * @param listItem + * @param listKey + * @param + * @param + * @return + */ + public static & ChildOf, K extends Identifier> InstanceIdentifierBuilder builder( + final Class listItem, final K listKey) { + return new InstanceIdentifierBuilderImpl().addNode(listItem, listKey); + } - > InstanceIdentifierBuilder child(Class container); - - & ChildOf, K extends Identifier> InstanceIdentifierBuilder child( - Class listItem, K listKey); + /** + * Create an instance identifier for a very specific object type. This method + * implements {@link #create(Iterable)} semantics, except it is used by internal + * callers, which have assured that the argument is an immutable Iterable. + * + * + * @param pathArguments The path to a specific node in the data tree + * @return InstanceIdentifier instance + * @throws IllegalArgumentException if pathArguments is empty or + * contains a null element. + */ + private static InstanceIdentifier internalCreate(final Iterable pathArguments) { + final Iterator it = Preconditions.checkNotNull(pathArguments, "pathArguments may not be null").iterator(); + final HashCodeBuilder hashBuilder = new HashCodeBuilder<>(); + boolean wildcard = false; + PathArgument a = null; + + while (it.hasNext()) { + a = it.next(); + Preconditions.checkArgument(a != null, "pathArguments may not contain null elements"); + + // TODO: sanity check ChildOf<>; + hashBuilder.addArgument(a); + + if (Identifiable.class.isAssignableFrom(a.getType()) && !(a instanceof IdentifiableItem)) { + wildcard = true; + } + } + Preconditions.checkArgument(a != null, "pathArguments may not be empty"); + return trustedCreate(a, pathArguments, hashBuilder.toInstance(), wildcard); } - @SuppressWarnings("rawtypes") - public static InstanceIdentifierBuilder builder() { - return new BuilderImpl(); + /** + * Create an instance identifier for a very specific object type. + * + * Example + *
+     *  List path = Arrays.asList(new Item(Nodes.class))
+     *  new InstanceIdentifier(path);
+     * 
+ * + * @param pathArguments The path to a specific node in the data tree + * @return InstanceIdentifier instance + * @throws IllegalArgumentException if pathArguments is empty or + * contains a null element. + */ + public static InstanceIdentifier create(final Iterable pathArguments) { + if (pathArguments instanceof ImmutableCollection) { + @SuppressWarnings("unchecked") + final Iterable immutableArguments = (Iterable) pathArguments; + return internalCreate(immutableArguments); + } else { + return internalCreate(ImmutableList.copyOf(pathArguments)); + } } - @SuppressWarnings({ "rawtypes", "unchecked" }) - public static InstanceIdentifierBuilder builder(InstanceIdentifier basePath) { - return new BuilderImpl(basePath.path,basePath.targetType); + /** + * Create an instance identifier for a very specific object type. + * + * For example + *
+     *      new InstanceIdentifier(Nodes.class)
+     * 
+ * would create an InstanceIdentifier for an object of type Nodes + * + * @param type The type of the object which this instance identifier represents + * @return InstanceIdentifier instance + */ + @SuppressWarnings("unchecked") + public static InstanceIdentifier create(final Class type) { + return (InstanceIdentifier) create(Collections. singletonList(new Item<>(type))); } - private static final class BuilderImpl implements InstanceIdentifierBuilder { + /** + * Return the key associated with the last component of the specified identifier. + * + * @param id instance identifier + * @return key associated with the last component + */ + public static & DataObject, K extends Identifier> K keyOf(final InstanceIdentifier id) { + @SuppressWarnings("unchecked") + final K ret = ((KeyedInstanceIdentifier)id).getKey(); + return ret; + } - private List path; - private Class target = null; + @SuppressWarnings({ "unchecked", "rawtypes" }) + static InstanceIdentifier trustedCreate(final PathArgument arg, final Iterable pathArguments, final int hash, boolean wildcarded) { + if (Identifiable.class.isAssignableFrom(arg.getType()) && !(wildcarded)) { + Identifier key = null; + if (arg instanceof IdentifiableItem) { + key = ((IdentifiableItem)arg).key; + } else { + wildcarded = true; + } - public BuilderImpl() { - this.path = new ArrayList<>(); + return new KeyedInstanceIdentifier(arg.getType(), pathArguments, wildcarded, hash, key); + } else { + return new InstanceIdentifier(arg.getType(), pathArguments, wildcarded, hash); } - + } + + /** + * Path argument of {@link InstanceIdentifier}. + *

+ * Interface which implementations are used as path components of the + * path in overall data tree. + */ + public interface PathArgument extends Comparable { + Class getType(); + } - public BuilderImpl(List prefix,Class target) { - this.path = new ArrayList<>(prefix); - this.target = target; + private static abstract class AbstractPathArgument implements PathArgument { + private final Class type; + + protected AbstractPathArgument(final Class type) { + this.type = Preconditions.checkNotNull(type, "Type may not be null."); } - @SuppressWarnings({ "unchecked", "rawtypes" }) @Override - public InstanceIdentifier toInstance() { - List immutablePath = Collections.unmodifiableList(new ArrayList(path)); - return new InstanceIdentifier(immutablePath, target); + public final Class getType() { + return type; } @Override - @SuppressWarnings("unchecked") - public InstanceIdentifierBuilder node(Class container) { - target = container; - path.add(new Item(container)); - return (InstanceIdentifierBuilder) this; + public int hashCode() { + return type.hashCode(); } @Override - @SuppressWarnings("unchecked") - public , K extends Identifier> InstanceIdentifierBuilder node( - Class listItem, K listKey) { - target = listItem; - path.add(new IdentifiableItem(listItem, listKey)); - return (InstanceIdentifierBuilder) this; + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final AbstractPathArgument other = (AbstractPathArgument) obj; + return type.equals(other.type); } - + @Override - public > InstanceIdentifierBuilder child(Class container) { - return node(container); + public int compareTo(PathArgument arg) { + return type.getCanonicalName().compareTo(arg.getType().getCanonicalName()); + } + } + + /** + * An Item represents an object that probably is only one of it's kind. For example a Nodes object is only one of + * a kind. In YANG terms this would probably represent a container. + * + * @param + */ + public static final class Item extends AbstractPathArgument { + public Item(final Class type) { + super(type); } - + @Override - public & ChildOf, K extends Identifier> InstanceIdentifierBuilder child( - Class listItem, K listKey) { - return node(listItem,listKey); + public String toString() { + return getType().getName(); } } - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((path == null) ? 0 : path.hashCode()); - return result; - } + /** + * An IdentifiableItem represents a object that is usually present in a collection and can be identified uniquely + * by a key. In YANG terms this would probably represent an item in a list. + * + * @param An object that is identifiable by an identifier + * @param The identifier of the object + */ + public static final class IdentifiableItem & DataObject, T extends Identifier> extends AbstractPathArgument { + private final T key; - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; + public IdentifiableItem(final Class type, final T key) { + super(type); + this.key = Preconditions.checkNotNull(key, "Key may not be null."); } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - InstanceIdentifier other = (InstanceIdentifier) obj; - if (path == null) { - if (other.path != null) { - return false; - } - } else if (!path.equals(other.path)) { - return false; + + public T getKey() { + return this.key; } - return true; - } - @Override - public boolean contains(final InstanceIdentifier other) { - if(other == null) { - throw new IllegalArgumentException("other should not be null"); + @Override + public boolean equals(final Object obj) { + return super.equals(obj) && key.equals(((IdentifiableItem) obj).getKey()); } - final int localSize = this.path.size(); - final List otherPath = other.getPath(); - if(localSize > other.path.size()) { - return false; + + @Override + public int hashCode() { + return super.hashCode() * 31 + key.hashCode(); } - for(int i = 0;i extends Builder> { + /** + * Append the specified container as a child of the current InstanceIdentifier referenced by the builder. + * + * This method should be used when you want to build an instance identifier by appending top-level + * elements + * + * Example, + *

+         *     InstanceIdentifier.builder().child(Nodes.class).build();
+         *
+         * 
+ * + * NOTE :- The above example is only for illustration purposes InstanceIdentifier.builder() has been deprecated + * and should not be used. Use InstanceIdentifier.builder(Nodes.class) instead + * + * @param container + * @param + * @return + */ + > InstanceIdentifierBuilder child( + Class container); + + /** + * Append the specified listItem as a child of the current InstanceIdentifier referenced by the builder. + * + * This method should be used when you want to build an instance identifier by appending a specific list element + * to the identifier + * + * @param listItem + * @param listKey + * @param + * @param + * @return + */ + & ChildOf, K extends Identifier> InstanceIdentifierBuilder child( + Class listItem, K listKey); + + /** + * Build an identifier which refers to a specific augmentation of the current InstanceIdentifier referenced by + * the builder + * + * @param container + * @param + * @return + */ + > InstanceIdentifierBuilder augmentation( + Class container); + + /** + * Build the instance identifier. + * + * @return + */ + InstanceIdentifier build(); } }