Use bindingEquals() generated for interfaces
[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 java.util.Optional;
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.MapEntryNode;
20 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
21 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
22
23 /**
24  * A base class for {@link DataObject}s backed by {@link DataObjectCodecContext}. While this class is public, it not
25  * part of API surface and is an implementation detail. The only reason for it being public is that it needs to be
26  * accessible by code generated at runtime.
27  *
28  * @param <T> DataObject type
29  */
30 public abstract class CodecDataObject<T extends DataObject> implements DataObject {
31     // An object representing a null value in a member field.
32     private static final @NonNull Object NULL_VALUE = new Object();
33
34     private static final VarHandle CACHED_HASH_CODE;
35
36     static {
37         try {
38             CACHED_HASH_CODE = MethodHandles.lookup().findVarHandle(CodecDataObject.class, "cachedHashcode",
39                 Integer.class);
40         } catch (NoSuchFieldException | IllegalAccessException e) {
41             throw new ExceptionInInitializerError(e);
42         }
43     }
44
45     private final @NonNull DataObjectCodecContext<T, ?> context;
46     @SuppressWarnings("rawtypes")
47     private final @NonNull NormalizedNodeContainer data;
48
49     // Accessed via a VarHandle
50     @SuppressWarnings("unused")
51     // FIXME: consider using a primitive int-based cache (with 0 being uninit)
52     private volatile Integer cachedHashcode;
53
54     protected CodecDataObject(final DataObjectCodecContext<T, ?> context, final NormalizedNodeContainer<?, ?, ?> data) {
55         this.data = requireNonNull(data, "Data must not be null");
56         this.context = requireNonNull(context, "Context must not be null");
57     }
58
59     @Override
60     public final int hashCode() {
61         final Integer cached = (Integer) CACHED_HASH_CODE.getAcquire(this);
62         return cached != null ? cached : loadHashCode();
63     }
64
65     @Override
66     public final boolean equals(final Object obj) {
67         // Indirection to keep checkstyle happy
68         return codecEquals(obj);
69     }
70
71     @Override
72     public abstract String toString();
73
74     protected final Object codecMember(final VarHandle handle, final String localName) {
75         final Object cached = handle.getAcquire(this);
76         return cached != null ? unmaskNull(cached) : loadMember(handle, context.getLeafChild(localName));
77     }
78
79     protected final Object codecMember(final VarHandle handle, final Class<? extends DataObject> bindingClass) {
80         final Object cached = handle.getAcquire(this);
81         return cached != null ? unmaskNull(cached) : loadMember(handle, context.streamChild(bindingClass));
82     }
83
84     protected final Object codecMember(final VarHandle handle, final NodeContextSupplier supplier) {
85         final Object cached = handle.getAcquire(this);
86         return cached != null ? unmaskNull(cached) : loadMember(handle, supplier.get());
87     }
88
89     protected final @NonNull Object codecKey(final VarHandle handle) {
90         final Object cached = handle.getAcquire(this);
91         return cached != null ? cached : loadKey(handle);
92     }
93
94     protected abstract int codecHashCode();
95
96     protected abstract boolean codecEquals(Object obj);
97
98     final @NonNull DataObjectCodecContext<T, ?> codecContext() {
99         return context;
100     }
101
102     @SuppressWarnings("rawtypes")
103     final @NonNull NormalizedNodeContainer codecData() {
104         return data;
105     }
106
107     // Helper split out of codecMember to aid its inlining
108     private Object loadMember(final VarHandle handle, final NodeCodecContext childCtx) {
109         @SuppressWarnings("unchecked")
110         final Optional<NormalizedNode<?, ?>> child = data.getChild(childCtx.getDomPathArgument());
111
112         // We do not want to use Optional.map() here because we do not want to invoke defaultObject() when we have
113         // normal value because defaultObject() may end up throwing an exception intentionally.
114         final Object obj = child.isPresent() ? childCtx.deserializeObject(child.get()) : childCtx.defaultObject();
115         final Object witness = handle.compareAndExchangeRelease(this, null, maskNull(obj));
116         return witness == null ? obj : unmaskNull(witness);
117     }
118
119     // Helper split out of codecKey to aid its inlining
120     private @NonNull Object loadKey(final VarHandle handle) {
121         verify(data instanceof MapEntryNode, "Unsupported value %s", data);
122         verify(context instanceof KeyedListNodeCodecContext, "Unexpected context %s", context);
123         final Object obj = ((KeyedListNodeCodecContext<?, ?>) context)
124                 .deserialize(((MapEntryNode) data).getIdentifier());
125         // key is known to be non-null, no need to mask it
126         final Object witness = handle.compareAndExchangeRelease(this, null, obj);
127         return witness == null ? obj : witness;
128     }
129
130     // Helper split out of hashCode() to aid its inlining
131     private int loadHashCode() {
132         final int result = codecHashCode();
133         final Object witness = CACHED_HASH_CODE.compareAndExchangeRelease(this, null, Integer.valueOf(result));
134         return witness == null ? result : (Integer) witness;
135     }
136
137     private static @NonNull Object maskNull(final @Nullable Object unmasked) {
138         return unmasked == null ? NULL_VALUE : unmasked;
139     }
140
141     private static @Nullable Object unmaskNull(final Object masked) {
142         return masked == NULL_VALUE ? null : masked;
143     }
144 }