Refactor binding-dom-adapter
[mdsal.git] / binding / mdsal-binding-dom-codec / src / main / java / org / opendaylight / mdsal / binding / dom / codec / impl / CodecDataObject.java
index cec788f3fdc050209d3b5bde997a23a86347ec87..d075bfb64aeb93830075020d6e1fe5413c6004d8 100644 (file)
@@ -12,8 +12,10 @@ import static java.util.Objects.requireNonNull;
 
 import com.google.common.base.MoreObjects;
 import com.google.common.base.MoreObjects.ToStringHelper;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
 import java.util.Optional;
-import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
 import org.opendaylight.yangtools.yang.binding.DataObject;
@@ -29,30 +31,42 @@ import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
  * @param <T> DataObject type
  */
 public abstract class CodecDataObject<T extends DataObject> implements DataObject {
+    // An object representing a null value in a member field.
     private static final @NonNull Object NULL_VALUE = new Object();
 
+    private static final VarHandle CACHED_HASH_CODE;
+
+    static {
+        try {
+            CACHED_HASH_CODE = MethodHandles.lookup().findVarHandle(CodecDataObject.class, "cachedHashcode",
+                Integer.class);
+        } catch (NoSuchFieldException | IllegalAccessException e) {
+            throw new ExceptionInInitializerError(e);
+        }
+    }
+
+    private final @NonNull DataObjectCodecContext<T, ?> context;
     @SuppressWarnings("rawtypes")
     private final @NonNull NormalizedNodeContainer data;
 
-    private volatile Integer cachedHashcode = null;
+    // Accessed via a VarHandle
+    @SuppressWarnings("unused")
+    // FIXME: consider using a primitive int-based cache (with 0 being uninit)
+    private volatile Integer cachedHashcode;
 
-    protected CodecDataObject(final NormalizedNodeContainer<?, ?, ?> data) {
+    protected CodecDataObject(final DataObjectCodecContext<T, ?> context, final NormalizedNodeContainer<?, ?, ?> data) {
         this.data = requireNonNull(data, "Data must not be null");
+        this.context = requireNonNull(context, "Context must not be null");
     }
 
     @Override
     public final int hashCode() {
-        final Integer cached = cachedHashcode;
-        if (cached != null) {
-            return cached;
-        }
-
-        final int result = codecAugmentedHashCode();
-        cachedHashcode = result;
-        return result;
+        final Integer cached = (Integer) CACHED_HASH_CODE.getAcquire(this);
+        return cached != null ? cached : loadHashCode();
     }
 
     @Override
+    @SuppressFBWarnings(value = "EQ_UNUSUAL", justification = "State is examined indirectly enough to confuse SpotBugs")
     public final boolean equals(final Object obj) {
         if (obj == this) {
             return true;
@@ -63,9 +77,8 @@ public abstract class CodecDataObject<T extends DataObject> implements DataObjec
         }
         @SuppressWarnings("unchecked")
         final T other = (T) iface.cast(obj);
-        if (other instanceof CodecDataObject) {
-            return data.equals(((CodecDataObject<?>) obj).data);
-        }
+        // Note: we do not want to compare NormalizedNode data here, as we may be looking at different instantiations
+        //       of the same grouping -- in which case normalized node will not compare as equal.
         return codecAugmentedEquals(other);
     }
 
@@ -75,18 +88,24 @@ public abstract class CodecDataObject<T extends DataObject> implements DataObjec
                 .toString();
     }
 
-    // TODO: consider switching to VarHandles for Java 9+, as that would disconnect us from the need to use a volatile
-    //       field and use acquire/release mechanics -- see http://gee.cs.oswego.edu/dl/html/j9mm.html for details.
-    protected final Object codecMember(final AtomicReferenceFieldUpdater<CodecDataObject<?>, Object> updater,
-            final NodeContextSupplier supplier) {
-        final Object cached = updater.get(this);
-        return cached != null ? unmaskNull(cached) : loadMember(updater, supplier);
+    protected final Object codecMember(final VarHandle handle, final String localName) {
+        final Object cached = handle.getAcquire(this);
+        return cached != null ? unmaskNull(cached) : loadMember(handle, context.getLeafChild(localName));
     }
 
-    protected final Object codecMember(final AtomicReferenceFieldUpdater<CodecDataObject<?>, Object> updater,
-            final IdentifiableItemCodec codec) {
-        final Object cached = updater.get(this);
-        return cached != null ? unmaskNull(cached) : loadKey(updater, codec);
+    protected final Object codecMember(final VarHandle handle, final Class<? extends DataObject> bindingClass) {
+        final Object cached = handle.getAcquire(this);
+        return cached != null ? unmaskNull(cached) : loadMember(handle, context.streamChild(bindingClass));
+    }
+
+    protected final Object codecMember(final VarHandle handle, final NodeContextSupplier supplier) {
+        final Object cached = handle.getAcquire(this);
+        return cached != null ? unmaskNull(cached) : loadMember(handle, supplier.get());
+    }
+
+    protected final Object codecKey(final VarHandle handle) {
+        final Object cached = handle.getAcquire(this);
+        return cached != null ? cached : loadKey(handle);
     }
 
     protected abstract int codecHashCode();
@@ -95,6 +114,10 @@ public abstract class CodecDataObject<T extends DataObject> implements DataObjec
 
     protected abstract ToStringHelper codecFillToString(ToStringHelper helper);
 
+    final @NonNull DataObjectCodecContext<T, ?> codecContext() {
+        return context;
+    }
+
     @SuppressWarnings("rawtypes")
     final @NonNull NormalizedNodeContainer codecData() {
         return data;
@@ -115,30 +138,33 @@ public abstract class CodecDataObject<T extends DataObject> implements DataObjec
         return codecFillToString(helper);
     }
 
-    // Helpers split out of codecMember to aid its inlining
-    private Object loadMember(final AtomicReferenceFieldUpdater<CodecDataObject<?>, Object> updater,
-            final NodeContextSupplier supplier) {
-        final NodeCodecContext context = supplier.get();
-
+    // Helper split out of codecMember to aid its inlining
+    private Object loadMember(final VarHandle handle, final NodeCodecContext childCtx) {
         @SuppressWarnings("unchecked")
-        final Optional<NormalizedNode<?, ?>> child = data.getChild(context.getDomPathArgument());
+        final Optional<NormalizedNode<?, ?>> child = data.getChild(childCtx.getDomPathArgument());
 
         // We do not want to use Optional.map() here because we do not want to invoke defaultObject() when we have
         // normal value because defaultObject() may end up throwing an exception intentionally.
-        return updateCache(updater, child.isPresent() ? context.deserializeObject(child.get())
-                : context.defaultObject());
+        final Object obj = child.isPresent() ? childCtx.deserializeObject(child.get()) : childCtx.defaultObject();
+        final Object witness = handle.compareAndExchangeRelease(this, null, maskNull(obj));
+        return witness == null ? obj : unmaskNull(witness);
     }
 
-    // Helpers split out of codecMember to aid its inlining
-    private Object loadKey(final AtomicReferenceFieldUpdater<CodecDataObject<?>, Object> updater,
-            final IdentifiableItemCodec codec) {
+    // Helper split out of codecKey to aid its inlining
+    private Object loadKey(final VarHandle handle) {
         verify(data instanceof MapEntryNode, "Unsupported value %s", data);
-        return updateCache(updater, codec.deserialize(((MapEntryNode) data).getIdentifier()).getKey());
+        verify(context instanceof KeyedListNodeCodecContext, "Unexpected context %s", context);
+        final Object obj = ((KeyedListNodeCodecContext<?>) context).deserialize(((MapEntryNode) data).getIdentifier());
+        // key is known to be non-null, no need to mask it
+        final Object witness = handle.compareAndExchangeRelease(this, null, obj);
+        return witness == null ? obj : witness;
     }
 
-    private Object updateCache(final AtomicReferenceFieldUpdater<CodecDataObject<?>, Object> updater,
-            final Object obj) {
-        return updater.compareAndSet(this, null, maskNull(obj)) ? obj : unmaskNull(updater.get(this));
+    // Helper split out of hashCode() to aid its inlining
+    private int loadHashCode() {
+        final int result = codecAugmentedHashCode();
+        final Object witness = CACHED_HASH_CODE.compareAndExchangeRelease(this, null, Integer.valueOf(result));
+        return witness == null ? result : (Integer) witness;
     }
 
     private static @NonNull Object maskNull(final @Nullable Object unmasked) {