Merge "Bug 3016 : This manual serialization of ImmutableList was done because in...
[yangtools.git] / yang / yang-data-api / src / main / java / org / opendaylight / yangtools / yang / data / api / YangInstanceIdentifier.java
1 /*
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
6  */
7 package org.opendaylight.yangtools.yang.data.api;
8
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 com.google.common.collect.Iterables;
15 import com.google.common.collect.Lists;
16 import java.io.IOException;
17 import java.io.ObjectInputStream;
18 import java.io.ObjectOutputStream;
19 import java.io.ObjectStreamException;
20 import java.io.Serializable;
21 import java.lang.reflect.Array;
22 import java.lang.reflect.Field;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.Iterator;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Map.Entry;
31 import java.util.Objects;
32 import java.util.Set;
33 import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
34 import org.opendaylight.yangtools.concepts.Builder;
35 import org.opendaylight.yangtools.concepts.Immutable;
36 import org.opendaylight.yangtools.concepts.Path;
37 import org.opendaylight.yangtools.util.HashCodeBuilder;
38 import org.opendaylight.yangtools.yang.common.QName;
39 import org.opendaylight.yangtools.yang.common.QNameModule;
40 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
41
42 /**
43  * Unique identifier of a particular node instance in the data tree.
44  *
45  * <p>
46  * Java representation of YANG Built-in type <code>instance-identifier</code>,
47  * which conceptually is XPath expression minimized to uniquely identify element
48  * in data tree which conforms to constraints maintained by YANG Model,
49  * effectively this makes Instance Identifier a path to element in data tree.
50  * <p>
51  * Constraints put in YANG specification on instance-identifier allowed it to be
52  * effectively represented in Java and it's evaluation does not require
53  * full-blown XPath processor.
54  * <p>
55  * <h3>Path Arguments</h3>
56  * Path to the node represented in instance identifier consists of
57  * {@link PathArgument} which carries necessary information to uniquely identify
58  * node on particular level in the subtree.
59  * <p>
60  * <ul>
61  * <li>{@link NodeIdentifier} - Identifier of node, which has cardinality
62  * <code>0..1</code> in particular subtree in data tree.</li>
63  * <li>{@link NodeIdentifierWithPredicates} - Identifier of node (list item),
64  * which has cardinality <code>0..n</code>.</li>
65  * <li>{@link NodeWithValue} - Identifier of instance <code>leaf</code> node or
66  * <code>leaf-list</code> node.</li>
67  * <li>{@link AugmentationIdentifier} - Identifier of instance of
68  * <code>augmentation</code> node.</li>
69  * </ul>
70  *
71  *
72  * @see <a href="http://tools.ietf.org/html/rfc6020#section-9.13">RFC6020</a>
73  */
74 public final class YangInstanceIdentifier implements Path<YangInstanceIdentifier>, Immutable, Serializable {
75     /**
76      * An empty {@link YangInstanceIdentifier}. It corresponds to the path of the conceptual
77      * root of the YANG namespace.
78      */
79     public static final YangInstanceIdentifier EMPTY = trustedCreate(Collections.<PathArgument>emptyList());
80     @SuppressWarnings("rawtypes")
81     private static final AtomicReferenceFieldUpdater<YangInstanceIdentifier, ImmutableList> LEGACYPATH_UPDATER =
82             AtomicReferenceFieldUpdater.newUpdater(YangInstanceIdentifier.class, ImmutableList.class, "legacyPath");
83     private static final AtomicReferenceFieldUpdater<YangInstanceIdentifier, String> TOSTRINGCACHE_UPDATER =
84             AtomicReferenceFieldUpdater.newUpdater(YangInstanceIdentifier.class, String.class, "toStringCache");
85     private static final Field PATHARGUMENTS_FIELD;
86
87     private static final long serialVersionUID = 3L;
88     private final transient Iterable<PathArgument> pathArguments;
89     private final int hash;
90
91     private transient volatile ImmutableList<PathArgument> legacyPath = null;
92     private transient volatile String toStringCache = null;
93
94     static {
95         final Field f;
96         try {
97             f = YangInstanceIdentifier.class.getDeclaredField("pathArguments");
98         } catch (NoSuchFieldException | SecurityException e) {
99             throw new ExceptionInInitializerError(e);
100         }
101         f.setAccessible(true);
102
103         PATHARGUMENTS_FIELD = f;
104     }
105
106     private ImmutableList<PathArgument> getLegacyPath() {
107         // Temporary variable saves a volatile read
108         ImmutableList<PathArgument> ret = legacyPath;
109         if (ret == null) {
110             // We could have used a synchronized block, but the window is quite
111             // small and worst that can happen is duplicate object construction.
112             ret = ImmutableList.copyOf(pathArguments);
113             LEGACYPATH_UPDATER.lazySet(this, ret);
114         }
115
116         return ret;
117     }
118
119     /**
120      * Returns a list of path arguments.
121      *
122      * @deprecated Use {@link #getPathArguments()} instead.
123      * @return Immutable list of path arguments.
124      */
125     @Deprecated
126     public List<PathArgument> getPath() {
127         return getLegacyPath();
128     }
129
130     /**
131      * Returns an ordered iteration of path arguments.
132      *
133      * @return Immutable iteration of path arguments.
134      */
135     public Iterable<PathArgument> getPathArguments() {
136         return pathArguments;
137     }
138
139     /**
140      * Returns an iterable of path arguments in reverse order. This is useful
141      * when walking up a tree organized this way.
142      *
143      * @return Immutable iterable of path arguments in reverse order.
144      */
145     public Iterable<PathArgument> getReversePathArguments() {
146         return getLegacyPath().reverse();
147     }
148
149     /**
150      * Returns the last PathArgument. This is equivalent of iterating
151      * to the last element of the iterable returned by {@link #getPathArguments()}.
152      *
153      * @return The last past argument, or null if there are no PathArguments.
154      */
155     public PathArgument getLastPathArgument() {
156         return Iterables.getFirst(getReversePathArguments(), null);
157     }
158
159     private YangInstanceIdentifier(final Iterable<PathArgument> path, final int hash) {
160         this.pathArguments = Preconditions.checkNotNull(path, "path must not be null.");
161         this.hash = hash;
162     }
163
164     private static YangInstanceIdentifier trustedCreate(final Iterable<PathArgument> path) {
165         final HashCodeBuilder<PathArgument> hash = new HashCodeBuilder<>();
166         for (PathArgument a : path) {
167             hash.addArgument(a);
168         }
169
170         return new YangInstanceIdentifier(path, hash.build());
171     }
172
173     public static YangInstanceIdentifier create(final Iterable<? extends PathArgument> path) {
174         if (Iterables.isEmpty(path)) {
175             return EMPTY;
176         }
177
178         return trustedCreate(ImmutableList.copyOf(path));
179     }
180
181     public static YangInstanceIdentifier create(final PathArgument... path) {
182         // We are forcing a copy, since we cannot trust the user
183         return create(Arrays.asList(path));
184     }
185
186     @Override
187     public int hashCode() {
188         /*
189          * The caching is safe, since the object contract requires
190          * immutability of the object and all objects referenced from this
191          * object.
192          * Used lists, maps are immutable. Path Arguments (elements) are also
193          * immutable, since the PathArgument contract requires immutability.
194          */
195         return hash;
196     }
197
198     @Override
199     public boolean equals(final Object obj) {
200         if (this == obj) {
201             return true;
202         }
203         if (obj == null) {
204             return false;
205         }
206         if (getClass() != obj.getClass()) {
207             return false;
208         }
209         YangInstanceIdentifier other = (YangInstanceIdentifier) obj;
210         if (this.hashCode() != obj.hashCode()) {
211             return false;
212         }
213         return Iterables.elementsEqual(pathArguments, other.pathArguments);
214     }
215
216     /**
217      * Constructs a new Instance Identifier with new {@link NodeIdentifier} added to the end of path arguments
218      *
219      * @param name QName of {@link NodeIdentifier}
220      * @return Instance Identifier with additional path argument added to the end.
221      */
222     public YangInstanceIdentifier node(final QName name) {
223         return node(new NodeIdentifier(name));
224     }
225
226     /**
227      *
228      * Constructs a new Instance Identifier with new {@link PathArgument} added to the end of path arguments
229      *
230      * @param arg Path argument which should be added to the end
231      * @return Instance Identifier with additional path argument added to the end.
232      */
233     public YangInstanceIdentifier node(final PathArgument arg) {
234         return new YangInstanceIdentifier(Iterables.concat(pathArguments, Collections.singleton(arg)), HashCodeBuilder.nextHashCode(hash, arg));
235     }
236
237     /**
238      * Get the relative path from an ancestor. This method attempts to perform
239      * the reverse of concatenating a base (ancestor) and a path.
240      *
241      * @param ancestor
242      *            Ancestor against which the relative path should be calculated
243      * @return This object's relative path from parent, or Optional.absent() if
244      *         the specified parent is not in fact an ancestor of this object.
245      */
246     public Optional<YangInstanceIdentifier> relativeTo(final YangInstanceIdentifier ancestor) {
247         final Iterator<?> lit = pathArguments.iterator();
248         final Iterator<?> oit = ancestor.pathArguments.iterator();
249         int common = 0;
250
251         while (oit.hasNext()) {
252             // Ancestor is not really an ancestor
253             if (!lit.hasNext() || !lit.next().equals(oit.next())) {
254                 return Optional.absent();
255             }
256
257             ++common;
258         }
259
260         if (common == 0) {
261             return Optional.of(this);
262         }
263         if (!lit.hasNext()) {
264             return Optional.of(EMPTY);
265         }
266         return Optional.of(trustedCreate(Iterables.skip(pathArguments, common)));
267     }
268
269     private static int hashCode(final Object value) {
270         if (value == null) {
271             return 0;
272         }
273
274         if (value.getClass().equals(byte[].class)) {
275             return Arrays.hashCode((byte[]) value);
276         }
277
278         if (value.getClass().isArray()) {
279             int hash = 0;
280             int length = Array.getLength(value);
281             for (int i = 0; i < length; i++) {
282                 hash += Objects.hashCode(Array.get(value, i));
283             }
284
285             return hash;
286         }
287
288         return Objects.hashCode(value);
289     }
290
291     // Static factories & helpers
292
293     /**
294      *
295      * Returns a new InstanceIdentifier with only one path argument of type {@link NodeIdentifier} with supplied QName
296      *
297      * @param name QName of first node identifier
298      * @return Instance Identifier with only one path argument of type {@link NodeIdentifier}
299      */
300     public static YangInstanceIdentifier of(final QName name) {
301         return create(new NodeIdentifier(name));
302     }
303
304     /**
305      *
306      * Returns new builder for InstanceIdentifier with empty path arguments.
307      *
308      * @return new builder for InstanceIdentifier with empty path arguments.
309      */
310     public static InstanceIdentifierBuilder builder() {
311         return new BuilderImpl();
312     }
313
314     /**
315      *
316      * Returns new builder for InstanceIdentifier with path arguments copied from original instance identifier.
317      *
318      * @param origin Instace Identifier from which path arguments are copied.
319      * @return new builder for InstanceIdentifier with path arguments copied from original instance identifier.
320      */
321     public static InstanceIdentifierBuilder builder(final YangInstanceIdentifier origin) {
322         return new BuilderImpl(origin.getPathArguments(), origin.hashCode());
323     }
324
325     /**
326      * Path argument / component of InstanceIdentifier
327      *
328      * Path argument uniquely identifies node in data tree on particular
329      * level.
330      * <p>
331      * This interface itself is used as common parent for actual
332      * path arguments types and should not be implemented by user code.
333      * <p>
334      * Path arguments SHOULD contain only minimum of information
335      * required to uniquely identify node on particular subtree level.
336      *
337      * For actual path arguments types see:
338      * <ul>
339      * <li>{@link NodeIdentifier} - Identifier of container or leaf
340      * <li>{@link NodeIdentifierWithPredicates} - Identifier of list entries, which have key defined
341      * <li>{@link AugmentationIdentifier} - Identifier of augmentation
342      * <li>{@link NodeWithValue} - Identifier of leaf-list entry
343      * </ul>
344      */
345     public interface PathArgument extends Comparable<PathArgument>, Immutable, Serializable {
346         /**
347          * If applicable returns unique QName of data node as defined in YANG
348          * Schema.
349          *
350          * This method may return null, if the corresponding schema node, does
351          * not have QName associated, such as in cases of augmentations.
352          *
353          * @return Node type
354          */
355         QName getNodeType();
356
357         /**
358          * Return the string representation of this object for use in context
359          * provided by a previous object. This method can be implemented in
360          * terms of {@link #toString()}, but implementations are encourage to
361          * reuse any context already emitted by the previous object.
362          *
363          * @param previous Previous path argument
364          * @return String representation
365          */
366         String toRelativeString(PathArgument previous);
367     }
368
369     private static abstract class AbstractPathArgument implements PathArgument {
370         private static final AtomicReferenceFieldUpdater<AbstractPathArgument, Integer> HASH_UPDATER =
371                 AtomicReferenceFieldUpdater.newUpdater(AbstractPathArgument.class, Integer.class, "hash");
372         private static final long serialVersionUID = -4546547994250849340L;
373         private final QName nodeType;
374         private volatile transient Integer hash = null;
375
376         protected AbstractPathArgument(final QName nodeType) {
377             this.nodeType = Preconditions.checkNotNull(nodeType);
378         }
379
380         @Override
381         public final QName getNodeType() {
382             return nodeType;
383         }
384
385         @Override
386         public int compareTo(final PathArgument o) {
387             return nodeType.compareTo(o.getNodeType());
388         }
389
390         protected int hashCodeImpl() {
391             return 31 + getNodeType().hashCode();
392         }
393
394         @Override
395         public final int hashCode() {
396             Integer ret = hash;
397             if (ret == null) {
398                 ret = hashCodeImpl();
399                 HASH_UPDATER.lazySet(this, ret);
400             }
401
402             return ret;
403         }
404
405         @Override
406         public boolean equals(final Object obj) {
407             if (this == obj) {
408                 return true;
409             }
410             if (obj == null || this.getClass() != obj.getClass()) {
411                 return false;
412             }
413
414             return getNodeType().equals(((AbstractPathArgument)obj).getNodeType());
415         }
416
417         @Override
418         public String toString() {
419             return getNodeType().toString();
420         }
421
422         @Override
423         public String toRelativeString(final PathArgument previous) {
424             if (previous instanceof AbstractPathArgument) {
425                 final QNameModule mod = ((AbstractPathArgument)previous).getNodeType().getModule();
426                 if (getNodeType().getModule().equals(mod)) {
427                     return getNodeType().getLocalName();
428                 }
429             }
430
431             return getNodeType().toString();
432         }
433     }
434
435     /**
436      * Fluent Builder of Instance Identifier instances
437      */
438     public interface InstanceIdentifierBuilder extends Builder<YangInstanceIdentifier> {
439         /**
440          * Adds {@link NodeIdentifier} with supplied QName to path arguments of resulting instance identifier.
441          *
442          * @param nodeType QName of {@link NodeIdentifier} which will be added
443          * @return this builder
444          */
445         InstanceIdentifierBuilder node(QName nodeType);
446
447         /**
448          * Adds {@link NodeIdentifierWithPredicates} with supplied QName and key values to path arguments of resulting instance identifier.
449          *
450          * @param nodeType QName of {@link NodeIdentifierWithPredicates} which will be added
451          * @param keyValues Map of key components and their respective values for {@link NodeIdentifierWithPredicates}
452          * @return this builder
453          */
454         InstanceIdentifierBuilder nodeWithKey(QName nodeType, Map<QName, Object> keyValues);
455
456         /**
457          * Adds {@link NodeIdentifierWithPredicates} with supplied QName and key, value.
458          *
459          * @param nodeType QName of {@link NodeIdentifierWithPredicates} which will be added
460          * @param key QName of key which will be added
461          * @param value value of key which will be added
462          * @return this builder
463          */
464         InstanceIdentifierBuilder nodeWithKey(QName nodeType, QName key, Object value);
465
466         /**
467          *
468          * Builds an {@link YangInstanceIdentifier} with path arguments from this builder
469          *
470          * @return {@link YangInstanceIdentifier}
471          */
472         @Override
473         YangInstanceIdentifier build();
474
475         /*
476          * @deprecated use #build()
477          */
478         @Deprecated
479         YangInstanceIdentifier toInstance();
480     }
481
482     /**
483      * Simple path argument identifying a {@link org.opendaylight.yangtools.yang.data.api.schema.ContainerNode} or
484      * {@link org.opendaylight.yangtools.yang.data.api.schema.LeafNode} leaf in particular subtree.
485      */
486     public static final class NodeIdentifier extends AbstractPathArgument {
487         private static final long serialVersionUID = -2255888212390871347L;
488
489         public NodeIdentifier(final QName node) {
490             super(node);
491         }
492     }
493
494     /**
495      * Composite path argument identifying a {@link org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode} leaf
496      * overall data tree.
497      */
498     public static final class NodeIdentifierWithPredicates extends AbstractPathArgument {
499         private static final long serialVersionUID = -4787195606494761540L;
500
501         private final Map<QName, Object> keyValues;
502
503         public NodeIdentifierWithPredicates(final QName node, final Map<QName, Object> keyValues) {
504             super(node);
505             this.keyValues = ImmutableMap.copyOf(keyValues);
506         }
507
508         public NodeIdentifierWithPredicates(final QName node, final QName key, final Object value) {
509             this(node, ImmutableMap.of(key, value));
510         }
511
512         public Map<QName, Object> getKeyValues() {
513             return keyValues;
514         }
515
516         @Override
517         protected int hashCodeImpl() {
518             final int prime = 31;
519             int result = super.hashCodeImpl();
520             result = prime * result;
521
522             for (Entry<QName, Object> entry : keyValues.entrySet()) {
523                 result += Objects.hashCode(entry.getKey()) + YangInstanceIdentifier.hashCode(entry.getValue());
524             }
525             return result;
526         }
527
528         @Override
529         public boolean equals(final Object obj) {
530             if (!super.equals(obj)) {
531                 return false;
532             }
533
534             final Map<QName, Object> otherKeyValues = ((NodeIdentifierWithPredicates) obj).keyValues;
535             if (keyValues.size() != otherKeyValues.size()) {
536                 return false;
537             }
538
539             for (Entry<QName, Object> entry : keyValues.entrySet()) {
540                 if (!otherKeyValues.containsKey(entry.getKey())
541                         || !Objects.deepEquals(entry.getValue(), otherKeyValues.get(entry.getKey()))) {
542
543                     return false;
544                 }
545             }
546
547             return true;
548         }
549
550         @Override
551         public String toString() {
552             return super.toString() + '[' + keyValues + ']';
553         }
554
555         @Override
556         public String toRelativeString(final PathArgument previous) {
557             return super.toRelativeString(previous) + '[' + keyValues + ']';
558         }
559     }
560
561     /**
562      * Simple path argument identifying a {@link LeafSetEntryNode} leaf
563      * overall data tree.
564      */
565     public static final class NodeWithValue extends AbstractPathArgument {
566         private static final long serialVersionUID = -3637456085341738431L;
567
568         private final Object value;
569
570         public NodeWithValue(final QName node, final Object value) {
571             super(node);
572             this.value = value;
573         }
574
575         public Object getValue() {
576             return value;
577         }
578
579         @Override
580         protected int hashCodeImpl() {
581             final int prime = 31;
582             int result = super.hashCodeImpl();
583             result = prime * result + ((value == null) ? 0 : YangInstanceIdentifier.hashCode(value));
584             return result;
585         }
586
587         @Override
588         public boolean equals(final Object obj) {
589             if (!super.equals(obj)) {
590                 return false;
591             }
592             final NodeWithValue other = (NodeWithValue) obj;
593             return Objects.deepEquals(value, other.value);
594         }
595
596         @Override
597         public String toString() {
598             return super.toString() + '[' + value + ']';
599         }
600
601         @Override
602         public String toRelativeString(final PathArgument previous) {
603             return super.toRelativeString(previous) + '[' + value + ']';
604         }
605     }
606
607     /**
608      * Composite path argument identifying a {@link org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode} node in
609      * particular subtree.
610      *
611      * Augmentation is uniquely identified by set of all possible child nodes.
612      * This is possible
613      * to identify instance of augmentation,
614      * since RFC6020 states that <code>augment</code> that augment
615      * statement must not add multiple nodes from same namespace
616      * / module to the target node.
617      *
618      *
619      * @see <a href="http://tools.ietf.org/html/rfc6020#section-7.15">RFC6020</a>
620      */
621     public static final class AugmentationIdentifier implements PathArgument {
622         private static final long serialVersionUID = -8122335594681936939L;
623         private final ImmutableSet<QName> childNames;
624
625         @Override
626         public QName getNodeType() {
627             // This should rather throw exception than return always null
628             throw new UnsupportedOperationException("Augmentation node has no QName");
629         }
630
631         /**
632          *
633          * Construct new augmentation identifier using supplied set of possible
634          * child nodes
635          *
636          * @param childNames
637          *            Set of possible child nodes.
638          */
639         public AugmentationIdentifier(final Set<QName> childNames) {
640             this.childNames = ImmutableSet.copyOf(childNames);
641         }
642
643         /**
644          * Returns set of all possible child nodes
645          *
646          * @return set of all possible child nodes.
647          */
648         public Set<QName> getPossibleChildNames() {
649             return childNames;
650         }
651
652         @Override
653         public String toString() {
654             final StringBuilder sb = new StringBuilder("AugmentationIdentifier{");
655             sb.append("childNames=").append(childNames).append('}');
656             return sb.toString();
657         }
658
659         @Override
660         public String toRelativeString(final PathArgument previous) {
661             return toString();
662         }
663
664         @Override
665         public boolean equals(final Object o) {
666             if (this == o) {
667                 return true;
668             }
669             if (!(o instanceof AugmentationIdentifier)) {
670                 return false;
671             }
672
673             AugmentationIdentifier that = (AugmentationIdentifier) o;
674             return childNames.equals(that.childNames);
675         }
676
677         @Override
678         public int hashCode() {
679             return childNames.hashCode();
680         }
681
682         @Override
683         public int compareTo(final PathArgument o) {
684             if (!(o instanceof AugmentationIdentifier)) {
685                 return -1;
686             }
687             AugmentationIdentifier other = (AugmentationIdentifier) o;
688             Set<QName> otherChildNames = other.getPossibleChildNames();
689             int thisSize = childNames.size();
690             int otherSize = otherChildNames.size();
691             if (thisSize == otherSize) {
692                 Iterator<QName> otherIterator = otherChildNames.iterator();
693                 for (QName name : childNames) {
694                     int c = name.compareTo(otherIterator.next());
695                     if (c != 0) {
696                         return c;
697                     }
698                 }
699                 return 0;
700             } else if (thisSize < otherSize) {
701                 return 1;
702             } else {
703                 return -1;
704             }
705         }
706     }
707
708     private static class BuilderImpl implements InstanceIdentifierBuilder {
709         private final HashCodeBuilder<PathArgument> hash;
710         private final List<PathArgument> path;
711
712         public BuilderImpl() {
713             this.hash = new HashCodeBuilder<>();
714             this.path = new ArrayList<>();
715         }
716
717         public BuilderImpl(final Iterable<PathArgument> prefix, final int hash) {
718             this.path = Lists.newArrayList(prefix);
719             this.hash = new HashCodeBuilder<>(hash);
720         }
721
722         @Override
723         public InstanceIdentifierBuilder node(final QName nodeType) {
724             final PathArgument arg = new NodeIdentifier(nodeType);
725             path.add(arg);
726             hash.addArgument(arg);
727             return this;
728         }
729
730         @Override
731         public InstanceIdentifierBuilder nodeWithKey(final QName nodeType, final QName key, final Object value) {
732             final PathArgument arg = new NodeIdentifierWithPredicates(nodeType, key, value);
733             path.add(arg);
734             hash.addArgument(arg);
735             return this;
736         }
737
738         @Override
739         public InstanceIdentifierBuilder nodeWithKey(final QName nodeType, final Map<QName, Object> keyValues) {
740             final PathArgument arg = new NodeIdentifierWithPredicates(nodeType, keyValues);
741             path.add(arg);
742             hash.addArgument(arg);
743             return this;
744         }
745
746         @Override
747         @Deprecated
748         public YangInstanceIdentifier toInstance() {
749             return build();
750         }
751
752         @Override
753         public YangInstanceIdentifier build() {
754             return new YangInstanceIdentifier(ImmutableList.copyOf(path), hash.build());
755         }
756     }
757
758     @Override
759     public boolean contains(final YangInstanceIdentifier other) {
760         Preconditions.checkArgument(other != null, "other should not be null");
761
762         final Iterator<?> lit = pathArguments.iterator();
763         final Iterator<?> oit = other.pathArguments.iterator();
764
765         while (lit.hasNext()) {
766             if (!oit.hasNext()) {
767                 return false;
768             }
769
770             if (!lit.next().equals(oit.next())) {
771                 return false;
772             }
773         }
774
775         return true;
776     }
777
778     @Override
779     public String toString() {
780         /*
781          * The toStringCache is safe, since the object contract requires
782          * immutability of the object and all objects referenced from this
783          * object.
784          * Used lists, maps are immutable. Path Arguments (elements) are also
785          * immutable, since the PathArgument contract requires immutability.
786          * The cache is thread-safe - if multiple computations occurs at the
787          * same time, cache will be overwritten with same result.
788          */
789         String ret = toStringCache;
790         if (ret == null) {
791             final StringBuilder builder = new StringBuilder("/");
792             PathArgument prev = null;
793             for (PathArgument argument : getPathArguments()) {
794                 if (prev != null) {
795                     builder.append('/');
796                 }
797                 builder.append(argument.toRelativeString(prev));
798                 prev = argument;
799             }
800
801             ret = builder.toString();
802             TOSTRINGCACHE_UPDATER.lazySet(this, ret);
803         }
804         return ret;
805     }
806
807     private void readObject(final ObjectInputStream inputStream) throws IOException, ClassNotFoundException {
808         inputStream.defaultReadObject();
809         legacyPath = ImmutableList.copyOf((Collection<PathArgument>)inputStream.readObject());
810
811         try {
812             PATHARGUMENTS_FIELD.set(this, legacyPath);
813         } catch (IllegalArgumentException | IllegalAccessException e) {
814             throw new IOException(e);
815         }
816     }
817
818     private Object readResolve() throws ObjectStreamException {
819         return legacyPath.isEmpty() ? EMPTY : this;
820     }
821
822     private void writeObject(final ObjectOutputStream outputStream) throws IOException {
823         /*
824          * This may look strange, but what we are doing here is side-stepping the fact
825          * that pathArguments is not generally serializable. We are forcing instantiation
826          * of the legacy path, which is an ImmutableList (thus Serializable) and write
827          * it out. The read path does the opposite -- it reads the legacyPath and then
828          * uses invocation API to set the field.
829          */
830         ImmutableList<PathArgument> pathArguments = getLegacyPath();
831         outputStream.defaultWriteObject();
832         outputStream.writeObject(pathArguments);
833     }
834 }