*/
package org.opendaylight.mdsal.binding.dom.codec.impl;
-import static com.google.common.base.Verify.verify;
import static java.util.Objects.requireNonNull;
-import com.google.common.base.MoreObjects;
-import com.google.common.base.MoreObjects.ToStringHelper;
-import java.util.Optional;
-import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
+import com.google.common.base.VerifyException;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
/**
* A base class for {@link DataObject}s backed by {@link DataObjectCodecContext}. While this class is public, it not
* @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();
- @SuppressWarnings("rawtypes")
- private final @NonNull NormalizedNodeContainer data;
+ private static final VarHandle CACHED_HASH_CODE;
- private volatile Integer cachedHashcode = null;
+ static {
+ try {
+ CACHED_HASH_CODE = MethodHandles.lookup().findVarHandle(CodecDataObject.class, "cachedHashcode",
+ Integer.class);
+ } catch (NoSuchFieldException | IllegalAccessException e) {
+ throw new ExceptionInInitializerError(e);
+ }
+ }
+
+ private final @NonNull AbstractDataObjectCodecContext<T, ?> context;
+ private final @NonNull DataContainerNode data;
- protected CodecDataObject(final DataObjectCodecContext<T, ?> context, final NormalizedNodeContainer<?, ?, ?> data) {
+ // Accessed via a VarHandle
+ // FIXME: consider using a primitive int-based cache (with 0 being uninit)
+ @SuppressWarnings("unused")
+ @SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "https://github.com/spotbugs/spotbugs/issues/2749")
+ private volatile Integer cachedHashcode;
+
+ protected CodecDataObject(final AbstractDataObjectCodecContext<T, ?> context, final DataContainerNode 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 var cached = (Integer) CACHED_HASH_CODE.getAcquire(this);
+ return cached != null ? cached : loadHashCode();
}
@Override
public final boolean equals(final Object obj) {
- if (obj == this) {
- return true;
- }
- final Class<? extends DataObject> iface = implementedInterface();
- if (!iface.isInstance(obj)) {
- return false;
- }
- @SuppressWarnings("unchecked")
- final T other = (T) iface.cast(obj);
- if (other instanceof CodecDataObject) {
- return data.equals(((CodecDataObject<?>) obj).data);
- }
- return codecAugmentedEquals(other);
+ // Indirection to keep checkstyle happy
+ return codecEquals(obj);
}
@Override
- public final String toString() {
- return codecAugmentedFillToString(MoreObjects.toStringHelper(implementedInterface()).omitNullValues())
- .toString();
- }
+ public abstract String 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.getStreamChild(bindingClass));
}
- protected abstract int codecHashCode();
-
- protected abstract boolean codecEquals(T other);
-
- protected abstract ToStringHelper codecFillToString(ToStringHelper helper);
+ protected final Object codecMember(final VarHandle handle, final CodecContextSupplier supplier) {
+ final Object cached = handle.getAcquire(this);
+ return cached != null ? unmaskNull(cached) : loadMember(handle, supplier.getCodecContext());
+ }
- @SuppressWarnings("rawtypes")
- final @NonNull NormalizedNodeContainer codecData() {
- return data;
+ protected final @NonNull Object codecMemberOrEmpty(final @Nullable Object value,
+ final @NonNull Class<? extends DataObject> bindingClass) {
+ return value != null ? value : emptyObject(bindingClass);
}
- // Non-final to allow specialization in AugmentableCodecDataObject
- int codecAugmentedHashCode() {
- return codecHashCode();
+ private @NonNull Object emptyObject(final @NonNull Class<? extends DataObject> bindingClass) {
+ final var childContext = context.getStreamChild(bindingClass);
+ if (childContext instanceof StructuralContainerCodecContext<?> structural) {
+ return structural.emptyObject();
+ }
+ throw new VerifyException("Unexpected context " + childContext);
}
- // Non-final to allow specialization in AugmentableCodecDataObject
- boolean codecAugmentedEquals(final T other) {
- return codecEquals(other);
+ protected final @NonNull Object codecKey(final VarHandle handle) {
+ final Object cached = handle.getAcquire(this);
+ return cached != null ? cached : loadKey(handle);
}
- // Non-final to allow specialization in AugmentableCodecDataObject
- ToStringHelper codecAugmentedFillToString(final ToStringHelper helper) {
- return codecFillToString(helper);
+ protected abstract int codecHashCode();
+
+ protected abstract boolean codecEquals(Object obj);
+
+ final @NonNull AbstractDataObjectCodecContext<T, ?> codecContext() {
+ return context;
}
- // 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();
+ final @NonNull DataContainerNode codecData() {
+ return data;
+ }
- @SuppressWarnings("unchecked")
- final Optional<NormalizedNode<?, ?>> child = data.getChild(context.getDomPathArgument());
+ // Helper split out of codecMember to aid its inlining
+ private Object loadMember(final VarHandle handle, final CodecContext childCtx) {
+ final var child = data.childByArg(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 != null ? childCtx.deserializeObject(child) : 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) {
- verify(data instanceof MapEntryNode, "Unsupported value %s", data);
- return updateCache(updater, codec.deserialize(((MapEntryNode) data).getIdentifier()).getKey());
+ // Helper split out of codecKey to aid its inlining
+ private @NonNull Object loadKey(final VarHandle handle) {
+ if (!(data instanceof MapEntryNode mapEntry)) {
+ throw new VerifyException("Unsupported value " + data);
+ }
+ if (!(context instanceof MapCodecContext<?, ?> listContext)) {
+ throw new VerifyException("Unexpected context " + context);
+ }
+
+ final Object obj = listContext.deserialize(mapEntry.name());
+ // 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 = codecHashCode();
+ final Object witness = CACHED_HASH_CODE.compareAndExchangeRelease(this, null, result);
+ return witness == null ? result : (Integer) witness;
}
private static @NonNull Object maskNull(final @Nullable Object unmasked) {