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;
11 import static org.opendaylight.mdsal.binding.dom.codec.impl.LazyBindingList.OBJ_AA;
13 import com.google.common.collect.AbstractIterator;
14 import com.google.common.collect.ImmutableMap;
15 import com.google.common.collect.Iterators;
16 import java.lang.invoke.MethodHandles;
17 import java.lang.invoke.MethodHandles.Lookup;
18 import java.lang.invoke.VarHandle;
19 import java.util.AbstractSet;
20 import java.util.Iterator;
22 import java.util.Map.Entry;
23 import org.eclipse.jdt.annotation.NonNull;
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.schema.MapEntryNode;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
33 * {@link LazyBindingMap.State} optimized for iterator access, mainly via {@link Map#values()}.
36 * @param <V> value type
38 final class LazyBindingMapIterState<K extends Identifier<V>, V extends DataObject & Identifiable<K>>
39 extends LazyBindingMap.State<K, V> {
40 private static final Logger LOG = LoggerFactory.getLogger(LazyBindingMapIterState.class);
41 private static final VarHandle ENTRY_SET;
42 private static final VarHandle KEY_SET;
43 private static final VarHandle LOOKUP_MAP;
46 final Lookup lookup = MethodHandles.lookup();
48 ENTRY_SET = lookup.findVarHandle(LazyBindingMapIterState.class, "entrySet", EntrySet.class);
49 KEY_SET = lookup.findVarHandle(LazyBindingMapIterState.class, "keySet", KeySet.class);
50 LOOKUP_MAP = lookup.findVarHandle(LazyBindingMapIterState.class, "lookupMap", ImmutableMap.class);
51 } catch (NoSuchFieldException | IllegalAccessException e) {
52 throw new ExceptionInInitializerError(e);
56 // Primary storage of transformed nodes. Other views are derived from this.
57 private final @NonNull Values<K, V> values;
59 // Secondary views derived from values, used via varhandles above
60 @SuppressWarnings("unused")
61 private volatile KeySet<K, V> keySet;
62 @SuppressWarnings("unused")
63 private volatile EntrySet<K, V> entrySet;
65 // Lookup map, instantiated on demand, used via varhandle above
66 @SuppressWarnings("unused")
67 private volatile ImmutableMap<K, V> lookupMap;
69 LazyBindingMapIterState(final LazyBindingMap<K, V> map) {
70 values = new Values<>(map);
74 boolean containsKey(final Object key) {
75 return lookupMap().containsKey(key);
79 V get(final Object key) {
80 return lookupMap().get(key);
84 Values<K, V> values() {
89 EntrySet<K, V> entrySet() {
90 final EntrySet<K, V> ret;
91 return (ret = (EntrySet<K, V>) ENTRY_SET.getAcquire(this)) != null ? ret : loadEntrySet();
95 KeySet<K, V> keySet() {
96 final KeySet<K, V> ret;
97 return (ret = (KeySet<K, V>) KEY_SET.getAcquire(this)) != null ? ret : loadKeySet();
100 private @NonNull ImmutableMap<K, V> lookupMap() {
101 final ImmutableMap<K, V> ret;
102 return (ret = (ImmutableMap<K, V>) LOOKUP_MAP.getAcquire(this)) != null ? ret : loadLookupMap();
105 // TODO: this is not exactly efficient, as we are forcing full materialization. We also take a lock here, as we
106 // do not want multiple threads indexing the same thing. Perhaps this should work with the LookupState
107 // somehow, so that we have a shared object view and two indices?
108 private synchronized @NonNull ImmutableMap<K, V> loadLookupMap() {
109 ImmutableMap<K, V> ret = (ImmutableMap<K, V>) LOOKUP_MAP.getAcquire(this);
111 lookupMap = ret = ImmutableMap.copyOf(entrySet());
112 if (LOG.isTraceEnabled()) {
113 LOG.trace("Inefficient instantiation of lookup secondary", new Throwable());
119 @SuppressWarnings("unchecked")
120 private @NonNull EntrySet<K, V> loadEntrySet() {
121 final EntrySet<K, V> ret = new EntrySet<>(values);
122 final Object witness;
123 return (witness = ENTRY_SET.compareAndExchangeRelease(this, null, ret)) == null ? ret
124 : (EntrySet<K, V>) witness;
127 @SuppressWarnings("unchecked")
128 private @NonNull KeySet<K, V> loadKeySet() {
129 final KeySet<K, V> ret = new KeySet<>(values);
130 final Object witness;
131 return (witness = KEY_SET.compareAndExchangeRelease(this, null, ret)) == null ? ret : (KeySet<K, V>) witness;
134 private static final class EntrySet<K extends Identifier<V>, V extends DataObject & Identifiable<K>>
135 extends AbstractSet<Entry<K, V>> implements Immutable {
136 private final Values<K, V> values;
138 EntrySet(final Values<K, V> values) {
139 this.values = requireNonNull(values);
144 return values.size();
148 public Iterator<Entry<K, V>> iterator() {
149 return Iterators.transform(values.iterator(), value -> Map.entry(value.key(), value));
153 @SuppressWarnings("checkstyle:parameterName")
154 public boolean contains(final Object o) {
155 // Key/Value are related, asking whether we have a particular Entry is asking whether values contain that
157 return values.contains(((Entry<?, ?>)o).getValue());
161 private static final class KeySet<K extends Identifier<V>, V extends DataObject & Identifiable<K>>
162 extends AbstractSet<K> implements Immutable {
163 private final Values<K, V> values;
165 KeySet(final Values<K, V> values) {
166 this.values = requireNonNull(values);
171 return values.size();
175 public Iterator<K> iterator() {
176 return Iterators.transform(values.iterator(), value -> value.key());
180 @SuppressWarnings("checkstyle:parameterName")
181 public boolean contains(final Object o) {
182 return values.map.containsKey(o);
187 * Lazily-populated translation of DOM values to binding values. This class is not completely lazy, as we allocate
188 * the array to hold all values upfront and populate it with MapEntry nodes. That allows us to perform lock-free
189 * access, as we just end up CASing MapEntryNodes with their Binding replacements.
191 private static final class Values<K extends Identifier<V>, V extends DataObject & Identifiable<K>>
192 extends AbstractSet<V> implements Immutable {
193 private final LazyBindingMap<K, V> map;
194 private final Object[] objects;
196 Values(final LazyBindingMap<K, V> map) {
197 this.map = requireNonNull(map);
198 objects = map.mapNode().body().toArray();
202 public Iterator<V> iterator() {
203 return new AbstractIterator<>() {
204 private int nextOffset;
207 protected V computeNext() {
208 return nextOffset < objects.length ? objectAt(nextOffset++) : endOfData();
214 @SuppressWarnings("checkstyle:parameterName")
215 public boolean contains(final Object o) {
216 return map.containsValue(o);
224 @NonNull V objectAt(final int offset) {
225 final Object obj = OBJ_AA.getAcquire(objects, offset);
226 return obj instanceof MapEntryNode ? loadObjectAt(offset, (MapEntryNode) obj) : (V) obj;
229 private @NonNull V loadObjectAt(final int offset, final MapEntryNode obj) {
230 final V ret = map.createValue(obj);
231 final Object witness;
232 return (witness = OBJ_AA.compareAndExchangeRelease(objects, offset, obj, ret)) == obj ? ret : (V) witness;