Bump upstreams
[mdsal.git] / binding / mdsal-binding-dom-codec / src / main / java / org / opendaylight / mdsal / binding / dom / codec / impl / LazyBindingMap.java
1 /*
2  * Copyright (c) 2020 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.annotations.VisibleForTesting;
13 import com.google.common.collect.ImmutableMap;
14 import com.google.common.collect.ImmutableMap.Builder;
15 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
16 import java.lang.invoke.MethodHandles;
17 import java.lang.invoke.VarHandle;
18 import java.util.AbstractMap;
19 import java.util.Collection;
20 import java.util.Map;
21 import java.util.Optional;
22 import java.util.Set;
23 import org.eclipse.jdt.annotation.NonNull;
24 import org.opendaylight.mdsal.binding.dom.codec.impl.MapCodecContext.Unordered;
25 import org.opendaylight.yangtools.concepts.Immutable;
26 import org.opendaylight.yangtools.yang.binding.DataObject;
27 import org.opendaylight.yangtools.yang.binding.Key;
28 import org.opendaylight.yangtools.yang.binding.KeyAware;
29 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
30 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
31 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34
35 /**
36  * Lazily-populated Map of binding DTOs. This implementation acts as the main entry point, so that we can decide on the
37  * translation strategy we are going to use. We make that decision based on the first method that touches the mappings
38  * (or materializes a view).
39  *
40  * @param <K> key type
41  * @param <V> value type
42  */
43 final class LazyBindingMap<K extends Key<V>, V extends DataObject & KeyAware<K>>
44         extends AbstractMap<K, V> implements Immutable {
45     private static final Logger LOG = LoggerFactory.getLogger(LazyBindingMap.class);
46     private static final String LAZY_CUTOFF_PROPERTY =
47             "org.opendaylight.mdsal.binding.dom.codec.impl.LazyBindingMap.max-eager-elements";
48     private static final int DEFAULT_LAZY_CUTOFF = 1;
49     @VisibleForTesting
50     static final int LAZY_CUTOFF;
51
52     private static final VarHandle STATE;
53
54     static {
55         try {
56             STATE = MethodHandles.lookup().findVarHandle(LazyBindingMap.class, "state",
57                 State.class);
58         } catch (NoSuchFieldException | IllegalAccessException e) {
59             throw new ExceptionInInitializerError(e);
60         }
61
62         final int value = Integer.getInteger(LAZY_CUTOFF_PROPERTY, DEFAULT_LAZY_CUTOFF);
63         if (value < 0) {
64             LOG.info("Lazy population of maps disabled");
65             LAZY_CUTOFF = Integer.MAX_VALUE;
66         } else {
67             LOG.info("Using lazy population for maps larger than {} element(s)", value);
68             LAZY_CUTOFF = value;
69         }
70     }
71
72     private final @NonNull Unordered<K, V> codec;
73     private final @NonNull MapNode mapNode;
74
75     // Used via VarHandle above
76     @SuppressWarnings("unused")
77     @SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "https://github.com/spotbugs/spotbugs/issues/2749")
78     private volatile State<K, V> state;
79
80     private LazyBindingMap(final Unordered<K, V> codec, final MapNode mapNode) {
81         this.codec = requireNonNull(codec);
82         this.mapNode = requireNonNull(mapNode);
83     }
84
85     static <K extends Key<V>, V extends DataObject & KeyAware<K>> @NonNull Map<K, V> of(final Unordered<K, V> codec,
86             final MapNode mapNode, final int size) {
87         if (size == 1) {
88             // Do not bother with lazy instantiation in case of a singleton
89             final V entry = codec.createBindingProxy(mapNode.body().iterator().next());
90             return Map.of(entry.key(), entry);
91         }
92         return size > LAZY_CUTOFF ? new LazyBindingMap<>(codec, mapNode) : eagerMap(codec, mapNode, size);
93     }
94
95     private static <K extends Key<V>, V extends DataObject & KeyAware<K>> @NonNull Map<K, V> eagerMap(
96             final Unordered<K, V> codec, final MapNode mapNode, final int size) {
97         final Builder<K, V> builder = ImmutableMap.builderWithExpectedSize(size);
98         for (MapEntryNode node : mapNode.body()) {
99             final V entry = codec.createBindingProxy(node);
100             builder.put(entry.key(), entry);
101         }
102         return builder.build();
103     }
104
105     @Override
106     public int size() {
107         return mapNode.size();
108     }
109
110     @Override
111     public V remove(final Object key) {
112         throw uoe();
113     }
114
115     @Override
116     @SuppressWarnings("checkstyle:parameterName")
117     public void putAll(final Map<? extends K, ? extends V> m) {
118         throw uoe();
119     }
120
121     @Override
122     public void clear() {
123         throw uoe();
124     }
125
126     @Override
127     public boolean containsKey(final Object key) {
128         return lookupState().containsKey(requireNonNull(key));
129     }
130
131     @Override
132     public boolean containsValue(final Object value) {
133         /*
134          * This implementation relies on the relationship specified by KeyAware/Key and its use in binding objects. The
135          * key is a wrapper object composed of a subset (or all) properties in the value, i.e. we have a partial index.
136          *
137          * Instead of performing an O(N) search, we extract the key from the value, look the for the corresponding
138          * mapping. If we find a mapping we check if the mapped value equals the the value being looked up.
139          *
140          * Note we prefer throwing ClassCastException/NullPointerException when presented with null or an object which
141          * cannot possibly be contained in this map.
142          */
143         final V cast = codec.getBindingClass().cast(requireNonNull(value));
144         final V found = get(cast.key());
145         return found != null && cast.equals(found);
146     }
147
148     @Override
149     public V get(final Object key) {
150         return lookupState().get(requireNonNull(key));
151     }
152
153     @Override
154     public Set<K> keySet() {
155         return iterState().keySet();
156     }
157
158     @Override
159     public Collection<V> values() {
160         return iterState().values();
161     }
162
163     @Override
164     public Set<Entry<K, V>> entrySet() {
165         return iterState().entrySet();
166     }
167
168     @NonNull V createValue(final MapEntryNode node) {
169         return codec.createBindingProxy(node);
170     }
171
172     Optional<V> lookupValue(final @NonNull Object key) {
173         final NodeIdentifierWithPredicates childId = codec.serialize((Key<?>) key);
174         return mapNode.findChildByArg(childId).map(codec::createBindingProxy);
175     }
176
177     @NonNull MapNode mapNode() {
178         return mapNode;
179     }
180
181     private @NonNull State<K, V> lookupState() {
182         final State<K, V> local;
183         return (local = (State<K, V>) STATE.getAcquire(this)) != null ? local : loadLookup();
184     }
185
186     private @NonNull State<K, V> iterState() {
187         final State<K, V> local;
188         return (local = (State<K, V>) STATE.getAcquire(this)) != null ? local : loadIter();
189     }
190
191     @SuppressWarnings("unchecked")
192     private @NonNull State<K, V> loadLookup() {
193         final State<K, V> ret = new LazyBindingMapLookupState<>(this);
194         final Object witness;
195         return (witness = STATE.compareAndExchangeRelease(this, null, ret)) == null ? ret : (State<K, V>) witness;
196     }
197
198     @SuppressWarnings("unchecked")
199     private @NonNull State<K, V> loadIter() {
200         final State<K, V> ret = new LazyBindingMapIterState<>(this);
201         final Object witness;
202         return (witness = STATE.compareAndExchangeRelease(this, null, ret)) == null ? ret : (State<K, V>) witness;
203     }
204
205     private static UnsupportedOperationException uoe() {
206         return new UnsupportedOperationException("Modification is not supported");
207     }
208
209     abstract static class State<K extends Key<V>, V extends DataObject & KeyAware<K>> {
210         abstract boolean containsKey(@NonNull Object key);
211
212         abstract V get(@NonNull Object key);
213
214         abstract @NonNull Set<K> keySet();
215
216         abstract @NonNull Collection<V> values();
217
218         abstract @NonNull Set<Entry<K, V>> entrySet();
219     }
220 }