/*
* 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());
}
}