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 com.google.common.base.MoreObjects;
14 import com.google.common.base.MoreObjects.ToStringHelper;
15 import java.util.Optional;
16 import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
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.binding.Identifier;
21 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
22 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
23 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
26 * A base class for {@link DataObject}s backed by {@link DataObjectCodecContext}. While this class is public, it not
27 * part of API surface and is an implementation detail. The only reason for it being public is that it needs to be
28 * accessible by code generated at runtime.
30 * @param <T> DataObject type
32 public abstract class CodecDataObject<T extends DataObject> implements DataObject {
33 // An object representing a null value in a member field.
34 private static final @NonNull Object NULL_VALUE = new Object();
36 private final @NonNull DataObjectCodecContext<T, ?> context;
37 @SuppressWarnings("rawtypes")
38 private final @NonNull NormalizedNodeContainer data;
40 private volatile Integer cachedHashcode = null;
42 protected CodecDataObject(final DataObjectCodecContext<T, ?> context, final NormalizedNodeContainer<?, ?, ?> data) {
43 this.data = requireNonNull(data, "Data must not be null");
44 this.context = requireNonNull(context, "Context must not be null");
48 public final int hashCode() {
49 final Integer cached = cachedHashcode;
54 final int result = codecAugmentedHashCode();
55 cachedHashcode = result;
60 public final boolean equals(final Object obj) {
64 final Class<? extends DataObject> iface = implementedInterface();
65 if (!iface.isInstance(obj)) {
68 @SuppressWarnings("unchecked")
69 final T other = (T) iface.cast(obj);
70 if (other instanceof CodecDataObject) {
71 return data.equals(((CodecDataObject<?>) obj).data);
73 return codecAugmentedEquals(other);
77 public final String toString() {
78 return codecAugmentedFillToString(MoreObjects.toStringHelper(implementedInterface()).omitNullValues())
82 // TODO: consider switching to VarHandles for Java 9+, as that would disconnect us from the need to use a volatile
83 // field and use acquire/release mechanics -- see http://gee.cs.oswego.edu/dl/html/j9mm.html for details.
84 protected final Object codecMember(final AtomicReferenceFieldUpdater<CodecDataObject<?>, Object> updater,
85 final String localName) {
86 final Object cached = updater.get(this);
87 return cached != null ? unmaskNull(cached) : loadMember(updater, context.getLeafChild(localName));
90 protected final Object codecMember(final AtomicReferenceFieldUpdater<CodecDataObject<?>, Object> updater,
91 final Class<? extends DataObject> bindingClass) {
92 final Object cached = updater.get(this);
93 return cached != null ? unmaskNull(cached) : loadMember(updater, context.streamChild(bindingClass));
96 protected final Object codecMember(final AtomicReferenceFieldUpdater<CodecDataObject<?>, Object> updater,
97 final NodeContextSupplier supplier) {
98 final Object cached = updater.get(this);
99 return cached != null ? unmaskNull(cached) : loadMember(updater, supplier.get());
102 protected final Object codecKey(final AtomicReferenceFieldUpdater<CodecDataObject<?>, Object> updater) {
103 final Object cached = updater.get(this);
104 return cached != null ? cached : loadKey(updater);
107 protected abstract int codecHashCode();
109 protected abstract boolean codecEquals(T other);
111 protected abstract ToStringHelper codecFillToString(ToStringHelper helper);
113 final @NonNull DataObjectCodecContext<T, ?> codecContext() {
117 @SuppressWarnings("rawtypes")
118 final @NonNull NormalizedNodeContainer codecData() {
122 // Non-final to allow specialization in AugmentableCodecDataObject
123 int codecAugmentedHashCode() {
124 return codecHashCode();
127 // Non-final to allow specialization in AugmentableCodecDataObject
128 boolean codecAugmentedEquals(final T other) {
129 return codecEquals(other);
132 // Non-final to allow specialization in AugmentableCodecDataObject
133 ToStringHelper codecAugmentedFillToString(final ToStringHelper helper) {
134 return codecFillToString(helper);
137 // Helpers split out of codecMember to aid its inlining
138 private Object loadMember(final AtomicReferenceFieldUpdater<CodecDataObject<?>, Object> updater,
139 final NodeCodecContext childCtx) {
140 @SuppressWarnings("unchecked")
141 final Optional<NormalizedNode<?, ?>> child = data.getChild(childCtx.getDomPathArgument());
143 // We do not want to use Optional.map() here because we do not want to invoke defaultObject() when we have
144 // normal value because defaultObject() may end up throwing an exception intentionally.
145 final Object obj = child.isPresent() ? childCtx.deserializeObject(child.get()) : childCtx.defaultObject();
146 return updater.compareAndSet(this, null, maskNull(obj)) ? obj : unmaskNull(updater.get(this));
149 // Helpers split out of codecMember to aid its inlining
150 private Object loadKey(final AtomicReferenceFieldUpdater<CodecDataObject<?>, Object> updater) {
151 verify(data instanceof MapEntryNode, "Unsupported value %s", data);
152 verify(context instanceof KeyedListNodeCodecContext, "Unexpected context %s", context);
153 final Identifier<?> key = ((KeyedListNodeCodecContext<?>) context).deserialize(
154 ((MapEntryNode) data).getIdentifier());
155 // key is known to be non-null, no need to mask it
156 return updater.compareAndSet(this, null, key) ? key : updater.get(this);
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;