b48bde535a3943eaa37bfb3e92921ee72f902477
[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 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.data.api.schema.MapEntryNode;
21 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
22 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
23
24 /**
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.
28  *
29  * @param <T> DataObject type
30  */
31 public abstract class CodecDataObject<T extends DataObject> implements DataObject {
32     private static final @NonNull Object NULL_VALUE = new Object();
33
34     @SuppressWarnings("rawtypes")
35     private final @NonNull NormalizedNodeContainer data;
36
37     private volatile Integer cachedHashcode = null;
38
39     protected CodecDataObject(final DataObjectCodecContext<T, ?> context, final NormalizedNodeContainer<?, ?, ?> data) {
40         this.data = requireNonNull(data, "Data must not be null");
41     }
42
43     @Override
44     public final int hashCode() {
45         final Integer cached = cachedHashcode;
46         if (cached != null) {
47             return cached;
48         }
49
50         final int result = codecAugmentedHashCode();
51         cachedHashcode = result;
52         return result;
53     }
54
55     @Override
56     public final boolean equals(final Object obj) {
57         if (obj == this) {
58             return true;
59         }
60         final Class<? extends DataObject> iface = implementedInterface();
61         if (!iface.isInstance(obj)) {
62             return false;
63         }
64         @SuppressWarnings("unchecked")
65         final T other = (T) iface.cast(obj);
66         if (other instanceof CodecDataObject) {
67             return data.equals(((CodecDataObject<?>) obj).data);
68         }
69         return codecAugmentedEquals(other);
70     }
71
72     @Override
73     public final String toString() {
74         return codecAugmentedFillToString(MoreObjects.toStringHelper(implementedInterface()).omitNullValues())
75                 .toString();
76     }
77
78     // TODO: consider switching to VarHandles for Java 9+, as that would disconnect us from the need to use a volatile
79     //       field and use acquire/release mechanics -- see http://gee.cs.oswego.edu/dl/html/j9mm.html for details.
80     protected final Object codecMember(final AtomicReferenceFieldUpdater<CodecDataObject<?>, Object> updater,
81             final NodeContextSupplier supplier) {
82         final Object cached = updater.get(this);
83         return cached != null ? unmaskNull(cached) : loadMember(updater, supplier);
84     }
85
86     protected final Object codecMember(final AtomicReferenceFieldUpdater<CodecDataObject<?>, Object> updater,
87             final IdentifiableItemCodec codec) {
88         final Object cached = updater.get(this);
89         return cached != null ? unmaskNull(cached) : loadKey(updater, codec);
90     }
91
92     protected abstract int codecHashCode();
93
94     protected abstract boolean codecEquals(T other);
95
96     protected abstract ToStringHelper codecFillToString(ToStringHelper helper);
97
98     @SuppressWarnings("rawtypes")
99     final @NonNull NormalizedNodeContainer codecData() {
100         return data;
101     }
102
103     // Non-final to allow specialization in AugmentableCodecDataObject
104     int codecAugmentedHashCode() {
105         return codecHashCode();
106     }
107
108     // Non-final to allow specialization in AugmentableCodecDataObject
109     boolean codecAugmentedEquals(final T other) {
110         return codecEquals(other);
111     }
112
113     // Non-final to allow specialization in AugmentableCodecDataObject
114     ToStringHelper codecAugmentedFillToString(final ToStringHelper helper) {
115         return codecFillToString(helper);
116     }
117
118     // Helpers split out of codecMember to aid its inlining
119     private Object loadMember(final AtomicReferenceFieldUpdater<CodecDataObject<?>, Object> updater,
120             final NodeContextSupplier supplier) {
121         final NodeCodecContext context = supplier.get();
122
123         @SuppressWarnings("unchecked")
124         final Optional<NormalizedNode<?, ?>> child = data.getChild(context.getDomPathArgument());
125
126         // We do not want to use Optional.map() here because we do not want to invoke defaultObject() when we have
127         // normal value because defaultObject() may end up throwing an exception intentionally.
128         return updateCache(updater, child.isPresent() ? context.deserializeObject(child.get())
129                 : context.defaultObject());
130     }
131
132     // Helpers split out of codecMember to aid its inlining
133     private Object loadKey(final AtomicReferenceFieldUpdater<CodecDataObject<?>, Object> updater,
134             final IdentifiableItemCodec codec) {
135         verify(data instanceof MapEntryNode, "Unsupported value %s", data);
136         return updateCache(updater, codec.deserialize(((MapEntryNode) data).getIdentifier()).getKey());
137     }
138
139     private Object updateCache(final AtomicReferenceFieldUpdater<CodecDataObject<?>, Object> updater,
140             final Object obj) {
141         return updater.compareAndSet(this, null, maskNull(obj)) ? obj : unmaskNull(updater.get(this));
142     }
143
144     private static @NonNull Object maskNull(final @Nullable Object unmasked) {
145         return unmasked == null ? NULL_VALUE : unmasked;
146     }
147
148     private static @Nullable Object unmaskNull(final Object masked) {
149         return masked == NULL_VALUE ? null : masked;
150     }
151 }