package org.infinispan.functional.decorators; import static org.infinispan.commons.marshall.MarshallableFunctions.removeIfValueEqualsReturnBoolean; import static org.infinispan.commons.marshall.MarshallableFunctions.removeReturnPrevOrNull; import static org.infinispan.commons.marshall.MarshallableFunctions.returnReadOnlyFindIsPresent; import static org.infinispan.commons.marshall.MarshallableFunctions.returnReadOnlyFindOrNull; import static org.infinispan.commons.marshall.MarshallableFunctions.setValueConsumer; import static org.infinispan.commons.marshall.MarshallableFunctions.setValueIfAbsentReturnPrevOrNull; import static org.infinispan.commons.marshall.MarshallableFunctions.setValueIfEqualsReturnBoolean; import static org.infinispan.commons.marshall.MarshallableFunctions.setValueIfPresentReturnPrevOrNull; import static org.infinispan.commons.marshall.MarshallableFunctions.setValueReturnPrevOrNull; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import org.infinispan.AdvancedCache; import org.infinispan.commons.api.functional.EntryView.ReadEntryView; import org.infinispan.commons.api.functional.FunctionalMap.ReadOnlyMap; import org.infinispan.commons.api.functional.FunctionalMap.ReadWriteMap; import org.infinispan.commons.api.functional.FunctionalMap.WriteOnlyMap; import org.infinispan.commons.api.functional.Listeners.ReadWriteListeners; import org.infinispan.commons.api.functional.Listeners.WriteListeners; import org.infinispan.functional.impl.FunctionalMapImpl; import org.infinispan.functional.impl.ReadOnlyMapImpl; import org.infinispan.functional.impl.ReadWriteMapImpl; import org.infinispan.functional.impl.WriteOnlyMapImpl; import org.infinispan.stream.CacheCollectors; /** * A {@link ConcurrentMap} implementation that uses the operations exposed by * {@link ReadOnlyMap}, {@link WriteOnlyMap} and {@link ReadWriteMap}, and * validates their usefulness. */ public final class FunctionalConcurrentMap<K, V> implements ConcurrentMap<K, V>, FunctionalListeners<K, V> { final ReadOnlyMap<K, V> readOnly; final WriteOnlyMap<K, V> writeOnly; final ReadWriteMap<K, V> readWrite; // Rudimentary constructor, we'll provide more idiomatic construction // via main Infinispan class which is still to be defined private FunctionalConcurrentMap(FunctionalMapImpl<K, V> map) { this.readOnly = ReadOnlyMapImpl.create(map); this.writeOnly = WriteOnlyMapImpl.create(map); this.readWrite = ReadWriteMapImpl.create(map); } public static <K, V> FunctionalConcurrentMap<K, V> create(AdvancedCache<K, V> cache) { return new FunctionalConcurrentMap<>(FunctionalMapImpl.create(cache)); } @Override public ReadWriteListeners<K, V> readWriteListeners() { return readWrite.listeners(); } @Override public WriteListeners<K, V> writeOnlyListeners() { return writeOnly.listeners(); } @Override public int size() { return (int) readOnly.keys().count(); } @Override public boolean isEmpty() { return !readOnly.keys().findAny().isPresent(); } @Override public boolean containsKey(Object key) { return await(readOnly.eval(toK(key), returnReadOnlyFindIsPresent())); } @Override public boolean containsValue(Object value) { return readOnly.entries().anyMatch(ro -> ro.get().equals(value)); } @Override public V get(Object key) { return await(readOnly.eval(toK(key), returnReadOnlyFindOrNull())); } @SuppressWarnings("unchecked") private K toK(Object key) { return (K) key; } @SuppressWarnings("unchecked") private V toV(Object value) { return (V) value; } @Override public V put(K key, V value) { return await(readWrite.eval(toK(key), value, setValueReturnPrevOrNull())); } @Override public V remove(Object key) { return await(readWrite.eval(toK(key), removeReturnPrevOrNull())); } @Override public void putAll(Map<? extends K, ? extends V> m) { await(writeOnly.evalMany(m, setValueConsumer())); } @Override public void clear() { await(writeOnly.truncate()); } @Override public Set<K> keySet() { return readOnly.keys().collect(CacheCollectors.serializableCollector(() -> Collectors.toSet())); } @Override public Collection<V> values() { return readOnly.entries().collect(ArrayList::new, (l, v) -> l.add(v.get()), ArrayList::addAll); } @Override public Set<Entry<K, V>> entrySet() { return readOnly.entries().collect(HashSet::new, (s, ro) -> s.add(new FunctionalMapEntry<>(ro, writeOnly)), HashSet::addAll); } @Override public V putIfAbsent(K key, V value) { return await(readWrite.eval(toK(key), value, setValueIfAbsentReturnPrevOrNull())); } @Override public boolean remove(Object key, Object value) { return await(readWrite.eval(toK(key), toV(value), removeIfValueEqualsReturnBoolean())); } @Override public boolean replace(K key, V oldValue, V newValue) { return await(readWrite.eval(toK(key), newValue, setValueIfEqualsReturnBoolean(oldValue))); } @Override public V replace(K key, V value) { return await(readWrite.eval(toK(key), value, setValueIfPresentReturnPrevOrNull())); } public static <T> T await(CompletableFuture<T> cf) { try { return cf.get(); } catch (InterruptedException | ExecutionException e) { throw new Error(e); } } private static final class FunctionalMapEntry<K, V> implements Entry<K, V> { final ReadEntryView<K, V> view; final WriteOnlyMap<K, V> writeOnly; private FunctionalMapEntry(ReadEntryView<K, V> view, WriteOnlyMap<K, V> writeOnly) { this.view = view; this.writeOnly = writeOnly; } @Override public K getKey() { return view.key(); } @Override public V getValue() { return view.get(); } @Override public V setValue(V value) { V prev = view.get(); await(writeOnly.eval(view.key(), value, setValueConsumer())); return prev; } @Override public boolean equals(Object o) { if (o == this) return true; if (o instanceof Entry) { Entry<?, ?> e = (Entry<?, ?>) o; if (Objects.equals(view.key(), e.getKey()) && Objects.equals(view.get(), e.getValue())) return true; } return false; } @Override public int hashCode() { return view.hashCode(); } @Override public String toString() { return "FunctionalMapEntry{" + "view=" + view + '}'; } } }