Do not compare NormalizedNodes in CodecDataObject
[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 edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
16 import java.util.Optional;
17 import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
18 import org.eclipse.jdt.annotation.NonNull;
19 import org.eclipse.jdt.annotation.Nullable;
20 import org.opendaylight.yangtools.yang.binding.DataObject;
21 import org.opendaylight.yangtools.yang.binding.Identifier;
22 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
23 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
24 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
25
26 /**
27  * A base class for {@link DataObject}s backed by {@link DataObjectCodecContext}. While this class is public, it not
28  * part of API surface and is an implementation detail. The only reason for it being public is that it needs to be
29  * accessible by code generated at runtime.
30  *
31  * @param <T> DataObject type
32  */
33 public abstract class CodecDataObject<T extends DataObject> implements DataObject {
34     // An object representing a null value in a member field.
35     private static final @NonNull Object NULL_VALUE = new Object();
36
37     private final @NonNull DataObjectCodecContext<T, ?> context;
38     @SuppressWarnings("rawtypes")
39     private final @NonNull NormalizedNodeContainer data;
40
41     private volatile Integer cachedHashcode = null;
42
43     protected CodecDataObject(final DataObjectCodecContext<T, ?> context, final NormalizedNodeContainer<?, ?, ?> data) {
44         this.data = requireNonNull(data, "Data must not be null");
45         this.context = requireNonNull(context, "Context must not be null");
46     }
47
48     @Override
49     public final int hashCode() {
50         final Integer cached = cachedHashcode;
51         if (cached != null) {
52             return cached;
53         }
54
55         final int result = codecAugmentedHashCode();
56         cachedHashcode = result;
57         return result;
58     }
59
60     @Override
61     @SuppressFBWarnings(value = "EQ_UNUSUAL", justification = "State is examined indirectly enough to confuse SpotBugs")
62     public final boolean equals(final Object obj) {
63         if (obj == this) {
64             return true;
65         }
66         final Class<? extends DataObject> iface = implementedInterface();
67         if (!iface.isInstance(obj)) {
68             return false;
69         }
70         @SuppressWarnings("unchecked")
71         final T other = (T) iface.cast(obj);
72         // Note: we do not want to compare NormalizedNode data here, as we may be looking at different instantiations
73         //       of the same grouping -- in which case normalized node will not compare as equal.
74         return codecAugmentedEquals(other);
75     }
76
77     @Override
78     public final String toString() {
79         return codecAugmentedFillToString(MoreObjects.toStringHelper(implementedInterface()).omitNullValues())
80                 .toString();
81     }
82
83     // TODO: consider switching to VarHandles for Java 9+, as that would disconnect us from the need to use a volatile
84     //       field and use acquire/release mechanics -- see http://gee.cs.oswego.edu/dl/html/j9mm.html for details.
85     protected final Object codecMember(final AtomicReferenceFieldUpdater<CodecDataObject<?>, Object> updater,
86             final String localName) {
87         final Object cached = updater.get(this);
88         return cached != null ? unmaskNull(cached) : loadMember(updater, context.getLeafChild(localName));
89     }
90
91     protected final Object codecMember(final AtomicReferenceFieldUpdater<CodecDataObject<?>, Object> updater,
92             final Class<? extends DataObject> bindingClass) {
93         final Object cached = updater.get(this);
94         return cached != null ? unmaskNull(cached) : loadMember(updater, context.streamChild(bindingClass));
95     }
96
97     protected final Object codecMember(final AtomicReferenceFieldUpdater<CodecDataObject<?>, Object> updater,
98             final NodeContextSupplier supplier) {
99         final Object cached = updater.get(this);
100         return cached != null ? unmaskNull(cached) : loadMember(updater, supplier.get());
101     }
102
103     protected final Object codecKey(final AtomicReferenceFieldUpdater<CodecDataObject<?>, Object> updater) {
104         final Object cached = updater.get(this);
105         return cached != null ? cached : loadKey(updater);
106     }
107
108     protected abstract int codecHashCode();
109
110     protected abstract boolean codecEquals(T other);
111
112     protected abstract ToStringHelper codecFillToString(ToStringHelper helper);
113
114     final @NonNull DataObjectCodecContext<T, ?> codecContext() {
115         return context;
116     }
117
118     @SuppressWarnings("rawtypes")
119     final @NonNull NormalizedNodeContainer codecData() {
120         return data;
121     }
122
123     // Non-final to allow specialization in AugmentableCodecDataObject
124     int codecAugmentedHashCode() {
125         return codecHashCode();
126     }
127
128     // Non-final to allow specialization in AugmentableCodecDataObject
129     boolean codecAugmentedEquals(final T other) {
130         return codecEquals(other);
131     }
132
133     // Non-final to allow specialization in AugmentableCodecDataObject
134     ToStringHelper codecAugmentedFillToString(final ToStringHelper helper) {
135         return codecFillToString(helper);
136     }
137
138     // Helpers split out of codecMember to aid its inlining
139     private Object loadMember(final AtomicReferenceFieldUpdater<CodecDataObject<?>, Object> updater,
140             final NodeCodecContext childCtx) {
141         @SuppressWarnings("unchecked")
142         final Optional<NormalizedNode<?, ?>> child = data.getChild(childCtx.getDomPathArgument());
143
144         // We do not want to use Optional.map() here because we do not want to invoke defaultObject() when we have
145         // normal value because defaultObject() may end up throwing an exception intentionally.
146         final Object obj = child.isPresent() ? childCtx.deserializeObject(child.get()) : childCtx.defaultObject();
147         return updater.compareAndSet(this, null, maskNull(obj)) ? obj : unmaskNull(updater.get(this));
148     }
149
150     // Helpers split out of codecMember to aid its inlining
151     private Object loadKey(final AtomicReferenceFieldUpdater<CodecDataObject<?>, Object> updater) {
152         verify(data instanceof MapEntryNode, "Unsupported value %s", data);
153         verify(context instanceof KeyedListNodeCodecContext, "Unexpected context %s", context);
154         final Identifier<?> key = ((KeyedListNodeCodecContext<?>) context).deserialize(
155             ((MapEntryNode) data).getIdentifier());
156         // key is known to be non-null, no need to mask it
157         return updater.compareAndSet(this, null, key) ? key : updater.get(this);
158     }
159
160     private static @NonNull Object maskNull(final @Nullable Object unmasked) {
161         return unmasked == null ? NULL_VALUE : unmasked;
162     }
163
164     private static @Nullable Object unmaskNull(final Object masked) {
165         return masked == NULL_VALUE ? null : masked;
166     }
167 }