1d7b2168cc841b76cf9d4365521169ccb5d92ec4
[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 com.google.common.base.Verify.verify;
11 import static java.util.Objects.requireNonNull;
12
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.DistinctNodeContainer;
19 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
20 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
21
22 /**
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.
26  *
27  * @param <T> DataObject type
28  */
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();
32
33     private static final VarHandle CACHED_HASH_CODE;
34
35     static {
36         try {
37             CACHED_HASH_CODE = MethodHandles.lookup().findVarHandle(CodecDataObject.class, "cachedHashcode",
38                 Integer.class);
39         } catch (NoSuchFieldException | IllegalAccessException e) {
40             throw new ExceptionInInitializerError(e);
41         }
42     }
43
44     private final @NonNull DataObjectCodecContext<T, ?> context;
45     @SuppressWarnings("rawtypes")
46     private final @NonNull DistinctNodeContainer data;
47
48     // Accessed via a VarHandle
49     @SuppressWarnings("unused")
50     // FIXME: consider using a primitive int-based cache (with 0 being uninit)
51     private volatile Integer cachedHashcode;
52
53     protected CodecDataObject(final DataObjectCodecContext<T, ?> context, final DistinctNodeContainer<?, ?> data) {
54         this.data = requireNonNull(data, "Data must not be null");
55         this.context = requireNonNull(context, "Context must not be null");
56     }
57
58     @Override
59     public final int hashCode() {
60         final Integer cached = (Integer) CACHED_HASH_CODE.getAcquire(this);
61         return cached != null ? cached : loadHashCode();
62     }
63
64     @Override
65     public final boolean equals(final Object obj) {
66         // Indirection to keep checkstyle happy
67         return codecEquals(obj);
68     }
69
70     @Override
71     public abstract String toString();
72
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));
76     }
77
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.streamChild(bindingClass));
81     }
82
83     protected final Object codecMember(final VarHandle handle, final NodeContextSupplier supplier) {
84         final Object cached = handle.getAcquire(this);
85         return cached != null ? unmaskNull(cached) : loadMember(handle, supplier.get());
86     }
87
88     protected final @NonNull Object codecMemberOrEmpty(final @Nullable Object value,
89             final @NonNull Class<? extends DataObject> bindingClass) {
90         return value != null ? value : emptyObject(bindingClass);
91     }
92
93     private @NonNull Object emptyObject(final @NonNull Class<? extends DataObject> bindingClass) {
94         final var childContext = context.streamChild(bindingClass);
95         verify(childContext instanceof NonPresenceContainerNodeCodecContext, "Unexpected context %s", childContext);
96         return ((NonPresenceContainerNodeCodecContext<?>) childContext).emptyObject();
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 DataObjectCodecContext<T, ?> codecContext() {
109         return context;
110     }
111
112     @SuppressWarnings("rawtypes")
113     final @NonNull DistinctNodeContainer codecData() {
114         return data;
115     }
116
117     // Helper split out of codecMember to aid its inlining
118     private Object loadMember(final VarHandle handle, final NodeCodecContext childCtx) {
119         @SuppressWarnings("unchecked")
120         final NormalizedNode child = data.childByArg(childCtx.getDomPathArgument());
121
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);
127     }
128
129     // Helper split out of codecKey to aid its inlining
130     private @NonNull Object loadKey(final VarHandle handle) {
131         verify(data instanceof MapEntryNode, "Unsupported value %s", data);
132         verify(context instanceof KeyedListNodeCodecContext, "Unexpected context %s", context);
133         final Object obj = ((KeyedListNodeCodecContext<?, ?>) context)
134                 .deserialize(((MapEntryNode) data).getIdentifier());
135         // key is known to be non-null, no need to mask it
136         final Object witness = handle.compareAndExchangeRelease(this, null, obj);
137         return witness == null ? obj : witness;
138     }
139
140     // Helper split out of hashCode() to aid its inlining
141     private int loadHashCode() {
142         final int result = codecHashCode();
143         final Object witness = CACHED_HASH_CODE.compareAndExchangeRelease(this, null, result);
144         return witness == null ? result : (Integer) witness;
145     }
146
147     private static @NonNull Object maskNull(final @Nullable Object unmasked) {
148         return unmasked == null ? NULL_VALUE : unmasked;
149     }
150
151     private static @Nullable Object unmaskNull(final Object masked) {
152         return masked == NULL_VALUE ? null : masked;
153     }
154 }