2 * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.yangtools.yang.data.api;
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static com.google.common.base.Verify.verify;
12 import static java.util.Objects.requireNonNull;
14 import com.google.common.annotations.Beta;
15 import com.google.common.base.VerifyException;
16 import com.google.common.cache.CacheBuilder;
17 import com.google.common.cache.CacheLoader;
18 import com.google.common.cache.LoadingCache;
19 import com.google.common.collect.ImmutableList;
20 import com.google.common.collect.ImmutableMap;
21 import com.google.common.collect.ImmutableSet;
22 import com.google.common.collect.Iterables;
23 import com.google.common.collect.Sets;
24 import java.io.Serializable;
25 import java.lang.invoke.MethodHandles;
26 import java.lang.invoke.VarHandle;
27 import java.lang.reflect.Array;
28 import java.util.AbstractMap.SimpleImmutableEntry;
29 import java.util.ArrayList;
30 import java.util.Arrays;
31 import java.util.Collection;
32 import java.util.Deque;
33 import java.util.Iterator;
34 import java.util.List;
36 import java.util.Map.Entry;
37 import java.util.Objects;
38 import java.util.Optional;
40 import java.util.function.Function;
41 import org.eclipse.jdt.annotation.NonNull;
42 import org.eclipse.jdt.annotation.Nullable;
43 import org.opendaylight.yangtools.concepts.HierarchicalIdentifier;
44 import org.opendaylight.yangtools.concepts.Immutable;
45 import org.opendaylight.yangtools.concepts.Mutable;
46 import org.opendaylight.yangtools.util.ImmutableOffsetMap;
47 import org.opendaylight.yangtools.util.SingletonSet;
48 import org.opendaylight.yangtools.yang.common.QName;
49 import org.opendaylight.yangtools.yang.common.QNameModule;
50 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
53 * Unique identifier of a particular node instance in the data tree.
56 * Java representation of YANG Built-in type {@code instance-identifier}, which conceptually is XPath expression
57 * minimized to uniquely identify element in data tree which conforms to constraints maintained by YANG Model,
58 * effectively this makes Instance Identifier a path to element in data tree.
61 * Constraints put in YANG specification on instance-identifier allowed it to be effectively represented in Java and its
62 * evaluation does not require a full-blown XPath processor.
64 * <h2>Path Arguments</h2>
65 * Path to the node represented in instance identifier consists of {@link PathArgument} which carries necessary
66 * information to uniquely identify node on particular level in the subtree.
69 * <li>{@link NodeIdentifier} - Identifier of node, which has cardinality {@code 0..1} in particular subtree in data
71 * <li>{@link NodeIdentifierWithPredicates} - Identifier of node (list item), which has cardinality {@code 0..n}</li>
72 * <li>{@link NodeWithValue} - Identifier of instance {@code leaf} node or {@code leaf-list} node</li>
73 * <li>{@link AugmentationIdentifier} - Identifier of instance of {@code augmentation} node</li>
76 * @see <a href="http://tools.ietf.org/html/rfc6020#section-9.13">RFC6020</a>
78 public abstract sealed class YangInstanceIdentifier implements HierarchicalIdentifier<YangInstanceIdentifier>
79 permits FixedYangInstanceIdentifier, StackedYangInstanceIdentifier {
80 private static final long serialVersionUID = 4L;
81 private static final VarHandle TO_STRING_CACHE;
82 private static final VarHandle HASH;
85 final var lookup = MethodHandles.lookup();
87 HASH = lookup.findVarHandle(YangInstanceIdentifier.class, "hash", int.class);
88 TO_STRING_CACHE = lookup.findVarHandle(YangInstanceIdentifier.class, "toStringCache", String.class);
89 } catch (NoSuchFieldException | IllegalAccessException e) {
90 throw new ExceptionInInitializerError(e);
94 @SuppressWarnings("unused")
96 @SuppressWarnings("unused")
97 private transient String toStringCache = null;
99 YangInstanceIdentifier() {
100 // Package-private to prevent outside subclassing
104 * Return An empty {@link YangInstanceIdentifier}. It corresponds to the path of the conceptual root of the YANG
107 * @return An empty YangInstanceIdentifier
109 public static @NonNull YangInstanceIdentifier empty() {
110 return FixedYangInstanceIdentifier.EMPTY_INSTANCE;
113 abstract @NonNull YangInstanceIdentifier createRelativeIdentifier(int skipFromRoot);
115 abstract @Nullable List<PathArgument> tryPathArguments();
117 abstract @Nullable List<PathArgument> tryReversePathArguments();
120 * Check if this instance identifier has empty path arguments, e.g. it is
121 * empty and corresponds to {@link #empty()}.
123 * @return True if this instance identifier is empty, false otherwise.
125 public abstract boolean isEmpty();
128 * Return an optimized version of this identifier, useful when the identifier
129 * will be used very frequently.
131 * @return A optimized equivalent instance.
133 public abstract @NonNull YangInstanceIdentifier toOptimized();
136 * Return the conceptual parent {@link YangInstanceIdentifier}, which has
137 * one item less in {@link #getPathArguments()}.
139 * @return Parent {@link YangInstanceIdentifier}, or null if this object is {@link #empty()}.
141 public abstract @Nullable YangInstanceIdentifier getParent();
144 * Return the conceptual parent {@link YangInstanceIdentifier}, which has one item less in
145 * {@link #getPathArguments()}.
147 * @return Parent {@link YangInstanceIdentifier}
148 * @throws VerifyException if this object is {@link #empty()}.
150 public abstract @NonNull YangInstanceIdentifier coerceParent();
153 * Return the ancestor {@link YangInstanceIdentifier} with a particular depth, e.g. number of path arguments.
155 * @param depth Ancestor depth
156 * @return Ancestor {@link YangInstanceIdentifier}
157 * @throws IllegalArgumentException if the specified depth is negative or is greater than the depth of this object.
159 public abstract @NonNull YangInstanceIdentifier getAncestor(int depth);
162 * Returns an ordered iteration of path arguments.
164 * @return Immutable iteration of path arguments.
166 public abstract @NonNull List<PathArgument> getPathArguments();
169 * Returns an iterable of path arguments in reverse order. This is useful
170 * when walking up a tree organized this way.
172 * @return Immutable iterable of path arguments in reverse order.
174 public abstract @NonNull List<PathArgument> getReversePathArguments();
177 * Returns the last PathArgument. This is equivalent of iterating
178 * to the last element of the iterable returned by {@link #getPathArguments()}.
180 * @return The last past argument, or null if there are no PathArguments.
182 public abstract PathArgument getLastPathArgument();
184 public static @NonNull YangInstanceIdentifier create(final Iterable<? extends PathArgument> path) {
185 return Iterables.isEmpty(path) ? empty() : new FixedYangInstanceIdentifier(ImmutableList.copyOf(path));
188 public static @NonNull YangInstanceIdentifier create(final PathArgument pathArgument) {
189 return new FixedYangInstanceIdentifier(ImmutableList.of(pathArgument));
192 public static @NonNull YangInstanceIdentifier create(final PathArgument... path) {
193 // We are forcing a copy, since we cannot trust the user
194 return create(Arrays.asList(path));
198 * Create a {@link YangInstanceIdentifier} by taking a snapshot of provided path and iterating it backwards.
200 * @param pathTowardsRoot Path towards root
201 * @return A {@link YangInstanceIdentifier} instance
202 * @throws NullPointerException if {@code pathTowardsRoot} or any of its members is null
204 public static @NonNull YangInstanceIdentifier createReverse(final Deque<PathArgument> pathTowardsRoot) {
205 final ImmutableList.Builder<PathArgument> builder = ImmutableList.builderWithExpectedSize(
206 pathTowardsRoot.size());
207 pathTowardsRoot.descendingIterator().forEachRemaining(builder::add);
208 return YangInstanceIdentifier.create(builder.build());
212 * Create a {@link YangInstanceIdentifier} by walking specified stack backwards and extracting path components
215 * @param stackTowardsRoot Stack towards root,
216 * @return A {@link YangInstanceIdentifier} instance
217 * @throws NullPointerException if {@code pathTowardsRoot} is null
219 public static <T> @NonNull YangInstanceIdentifier createReverse(final Deque<? extends T> stackTowardsRoot,
220 final Function<T, PathArgument> function) {
221 final ImmutableList.Builder<PathArgument> builder = ImmutableList.builderWithExpectedSize(
222 stackTowardsRoot.size());
223 final Iterator<? extends T> it = stackTowardsRoot.descendingIterator();
224 while (it.hasNext()) {
225 builder.add(function.apply(it.next()));
227 return YangInstanceIdentifier.create(builder.build());
230 boolean pathArgumentsEqual(final YangInstanceIdentifier other) {
231 return getPathArguments().equals(other.getPathArguments());
235 public final boolean equals(final Object obj) {
236 return this == obj || obj instanceof YangInstanceIdentifier other && pathArgumentsEqual(other);
240 * Constructs a new Instance Identifier with new {@link NodeIdentifier} added to the end of path arguments.
242 * @param name QName of {@link NodeIdentifier}
243 * @return Instance Identifier with additional path argument added to the end.
245 public final @NonNull YangInstanceIdentifier node(final QName name) {
246 return node(new NodeIdentifier(name));
250 * Constructs a new Instance Identifier with new {@link PathArgument} added to the end of path arguments.
252 * @param arg Path argument which should be added to the end
253 * @return Instance Identifier with additional path argument added to the end.
255 public final @NonNull YangInstanceIdentifier node(final PathArgument arg) {
256 return new StackedYangInstanceIdentifier(this, arg);
260 * Get the relative path from an ancestor. This method attempts to perform
261 * the reverse of concatenating a base (ancestor) and a path.
264 * Ancestor against which the relative path should be calculated
265 * @return This object's relative path from parent, or Optional.absent() if
266 * the specified parent is not in fact an ancestor of this object.
268 public Optional<YangInstanceIdentifier> relativeTo(final YangInstanceIdentifier ancestor) {
269 if (this == ancestor) {
270 return Optional.of(empty());
272 if (ancestor.isEmpty()) {
273 return Optional.of(this);
276 final Iterator<PathArgument> lit = getPathArguments().iterator();
277 final Iterator<PathArgument> oit = ancestor.getPathArguments().iterator();
280 while (oit.hasNext()) {
281 // Ancestor is not really an ancestor
282 if (!lit.hasNext() || !lit.next().equals(oit.next())) {
283 return Optional.empty();
290 return Optional.of(this);
292 if (!lit.hasNext()) {
293 return Optional.of(empty());
296 return Optional.of(createRelativeIdentifier(common));
300 public final boolean contains(final YangInstanceIdentifier other) {
305 checkArgument(other != null, "other should not be null");
306 final Iterator<PathArgument> lit = getPathArguments().iterator();
307 final Iterator<PathArgument> oit = other.getPathArguments().iterator();
309 while (lit.hasNext()) {
310 if (!oit.hasNext()) {
314 if (!lit.next().equals(oit.next())) {
323 public final String toString() {
325 * The toStringCache is safe, since the object contract requires
326 * immutability of the object and all objects referenced from this
328 * Used lists, maps are immutable. Path Arguments (elements) are also
329 * immutable, since the PathArgument contract requires immutability.
330 * The cache is thread-safe - if multiple computations occurs at the
331 * same time, cache will be overwritten with same result.
333 final String ret = (String) TO_STRING_CACHE.getAcquire(this);
334 return ret != null ? ret : loadToString();
337 private String loadToString() {
338 final StringBuilder builder = new StringBuilder("/");
339 PathArgument prev = null;
340 for (PathArgument argument : getPathArguments()) {
344 builder.append(argument.toRelativeString(prev));
348 final String ret = builder.toString();
349 final String witness = (String) TO_STRING_CACHE.compareAndExchangeRelease(this, null, ret);
350 return witness == null ? ret : witness;
354 public final int hashCode() {
356 * The caching is safe, since the object contract requires
357 * immutability of the object and all objects referenced from this
359 * Used lists, maps are immutable. Path Arguments (elements) are also
360 * immutable, since the PathArgument contract requires immutability.
362 final int local = (int) HASH.getAcquire(this);
363 return local != 0 ? local : loadHashCode();
366 private static int hashCode(final Object value) {
371 if (byte[].class.equals(value.getClass())) {
372 return Arrays.hashCode((byte[]) value);
375 if (value.getClass().isArray()) {
377 int length = Array.getLength(value);
378 for (int i = 0; i < length; i++) {
379 hash += Objects.hashCode(Array.get(value, i));
385 return Objects.hashCode(value);
388 private int loadHashCode() {
389 final int computed = computeHashCode();
390 HASH.setRelease(this, computed);
394 abstract int computeHashCode();
396 final Object writeReplace() {
397 return new YIDv1(this);
400 // Static factories & helpers
403 * Returns a new InstanceIdentifier with only one path argument of type {@link NodeIdentifier} with supplied
406 * @param name QName of first node identifier
407 * @return Instance Identifier with only one path argument of type {@link NodeIdentifier}
409 public static @NonNull YangInstanceIdentifier of(final QName name) {
410 return create(new NodeIdentifier(name));
414 * Returns new builder for InstanceIdentifier with empty path arguments.
416 * @return new builder for InstanceIdentifier with empty path arguments.
418 public static @NonNull InstanceIdentifierBuilder builder() {
419 return new YangInstanceIdentifierBuilder();
423 * Returns new builder for InstanceIdentifier with path arguments copied from original instance identifier.
425 * @param origin InstanceIdentifier from which path arguments are copied.
426 * @return new builder for InstanceIdentifier with path arguments copied from original instance identifier.
428 public static @NonNull InstanceIdentifierBuilder builder(final YangInstanceIdentifier origin) {
429 return new YangInstanceIdentifierBuilder(origin.getPathArguments());
433 * Path argument / component of InstanceIdentifier.
434 * Path argument uniquely identifies node in data tree on particular
438 * This interface itself is used as common parent for actual
439 * path arguments types and should not be implemented by user code.
442 * Path arguments SHOULD contain only minimum of information
443 * required to uniquely identify node on particular subtree level.
446 * For actual path arguments types see:
448 * <li>{@link NodeIdentifier} - Identifier of container or leaf
449 * <li>{@link NodeIdentifierWithPredicates} - Identifier of list entries, which have key defined
450 * <li>{@link AugmentationIdentifier} - Identifier of augmentation
451 * <li>{@link NodeWithValue} - Identifier of leaf-list entry
454 public interface PathArgument extends Comparable<PathArgument>, Immutable, Serializable {
456 * Returns unique QName of data node as defined in YANG Schema, if available.
459 * @throws UnsupportedOperationException if node type is not applicable, for example in case of an augmentation.
461 @NonNull QName getNodeType();
464 * Return the string representation of this object for use in context
465 * provided by a previous object. This method can be implemented in
466 * terms of {@link #toString()}, but implementations are encourage to
467 * reuse any context already emitted by the previous object.
469 * @param previous Previous path argument
470 * @return String representation
472 @NonNull String toRelativeString(PathArgument previous);
475 private abstract static class AbstractPathArgument implements PathArgument {
476 private static final long serialVersionUID = -4546547994250849340L;
477 private final @NonNull QName nodeType;
478 private transient volatile int hashValue;
480 protected AbstractPathArgument(final QName nodeType) {
481 this.nodeType = requireNonNull(nodeType);
485 public final QName getNodeType() {
490 @SuppressWarnings("checkstyle:parameterName")
491 public int compareTo(final PathArgument o) {
492 return nodeType.compareTo(o.getNodeType());
495 protected int hashCodeImpl() {
496 return nodeType.hashCode();
500 public final int hashCode() {
502 return (local = hashValue) != 0 ? local : (hashValue = hashCodeImpl());
506 public boolean equals(final Object obj) {
510 if (obj == null || this.getClass() != obj.getClass()) {
514 return getNodeType().equals(((AbstractPathArgument)obj).getNodeType());
518 public String toString() {
519 return getNodeType().toString();
523 public String toRelativeString(final PathArgument previous) {
524 if (previous instanceof AbstractPathArgument) {
525 final QNameModule mod = previous.getNodeType().getModule();
526 if (getNodeType().getModule().equals(mod)) {
527 return getNodeType().getLocalName();
531 return getNodeType().toString();
534 abstract Object writeReplace();
538 * Simple path argument identifying a {@link org.opendaylight.yangtools.yang.data.api.schema.ContainerNode} or
539 * {@link org.opendaylight.yangtools.yang.data.api.schema.LeafNode} leaf in particular subtree.
541 public static final class NodeIdentifier extends AbstractPathArgument {
542 private static final long serialVersionUID = -2255888212390871347L;
543 private static final LoadingCache<QName, NodeIdentifier> CACHE = CacheBuilder.newBuilder().weakValues()
544 .build(new CacheLoader<QName, NodeIdentifier>() {
546 public NodeIdentifier load(final QName key) {
547 return new NodeIdentifier(key);
551 public NodeIdentifier(final QName node) {
556 * Return a NodeIdentifier for a particular QName. Unlike the constructor, this factory method uses a global
557 * instance cache, resulting in object reuse for equal inputs.
559 * @param node Node's QName
560 * @return A {@link NodeIdentifier}
562 public static @NonNull NodeIdentifier create(final QName node) {
563 return CACHE.getUnchecked(node);
567 Object writeReplace() {
568 return new NIv1(this);
573 * Composite path argument identifying a {@link org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode} leaf
576 public abstract static class NodeIdentifierWithPredicates extends AbstractPathArgument {
578 public static final class Singleton extends NodeIdentifierWithPredicates {
579 private static final long serialVersionUID = 1L;
581 private final @NonNull QName key;
582 private final @NonNull Object value;
584 Singleton(final QName node, final QName key, final Object value) {
586 this.key = requireNonNull(key);
587 this.value = requireNonNull(value);
591 public SingletonSet<Entry<QName, Object>> entrySet() {
592 return SingletonSet.of(singleEntry());
596 public SingletonSet<QName> keySet() {
597 return SingletonSet.of(key);
601 public boolean containsKey(final QName qname) {
602 return key.equals(requireNonNull(qname));
606 public SingletonSet<Object> values() {
607 return SingletonSet.of(value);
616 public ImmutableMap<QName, Object> asMap() {
617 return ImmutableMap.of(key, value);
621 * Return the single entry contained in this object. This is equivalent to
622 * {@code entrySet().iterator().next()}.
624 * @return A single entry.
626 public @NonNull Entry<QName, Object> singleEntry() {
627 return new SimpleImmutableEntry<>(key, value);
631 boolean equalMapping(final NodeIdentifierWithPredicates other) {
632 final Singleton single = (Singleton) other;
633 return key.equals(single.key) && Objects.deepEquals(value, single.value);
637 Object keyValue(final QName qname) {
638 return key.equals(qname) ? value : null;
642 private static final class Regular extends NodeIdentifierWithPredicates {
643 private static final long serialVersionUID = 1L;
645 private final @NonNull Map<QName, Object> keyValues;
647 Regular(final QName node, final Map<QName, Object> keyValues) {
649 this.keyValues = requireNonNull(keyValues);
653 public Set<Entry<QName, Object>> entrySet() {
654 return keyValues.entrySet();
658 public Set<QName> keySet() {
659 return keyValues.keySet();
663 public boolean containsKey(final QName qname) {
664 return keyValues.containsKey(requireNonNull(qname));
668 public Collection<Object> values() {
669 return keyValues.values();
674 return keyValues.size();
678 public Map<QName, Object> asMap() {
683 Object keyValue(final QName qname) {
684 return keyValues.get(qname);
688 boolean equalMapping(final NodeIdentifierWithPredicates other) {
689 final Map<QName, Object> otherKeyValues = ((Regular) other).keyValues;
690 // TODO: benchmark to see if just calling equals() on the two maps is not faster
691 if (keyValues == otherKeyValues) {
694 if (keyValues.size() != otherKeyValues.size()) {
698 for (Entry<QName, Object> entry : entrySet()) {
699 final Object otherValue = otherKeyValues.get(entry.getKey());
700 if (otherValue == null || !Objects.deepEquals(entry.getValue(), otherValue)) {
709 private static final long serialVersionUID = -4787195606494761540L;
711 NodeIdentifierWithPredicates(final QName node) {
715 public static @NonNull NodeIdentifierWithPredicates of(final QName node) {
716 return new Regular(node, ImmutableMap.of());
719 public static @NonNull NodeIdentifierWithPredicates of(final QName node, final QName key, final Object value) {
720 return new Singleton(node, key, value);
723 public static @NonNull NodeIdentifierWithPredicates of(final QName node, final Entry<QName, Object> entry) {
724 return of(node, entry.getKey(), entry.getValue());
727 public static @NonNull NodeIdentifierWithPredicates of(final QName node, final Map<QName, Object> keyValues) {
728 return keyValues.size() == 1 ? of(keyValues, node)
729 // Retains ImmutableMap for empty maps. For larger sizes uses a shared key set.
730 : new Regular(node, ImmutableOffsetMap.unorderedCopyOf(keyValues));
733 public static @NonNull NodeIdentifierWithPredicates of(final QName node,
734 final ImmutableOffsetMap<QName, Object> keyValues) {
735 return keyValues.size() == 1 ? of(keyValues, node) : new Regular(node, keyValues);
738 private static @NonNull NodeIdentifierWithPredicates of(final Map<QName, Object> keyValues, final QName node) {
739 return of(node, keyValues.entrySet().iterator().next());
743 * Return the set of predicates keys and values. Keys are guaranteeed to be unique.
745 * @return Predicate set.
747 public abstract @NonNull Set<Entry<QName, Object>> entrySet();
750 * Return the predicate key in the iteration order of {@link #entrySet()}.
752 * @return Predicate values.
754 public abstract @NonNull Set<QName> keySet();
757 * Determine whether a particular predicate key is present.
759 * @param key Predicate key
760 * @return True if the predicate is present, false otherwise
761 * @throws NullPointerException if {@code key} is null
763 public abstract boolean containsKey(QName key);
766 * Return the predicate values in the iteration order of {@link #entrySet()}.
768 * @return Predicate values.
770 public abstract @NonNull Collection<Object> values();
773 public final @Nullable Object getValue(final QName key) {
774 return keyValue(requireNonNull(key));
778 public final <T> @Nullable T getValue(final QName key, final Class<T> valueClass) {
779 return valueClass.cast(getValue(key));
783 * Return the number of predicates present.
785 * @return The number of predicates present.
787 public abstract int size();
790 * A Map-like view of this identifier's predicates. The view is expected to be stable and effectively-immutable.
792 * @return Map of predicates.
795 public abstract @NonNull Map<QName, Object> asMap();
798 protected final int hashCodeImpl() {
799 int result = 31 * super.hashCodeImpl();
800 for (Entry<QName, Object> entry : entrySet()) {
801 result += entry.getKey().hashCode() + YangInstanceIdentifier.hashCode(entry.getValue());
807 @SuppressWarnings("checkstyle:equalsHashCode")
808 public final boolean equals(final Object obj) {
809 return super.equals(obj) && equalMapping((NodeIdentifierWithPredicates) obj);
812 abstract boolean equalMapping(NodeIdentifierWithPredicates other);
814 abstract @Nullable Object keyValue(@NonNull QName qname);
817 public final String toString() {
818 return super.toString() + '[' + asMap() + ']';
822 public final String toRelativeString(final PathArgument previous) {
823 return super.toRelativeString(previous) + '[' + asMap() + ']';
827 final Object writeReplace() {
828 return new NIPv2(this);
833 * Simple path argument identifying a {@link LeafSetEntryNode} leaf
836 public static final class NodeWithValue<T> extends AbstractPathArgument {
837 private static final long serialVersionUID = -3637456085341738431L;
839 private final @NonNull T value;
841 public NodeWithValue(final QName node, final T value) {
843 this.value = requireNonNull(value);
846 public @NonNull T getValue() {
851 protected int hashCodeImpl() {
852 return 31 * super.hashCodeImpl() + YangInstanceIdentifier.hashCode(value);
856 @SuppressWarnings("checkstyle:equalsHashCode")
857 public boolean equals(final Object obj) {
858 if (!super.equals(obj)) {
861 final NodeWithValue<?> other = (NodeWithValue<?>) obj;
862 return Objects.deepEquals(value, other.value);
866 public String toString() {
867 return super.toString() + '[' + value + ']';
871 public String toRelativeString(final PathArgument previous) {
872 return super.toRelativeString(previous) + '[' + value + ']';
876 Object writeReplace() {
877 return new NIVv1(this);
882 * Composite path argument identifying a {@link org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode}
883 * node in particular subtree.
886 * Augmentation is uniquely identified by set of all possible child nodes.
888 * to identify instance of augmentation,
889 * since RFC6020 states that <code>augment</code> that augment
890 * statement must not add multiple nodes from same namespace
891 * / module to the target node.
893 * @see <a href="http://tools.ietf.org/html/rfc6020#section-7.15">RFC6020</a>
895 public static final class AugmentationIdentifier implements PathArgument {
896 private static final long serialVersionUID = -8122335594681936939L;
898 private static final LoadingCache<ImmutableSet<QName>, AugmentationIdentifier> CACHE = CacheBuilder.newBuilder()
899 .weakValues().build(new CacheLoader<ImmutableSet<QName>, AugmentationIdentifier>() {
901 public AugmentationIdentifier load(final ImmutableSet<QName> key) {
902 return new AugmentationIdentifier(key);
906 private final @NonNull ImmutableSet<QName> childNames;
909 public QName getNodeType() {
910 // This should rather throw exception than return always null
911 throw new UnsupportedOperationException("Augmentation node has no QName");
915 * Construct new augmentation identifier using supplied set of possible
919 * Set of possible child nodes.
921 public AugmentationIdentifier(final ImmutableSet<QName> childNames) {
922 this.childNames = requireNonNull(childNames);
926 * Construct new augmentation identifier using supplied set of possible
930 * Set of possible child nodes.
932 public AugmentationIdentifier(final Set<QName> childNames) {
933 this.childNames = ImmutableSet.copyOf(childNames);
937 * Return an AugmentationIdentifier for a particular set of QNames. Unlike the constructor, this factory method
938 * uses a global instance cache, resulting in object reuse for equal inputs.
940 * @param childNames Set of possible child nodes
941 * @return An {@link AugmentationIdentifier}
943 public static @NonNull AugmentationIdentifier create(final ImmutableSet<QName> childNames) {
944 return CACHE.getUnchecked(childNames);
948 * Return an AugmentationIdentifier for a particular set of QNames. Unlike the constructor, this factory method
949 * uses a global instance cache, resulting in object reuse for equal inputs.
951 * @param childNames Set of possible child nodes
952 * @return An {@link AugmentationIdentifier}
954 public static @NonNull AugmentationIdentifier create(final Set<QName> childNames) {
955 final AugmentationIdentifier existing = CACHE.getIfPresent(childNames);
956 return existing != null ? existing : create(ImmutableSet.copyOf(childNames));
960 * Returns set of all possible child nodes.
962 * @return set of all possible child nodes.
964 public @NonNull Set<QName> getPossibleChildNames() {
969 public String toString() {
970 return "AugmentationIdentifier{" + "childNames=" + childNames + '}';
974 public String toRelativeString(final PathArgument previous) {
979 public boolean equals(final Object obj) {
980 return this == obj || obj instanceof AugmentationIdentifier other && childNames.equals(other.childNames);
984 public int hashCode() {
985 return childNames.hashCode();
989 @SuppressWarnings("checkstyle:parameterName")
990 public int compareTo(final PathArgument o) {
991 if (!(o instanceof AugmentationIdentifier other)) {
994 Set<QName> otherChildNames = other.getPossibleChildNames();
995 int thisSize = childNames.size();
996 int otherSize = otherChildNames.size();
997 if (thisSize == otherSize) {
998 // Quick Set-based comparison
999 if (childNames.equals(otherChildNames)) {
1003 // We already know the sets are not equal, but have equal size, hence the sets differ in their elements,
1004 // but potentially share a common set of elements. The most consistent way of comparing them is using
1005 // total ordering defined by QName's compareTo. Hence convert both sets to lists ordered
1006 // by QName.compareTo() and decide on the first differing element.
1007 final List<QName> diff = new ArrayList<>(Sets.symmetricDifference(childNames, otherChildNames));
1008 verify(!diff.isEmpty(), "Augmentation identifiers %s and %s report no difference", this, o);
1009 diff.sort(QName::compareTo);
1010 return childNames.contains(diff.get(0)) ? -1 : 1;
1011 } else if (thisSize < otherSize) {
1018 private Object writeReplace() {
1019 return new AIv1(this);
1024 * Fluent Builder of Instance Identifier instances.
1026 public interface InstanceIdentifierBuilder extends Mutable {
1028 * Adds a {@link PathArgument} to path arguments of resulting instance identifier.
1030 * @param arg A {@link PathArgument} to be added
1031 * @return this builder
1033 @NonNull InstanceIdentifierBuilder node(PathArgument arg);
1036 * Adds {@link NodeIdentifier} with supplied QName to path arguments of resulting instance identifier.
1038 * @param nodeType QName of {@link NodeIdentifier} which will be added
1039 * @return this builder
1041 @NonNull InstanceIdentifierBuilder node(QName nodeType);
1044 * Adds {@link NodeIdentifierWithPredicates} with supplied QName and key values to path arguments of resulting
1045 * instance identifier.
1047 * @param nodeType QName of {@link NodeIdentifierWithPredicates} which will be added
1048 * @param keyValues Map of key components and their respective values for {@link NodeIdentifierWithPredicates}
1049 * @return this builder
1051 @NonNull InstanceIdentifierBuilder nodeWithKey(QName nodeType, Map<QName, Object> keyValues);
1054 * Adds {@link NodeIdentifierWithPredicates} with supplied QName and key, value.
1056 * @param nodeType QName of {@link NodeIdentifierWithPredicates} which will be added
1057 * @param key QName of key which will be added
1058 * @param value value of key which will be added
1059 * @return this builder
1061 @NonNull InstanceIdentifierBuilder nodeWithKey(QName nodeType, QName key, Object value);
1064 * Adds a collection of {@link PathArgument}s to path arguments of resulting instance identifier.
1066 * @param args {@link PathArgument}s to be added
1067 * @return this builder
1068 * @throws NullPointerException if any of the arguments is null
1070 @NonNull InstanceIdentifierBuilder append(Collection<? extends PathArgument> args);
1073 * Adds a collection of {@link PathArgument}s to path arguments of resulting instance identifier.
1075 * @param args {@link PathArgument}s to be added
1076 * @return this builder
1077 * @throws NullPointerException if any of the arguments is null
1079 default @NonNull InstanceIdentifierBuilder append(final PathArgument... args) {
1080 return append(Arrays.asList(args));
1084 * Builds an {@link YangInstanceIdentifier} with path arguments from this builder.
1086 * @return {@link YangInstanceIdentifier}
1088 @NonNull YangInstanceIdentifier build();