2 * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
3 * This program and the accompanying materials are made available under the
4 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
5 * and is available at http://www.eclipse.org/legal/epl-v10.html
7 package org.opendaylight.yangtools.yang.data.api;
9 import com.google.common.base.Optional;
10 import com.google.common.base.Preconditions;
11 import com.google.common.collect.ImmutableList;
12 import com.google.common.collect.ImmutableMap;
13 import com.google.common.collect.ImmutableSet;
14 import java.io.Serializable;
15 import java.lang.reflect.Array;
16 import java.util.Arrays;
17 import java.util.Iterator;
18 import java.util.List;
20 import java.util.Map.Entry;
21 import java.util.Objects;
23 import org.opendaylight.yangtools.concepts.Builder;
24 import org.opendaylight.yangtools.concepts.Immutable;
25 import org.opendaylight.yangtools.concepts.Path;
26 import org.opendaylight.yangtools.yang.common.QName;
27 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
30 * Unique identifier of a partical node instance in the data tree.
34 * Java representation of YANG Built-in type <code>instance-identifier</code>,
35 * which conceptually is XPath expression minimised to uniquely identify element
36 * in data tree which conforms to constraints maintained by YANG Model,
37 * effectively this makes Instance Identifier a path to element in data tree.
39 * Constraints put in YANG specification on instance-identifier allowed it to be
40 * effectively represented in Java and it's evaluation does not require
41 * full-blown XPath processor.
43 * <h3>Path Arguments</h3>
44 * Path to the node represented in instance identifier consists of
45 * {@link PathArgument} which carries necessary information to uniquely identify
46 * node on particular level in the subtree.
49 * <li>{@link NodeIdentifier} - Identifier of node, which has cardinality
50 * <code>0..1</code> in particular subtree in data tree.</li>
51 * <li>{@link NodeIdentifierWithPredicates} - Identifier of node (list item),
52 * which has cardinality <code>0..n</code>.</li>
53 * <li>{@link NodeWithValue} - Identifier of instance <code>leaf</code> node or
54 * <code>leaf-list</code> node.</li>
55 * <li>{@link AugmentationIdentifier} - Identifier of instance of
56 * <code>augmentation</code> node.</li>
60 * @see http://tools.ietf.org/html/rfc6020#section-9.13
64 public class InstanceIdentifier implements Path<InstanceIdentifier>, Immutable, Serializable {
66 private static final long serialVersionUID = 8467409862384206193L;
67 private final List<PathArgument> path;
69 private transient String toStringCache = null;
70 private transient Integer hashCodeCache = null;
74 * Returns a list of path arguments.
76 * @deprecated Use {@link #getPathArguments()} instead.
77 * @return Immutable list of path arguments.
80 public List<PathArgument> getPath() {
86 * Returns a ordered iteration of path arguments.
88 * @return Immutable iteration of path arguments.
90 public Iterable<PathArgument> getPathArguments() {
97 * @deprecated Use {@link #create(Iterable)} instead.
101 public InstanceIdentifier(final List<? extends PathArgument> path) {
102 this.path = ImmutableList.copyOf(path);
105 private InstanceIdentifier(final Iterable<? extends PathArgument> path) {
106 Preconditions.checkNotNull(path, "path must not be null.");
107 this.path = ImmutableList.copyOf(path);
110 private InstanceIdentifier(final NodeIdentifier nodeIdentifier) {
111 this.path = ImmutableList.<PathArgument> of(nodeIdentifier);
114 public static final InstanceIdentifier create(final Iterable<? extends PathArgument> path) {
115 return new InstanceIdentifier(path);
119 public int hashCode() {
121 * The hashCodeCache is safe, since the object contract requires
122 * immutability of the object and all objects referenced from this
124 * Used lists, maps are immutable. Path Arguments (elements) are also
125 * immutable, since the PathArgument contract requires immutability.
126 * The cache is thread-safe - if multiple computations occurs at the
127 * same time, cache will be overwritten with same result.
129 if (hashCodeCache == null) {
130 final int prime = 31;
132 result = prime * result + path.hashCode();
133 hashCodeCache = result;
135 return hashCodeCache;
139 public boolean equals(final Object obj) {
146 if (getClass() != obj.getClass()) {
149 InstanceIdentifier other = (InstanceIdentifier) obj;
150 if (this.hashCode() != obj.hashCode()) {
154 if (other.path != null) {
157 } else if (!path.equals(other.path)) {
165 * Constructs a new Instance Identifier with new {@link NodeIdentifier} added to the end of path arguments
167 * @param name QName of {@link NodeIdentifier}
168 * @return Instance Identifier with additional path argument added to the end.
170 public InstanceIdentifier node(final QName name) {
171 return node(new NodeIdentifier(name));
176 * Constructs a new Instance Identifier with new {@link PathArgument} added to the end of path arguments
178 * @param arg Path argument which should be added to the end
179 * @return Instance Identifier with additional path argument added to the end.
181 public InstanceIdentifier node(final PathArgument arg) {
182 return create(ImmutableList.<PathArgument> builder().addAll(path).add(arg).build());
186 * Get the relative path from an ancestor. This method attempts to perform
188 * of concatenating a base (ancestor) and a path.
191 * Ancestor against which the relative path should be calculated
192 * @return This object's relative path from parent, or Optional.absent() if
194 * specified parent is not in fact an ancestor of this object.
196 public Optional<InstanceIdentifier> relativeTo(final InstanceIdentifier ancestor) {
197 if (ancestor.contains(this)) {
198 final int common = ancestor.path.size();
199 return Optional.of(new InstanceIdentifier(path.subList(common, path.size())));
201 return Optional.absent();
205 static int hashCode(final Object value) {
210 if (value.getClass().equals(byte[].class)) {
211 return Arrays.hashCode((byte[]) value);
214 if (value.getClass().isArray()) {
216 int length = Array.getLength(value);
217 for (int i = 0; i < length; i++) {
218 hash += Objects.hashCode(Array.get(value, i));
224 return Objects.hashCode(value);
227 // Static factories & helpers
231 * Returns a new InstanceIdentifier with only one path argument of type {@link NodeIdentifier} with supplied QName
233 * @param name QName of first node identifier
234 * @return Instance Identifier with only one path argument of type {@link NodeIdentifier}
236 public static InstanceIdentifier of(final QName name) {
237 return new InstanceIdentifier(new NodeIdentifier(name));
242 * Returns new builder for InstanceIdentifier with empty path arguments.
244 * @return new builder for InstanceIdentifier with empty path arguments.
246 static public InstanceIdentifierBuilder builder() {
247 return new BuilderImpl();
252 * Returns new builder for InstanceIdentifier with path arguments copied from original instance identifier.
254 * @param origin Instace Identifier from which path arguments are copied.
255 * @return new builder for InstanceIdentifier with path arguments copied from original instance identifier.
257 static public InstanceIdentifierBuilder builder(final InstanceIdentifier origin) {
258 return new BuilderImpl(origin.getPath());
262 * Returns new builder for InstanceIdentifier with first path argument set to {@link NodeIdentifier}.
264 * @param node QName of first {@link NodeIdentifier} path argument.
265 * @return new builder for InstanceIdentifier with first path argument set to {@link NodeIdentifier}.
267 public static InstanceIdentifierBuilder builder(final QName node) {
268 return builder().node(node);
273 * Path argument / component of InstanceIdentifier
275 * Path argument uniquelly identifies node in data tree on particular
278 * This interface itself is used as common parent for actual
279 * path arguments types and should not be implemented by user code.
281 * Path arguments SHOULD contain only minimum of information
282 * required to uniquely identify node on particular subtree level.
284 * For actual path arguments types see:
286 * <li>{@link NodeIdentifier} - Identifier of container or leaf
287 * <li>{@link NodeIdentifierWithPredicates} - Identifier of list entries, which have key defined
288 * <li>{@link AugmentationIdentifier} - Identifier of augmentation
289 * <li>{@link NodeWithValue} - Identifier of leaf-list entry
295 public interface PathArgument extends Comparable<PathArgument>, Immutable, Serializable {
298 * If applicable returns unique QName of data node as defined in YANG
301 * This method may return null, if the corresponding schema node, does
302 * not have QName associated, such as in cases of augmentations.
310 private static abstract class AbstractPathArgument implements PathArgument {
311 private static final long serialVersionUID = -4546547994250849340L;
312 protected final QName nodeType;
314 protected AbstractPathArgument(final QName nodeType) {
315 this.nodeType = Preconditions.checkNotNull(nodeType);
319 public QName getNodeType() {
324 public int compareTo(final PathArgument o) {
325 return nodeType.compareTo(o.getNodeType());
332 * Fluent Builder of Instance Identifier instances
337 public interface InstanceIdentifierBuilder extends Builder<InstanceIdentifier> {
341 * Adds {@link NodeIdentifier} with supplied QName to path arguments of resulting instance identifier.
343 * @param nodeType QName of {@link NodeIdentifier} which will be added
344 * @return this builder
346 InstanceIdentifierBuilder node(QName nodeType);
350 * Adds {@link NodeIdentifierWithPredicates} with supplied QName and key values to path arguments of resulting instance identifier.
352 * @param nodeType QName of {@link NodeIdentifierWithPredicates} which will be added
353 * @param keyValues Map of key components and their respective values for {@link NodeIdentifierWithPredicates}
354 * @return this builder
356 InstanceIdentifierBuilder nodeWithKey(QName nodeType, Map<QName, Object> keyValues);
360 * Adds {@link NodeIdentifierWithPredicates} with supplied QName and key, value.
362 * @param nodeType QName of {@link NodeIdentifierWithPredicates} which will be added
363 * @param key QName of key which will be added
364 * @param value value of key which will be added
365 * @return this builder
367 InstanceIdentifierBuilder nodeWithKey(QName nodeType, QName key, Object value);
372 * @deprecated use {@link #build()}
376 InstanceIdentifier getIdentifier();
380 * Builds an {@link InstanceIdentifier} with path arguments from this builder
382 * @return {@link InstanceIdentifier}
384 InstanceIdentifier build();
388 * Simple path argument identifying a {@link org.opendaylight.yangtools.yang.data.api.schema.ContainerNode} or
389 * {@link org.opendaylight.yangtools.yang.data.api.schema.LeafNode} leaf in particular subtree.
391 public static final class NodeIdentifier extends AbstractPathArgument {
392 private static final long serialVersionUID = -2255888212390871347L;
394 public NodeIdentifier(final QName node) {
399 public int hashCode() {
400 return 31 + nodeType.hashCode();
404 public boolean equals(final Object obj) {
408 if (!(obj instanceof NodeIdentifier)) {
411 final NodeIdentifier other = (NodeIdentifier) obj;
412 return nodeType.equals(other.nodeType);
416 public String toString() {
417 return nodeType.toString();
423 * Composite path argument identifying a {@link org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode} leaf
426 public static final class NodeIdentifierWithPredicates extends AbstractPathArgument {
427 private static final long serialVersionUID = -4787195606494761540L;
429 private final Map<QName, Object> keyValues;
431 public NodeIdentifierWithPredicates(final QName node, final Map<QName, Object> keyValues) {
433 this.keyValues = ImmutableMap.copyOf(keyValues);
436 public NodeIdentifierWithPredicates(final QName node, final QName key, final Object value) {
437 this(node, ImmutableMap.of(key, value));
441 public QName getNodeType() {
445 public Map<QName, Object> getKeyValues() {
450 public int hashCode() {
451 final int prime = 31;
453 result = prime * result + ((keyValues == null) ? 0 : hashKeyValues());
454 result = prime * result + ((nodeType == null) ? 0 : nodeType.hashCode());
458 private int hashKeyValues() {
460 for (Entry<QName, Object> entry : keyValues.entrySet()) {
461 hash += Objects.hashCode(entry.getKey()) + InstanceIdentifier.hashCode(entry.getValue());
468 public boolean equals(final Object obj) {
475 if (getClass() != obj.getClass()) {
478 NodeIdentifierWithPredicates other = (NodeIdentifierWithPredicates) obj;
479 if (keyValues == null) {
480 if (other.keyValues != null) {
483 } else if (!keyValuesEquals(other.keyValues)) {
486 if (nodeType == null) {
487 if (other.nodeType != null) {
490 } else if (!nodeType.equals(other.nodeType)) {
496 private boolean keyValuesEquals(final Map<QName, Object> otherKeyValues) {
497 if (otherKeyValues == null || keyValues.size() != otherKeyValues.size()) {
501 boolean result = true;
502 for (Entry<QName, Object> entry : keyValues.entrySet()) {
503 if (!otherKeyValues.containsKey(entry.getKey())
504 || !Objects.deepEquals(entry.getValue(), otherKeyValues.get(entry.getKey()))) {
515 public String toString() {
516 return nodeType + "[" + keyValues + "]";
521 * Simple path argument identifying a {@link LeafSetEntryNode} leaf
524 public static final class NodeWithValue extends AbstractPathArgument {
525 private static final long serialVersionUID = -3637456085341738431L;
527 private final Object value;
529 public NodeWithValue(final QName node, final Object value) {
535 public QName getNodeType() {
539 public Object getValue() {
544 public int hashCode() {
545 final int prime = 31;
547 result = prime * result + ((value == null) ? 0 : InstanceIdentifier.hashCode(value));
548 result = prime * result + ((nodeType == null) ? 0 : nodeType.hashCode());
553 public boolean equals(final Object obj) {
560 if (getClass() != obj.getClass()) {
563 NodeWithValue other = (NodeWithValue) obj;
564 return Objects.deepEquals(value, other.value) && Objects.equals(nodeType, other.nodeType);
568 public String toString() {
569 return nodeType + "[" + value + "]";
575 * Composite path argument identifying a {@link org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode} node in
576 * particular subtree.
578 * Augmentation is uniquely identified by set of all possible child nodes.
580 * to identify instance of augmentation,
581 * since RFC6020 states that <code>augment</code> that augment
582 * statement must not add multiple nodes from same namespace
583 * / module to the target node.
586 * @see http://tools.ietf.org/html/rfc6020#section-7.15
588 public static final class AugmentationIdentifier implements PathArgument {
589 private static final long serialVersionUID = -8122335594681936939L;
590 private final ImmutableSet<QName> childNames;
593 public QName getNodeType() {
594 // This should rather throw exception than return always null
595 throw new UnsupportedOperationException("Augmentation node has no QName");
600 * Construct new augmentation identifier using supplied set of possible
604 * Set of possible child nodes.
606 public AugmentationIdentifier(final Set<QName> childNames) {
607 this.childNames = ImmutableSet.copyOf(childNames);
611 * Augmentation node has no QName
614 * {@link AugmentationIdentifier#AugmentationIdentifier(Set)}
618 public AugmentationIdentifier(final QName nodeType, final Set<QName> childNames) {
624 * Returns set of all possible child nodes
626 * @return set of all possible child nodes.
628 public Set<QName> getPossibleChildNames() {
633 public String toString() {
634 final StringBuffer sb = new StringBuffer("AugmentationIdentifier{");
635 sb.append("childNames=").append(childNames);
637 return sb.toString();
641 public boolean equals(final Object o) {
645 if (!(o instanceof AugmentationIdentifier)) {
649 AugmentationIdentifier that = (AugmentationIdentifier) o;
651 if (!childNames.equals(that.childNames)) {
659 public int hashCode() {
660 return childNames.hashCode();
664 public int compareTo(PathArgument o) {
665 if (!(o instanceof AugmentationIdentifier)) {
668 AugmentationIdentifier other = (AugmentationIdentifier) o;
669 Set<QName> otherChildNames = other.getPossibleChildNames();
670 int thisSize = childNames.size();
671 int otherSize = otherChildNames.size();
672 if (thisSize == otherSize) {
673 Iterator<QName> otherIterator = otherChildNames.iterator();
674 for (QName name : childNames) {
675 int c = name.compareTo(otherIterator.next());
681 } else if (thisSize < otherSize) {
689 private static class BuilderImpl implements InstanceIdentifierBuilder {
691 private final ImmutableList.Builder<PathArgument> path;
693 public BuilderImpl() {
694 path = ImmutableList.<PathArgument> builder();
697 public BuilderImpl(final List<? extends PathArgument> prefix) {
698 path = ImmutableList.<PathArgument> builder();
703 public InstanceIdentifierBuilder node(final QName nodeType) {
704 path.add(new NodeIdentifier(nodeType));
709 public InstanceIdentifierBuilder nodeWithKey(final QName nodeType, final QName key, final Object value) {
710 path.add(new NodeIdentifierWithPredicates(nodeType, key, value));
715 public InstanceIdentifierBuilder nodeWithKey(final QName nodeType, final Map<QName, Object> keyValues) {
716 path.add(new NodeIdentifierWithPredicates(nodeType, keyValues));
722 public InstanceIdentifier toInstance() {
727 public InstanceIdentifier build() {
728 return new InstanceIdentifier(path.build());
733 public InstanceIdentifier getIdentifier() {
739 public boolean contains(final InstanceIdentifier other) {
741 throw new IllegalArgumentException("other should not be null");
743 final int localSize = this.path.size();
744 final List<PathArgument> otherPath = other.getPath();
745 if (localSize > other.path.size()) {
748 for (int i = 0; i < localSize; i++) {
749 if (!path.get(i).equals(otherPath.get(i))) {
757 public String toString() {
759 * The toStringCache is safe, since the object contract requires
760 * immutability of the object and all objects referenced from this
762 * Used lists, maps are immutable. Path Arguments (elements) are also
763 * immutable, since the PathArgument contract requires immutability.
764 * The cache is thread-safe - if multiple computations occurs at the
765 * same time, cache will be overwritten with same result.
767 if (toStringCache != null) {
768 return toStringCache;
771 final StringBuilder builder = new StringBuilder('/');
772 boolean first = true;
773 for (PathArgument argument : path) {
779 builder.append(argument.toString());
782 toStringCache = builder.toString();
783 return toStringCache;