net.openhft.chronicle.map.MapMethods Maven / Gradle / Ivy
/*
* Copyright 2012-2018 Chronicle Map Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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 {
/**
* Backing {@link ChronicleMap#containsKey(Object)} method.
*
* @implNote the default implementation is equivalent to {@code
* return q.entry() != null;
* }
*/
default boolean containsKey(MapQueryContext q) {
return q.entry() != null;
}
/**
* Backing {@link ChronicleMap#get}, {@link ChronicleMap#getUsing} and
* {@link ChronicleMap#getOrDefault} methods.
*
* @implNote the default implementation is equivalent to {@code
* MapEntry entry = q.entry();
* if (entry != null)
* returnValue.returnValue(entry.value());
* }
*/
default void get(MapQueryContext q, ReturnValue returnValue) {
returnCurrentValueIfPresent(q, returnValue);
}
/**
* Backing {@link ChronicleMap#put(Object, Object)} method.
*
* @implNote the default implementation is equivalent to {@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 entry = q.entry();
* if (entry != null) {
* returnValue.returnValue(entry.value());
* q.replaceValue(entry, value);
* } else {
* q.insert(q.absentEntry(), value);
* }}
*/
default void put(MapQueryContext q, Data value, ReturnValue 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 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 {@code
* if (q.readLock().tryLock()) {
* MapEntry entry = q.entry();
* if (entry != null) {
* returnValue.returnValue(entry.value());
* return;
* }
* // Key is absent
* q.readLock().unlock();
* }
* q.updateLock().lock();
* MapEntry entry = q.entry();
* if (entry != null) {
* returnValue.returnValue(entry.value());
* return;
* }
* // Key is absent
* q.insert(q.absentEntry(), value);
* }
*/
default void putIfAbsent(MapQueryContext q,
Data value, ReturnValue 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 {@code
* if (q.readLock().tryLock()) {
* MapEntry entry = q.entry();
* if (entry != null) {
* returnValue.returnValue(entry.value());
* return;
* }
* // Key is absent
* q.readLock().unlock();
* }
* q.updateLock().lock();
* MapEntry 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());
* }
*/
default void acquireUsing(MapQueryContext q, ReturnValue 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 {@code
* if (q.readLock().tryLock()) {
* MapEntry entry = q.entry();
* if (entry != null) {
* returnValue.returnValue(entry.value());
* return;
* }
* // Key is absent
* q.readLock().unlock();
* }
* q.updateLock().lock();
* MapEntry 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());
* }
*/
default void computeIfAbsent(MapQueryContext q,
Function super K, ? extends V> mappingFunction,
ReturnValue 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 {@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 entry = q.entry();
* if (entry != null) {
* returnValue.returnValue(entry.value());
* q.remove(entry);
* }}
*/
default void remove(MapQueryContext q, ReturnValue 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 entry = q.entry();
if (entry != null) {
returnValue.returnValue(entry.value());
q.remove(entry);
}
}
/**
* Backing {@link ChronicleMap#remove(Object, Object)} method.
*
* @return if the entry was removed
* @implNote the default implementation is equivalent to {@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 entry = q.entry();
* if (entry != null && Data.bytesEquivalent(entry.value(), value)) {
* q.remove(entry);
* return true;
* } else {
* return false;
* }}
*/
default boolean remove(MapQueryContext q, Data 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 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 {@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 entry = q.entry();
* if (entry != null) {
* returnValue.returnValue(entry.value());
* q.replaceValue(entry, value);
* }}
*/
default void replace(MapQueryContext q,
Data value, ReturnValue 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 entry = q.entry();
if (entry != null) {
returnValue.returnValue(entry.value());
q.replaceValue(entry, value);
}
}
/**
* Backing {@link ChronicleMap#replace(Object, Object, Object)} method.
*
* @return if the entry was replaced
* @implNote the default implementation is equivalent to {@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 entry = q.entry();
* if (entry != null && Data.bytesEquivalent(((MapEntry) entry).value(), oldValue)) {
* q.replaceValue(entry, newValue);
* return true;
* } else {
* return false;
* }}
*/
default boolean replace(MapQueryContext q,
Data oldValue, Data 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 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 {@code
* q.updateLock().lock();
* MapEntry entry = q.entry();
* V oldValue = entry != null ? entry.value().get() : null;
* V newValue = remappingFunction.apply(q.queriedKey().get(), oldValue);
* if (newValue != null) {
* Data 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);
* }}
*/
default void compute(MapQueryContext q,
BiFunction super K, ? super V, ? extends V> remappingFunction,
ReturnValue returnValue) {
q.updateLock().lock();
MapEntry entry = q.entry();
V oldValue = entry != null ? entry.value().get() : null;
V newValue = remappingFunction.apply(q.queriedKey().get(), oldValue);
if (newValue != null) {
Data 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 {@code
* q.updateLock().lock();
* MapEntry 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);
* }
* }}
*/
default void computeIfPresent(MapQueryContext q,
BiFunction super K, ? super V, ? extends V> remappingFunction,
ReturnValue returnValue) {
q.updateLock().lock();
MapEntry 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 {@code
* q.updateLock().lock();
* Data newValueData;
* MapEntry 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());
* }
*/
default void merge(MapQueryContext q, Data value,
BiFunction super V, ? super V, ? extends V> remappingFunction,
ReturnValue returnValue) {
q.updateLock().lock();
Data newValueData;
MapEntry 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());
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy