/* * Copyright (C) 2012, 2016 higherfrequencytrading.com * Copyright (C) 2016 Roman Leventov * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package net.openhft.chronicle.map; import net.openhft.chronicle.hash.Data; import java.util.function.BiFunction; import java.util.function.Function; import static net.openhft.chronicle.hash.Data.bytesEquivalent; import static net.openhft.chronicle.map.MapMethodsSupport.returnCurrentValueIfPresent; import static net.openhft.chronicle.map.MapMethodsSupport.tryReturnCurrentValueIfPresent; /** * SPI interface for customizing behaviour of the specific Map's methods with individual keys. * * @see ChronicleMapBuilder#mapMethods(MapMethods) */ public interface MapMethods<K, V, R> { /** * Backing {@link ChronicleMap#containsKey(Object)} method. * * @implNote the default implementation is equivalent to <pre>{@code * return q.entry() != null; * }</pre> */ default boolean containsKey(MapQueryContext<K, V, R> q) { return q.entry() != null; } /** * Backing {@link ChronicleMap#get}, {@link ChronicleMap#getUsing} and * {@link ChronicleMap#getOrDefault} methods. * * @implNote the default implementation is equivalent to <pre>{@code * MapEntry<K, V> entry = q.entry(); * if (entry != null) * returnValue.returnValue(entry.value()); * }</pre> */ default void get(MapQueryContext<K, V, R> q, ReturnValue<V> returnValue) { returnCurrentValueIfPresent(q, returnValue); } /** * Backing {@link ChronicleMap#put(Object, Object)} method. * * @implNote the default implementation is equivalent to <pre>{@code * // We cannot read the previous value under read lock, because then we will need * // to release the read lock -> acquire write lock, the value might be updated in * // between, that will break ConcurrentMap.put() atomicity guarantee. So, we acquire * // update lock from the start: * q.updateLock().lock(); * MapEntry<K, V> entry = q.entry(); * if (entry != null) { * returnValue.returnValue(entry.value()); * q.replaceValue(entry, value); * } else { * q.insert(q.absentEntry(), value); * }}</pre> */ default void put(MapQueryContext<K, V, R> q, Data<V> value, ReturnValue<V> returnValue) { // We cannot read the previous value under read lock, because then we will need // to release the read lock -> acquire write lock, the value might be updated in // between, that will break ConcurrentMap.put() atomicity guarantee. So, we acquire // update lock from the start: q.updateLock().lock(); MapEntry<K, V> entry = q.entry(); if (entry != null) { returnValue.returnValue(entry.value()); q.replaceValue(entry, value); } else { q.insert(q.absentEntry(), value); } } /** * Backing {@link ChronicleMap#putIfAbsent(Object, Object)} method. * * @implNote the default implementation is equivalent to <pre>{@code * if (q.readLock().tryLock()) { * MapEntry<K, V> entry = q.entry(); * if (entry != null) { * returnValue.returnValue(entry.value()); * return; * } * // Key is absent * q.readLock().unlock(); * } * q.updateLock().lock(); * MapEntry<K, V> entry = q.entry(); * if (entry != null) { * returnValue.returnValue(entry.value()); * return; * } * // Key is absent * q.insert(q.absentEntry(), value); * }</pre> */ default void putIfAbsent(MapQueryContext<K, V, R> q, Data<V> value, ReturnValue<V> returnValue) { if (tryReturnCurrentValueIfPresent(q, returnValue)) return; // Key is absent q.insert(q.absentEntry(), value); } /** * Backing {@link ChronicleMap#acquireUsing(Object, Object)} method. * * @implNote the default implementation is equivalent to <pre>{@code * if (q.readLock().tryLock()) { * MapEntry<K, V> entry = q.entry(); * if (entry != null) { * returnValue.returnValue(entry.value()); * return; * } * // Key is absent * q.readLock().unlock(); * } * q.updateLock().lock(); * MapEntry<K, V> entry = q.entry(); * if (entry != null) { * returnValue.returnValue(entry.value()); * return; * } * // Key is absent * q.insert(q.absentEntry(), q.defaultValue(q.absentEntry())); * // Meaningful to return the default as newly-inserted, not the default entry itself. * // map.acquireUsing() is most useful for value interfaces, for which it makes big * // difference -- what bytes to refer. Consider map.acquireUsing(...).incrementValue(); * returnValue.returnValue(q.entry().value()); * }</pre> */ default void acquireUsing(MapQueryContext<K, V, R> q, ReturnValue<V> returnValue) { if (tryReturnCurrentValueIfPresent(q, returnValue)) return; // Key is absent q.insert(q.absentEntry(), q.defaultValue(q.absentEntry())); // meaningful to return the default as newly-inserted, not the default entry itself. // map.acquireUsing() is most useful for value interfaces, for which it makes big // difference -- what bytes to refer. consider map.acquireUsing(...).incrementValue(); // The same reasoning is applied in all same occurrences in this class file returnValue.returnValue(q.entry().value()); } /** * Backing {@link ChronicleMap#computeIfAbsent(Object, Function)} method. * * @implNote the default implementation is equivalent to <pre>{@code * if (q.readLock().tryLock()) { * MapEntry<K, V> entry = q.entry(); * if (entry != null) { * returnValue.returnValue(entry.value()); * return; * } * // Key is absent * q.readLock().unlock(); * } * q.updateLock().lock(); * MapEntry<K, V> entry = q.entry(); * if (entry != null) { * returnValue.returnValue(entry.value()); * return; * } * // Key is absent * q.insert(q.absentEntry(), q.wrapValueAsData(mappingFunction.apply(q.queriedKey().get()))); * returnValue.returnValue(q.entry().value()); * }</pre> */ default void computeIfAbsent(MapQueryContext<K, V, R> q, Function<? super K, ? extends V> mappingFunction, ReturnValue<V> returnValue) { if (tryReturnCurrentValueIfPresent(q, returnValue)) return; // Key is absent q.insert(q.absentEntry(), q.wrapValueAsData(mappingFunction.apply(q.queriedKey().get()))); returnValue.returnValue(q.entry().value()); } /** * Backing {@link ChronicleMap#remove(Object)} method. * * @implNote the default implementation is equivalent to <pre>{@code * // We cannot read the previous value under read lock, because then we will need * // to release the read lock -> acquire write lock, the value might be updated in * // between, that will break ConcurrentMap.remove() atomicity guarantee. So, we acquire * // update lock from the start: * q.updateLock().lock(); * MapEntry<K, V> entry = q.entry(); * if (entry != null) { * returnValue.returnValue(entry.value()); * q.remove(entry); * }}</pre> */ default void remove(MapQueryContext<K, V, R> q, ReturnValue<V> returnValue) { // We cannot read the previous value under read lock, because then we will need // to release the read lock -> acquire write lock, the value might be updated in // between, that will break ConcurrentMap.remove() atomicity guarantee. So, we acquire // update lock from the start: q.updateLock().lock(); MapEntry<K, V> entry = q.entry(); if (entry != null) { returnValue.returnValue(entry.value()); q.remove(entry); } } /** * Backing {@link ChronicleMap#remove(Object, Object)} method. * * @implNote the default implementation is equivalent to <pre>{@code * // remove(key, value) should find the entry & remove most of the time, * // so don't try to check key presence and value equivalence under read lock first, * // as in putIfAbsent()/acquireUsing(), start with update lock: * q.updateLock().lock(); * MapEntry<K, V> entry = q.entry(); * if (entry != null && Data.bytesEquivalent(entry.value(), value)) { * q.remove(entry); * return true; * } else { * return false; * }}</pre> * * @return if the entry was removed */ default boolean remove(MapQueryContext<K, V, R> q, Data<V> value) { // remove(key, value) should find the entry & remove most of the time, // so don't try to check key presence and value equivalence under read lock first, // as in putIfAbsent()/acquireUsing(), start with update lock: q.updateLock().lock(); MapEntry<K, V> entry = q.entry(); if (entry != null && bytesEquivalent(value, entry.value())) { q.remove(entry); return true; } else { return false; } } /** * Backing {@link ChronicleMap#replace(Object, Object)} method. * * @implNote the default implementation is equivalent to <pre>{@code * // replace(key, value) should find the key & put the value most of the time, * // so don't try to check key presence under read lock first, * // as in putIfAbsent()/acquireUsing(), start with update lock: * q.updateLock().lock(); * MapEntry<K, V> entry = q.entry(); * if (entry != null) { * returnValue.returnValue(entry.value()); * q.replaceValue(entry, value); * }}</pre> */ default void replace(MapQueryContext<K, V, R> q, Data<V> value, ReturnValue<V> returnValue) { // replace(key, value) should find the key & put the value most of the time, // so don't try to check key presence under read lock first, // as in putIfAbsent()/acquireUsing(), start with update lock: q.updateLock().lock(); MapEntry<K, V> entry = q.entry(); if (entry != null) { returnValue.returnValue(entry.value()); q.replaceValue(entry, value); } } /** * Backing {@link ChronicleMap#replace(Object, Object, Object)} method. * * @implNote the default implementation is equivalent to <pre>{@code * // replace(key, old, new) should find the entry & put new value most of the time, * // so don't try to check key presence and value equivalence under read lock first, * // as in putIfAbsent()/acquireUsing(), start with update lock: * q.updateLock().lock(); * MapEntry<K, V> entry = q.entry(); * if (entry != null && Data.bytesEquivalent(((MapEntry<K, V>) entry).value(), oldValue)) { * q.replaceValue(entry, newValue); * return true; * } else { * return false; * }}</pre> * * @return if the entry was replaced */ default boolean replace(MapQueryContext<K, V, R> q, Data<V> oldValue, Data<V> newValue) { // replace(key, old, new) should find the entry & put new value most of the time, // so don't try to check key presence and value equivalence under read lock first, // as in putIfAbsent()/acquireUsing(), start with update lock: q.updateLock().lock(); MapEntry<K, V> entry = q.entry(); if (entry != null && bytesEquivalent(oldValue, entry.value())) { q.replaceValue(entry, newValue); return true; } else { return false; } } /** * Backing {@link ChronicleMap#compute(Object, BiFunction)} method. * * @implNote the default implementation is equivalent to <pre>{@code * q.updateLock().lock(); * MapEntry<K, V> entry = q.entry(); * V oldValue = entry != null ? entry.value().get() : null; * V newValue = remappingFunction.apply(q.queriedKey().get(), oldValue); * if (newValue != null) { * Data<V, ?> newValueData = q.wrapValueAsData(newValue); * if (entry != null) { * q.replaceValue(entry, newValueData); * } else { * q.insert(q.absentEntry(), newValueData); * entry = q.entry(); * } * returnValue.returnValue(entry.value()); * } else if (entry != null) { * q.remove(entry); * }}</pre> */ default void compute(MapQueryContext<K, V, R> q, BiFunction<? super K, ? super V, ? extends V> remappingFunction, ReturnValue<V> returnValue) { q.updateLock().lock(); MapEntry<K, V> entry = q.entry(); V oldValue = entry != null ? entry.value().get() : null; V newValue = remappingFunction.apply(q.queriedKey().get(), oldValue); if (newValue != null) { Data<V> newValueData = q.wrapValueAsData(newValue); if (entry != null) { q.replaceValue(entry, newValueData); } else { q.insert(q.absentEntry(), newValueData); entry = q.entry(); assert entry != null; } returnValue.returnValue(entry.value()); } else if (entry != null) { q.remove(entry); } } /** * Backing {@link ChronicleMap#computeIfPresent(Object, BiFunction)} method. * * @implNote the default implementation is equivalent to <pre>{@code * q.updateLock().lock(); * MapEntry<K, V> entry = q.entry(); * if (entry != null) { * V oldValue = entry.value().get(); * V newValue = remappingFunction.apply(q.queriedKey().get(), oldValue); * if (newValue != null ) { * q.replaceValue(entry, q.wrapValueAsData(newValue)); * returnValue.returnValue(q.entry().value()); * } else { * q.remove(entry); * } * }}</pre> */ default void computeIfPresent(MapQueryContext<K, V, R> q, BiFunction<? super K, ? super V, ? extends V> remappingFunction, ReturnValue<V> returnValue) { q.updateLock().lock(); MapEntry<K, V> entry = q.entry(); if (entry != null) { V oldValue = entry.value().get(); V newValue = remappingFunction.apply(q.queriedKey().get(), oldValue); if (newValue != null ) { q.replaceValue(entry, q.wrapValueAsData(newValue)); returnValue.returnValue(q.entry().value()); } else { q.remove(entry); } } } /** * Backing {@link ChronicleMap#merge(Object, Object, BiFunction)} method. * * @implNote the default implementation is equivalent to <pre>{@code * q.updateLock().lock(); * Data<V, ?> newValueData; * MapEntry<K, V> entry = q.entry(); * if (entry != null) { * V oldValue = entry.value().get(); * V newValue = remappingFunction.apply(oldValue, value.get()); * if (newValue == null) { * q.remove(entry); * return; * } * newValueData = q.wrapValueAsData(newValue); * q.replaceValue(entry, newValueData); * } else { * newValueData = value; * q.insert(q.absentEntry(), newValueData); * entry = q.entry(); * } * returnValue.returnValue(entry.value()); * }</pre> */ default void merge(MapQueryContext<K, V, R> q, Data<V> value, BiFunction<? super V, ? super V, ? extends V> remappingFunction, ReturnValue<V> returnValue) { q.updateLock().lock(); Data<V> newValueData; MapEntry<K, V> entry = q.entry(); if (entry != null) { V oldValue = entry.value().get(); V newValue = remappingFunction.apply(oldValue, value.get()); if (newValue == null) { q.remove(entry); return; } newValueData = q.wrapValueAsData(newValue); q.replaceValue(entry, newValueData); } else { newValueData = value; q.insert(q.absentEntry(), newValueData); entry = q.entry(); assert entry != null; } returnValue.returnValue(entry.value()); } }