2 * Copyright (c) 2019 PANTHEON.tech, s.r.o. 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.mdsal.binding.dom.codec.impl;
10 import static java.util.Objects.requireNonNull;
12 import com.google.common.base.VerifyException;
13 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
14 import java.lang.invoke.MethodHandles;
15 import java.lang.invoke.VarHandle;
16 import org.eclipse.jdt.annotation.NonNull;
17 import org.eclipse.jdt.annotation.Nullable;
18 import org.opendaylight.yangtools.yang.binding.DataObject;
19 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
20 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
23 * A base class for {@link DataObject}s backed by {@link DataObjectCodecContext}. While this class is public, it not
24 * part of API surface and is an implementation detail. The only reason for it being public is that it needs to be
25 * accessible by code generated at runtime.
27 * @param <T> DataObject type
29 public abstract class CodecDataObject<T extends DataObject> implements DataObject {
30 // An object representing a null value in a member field.
31 private static final @NonNull Object NULL_VALUE = new Object();
33 private static final VarHandle CACHED_HASH_CODE;
37 CACHED_HASH_CODE = MethodHandles.lookup().findVarHandle(CodecDataObject.class, "cachedHashcode",
39 } catch (NoSuchFieldException | IllegalAccessException e) {
40 throw new ExceptionInInitializerError(e);
44 private final @NonNull AbstractDataObjectCodecContext<T, ?> context;
45 private final @NonNull DataContainerNode data;
47 // Accessed via a VarHandle
48 @SuppressWarnings("unused")
49 // FIXME: consider using a primitive int-based cache (with 0 being uninit)
50 @SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "https://github.com/spotbugs/spotbugs/issues/2749")
51 private volatile Integer cachedHashcode;
53 protected CodecDataObject(final AbstractDataObjectCodecContext<T, ?> context, final DataContainerNode data) {
54 this.data = requireNonNull(data, "Data must not be null");
55 this.context = requireNonNull(context, "Context must not be null");
59 public final int hashCode() {
60 final var cached = (Integer) CACHED_HASH_CODE.getAcquire(this);
61 return cached != null ? cached : loadHashCode();
65 public final boolean equals(final Object obj) {
66 // Indirection to keep checkstyle happy
67 return codecEquals(obj);
71 public abstract String toString();
73 protected final Object codecMember(final VarHandle handle, final String localName) {
74 final Object cached = handle.getAcquire(this);
75 return cached != null ? unmaskNull(cached) : loadMember(handle, context.getLeafChild(localName));
78 protected final Object codecMember(final VarHandle handle, final Class<? extends DataObject> bindingClass) {
79 final Object cached = handle.getAcquire(this);
80 return cached != null ? unmaskNull(cached) : loadMember(handle, context.getStreamChild(bindingClass));
83 protected final Object codecMember(final VarHandle handle, final CodecContextSupplier supplier) {
84 final Object cached = handle.getAcquire(this);
85 return cached != null ? unmaskNull(cached) : loadMember(handle, supplier.get());
88 protected final @NonNull Object codecMemberOrEmpty(final @Nullable Object value,
89 final @NonNull Class<? extends DataObject> bindingClass) {
90 return value != null ? value : emptyObject(bindingClass);
93 private @NonNull Object emptyObject(final @NonNull Class<? extends DataObject> bindingClass) {
94 final var childContext = context.getStreamChild(bindingClass);
95 if (childContext instanceof StructuralContainerCodecContext<?> structural) {
96 return structural.emptyObject();
98 throw new VerifyException("Unexpected context " + childContext);
101 protected final @NonNull Object codecKey(final VarHandle handle) {
102 final Object cached = handle.getAcquire(this);
103 return cached != null ? cached : loadKey(handle);
106 protected abstract int codecHashCode();
108 protected abstract boolean codecEquals(Object obj);
110 final @NonNull AbstractDataObjectCodecContext<T, ?> codecContext() {
114 final @NonNull DataContainerNode codecData() {
118 // Helper split out of codecMember to aid its inlining
119 private Object loadMember(final VarHandle handle, final CodecContext childCtx) {
120 final var child = data.childByArg(childCtx.getDomPathArgument());
122 // We do not want to use Optional.map() here because we do not want to invoke defaultObject() when we have
123 // normal value because defaultObject() may end up throwing an exception intentionally.
124 final Object obj = child != null ? childCtx.deserializeObject(child) : childCtx.defaultObject();
125 final Object witness = handle.compareAndExchangeRelease(this, null, maskNull(obj));
126 return witness == null ? obj : unmaskNull(witness);
129 // Helper split out of codecKey to aid its inlining
130 private @NonNull Object loadKey(final VarHandle handle) {
131 if (!(data instanceof MapEntryNode mapEntry)) {
132 throw new VerifyException("Unsupported value " + data);
134 if (!(context instanceof MapCodecContext<?, ?> listContext)) {
135 throw new VerifyException("Unexpected context " + context);
138 final Object obj = listContext.deserialize(mapEntry.name());
139 // key is known to be non-null, no need to mask it
140 final Object witness = handle.compareAndExchangeRelease(this, null, obj);
141 return witness == null ? obj : witness;
144 // Helper split out of hashCode() to aid its inlining
145 private int loadHashCode() {
146 final int result = codecHashCode();
147 final Object witness = CACHED_HASH_CODE.compareAndExchangeRelease(this, null, result);
148 return witness == null ? result : (Integer) witness;
151 private static @NonNull Object maskNull(final @Nullable Object unmasked) {
152 return unmasked == null ? NULL_VALUE : unmasked;
155 private static @Nullable Object unmaskNull(final Object masked) {
156 return masked == NULL_VALUE ? null : masked;