Merge "Bug 584 - Milestone : Increasing unit test coverage, from 44.9% to 60%"
[yangtools.git] / yang / yang-data-api / src / main / java / org / opendaylight / yangtools / yang / data / api / YangInstanceIdentifier.java
index 60432b512faff22c47d38167c471a22ed17bbf14..c882db6693bb9b2a9b97ecb31650f8d95c94004f 100644 (file)
@@ -13,9 +13,12 @@ import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
-
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.io.Serializable;
 import java.lang.reflect.Array;
+import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -25,20 +28,21 @@ import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.Set;
-
+import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
 import org.opendaylight.yangtools.concepts.Builder;
 import org.opendaylight.yangtools.concepts.Immutable;
 import org.opendaylight.yangtools.concepts.Path;
 import org.opendaylight.yangtools.util.HashCodeBuilder;
 import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
 
 /**
- * Unique identifier of a partical node instance in the data tree.
+ * Unique identifier of a particular node instance in the data tree.
  *
  * <p>
  * Java representation of YANG Built-in type <code>instance-identifier</code>,
- * which conceptually is XPath expression minimised to uniquely identify element
+ * which conceptually is XPath expression minimized to uniquely identify element
  * in data tree which conforms to constraints maintained by YANG Model,
  * effectively this makes Instance Identifier a path to element in data tree.
  * <p>
@@ -63,28 +67,47 @@ import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
  * </ul>
  *
  *
- * @see http://tools.ietf.org/html/rfc6020#section-9.13
+ * @see <a href="http://tools.ietf.org/html/rfc6020#section-9.13">RFC6020</a>
  */
 public final class YangInstanceIdentifier implements Path<YangInstanceIdentifier>, Immutable, Serializable {
+    @SuppressWarnings("rawtypes")
+    private static final AtomicReferenceFieldUpdater<YangInstanceIdentifier, ImmutableList> LEGACYPATH_UPDATER =
+            AtomicReferenceFieldUpdater.newUpdater(YangInstanceIdentifier.class, ImmutableList.class, "legacyPath");
+    private static final AtomicReferenceFieldUpdater<YangInstanceIdentifier, String> TOSTRINGCACHE_UPDATER =
+            AtomicReferenceFieldUpdater.newUpdater(YangInstanceIdentifier.class, String.class, "toStringCache");
     private static final YangInstanceIdentifier EMPTY = trustedCreate(Collections.<PathArgument>emptyList());
+    private static final Field PATHARGUMENTS_FIELD;
 
-    private static final long serialVersionUID = 2L;
-    private final Iterable<PathArgument> pathArguments;
+    private static final long serialVersionUID = 3L;
+    private transient final Iterable<PathArgument> pathArguments;
     private final int hash;
 
-    private transient ImmutableList<PathArgument> legacyPath = null;
-    private transient String toStringCache = null;
+    private volatile ImmutableList<PathArgument> legacyPath = null;
+    private transient volatile String toStringCache = null;
+
+    static {
+        final Field f;
+        try {
+            f = YangInstanceIdentifier.class.getDeclaredField("pathArguments");
+        } catch (NoSuchFieldException | SecurityException e) {
+            throw new ExceptionInInitializerError(e);
+        }
+        f.setAccessible(true);
+
+        PATHARGUMENTS_FIELD = f;
+    }
 
     private final ImmutableList<PathArgument> getLegacyPath() {
-        if (legacyPath == null) {
-            synchronized (this) {
-                if (legacyPath == null) {
-                    legacyPath = ImmutableList.copyOf(pathArguments);
-                }
-            }
+        // Temporary variable saves a volatile read
+        ImmutableList<PathArgument> ret = legacyPath;
+        if (ret == null) {
+            // We could have used a synchronized block, but the window is quite
+            // small and worst that can happen is duplicate object construction.
+            ret = ImmutableList.copyOf(pathArguments);
+            LEGACYPATH_UPDATER.lazySet(this, ret);
         }
 
-        return legacyPath;
+        return ret;
     }
 
     /**
@@ -293,23 +316,10 @@ public final class YangInstanceIdentifier implements Path<YangInstanceIdentifier
         return new BuilderImpl(origin.getPathArguments(), origin.hashCode());
     }
 
-    /**
-     * Returns new builder for InstanceIdentifier with first path argument set to {@link NodeIdentifier}.
-     *
-     * @param node QName of first {@link NodeIdentifier} path argument.
-     * @return  new builder for InstanceIdentifier with first path argument set to {@link NodeIdentifier}.
-     *
-     * @deprecated Either use {@link #node(QName)} or instantiate an intermediate builder.
-     */
-    @Deprecated
-    public static InstanceIdentifierBuilder builder(final QName node) {
-        return builder().node(node);
-    }
-
     /**
      * Path argument / component of InstanceIdentifier
      *
-     * Path argument uniquelly identifies node in data tree on particular
+     * Path argument uniquely identifies node in data tree on particular
      * level.
      * <p>
      * This interface itself is used as common parent for actual
@@ -337,11 +347,25 @@ public final class YangInstanceIdentifier implements Path<YangInstanceIdentifier
          * @return Node type
          */
         QName getNodeType();
+
+        /**
+         * Return the string representation of this object for use in context
+         * provided by a previous object. This method can be implemented in
+         * terms of {@link #toString()}, but implementations are encourage to
+         * reuse any context already emitted by the previous object.
+         *
+         * @param previous Previous path argument
+         * @return String representation
+         */
+        String toRelativeString(PathArgument previous);
     }
 
     private static abstract class AbstractPathArgument implements PathArgument {
+        private static final AtomicReferenceFieldUpdater<AbstractPathArgument, Integer> HASH_UPDATER =
+                AtomicReferenceFieldUpdater.newUpdater(AbstractPathArgument.class, Integer.class, "hash");
         private static final long serialVersionUID = -4546547994250849340L;
         private final QName nodeType;
+        private volatile transient Integer hash = null;
 
         protected AbstractPathArgument(final QName nodeType) {
             this.nodeType = Preconditions.checkNotNull(nodeType);
@@ -357,11 +381,21 @@ public final class YangInstanceIdentifier implements Path<YangInstanceIdentifier
             return nodeType.compareTo(o.getNodeType());
         }
 
-        @Override
-        public int hashCode() {
+        protected int hashCodeImpl() {
             return 31 + getNodeType().hashCode();
         }
 
+        @Override
+        public final int hashCode() {
+            Integer ret = hash;
+            if (ret == null) {
+                ret = hashCodeImpl();
+                HASH_UPDATER.lazySet(this, ret);
+            }
+
+            return ret;
+        }
+
         @Override
         public boolean equals(final Object obj) {
             if (this == obj) {
@@ -378,6 +412,18 @@ public final class YangInstanceIdentifier implements Path<YangInstanceIdentifier
         public String toString() {
             return getNodeType().toString();
         }
+
+        @Override
+        public String toRelativeString(final PathArgument previous) {
+            if (previous instanceof AbstractPathArgument) {
+                final QNameModule mod = ((AbstractPathArgument)previous).getNodeType().getModule();
+                if (getNodeType().getModule().equals(mod)) {
+                    return getNodeType().getLocalName();
+                }
+            }
+
+            return getNodeType().toString();
+        }
     }
 
     /**
@@ -459,9 +505,9 @@ public final class YangInstanceIdentifier implements Path<YangInstanceIdentifier
         }
 
         @Override
-        public int hashCode() {
+        protected int hashCodeImpl() {
             final int prime = 31;
-            int result = super.hashCode();
+            int result = super.hashCodeImpl();
             result = prime * result;
 
             for (Entry<QName, Object> entry : keyValues.entrySet()) {
@@ -496,6 +542,11 @@ public final class YangInstanceIdentifier implements Path<YangInstanceIdentifier
         public String toString() {
             return super.toString() + '[' + keyValues + ']';
         }
+
+        @Override
+        public String toRelativeString(final PathArgument previous) {
+            return super.toRelativeString(previous) + '[' + keyValues + ']';
+        }
     }
 
     /**
@@ -517,9 +568,9 @@ public final class YangInstanceIdentifier implements Path<YangInstanceIdentifier
         }
 
         @Override
-        public int hashCode() {
+        protected int hashCodeImpl() {
             final int prime = 31;
-            int result = super.hashCode();
+            int result = super.hashCodeImpl();
             result = prime * result + ((value == null) ? 0 : YangInstanceIdentifier.hashCode(value));
             return result;
         }
@@ -537,6 +588,11 @@ public final class YangInstanceIdentifier implements Path<YangInstanceIdentifier
         public String toString() {
             return super.toString() + '[' + value + ']';
         }
+
+        @Override
+        public String toRelativeString(final PathArgument previous) {
+            return super.toRelativeString(previous) + '[' + value + ']';
+        }
     }
 
     /**
@@ -551,7 +607,7 @@ public final class YangInstanceIdentifier implements Path<YangInstanceIdentifier
      * / module to the target node.
      *
      *
-     * @see http://tools.ietf.org/html/rfc6020#section-7.15
+     * @see <a href="http://tools.ietf.org/html/rfc6020#section-7.15">RFC6020</a>
      */
     public static final class AugmentationIdentifier implements PathArgument {
         private static final long serialVersionUID = -8122335594681936939L;
@@ -576,19 +632,6 @@ public final class YangInstanceIdentifier implements Path<YangInstanceIdentifier
         }
 
         /**
-         * Augmentation node has no QName
-         *
-         * @deprecated Use
-         *             {@link AugmentationIdentifier#AugmentationIdentifier(Set)}
-         *             instead.
-         */
-        @Deprecated
-        public AugmentationIdentifier(final QName nodeType, final Set<QName> childNames) {
-            this(childNames);
-        }
-
-        /**
-         *
          * Returns set of all possible child nodes
          *
          * @return set of all possible child nodes.
@@ -604,6 +647,11 @@ public final class YangInstanceIdentifier implements Path<YangInstanceIdentifier
             return sb.toString();
         }
 
+        @Override
+        public String toRelativeString(final PathArgument previous) {
+            return toString();
+        }
+
         @Override
         public boolean equals(final Object o) {
             if (this == o) {
@@ -729,22 +777,43 @@ public final class YangInstanceIdentifier implements Path<YangInstanceIdentifier
          * The cache is thread-safe - if multiple computations occurs at the
          * same time, cache will be overwritten with same result.
          */
-        if (toStringCache != null) {
-            return toStringCache;
+        String ret = toStringCache;
+        if (ret == null) {
+            final StringBuilder builder = new StringBuilder("/");
+            PathArgument prev = null;
+            for (PathArgument argument : getPathArguments()) {
+                if (prev != null) {
+                    builder.append('/');
+                }
+                builder.append(argument.toRelativeString(prev));
+                prev = argument;
+            }
+
+            ret = builder.toString();
+            TOSTRINGCACHE_UPDATER.lazySet(this, ret);
         }
+        return ret;
+    }
 
-        final StringBuilder builder = new StringBuilder('/');
-        boolean first = true;
-        for (PathArgument argument : getPathArguments()) {
-            if (first) {
-                first = false;
-            } else {
-                builder.append('/');
-            }
-            builder.append(argument.toString());
+    private void readObject(final ObjectInputStream inputStream) throws IOException, ClassNotFoundException {
+        inputStream.defaultReadObject();
+
+        try {
+            PATHARGUMENTS_FIELD.set(this, legacyPath);
+        } catch (IllegalArgumentException | IllegalAccessException e) {
+            throw new IOException(e);
         }
+    }
 
-        toStringCache = builder.toString();
-        return toStringCache;
+    private void writeObject(final ObjectOutputStream outputStream) throws IOException {
+        /*
+         * This may look strange, but what we are doing here is side-stepping the fact
+         * that pathArguments is not generally serializable. We are forcing instantiation
+         * of the legacy path, which is an ImmutableList (thus Serializable) and write
+         * it out. The read path does the opposite -- it reads the legacyPath and then
+         * uses invocation API to set the field.
+         */
+        getLegacyPath();
+        outputStream.defaultWriteObject();
     }
 }