2 * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved.
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
8 package org.opendaylight.mdsal.binding.dom.codec.impl;
10 import static java.util.Objects.requireNonNull;
12 import com.google.common.annotations.VisibleForTesting;
13 import com.google.common.collect.ImmutableMap;
14 import com.google.common.collect.ImmutableMap.Builder;
15 import java.lang.invoke.MethodHandles;
16 import java.lang.invoke.VarHandle;
17 import java.util.AbstractMap;
18 import java.util.Collection;
20 import java.util.Optional;
22 import org.eclipse.jdt.annotation.NonNull;
23 import org.opendaylight.mdsal.binding.dom.codec.impl.KeyedListNodeCodecContext.Unordered;
24 import org.opendaylight.yangtools.concepts.Immutable;
25 import org.opendaylight.yangtools.yang.binding.DataObject;
26 import org.opendaylight.yangtools.yang.binding.Identifiable;
27 import org.opendaylight.yangtools.yang.binding.Identifier;
28 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
29 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
30 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
35 * Lazily-populated Map of binding DTOs. This implementation acts as the main entry point, so that we can decide on the
36 * translation strategy we are going to use. We make that decision based on the first method that touches the mappings
37 * (or materializes a view).
40 * @param <V> value type
42 final class LazyBindingMap<K extends Identifier<V>, V extends DataObject & Identifiable<K>>
43 extends AbstractMap<K, V> implements Immutable {
44 private static final Logger LOG = LoggerFactory.getLogger(LazyBindingMap.class);
45 private static final String LAZY_CUTOFF_PROPERTY =
46 "org.opendaylight.mdsal.binding.dom.codec.impl.LazyBindingMap.max-eager-elements";
47 private static final int DEFAULT_LAZY_CUTOFF = 1;
49 static final int LAZY_CUTOFF;
51 private static final VarHandle STATE;
55 STATE = MethodHandles.lookup().findVarHandle(LazyBindingMap.class, "state",
57 } catch (NoSuchFieldException | IllegalAccessException e) {
58 throw new ExceptionInInitializerError(e);
61 final int value = Integer.getInteger(LAZY_CUTOFF_PROPERTY, DEFAULT_LAZY_CUTOFF);
63 LOG.info("Lazy population of maps disabled");
64 LAZY_CUTOFF = Integer.MAX_VALUE;
66 LOG.info("Using lazy population for maps larger than {} element(s)", value);
71 private final @NonNull Unordered<K, V> codec;
72 private final @NonNull MapNode mapNode;
74 // Used via VarHandle above
75 @SuppressWarnings("unused")
76 private volatile State<K, V> state;
78 private LazyBindingMap(final Unordered<K, V> codec, final MapNode mapNode) {
79 this.codec = requireNonNull(codec);
80 this.mapNode = requireNonNull(mapNode);
83 static <K extends Identifier<V>, V extends DataObject & Identifiable<K>> @NonNull Map<K, V> create(
84 final Unordered<K, V> codec, final MapNode mapNode, final int size) {
86 // Do not bother with lazy instantiation in case of a singleton
87 final V entry = codec.createBindingProxy(mapNode.body().iterator().next());
88 return Map.of(entry.key(), entry);
90 return size > LAZY_CUTOFF ? new LazyBindingMap<>(codec, mapNode) : eagerMap(codec, mapNode, size);
93 private static <K extends Identifier<V>, V extends DataObject & Identifiable<K>> @NonNull Map<K, V> eagerMap(
94 final Unordered<K, V> codec, final MapNode mapNode, final int size) {
95 final Builder<K, V> builder = ImmutableMap.builderWithExpectedSize(size);
96 for (MapEntryNode node : mapNode.body()) {
97 final V entry = codec.createBindingProxy(node);
98 builder.put(entry.key(), entry);
100 return builder.build();
105 return mapNode.size();
109 public V remove(final Object key) {
114 @SuppressWarnings("checkstyle:parameterName")
115 public void putAll(final Map<? extends K, ? extends V> m) {
120 public void clear() {
125 public boolean containsKey(final Object key) {
126 return lookupState().containsKey(requireNonNull(key));
130 public boolean containsValue(final Object value) {
132 * This implementation relies on the relationship specified by Identifiable/Identifier and its use in binding
133 * objects. The key is a wrapper object composed of a subset (or all) properties in the value, i.e. we have
136 * Instead of performing an O(N) search, we extract the key from the value, look the for the corresponding
137 * mapping. If we find a mapping we check if the mapped value equals the the value being looked up.
139 * Note we prefer throwing ClassCastException/NullPointerException when presented with null or an object which
140 * cannot possibly be contained in this map.
142 final V cast = codec.getBindingClass().cast(requireNonNull(value));
143 final V found = get(cast.key());
144 return found != null && cast.equals(found);
148 public V get(final Object key) {
149 return lookupState().get(requireNonNull(key));
153 public Set<K> keySet() {
154 return iterState().keySet();
158 public Collection<V> values() {
159 return iterState().values();
163 public Set<Entry<K, V>> entrySet() {
164 return iterState().entrySet();
167 @NonNull V createValue(final MapEntryNode node) {
168 return codec.createBindingProxy(node);
171 Optional<V> lookupValue(final @NonNull Object key) {
172 final NodeIdentifierWithPredicates childId = codec.serialize((Identifier<?>) key);
173 return mapNode.findChildByArg(childId).map(codec::createBindingProxy);
176 @NonNull MapNode mapNode() {
180 private @NonNull State<K, V> lookupState() {
181 final State<K, V> local;
182 return (local = (State<K, V>) STATE.getAcquire(this)) != null ? local : loadLookup();
185 private @NonNull State<K, V> iterState() {
186 final State<K, V> local;
187 return (local = (State<K, V>) STATE.getAcquire(this)) != null ? local : loadIter();
190 @SuppressWarnings("unchecked")
191 private @NonNull State<K, V> loadLookup() {
192 final State<K, V> ret = new LazyBindingMapLookupState<>(this);
193 final Object witness;
194 return (witness = STATE.compareAndExchangeRelease(this, null, ret)) == null ? ret : (State<K, V>) witness;
197 @SuppressWarnings("unchecked")
198 private @NonNull State<K, V> loadIter() {
199 final State<K, V> ret = new LazyBindingMapIterState<>(this);
200 final Object witness;
201 return (witness = STATE.compareAndExchangeRelease(this, null, ret)) == null ? ret : (State<K, V>) witness;
204 private static UnsupportedOperationException uoe() {
205 return new UnsupportedOperationException("Modification is not supported");
208 abstract static class State<K extends Identifier<V>, V extends DataObject & Identifiable<K>> {
209 abstract boolean containsKey(@NonNull Object key);
211 abstract V get(@NonNull Object key);
213 abstract @NonNull Set<K> keySet();
215 abstract @NonNull Collection<V> values();
217 abstract @NonNull Set<Entry<K, V>> entrySet();