Optimize CodecDataObject dispatch
[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.MoreObjects;
13 import com.google.common.base.MoreObjects.ToStringHelper;
14 import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
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.NormalizedNodeContainer;
19
20 /**
21  * A base class for {@link DataObject}s backed by {@link DataObjectCodecContext}. While this class is public, it not
22  * part of API surface and is an implementation detail. The only reason for it being public is that it needs to be
23  * accessible by code generated at runtime.
24  *
25  * @param <T> DataObject type
26  */
27 public abstract class CodecDataObject<T extends DataObject> implements DataObject {
28     private static final @NonNull Object NULL_VALUE = new Object();
29
30     @SuppressWarnings("rawtypes")
31     private final @NonNull NormalizedNodeContainer data;
32     final @NonNull DataObjectCodecContext<T, ?> context;
33
34     private volatile Integer cachedHashcode = null;
35
36     public CodecDataObject(final DataObjectCodecContext<T, ?> ctx, final NormalizedNodeContainer<?, ?, ?> data) {
37         this.context = requireNonNull(ctx, "Context must not be null");
38         this.data = requireNonNull(data, "Data must not be null");
39     }
40
41     @Override
42     public final int hashCode() {
43         final Integer cached = cachedHashcode;
44         if (cached != null) {
45             return cached;
46         }
47
48         final int result = codecAugmentedHashCode();
49         cachedHashcode = result;
50         return result;
51     }
52
53     @Override
54     public final boolean equals(final Object obj) {
55         if (obj == this) {
56             return true;
57         }
58         final Class<? extends DataObject> iface = implementedInterface();
59         if (!iface.isInstance(obj)) {
60             return false;
61         }
62         @SuppressWarnings("unchecked")
63         final T other = (T) iface.cast(obj);
64         if (other instanceof CodecDataObject) {
65             return data.equals(((CodecDataObject<?>) obj).data);
66         }
67         return codecAugmentedEquals(other);
68     }
69
70     @Override
71     public final String toString() {
72         return codecAugmentedFillToString(MoreObjects.toStringHelper(implementedInterface()).omitNullValues())
73                 .toString();
74     }
75
76     // TODO: consider switching to VarHandles for Java 9+
77     protected final Object codecMember(final AtomicReferenceFieldUpdater<CodecDataObject<?>, Object> updater,
78             final int offset) {
79         final Object cached = updater.get(this);
80         return cached != null ? unmaskNull(cached) : loadMember(updater, offset);
81     }
82
83     protected abstract int codecHashCode();
84
85     protected abstract boolean codecEquals(T other);
86
87     protected abstract ToStringHelper codecFillToString(ToStringHelper helper);
88
89     @SuppressWarnings("rawtypes")
90     final @NonNull NormalizedNodeContainer codecData() {
91         return data;
92     }
93
94     // Non-final to allow specialization in AugmentableCodecDataObject
95     int codecAugmentedHashCode() {
96         return codecHashCode();
97     }
98
99     // Non-final to allow specialization in AugmentableCodecDataObject
100     boolean codecAugmentedEquals(final T other) {
101         return codecEquals(other);
102     }
103
104     // Non-final to allow specialization in AugmentableCodecDataObject
105     ToStringHelper codecAugmentedFillToString(final ToStringHelper helper) {
106         return codecFillToString(helper);
107     }
108
109     // Helpers split out of codecMember to aid its inlining
110     private Object loadMember(final AtomicReferenceFieldUpdater<CodecDataObject<?>, Object> updater,
111             final int offset) {
112         final Object decoded = context.getBindingChildValue(data, offset);
113         return updater.compareAndSet(this, null, maskNull(decoded)) ? decoded : unmaskNull(updater.get(this));
114     }
115
116     private static @NonNull Object maskNull(final @Nullable Object unmasked) {
117         return unmasked == null ? NULL_VALUE : unmasked;
118     }
119
120     private static @Nullable Object unmaskNull(final Object masked) {
121         return masked == NULL_VALUE ? null : masked;
122     }
123 }