All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.openhft.chronicle.map.MapMethods Maven / Gradle / Ivy

There is a newer version: 3.27ea0
Show newest version
/*
 * 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 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 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 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 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