18b195540c624964cb4403eff27c6a9a7f15e0d3
[mdsal.git] / binding / mdsal-binding-dom-codec / src / main / java / org / opendaylight / mdsal / binding / dom / codec / impl / CodecDataObject.java
1 /*
2  * Copyright (c) 2019 PANTHEON.tech, s.r.o. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.mdsal.binding.dom.codec.impl;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.base.VerifyException;
13 import java.lang.invoke.MethodHandles;
14 import java.lang.invoke.VarHandle;
15 import org.eclipse.jdt.annotation.NonNull;
16 import org.eclipse.jdt.annotation.Nullable;
17 import org.opendaylight.yangtools.yang.binding.DataObject;
18 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
19 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
20
21 /**
22  * A base class for {@link DataObject}s backed by {@link DataObjectCodecContext}. While this class is public, it not
23  * part of API surface and is an implementation detail. The only reason for it being public is that it needs to be
24  * accessible by code generated at runtime.
25  *
26  * @param <T> DataObject type
27  */
28 public abstract class CodecDataObject<T extends DataObject> implements DataObject {
29     // An object representing a null value in a member field.
30     private static final @NonNull Object NULL_VALUE = new Object();
31
32     private static final VarHandle CACHED_HASH_CODE;
33
34     static {
35         try {
36             CACHED_HASH_CODE = MethodHandles.lookup().findVarHandle(CodecDataObject.class, "cachedHashcode",
37                 Integer.class);
38         } catch (NoSuchFieldException | IllegalAccessException e) {
39             throw new ExceptionInInitializerError(e);
40         }
41     }
42
43     private final @NonNull AbstractDataObjectCodecContext<T, ?> context;
44     private final @NonNull DataContainerNode data;
45
46     // Accessed via a VarHandle
47     @SuppressWarnings("unused")
48     // FIXME: consider using a primitive int-based cache (with 0 being uninit)
49     private volatile Integer cachedHashcode;
50
51     protected CodecDataObject(final AbstractDataObjectCodecContext<T, ?> context, final DataContainerNode data) {
52         this.data = requireNonNull(data, "Data must not be null");
53         this.context = requireNonNull(context, "Context must not be null");
54     }
55
56     @Override
57     public final int hashCode() {
58         final var cached = (Integer) CACHED_HASH_CODE.getAcquire(this);
59         return cached != null ? cached : loadHashCode();
60     }
61
62     @Override
63     public final boolean equals(final Object obj) {
64         // Indirection to keep checkstyle happy
65         return codecEquals(obj);
66     }
67
68     @Override
69     public abstract String toString();
70
71     protected final Object codecMember(final VarHandle handle, final String localName) {
72         final Object cached = handle.getAcquire(this);
73         return cached != null ? unmaskNull(cached) : loadMember(handle, context.getLeafChild(localName));
74     }
75
76     protected final Object codecMember(final VarHandle handle, final Class<? extends DataObject> bindingClass) {
77         final Object cached = handle.getAcquire(this);
78         return cached != null ? unmaskNull(cached) : loadMember(handle, context.getStreamChild(bindingClass));
79     }
80
81     protected final Object codecMember(final VarHandle handle, final CodecContextSupplier supplier) {
82         final Object cached = handle.getAcquire(this);
83         return cached != null ? unmaskNull(cached) : loadMember(handle, supplier.get());
84     }
85
86     protected final @NonNull Object codecMemberOrEmpty(final @Nullable Object value,
87             final @NonNull Class<? extends DataObject> bindingClass) {
88         return value != null ? value : emptyObject(bindingClass);
89     }
90
91     private @NonNull Object emptyObject(final @NonNull Class<? extends DataObject> bindingClass) {
92         final var childContext = context.getStreamChild(bindingClass);
93         if (childContext instanceof StructuralContainerCodecContext<?> structural) {
94             return structural.emptyObject();
95         }
96         throw new VerifyException("Unexpected context " + childContext);
97     }
98
99     protected final @NonNull Object codecKey(final VarHandle handle) {
100         final Object cached = handle.getAcquire(this);
101         return cached != null ? cached : loadKey(handle);
102     }
103
104     protected abstract int codecHashCode();
105
106     protected abstract boolean codecEquals(Object obj);
107
108     final @NonNull AbstractDataObjectCodecContext<T, ?> codecContext() {
109         return context;
110     }
111
112     final @NonNull DataContainerNode codecData() {
113         return data;
114     }
115
116     // Helper split out of codecMember to aid its inlining
117     private Object loadMember(final VarHandle handle, final CodecContext childCtx) {
118         final var child = data.childByArg(childCtx.getDomPathArgument());
119
120         // We do not want to use Optional.map() here because we do not want to invoke defaultObject() when we have
121         // normal value because defaultObject() may end up throwing an exception intentionally.
122         final Object obj = child != null ? childCtx.deserializeObject(child) : childCtx.defaultObject();
123         final Object witness = handle.compareAndExchangeRelease(this, null, maskNull(obj));
124         return witness == null ? obj : unmaskNull(witness);
125     }
126
127     // Helper split out of codecKey to aid its inlining
128     private @NonNull Object loadKey(final VarHandle handle) {
129         if (!(data instanceof MapEntryNode mapEntry)) {
130             throw new VerifyException("Unsupported value " + data);
131         }
132         if (!(context instanceof MapCodecContext<?, ?> listContext)) {
133             throw new VerifyException("Unexpected context " + context);
134         }
135
136         final Object obj = listContext.deserialize(mapEntry.name());
137         // key is known to be non-null, no need to mask it
138         final Object witness = handle.compareAndExchangeRelease(this, null, obj);
139         return witness == null ? obj : witness;
140     }
141
142     // Helper split out of hashCode() to aid its inlining
143     private int loadHashCode() {
144         final int result = codecHashCode();
145         final Object witness = CACHED_HASH_CODE.compareAndExchangeRelease(this, null, result);
146         return witness == null ? result : (Integer) witness;
147     }
148
149     private static @NonNull Object maskNull(final @Nullable Object unmasked) {
150         return unmasked == null ? NULL_VALUE : unmasked;
151     }
152
153     private static @Nullable Object unmaskNull(final Object masked) {
154         return masked == NULL_VALUE ? null : masked;
155     }
156 }