/*
* 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 net.openhft.chronicle.map.replication.MapRemoteOperations;
import org.jetbrains.annotations.NotNull;
/**
* SPI interface for customizing "low-level" modification operations on {@link ChronicleMap}
* entries. All {@code ChronicleMap} modifications operate via an instance of this interface:
* <ul>
* <li>Ordinary query operations: {@code map.put()}, {@code map.compute()}, {@code map.remove()}
* </li>
* <li>Operations with entry during iterations on the map, or the key/value/entry views,
* like {@code iterator.remove()}.</li>
* <li>Remote map operation calls and Replicated {@code ChronicleMap} reconciliation operations:
* see {@link MapRemoteOperations}.</li>
* </ul>
*
* <p>By default {@link #remove}, {@link #insert} and {@link #replaceValue} return {@code null},
* but subclasses could return something more sensible, to be used in higher-level SPI interfaces,
* namely {@link MapMethods} and {@link MapRemoteOperations}. For example, in bidirectional map
* implementation (i. e. a map that preserves the uniqueness of its values as well as that of
* its keys), that includes two {@code ChronicleMaps}, the {@code MapEntryOperations}' methods return
* type could be used to indicate if we were successful to lock both maps before performing
* the update: <pre><code>
* enum DualLockSuccess {SUCCESS, FAIL}
*
* class{@code BiMapEntryOperations<K, V>}
* implements{@code MapEntryOperations<K, V, DualLockSuccess>} {
* {@code ChronicleMap<V, K>} reverse;
*
* public void{@code setReverse(ChronicleMap<V, K>} reverse) {
* this.reverse = reverse;
* }
*
* {@literal @}Override
* public DualLockSuccess{@literal remove(@}NotNull{@code MapEntry<K, V>} entry) {
* try{@code (ExternalMapQueryContext<V, K, ?>} rq = reverse.queryContext(entry.value())) {
* if (!rq.updateLock().tryLock()) {
* if (entry.context() instanceof MapQueryContext)
* return FAIL;
* throw new IllegalStateException("Concurrent modifications to reverse map " +
* "during remove during iteration");
* }
* {@code MapEntry<V, K>} reverseEntry = rq.entry();
* if (reverseEntry != null) {
* entry.doRemove();
* reverseEntry.doRemove();
* return SUCCESS;
* } else {
* throw new IllegalStateException(entry.key() + " maps to " + entry.value() +
* ", but in the reverse map this value is absent");
* }
* }
* }
*
* // ... other methods
* }
*
* class{@code BiMapMethods<K, V>} implements{@code MapMethods<K, V, DualLockSuccess>} {
* {@literal @}Override
* public void{@code remove(MapQueryContext<K, V, DualLockSuccess>} q,
* {@code ReturnValue<V>} returnValue) {
* while (true) {
* q.updateLock().lock();
* try {
* {@code MapEntry<K, V>} entry = q.entry();
* if (entry != null) {
* returnValue.returnValue(entry.value());
* if (q.remove(entry) == SUCCESS)
* return;
* }
* } finally {
* q.readLock().unlock();
* }
* }
* }
*
* // ... other methods
* }</code></pre>
*
* @param <K> the map key type
* @param <V> the map value type
* @param <R> methods return type, used for communication between lower- and higher-level SPI
* @see ChronicleMapBuilder#entryOperations(MapEntryOperations)
*/
public interface MapEntryOperations<K, V, R> {
/**
* Removes the given entry from the map.
*
* @implNote default implementation calls {@link MapEntry#doRemove()} on the given entry
* and returns {@code null}.
*
* @param entry the entry to remove
* @return result of operation, understandable by higher-level SPIs, e. g. custom
* {@link MapMethods} implementation
* @throws IllegalStateException if some locking/state conditions required to perform remove
* operation are not met
* @throws RuntimeException if removal was unconditionally unsuccessful due to any reason
*/
default R remove(@NotNull MapEntry<K, V> entry) {
entry.doRemove();
return null;
}
/**
* Replaces the given entry's value with the new one.
*
* @implNote default implementation calls {@link MapEntry#doReplaceValue(Data)
* entry.doReplaceValue(newValue)} and returns {@code null}.
*
* @param entry the entry to replace the value in
* @return result of operation, understandable by higher-level SPIs, e. g. custom
* {@link MapMethods} implementation
* @throws IllegalStateException if some locking/state conditions required to perform replace
* operation are not met
* @throws RuntimeException if value replacement was unconditionally unsuccessful due
* to any reason
*/
default R replaceValue(@NotNull MapEntry<K, V> entry, Data<V> newValue) {
entry.doReplaceValue(newValue);
return null;
}
/**
* Inserts the new entry into the map, of {@link MapAbsentEntry#absentKey() the key} from
* the given insertion context (<code>absentEntry</code>) and the given {@code value}.
*
* @implNote default implementation calls {@link MapAbsentEntry#doInsert(Data)
* absentEntry.doInsert(value)} and returns {@code null}.
*
* @return result of operation, understandable by higher-level SPIs, e. g. custom
* {@link MapMethods} implementation
* @throws IllegalStateException if some locking/state conditions required to perform insertion
* operation are not met
* @throws RuntimeException if insertion was unconditionally unsuccessful due to any reason
*/
default R insert(@NotNull MapAbsentEntry<K, V> absentEntry, Data<V> value) {
absentEntry.doInsert(value);
return null;
}
}