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 com.google.common.base.Verify.verify;
11 import static java.util.Objects.requireNonNull;
13 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
14 import java.lang.invoke.MethodHandles;
15 import java.lang.invoke.VarHandle;
16 import java.util.Optional;
17 import org.eclipse.jdt.annotation.NonNull;
18 import org.eclipse.jdt.annotation.Nullable;
19 import org.opendaylight.yangtools.yang.binding.DataObject;
20 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
21 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
22 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
25 * A base class for {@link DataObject}s backed by {@link DataObjectCodecContext}. While this class is public, it not
26 * part of API surface and is an implementation detail. The only reason for it being public is that it needs to be
27 * accessible by code generated at runtime.
29 * @param <T> DataObject type
31 public abstract class CodecDataObject<T extends DataObject> implements DataObject {
32 // An object representing a null value in a member field.
33 private static final @NonNull Object NULL_VALUE = new Object();
35 private static final VarHandle CACHED_HASH_CODE;
39 CACHED_HASH_CODE = MethodHandles.lookup().findVarHandle(CodecDataObject.class, "cachedHashcode",
41 } catch (NoSuchFieldException | IllegalAccessException e) {
42 throw new ExceptionInInitializerError(e);
46 private final @NonNull DataObjectCodecContext<T, ?> context;
47 @SuppressWarnings("rawtypes")
48 private final @NonNull NormalizedNodeContainer data;
50 // Accessed via a VarHandle
51 @SuppressWarnings("unused")
52 // FIXME: consider using a primitive int-based cache (with 0 being uninit)
53 private volatile Integer cachedHashcode;
55 protected CodecDataObject(final DataObjectCodecContext<T, ?> context, final NormalizedNodeContainer<?, ?, ?> data) {
56 this.data = requireNonNull(data, "Data must not be null");
57 this.context = requireNonNull(context, "Context must not be null");
61 public final int hashCode() {
62 final Integer cached = (Integer) CACHED_HASH_CODE.getAcquire(this);
63 return cached != null ? cached : loadHashCode();
67 @SuppressFBWarnings(value = "EQ_UNUSUAL", justification = "State is examined indirectly enough to confuse SpotBugs")
68 public final boolean equals(final Object obj) {
72 final Class<? extends DataObject> iface = implementedInterface();
73 if (!iface.isInstance(obj)) {
76 @SuppressWarnings("unchecked")
77 final T other = (T) iface.cast(obj);
78 // Note: we do not want to compare NormalizedNode data here, as we may be looking at different instantiations
79 // of the same grouping -- in which case normalized node will not compare as equal.
80 return codecAugmentedEquals(other);
84 public abstract String toString();
86 protected final Object codecMember(final VarHandle handle, final String localName) {
87 final Object cached = handle.getAcquire(this);
88 return cached != null ? unmaskNull(cached) : loadMember(handle, context.getLeafChild(localName));
91 protected final Object codecMember(final VarHandle handle, final Class<? extends DataObject> bindingClass) {
92 final Object cached = handle.getAcquire(this);
93 return cached != null ? unmaskNull(cached) : loadMember(handle, context.streamChild(bindingClass));
96 protected final Object codecMember(final VarHandle handle, final NodeContextSupplier supplier) {
97 final Object cached = handle.getAcquire(this);
98 return cached != null ? unmaskNull(cached) : loadMember(handle, supplier.get());
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(T other);
110 final @NonNull DataObjectCodecContext<T, ?> codecContext() {
114 @SuppressWarnings("rawtypes")
115 final @NonNull NormalizedNodeContainer codecData() {
119 // Non-final to allow specialization in AugmentableCodecDataObject
120 int codecAugmentedHashCode() {
121 return codecHashCode();
124 // Non-final to allow specialization in AugmentableCodecDataObject
125 boolean codecAugmentedEquals(final T other) {
126 return codecEquals(other);
129 // Helper split out of codecMember to aid its inlining
130 private Object loadMember(final VarHandle handle, final NodeCodecContext childCtx) {
131 @SuppressWarnings("unchecked")
132 final Optional<NormalizedNode<?, ?>> child = data.getChild(childCtx.getDomPathArgument());
134 // We do not want to use Optional.map() here because we do not want to invoke defaultObject() when we have
135 // normal value because defaultObject() may end up throwing an exception intentionally.
136 final Object obj = child.isPresent() ? childCtx.deserializeObject(child.get()) : childCtx.defaultObject();
137 final Object witness = handle.compareAndExchangeRelease(this, null, maskNull(obj));
138 return witness == null ? obj : unmaskNull(witness);
141 // Helper split out of codecKey to aid its inlining
142 private @NonNull Object loadKey(final VarHandle handle) {
143 verify(data instanceof MapEntryNode, "Unsupported value %s", data);
144 verify(context instanceof KeyedListNodeCodecContext, "Unexpected context %s", context);
145 final Object obj = ((KeyedListNodeCodecContext<?, ?>) context)
146 .deserialize(((MapEntryNode) data).getIdentifier());
147 // key is known to be non-null, no need to mask it
148 final Object witness = handle.compareAndExchangeRelease(this, null, obj);
149 return witness == null ? obj : witness;
152 // Helper split out of hashCode() to aid its inlining
153 private int loadHashCode() {
154 final int result = codecAugmentedHashCode();
155 final Object witness = CACHED_HASH_CODE.compareAndExchangeRelease(this, null, Integer.valueOf(result));
156 return witness == null ? result : (Integer) witness;
159 private static @NonNull Object maskNull(final @Nullable Object unmasked) {
160 return unmasked == null ? NULL_VALUE : unmasked;
163 private static @Nullable Object unmaskNull(final Object masked) {
164 return masked == NULL_VALUE ? null : masked;