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

org.redisson.RedissonMap Maven / Gradle / Ivy

Go to download

Easy Redis Java client and Real-Time Data Platform. Valkey compatible. Sync/Async/RxJava3/Reactive API. Client side caching. Over 50 Redis based Java objects and services: JCache API, Apache Tomcat, Hibernate, Spring, Set, Multimap, SortedSet, Map, List, Queue, Deque, Semaphore, Lock, AtomicLong, Map Reduce, Bloom filter, Scheduler, RPC

There is a newer version: 3.40.2
Show newest version
/**
 * Copyright (c) 2013-2024 Nikita Koksharov
 *
 * 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 org.redisson;

import io.netty.buffer.ByteBuf;
import io.netty.util.ReferenceCountUtil;
import org.redisson.api.*;
import org.redisson.api.MapOptions.WriteMode;
import org.redisson.api.listener.MapPutListener;
import org.redisson.api.listener.MapRemoveListener;
import org.redisson.api.listener.TrackingListener;
import org.redisson.api.map.RetryableMapWriterAsync;
import org.redisson.api.mapreduce.RMapReduce;
import org.redisson.client.RedisClient;
import org.redisson.client.codec.Codec;
import org.redisson.client.codec.LongCodec;
import org.redisson.client.codec.StringCodec;
import org.redisson.client.protocol.RedisCommand;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.client.protocol.convertor.NumberConvertor;
import org.redisson.client.protocol.decoder.*;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.connection.decoder.MapGetAllDecoder;
import org.redisson.iterator.RedissonMapIterator;
import org.redisson.iterator.RedissonMapKeyIterator;
import org.redisson.mapreduce.RedissonMapReduce;
import org.redisson.misc.CompletableFutureWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

/**
 * Distributed and concurrent implementation of {@link java.util.concurrent.ConcurrentMap}
 * and {@link java.util.Map}
 *
 * @author Nikita Koksharov
 *
 * @param  key
 * @param  value
 */
public class RedissonMap extends RedissonExpirable implements RMap {

    private final Logger log = LoggerFactory.getLogger(getClass());
    
    final RedissonClient redisson;
    final MapOptions options;
    final WriteBehindService writeBehindService;
    final MapWriteBehindTask writeBehindTask;
    
    public RedissonMap(CommandAsyncExecutor commandExecutor, String name, RedissonClient redisson, MapOptions options, WriteBehindService writeBehindService) {
        super(commandExecutor, name);
        this.redisson = redisson;
        this.options = options;
        if (options != null
                && options.getWriteMode() == WriteMode.WRITE_BEHIND
                    && (options.getWriter() != null || options.getWriterAsync() != null)) {
            this.writeBehindService = writeBehindService;
            writeBehindTask = writeBehindService.start(getRawName(), options);
        } else {
            this.writeBehindService = null;
            writeBehindTask = null;
        }
        if (options != null
                && options.getWriterRetryAttempts()>1
                && options.getWriterAsync() != null){
            ((RetryableMapWriterAsync) options.getWriterAsync()).setServiceManager(commandExecutor.getServiceManager());
        }
    }

    public RedissonMap(Codec codec, CommandAsyncExecutor commandExecutor, String name) {
        super(codec, commandExecutor, name);
        this.name = name;
        this.redisson = null;
        this.options = null;
        this.writeBehindService = null;
        writeBehindTask = null;
    }

    public RedissonMap(Codec codec, CommandAsyncExecutor commandExecutor, String name, RedissonClient redisson, MapOptions options, WriteBehindService writeBehindService) {
        super(codec, commandExecutor, name);
        this.redisson = redisson;
        this.options = options;
        if (options != null
                && options.getWriteMode() == WriteMode.WRITE_BEHIND
                    && (options.getWriter() != null || options.getWriterAsync() != null)) {
            this.writeBehindService = writeBehindService;
            writeBehindTask = writeBehindService.start(getRawName(), options);
        } else {
            this.writeBehindService = null;
            writeBehindTask = null;
        }
        if (options != null
                && options.getWriterRetryAttempts()>1
                && options.getWriterAsync() != null){
            ((RetryableMapWriterAsync) options.getWriterAsync()).setServiceManager(commandExecutor.getServiceManager());
        }
    }

    @Override
    public  RMapReduce mapReduce() {
        return new RedissonMapReduce<>(this, redisson, commandExecutor);
    }

    @Override
    public RPermitExpirableSemaphore getPermitExpirableSemaphore(K key) {
        String lockName = getLockByMapKey(key, "permitexpirablesemaphore");
        return new RedissonPermitExpirableSemaphore(commandExecutor, lockName);
    }

    @Override
    public RSemaphore getSemaphore(K key) {
        String lockName = getLockByMapKey(key, "semaphore");
        return new RedissonSemaphore(commandExecutor, lockName);
    }
    
    @Override
    public RCountDownLatch getCountDownLatch(K key) {
        String lockName = getLockByMapKey(key, "countdownlatch");
        return new RedissonCountDownLatch(commandExecutor, lockName);
    }
    
    @Override
    public RLock getFairLock(K key) {
        String lockName = getLockByMapKey(key, "fairlock");
        return new RedissonFairLock(commandExecutor, lockName);
    }
    
    @Override
    public RLock getLock(K key) {
        String lockName = getLockByMapKey(key, "lock");
        return new RedissonLock(commandExecutor, lockName);
    }
    
    @Override
    public RReadWriteLock getReadWriteLock(K key) {
        String lockName = getLockByMapKey(key, "rw_lock");
        return new RedissonReadWriteLock(commandExecutor, lockName);
    }
    
    @Override
    public int size() {
        return get(sizeAsync());
    }

    @Override
    public V merge(K key, V value, BiFunction remappingFunction) {
        checkNotBatch();

        checkKey(key);
        checkValue(value);
        Objects.requireNonNull(remappingFunction);

        RLock lock = getLock(key);
        lock.lock();
        try {
            V oldValue = get(key);
            V newValue = value;
            if (oldValue != null) {
                newValue = remappingFunction.apply(oldValue, value);
            }

            if (newValue == null) {
                fastRemove(key);
            } else {
                fastPut(key, newValue);
            }
            return newValue;
        } finally {
            lock.unlock();
        }
    }

    @Override
    public RFuture mergeAsync(K key, V value, BiFunction remappingFunction) {
        checkNotBatch();

        checkKey(key);
        checkValue(value);
        Objects.requireNonNull(remappingFunction);

        RLock lock = getLock(key);
        long threadId = Thread.currentThread().getId();
        CompletionStage f = lock.lockAsync(threadId).thenCompose(r -> {
            RFuture oldValueFuture = getAsync(key, threadId);
            return oldValueFuture.thenCompose(oldValue -> {
                CompletableFuture newValuePromise = CompletableFuture.completedFuture(value);
                if (oldValue != null) {
                    newValuePromise = CompletableFuture.supplyAsync(() -> remappingFunction.apply(oldValue, value),
                            getServiceManager().getExecutor());
                }
                return newValuePromise
                        .thenCompose(newValue -> {
                            RFuture future;
                            if (newValue != null) {
                                future = fastPutAsync(key, newValue);
                            } else {
                                future = fastRemoveAsync(key);
                            }
                            return future.thenApply(res -> newValue);
                        });
            }).whenComplete((c, e) -> {
                lock.unlockAsync(threadId);
            });
        });
        return new CompletableFutureWrapper<>(f);
    }

    @Override
    public RFuture computeAsync(K key, BiFunction remappingFunction) {
        checkNotBatch();
        checkKey(key);
        Objects.requireNonNull(remappingFunction);

        RLock lock = getLock(key);
        long threadId = Thread.currentThread().getId();
        CompletionStage f = (CompletionStage) lock.lockAsync(threadId)
                .thenCompose(r -> {
                RFuture oldValueFuture = getAsync(key, threadId);
                return oldValueFuture.thenCompose(oldValue -> {
                    return CompletableFuture.supplyAsync(() -> remappingFunction.apply(key, oldValue), getServiceManager().getExecutor())
                            .thenCompose(newValue -> {
                                if (newValue == null) {
                                    if (oldValue != null) {
                                        return fastRemoveAsync(key)
                                                .thenApply(rr -> newValue);
                                    }
                                    return CompletableFuture.completedFuture(newValue);
                                }
                                return fastPutAsync(key, newValue)
                                        .thenApply(rr -> newValue);
                            });
                }).whenComplete((c, e) -> {
                    lock.unlockAsync(threadId);
                });
        });
        return new CompletableFutureWrapper<>(f);

    }

    @Override
    public V compute(K key, BiFunction remappingFunction) {
        checkNotBatch();

        checkKey(key);
        Objects.requireNonNull(remappingFunction);

        RLock lock = getLock(key);
        lock.lock();
        try {
            V oldValue = get(key);

            V newValue = remappingFunction.apply(key, oldValue);
            if (newValue == null) {
                if (oldValue != null) {
                    fastRemove(key);
                }
            } else {
                fastPut(key, newValue);
            }
            return newValue;
        } finally {
            lock.unlock();
        }
    }

    @Override
    public RFuture computeIfAbsentAsync(K key, Function mappingFunction) {
        checkNotBatch();

        checkKey(key);
        Objects.requireNonNull(mappingFunction);

        RLock lock = getLock(key);
        long threadId = Thread.currentThread().getId();
        CompletionStage f = lock.lockAsync(threadId)
                .thenCompose(r -> {
                    RFuture oldValueFuture = getAsync(key, threadId);
                    return oldValueFuture.thenCompose(oldValue -> {
                        if (oldValue != null) {
                            return CompletableFuture.completedFuture(oldValue);
                        }

                        return CompletableFuture.supplyAsync(() -> mappingFunction.apply(key), getServiceManager().getExecutor())
                                .thenCompose(newValue -> {
                                    if (newValue != null) {
                                        return fastPutAsync(key, newValue).thenApply(rr -> newValue);
                                    }
                                    return CompletableFuture.completedFuture(null);
                                });
                    }).whenComplete((c, e) -> {
                        lock.unlockAsync(threadId);
                    });
                });

        return new CompletableFutureWrapper<>(f);
    }

    @Override
    public V computeIfAbsent(K key, Function mappingFunction) {
        checkNotBatch();

        checkKey(key);
        Objects.requireNonNull(mappingFunction);

        V value = get(key);
        if (value != null) {
            return value;
        }

        RLock lock = getLock(key);
        lock.lock();
        try {
            value = get(key);
            if (value == null) {
                V newValue = mappingFunction.apply(key);
                if (newValue != null) {
                    fastPut(key, newValue);
                    return newValue;
                }
                return null;
            }
            return value;
        } finally {
            lock.unlock();
        }
    }

    @Override
    public RFuture computeIfPresentAsync(K key, BiFunction remappingFunction) {
        checkNotBatch();

        checkKey(key);
        Objects.requireNonNull(remappingFunction);

        RLock lock = getLock(key);
        long threadId = Thread.currentThread().getId();
        CompletionStage f = (CompletionStage) lock.lockAsync(threadId)
                .thenCompose(r -> {
                    RFuture oldValueFuture = getAsync(key, threadId);
                    return oldValueFuture.thenCompose(oldValue -> {
                        if (oldValue == null) {
                            return CompletableFuture.completedFuture(null);
                        }

                        return CompletableFuture.supplyAsync(() -> remappingFunction.apply(key, oldValue), getServiceManager().getExecutor())
                                .thenCompose(newValue -> {
                                    if (newValue != null) {
                                        return fastPutAsync(key, newValue).thenApply(rr -> newValue);
                                    }
                                    return fastRemoveAsync(key).thenApply(rr -> null);
                                });
                    }).whenComplete((c, e) -> {
                        lock.unlockAsync(threadId);
                    });
                });

        return new CompletableFutureWrapper<>(f);
    }

    @Override
    public V computeIfPresent(K key, BiFunction remappingFunction) {
        checkNotBatch();

        checkKey(key);
        Objects.requireNonNull(remappingFunction);

        RLock lock = getLock(key);
        lock.lock();
        try {
            V oldValue = get(key);
            if (oldValue == null) {
                return null;
            }

            V newValue = remappingFunction.apply(key, oldValue);
            if (newValue != null) {
                fastPut(key, newValue);
                return newValue;
            }
            fastRemove(key);
            return null;
        } finally {
            lock.unlock();
        }
    }

    @Override
    public RFuture sizeAsync() {
        return commandExecutor.readAsync(getRawName(), codec, RedisCommands.HLEN, getRawName());
    }

    @Override
    public int valueSize(K key) {
        return get(valueSizeAsync(key));
    }
    
    @Override
    public RFuture valueSizeAsync(K key) {
        checkKey(key);

        String name = getRawName(key);
        return commandExecutor.readAsync(name, codec, RedisCommands.HSTRLEN, name, encodeMapKey(key));
    }

    protected void checkKey(Object key) {
        if (key == null) {
            throw new NullPointerException("map key can't be null");
        }
    }
    
    @Override
    public boolean isEmpty() {
        return size() == 0;
    }

    @Override
    public boolean containsKey(Object key) {
        return get(containsKeyAsync(key));
    }

    @Override
    public RFuture containsKeyAsync(Object key) {
        checkKey(key);

        CompletableFuture promise = new CompletableFuture<>();
        return containsKeyAsync(key, promise);
    }

    protected RFuture containsKeyOperationAsync(String name, Object key) {
        return commandExecutor.readAsync(name, codec, RedisCommands.HEXISTS, name, encodeMapKey(key));
    }

    protected RFuture containsKeyAsync(Object key, CompletableFuture promise) {
        String name = getRawName(key);
        RFuture future = containsKeyOperationAsync(name, key);
        if (hasNoLoader()) {
            return future;
        }

        CompletionStage result = future.thenCompose(res -> {
            if (!res) {
                CompletableFuture f = loadValue((K) key, false);
                commandExecutor.transfer(f, promise);
                return promise.thenApply(r -> r != null);
            }
            promise.complete(null);
            return CompletableFuture.completedFuture(res);
        });
        return new CompletableFutureWrapper<>(result);
    }

    @Override
    public boolean containsValue(Object value) {
        return get(containsValueAsync(value));
    }

    @Override
    public RFuture containsValueAsync(Object value) {
        checkValue(value);
        
        return commandExecutor.evalReadAsync(getRawName(), codec, RedisCommands.EVAL_BOOLEAN,
                "local s = redis.call('hvals', KEYS[1]);" +
                        "for i = 1, #s, 1 do "
                            + "if ARGV[1] == s[i] then "
                                + "return 1 "
                            + "end "
                       + "end;" +
                     "return 0",
                Collections.singletonList(getRawName()), encodeMapValue(value));
    }

    @Override
    public Map getAll(Set keys) {
        if (keys.getClass().getPackage().getName().startsWith("org.redisson")) {
            keys = new HashSet<>(keys);
        }
        return get(getAllAsync(keys));
    }

    @Override
    public Set randomKeys(int count) {
        return get(randomKeysAsync(count));
    }

    @Override
    public Map randomEntries(int count) {
        return get(randomEntriesAsync(count));
    }

    @Override
    public RFuture> randomKeysAsync(int count) {
        return commandExecutor.readAsync(getRawName(), codec, RedisCommands.HRANDFIELD_KEYS, getRawName(), count);
    }

    @Override
    public RFuture> randomEntriesAsync(int count) {
        return commandExecutor.readAsync(getRawName(), codec, RedisCommands.HRANDFIELD, getRawName(), count, "WITHVALUES");
    }

    @Override
    public RFuture> getAllAsync(Set keys) {
        if (keys.isEmpty()) {
            return new CompletableFutureWrapper<>(Collections.emptyMap());
        }

        RFuture> future = getAllOperationAsync(keys);
        if (hasNoLoader()) {
            return future;
        }

        CompletionStage> f = future.thenCompose(res -> {
            if (!res.keySet().containsAll(keys)) {
                Set newKeys = new HashSet(keys);
                newKeys.removeAll(res.keySet());

                CompletionStage> ff = loadAllMapAsync(newKeys.spliterator(), false, 1);
                return ff.thenApply(map -> {
                    res.putAll(map);
                    return res;
                });
            }
            return CompletableFuture.completedFuture(res);
        });
        return new CompletableFutureWrapper<>(f);
    }

    protected boolean hasNoLoader() {
        return options == null || (options.getLoader() == null && options.getLoaderAsync() == null);
    }

    public RFuture> getAllOperationAsync(Set keys) {
        List args = new ArrayList<>(keys.size() + 1);
        args.add(getRawName());
        encodeMapKeys(args, keys);
        RFuture> future = commandExecutor.readAsync(getRawName(), codec, new RedisCommand<>("HMGET",
                        new MapValueDecoder(new MapGetAllDecoder(new ArrayList<>(keys), 0))),
                args.toArray());
        return future;
    }
    
    @Override
    public V get(Object key) {
        return get(getAsync((K) key));
    }

    @Override
    public V put(K key, V value) {
        return get(putAsync(key, value));
    }

    @Override
    public V remove(Object key) {
        return get(removeAsync((K) key));
    }

    @Override
    public final void putAll(Map map) {
        get(putAllAsync(map));
    }

    @Override
    public void putAll(Map map, int batchSize) {
        get(putAllAsync(map, batchSize));
    }
    
    @Override
    public RFuture putAllAsync(Map map, int batchSize) {
        Map batch = new HashMap();
        AtomicInteger counter = new AtomicInteger();
        Iterator> iter = ((Map) map).entrySet().iterator();

        CompletionStage f = putAllAsync(batch, iter, counter, batchSize);
        return new CompletableFutureWrapper<>(f);
    }
    
    private CompletionStage putAllAsync(Map batch, Iterator> iter,
                                                    AtomicInteger counter, int batchSize) {
        batch.clear();
        
        while (iter.hasNext()) {
            Entry entry = iter.next();
            batch.put(entry.getKey(), entry.getValue());
            counter.incrementAndGet();
            if (counter.get() % batchSize == 0) {
                RFuture future = putAllAsync(batch);
                return future.thenCompose(res -> {
                    return putAllAsync(batch, iter, counter, batchSize);
                });
            }
        }
        
        if (batch.isEmpty()) {
            return CompletableFuture.completedFuture(null);
        }
        
        return putAllAsync(batch);
    }
    
    @Override
    public final RFuture putAllAsync(Map map) {
        if (map.isEmpty()) {
            return new CompletableFutureWrapper<>((Void) null);
        }

        RFuture future = putAllOperationAsync(map);
        if (hasNoWriter()) {
            return future;
        }
        
        return mapWriterFuture(future, new MapWriterTask.Add(map));
    }

    protected final  RFuture mapWriterFuture(RFuture future, MapWriterTask task) {
        return mapWriterFuture(future, task, r -> true);
    }
    
    protected final  RFuture mapWriterFuture(RFuture future, MapWriterTask task, Function condition) {
        if (options != null && options.getWriteMode() == WriteMode.WRITE_BEHIND) {
            CompletionStage f = future.whenComplete((res, e) -> {
                if (e == null && condition.apply(res)) {
                    writeBehindTask.addTask(task);
                }
            });
            return new CompletableFutureWrapper<>(f);
        }

        CompletionStage f = future.thenCompose(res -> {
            if (condition.apply(res)) {
                if (options.getWriter() != null) {
                    return CompletableFuture.supplyAsync(() -> {
                        if (task instanceof MapWriterTask.Add) {
                            options.getWriter().write(task.getMap());
                        } else {
                            options.getWriter().delete(task.getKeys());
                        }
                        return res;
                    }, getServiceManager().getExecutor());
                }

                if (task instanceof MapWriterTask.Add) {
                    return options.getWriterAsync().write(task.getMap())
                                                    .thenApply(r -> res);
                } else {
                    return options.getWriterAsync().delete(task.getKeys())
                                                    .thenApply(r -> res);
                }
            }
            return CompletableFuture.completedFuture(res);
        });

        return new CompletableFutureWrapper<>(f);
    }

    protected RFuture putAllOperationAsync(Map map) {
        List params = new ArrayList<>(map.size()*2 + 1);
        params.add(getRawName());
        encodeMapKeys(params, map);

        RFuture future = commandExecutor.writeAsync(getRawName(), codec, RedisCommands.HMSET, params.toArray());
        return future;
    }

    @Override
    public void clear() {
        delete();
    }

    @Override
    public Set keySet() {
        return keySet(null);
    }
    
    @Override
    public Set keySet(String pattern) {
        return keySet(pattern, 10);
    }
    
    @Override
    public Set keySet(String pattern, int count) {
        return new KeySet(pattern, count);
    }

    @Override
    public Set keySet(int count) {
        return keySet(null, count);
    }

    @Override
    public Collection values() {
        return values(null);
    }

    @Override
    public Collection values(String keyPattern, int count) {
        return new Values(keyPattern, count);
    }
    
    @Override
    public Collection values(String keyPattern) {
        return values(keyPattern, 10);
    }

    @Override
    public Collection values(int count) {
        return values(null, count);
    }
    
    @Override
    public Set> entrySet() {
        return entrySet(null);
    }
    
    @Override
    public Set> entrySet(String keyPattern) {
        return entrySet(keyPattern, 10);
    }

    @Override
    public Set> entrySet(String keyPattern, int count) {
        return new EntrySet(keyPattern, count);
    }

    @Override
    public Set> entrySet(int count) {
        return entrySet(null, count);
    }
    
    @Override
    public Set readAllKeySet() {
        return get(readAllKeySetAsync());
    }

    @Override
    public RFuture> readAllKeySetAsync() {
        return commandExecutor.readAsync(getRawName(), codec, RedisCommands.HKEYS, getRawName());
    }

    @Override
    public Collection readAllValues() {
        return get(readAllValuesAsync());
    }

    @Override
    public RFuture> readAllValuesAsync() {
        return commandExecutor.readAsync(getRawName(), codec, RedisCommands.HVALS, getRawName());
    }

    @Override
    public Set> readAllEntrySet() {
        return get(readAllEntrySetAsync());
    }

    @Override
    public RFuture>> readAllEntrySetAsync() {
        return commandExecutor.readAsync(getRawName(), codec, RedisCommands.HGETALL_ENTRY, getRawName());
    }

    @Override
    public Map readAllMap() {
        return get(readAllMapAsync());
    }

    @Override
    public RFuture> readAllMapAsync() {
        return commandExecutor.readAsync(getRawName(), codec, RedisCommands.HGETALL, getRawName());
    }

    @Override
    public V putIfExists(K key, V value) {
        return get(putIfExistsAsync(key, value));
    }

    @Override
    public RFuture putIfExistsAsync(K key, V value) {
        checkKey(key);
        checkValue(value);

        RFuture future = putIfExistsOperationAsync(key, value);
        if (hasNoWriter()) {
            return future;
        }

        MapWriterTask.Add task = new MapWriterTask.Add(key, value);
        return mapWriterFuture(future, task, Objects::nonNull);
    }

    protected RFuture putIfExistsOperationAsync(K key, V value) {
        String name = getRawName(key);
        return commandExecutor.evalWriteAsync(name, codec, RedisCommands.EVAL_MAP_VALUE,
            "local value = redis.call('hget', KEYS[1], ARGV[1]); "
                + "if value ~= false then "
                    + "redis.call('hset', KEYS[1], ARGV[1], ARGV[2]); "
                    + "return value; "
                + "end; "
                + "return nil; ",
                Collections.singletonList(name), encodeMapKey(key), encodeMapValue(value));
    }

    @Override
    public V putIfAbsent(K key, V value) {
        return get(putIfAbsentAsync(key, value));
    }

    @Override
    public RFuture putIfAbsentAsync(K key, V value) {
        checkKey(key);
        checkValue(value);
        
        RFuture future = putIfAbsentOperationAsync(key, value);
        if (hasNoWriter()) {
            return future;
        }
        
        MapWriterTask.Add task = new MapWriterTask.Add(key, value);
        return mapWriterFuture(future, task, Objects::isNull);
    }

    protected boolean hasNoWriter() {
        return options == null || (options.getWriter() == null && options.getWriterAsync() == null);
    }

    protected RFuture putIfAbsentOperationAsync(K key, V value) {
        String name = getRawName(key);
        return commandExecutor.evalWriteNoRetryAsync(name, codec, RedisCommands.EVAL_MAP_VALUE,
                 "if redis.call('hsetnx', KEYS[1], ARGV[1], ARGV[2]) == 1 then "
                    + "return nil "
                + "else "
                    + "return redis.call('hget', KEYS[1], ARGV[1]) "
                + "end",
                Collections.singletonList(name), encodeMapKey(key), encodeMapValue(value));
    }

    @Override
    public boolean fastPutIfAbsent(K key, V value) {
        return get(fastPutIfAbsentAsync(key, value));
    }

    @Override
    public RFuture fastPutIfAbsentAsync(K key, V value) {
        checkKey(key);
        checkValue(value);
        
        RFuture future = fastPutIfAbsentOperationAsync(key, value);
        if (hasNoWriter()) {
            return future;
        }
        
        MapWriterTask.Add task = new MapWriterTask.Add(key, value);
        return mapWriterFuture(future, task, Function.identity());
    }

    protected RFuture fastPutIfAbsentOperationAsync(K key, V value) {
        String name = getRawName(key);
        return commandExecutor.writeAsync(name, codec, RedisCommands.HSETNX, name, encodeMapKey(key), encodeMapValue(value));
    }

    @Override
    public boolean fastPutIfExists(K key, V value) {
        return get(fastPutIfExistsAsync(key, value));
    }

    @Override
    public RFuture fastPutIfExistsAsync(K key, V value) {
        checkKey(key);
        checkValue(value);

        RFuture future = fastPutIfExistsOperationAsync(key, value);
        if (hasNoWriter()) {
            return future;
        }

        MapWriterTask.Add task = new MapWriterTask.Add(key, value);
        return mapWriterFuture(future, task, Function.identity());
    }

    protected RFuture fastPutIfExistsOperationAsync(K key, V value) {
        String name = getRawName(key);
        return commandExecutor.evalWriteAsync(name, codec, RedisCommands.EVAL_BOOLEAN,
            "local value = redis.call('hget', KEYS[1], ARGV[1]); "
                + "if value ~= false then "
                    + "redis.call('hset', KEYS[1], ARGV[1], ARGV[2]); "
                    + "return 1; "
                + "end; "
                + "return 0; ",
                Collections.singletonList(name),
                encodeMapKey(key), encodeMapValue(value));
    }

    @Override
    public boolean remove(Object key, Object value) {
        return get(removeAsync(key, value));
    }

    @Override
    public RFuture removeAsync(Object key, Object value) {
        checkKey(key);
        checkValue(value);
        
        RFuture future = removeOperationAsync(key, value);
        if (hasNoWriter()) {
            return future;
        }
        
        MapWriterTask.Remove listener = new MapWriterTask.Remove(key);
        return mapWriterFuture(future, listener, Function.identity());
    }

    protected RFuture removeOperationAsync(Object key, Object value) {
        String name = getRawName(key);
        RFuture future = commandExecutor.evalWriteAsync(name, codec, RedisCommands.EVAL_BOOLEAN,
                "if redis.call('hget', KEYS[1], ARGV[1]) == ARGV[2] then "
                        + "return redis.call('hdel', KEYS[1], ARGV[1]) "
                + "else "
                    + "return 0 "
                + "end",
            Collections.singletonList(name), encodeMapKey(key), encodeMapValue(value));
        return future;
    }

    protected void checkValue(Object value) {
        if (value == null) {
            throw new NullPointerException("map value can't be null");
        }
    }

    protected void encodeMapKeys(Collection params, Map map) {
        try {
            for (java.util.Map.Entry t : map.entrySet()) {
                checkKey(t.getKey());
                checkValue(t.getValue());

                params.add(encodeMapKey(t.getKey()));
                params.add(encodeMapValue(t.getValue()));
            }
        } catch (Exception e) {
            params.forEach(v -> {
                ReferenceCountUtil.safeRelease(v);
            });
            throw e;
        }
    }

    @Override
    public boolean replace(K key, V oldValue, V newValue) {
        return get(replaceAsync(key, oldValue, newValue));
    }

    @Override
    public RFuture replaceAsync(K key, V oldValue, V newValue) {
        checkKey(key);
        if (oldValue == null) {
            throw new NullPointerException("map oldValue can't be null");
        }
        if (newValue == null) {
            throw new NullPointerException("map newValue can't be null");
        }

        RFuture future = replaceOperationAsync(key, oldValue, newValue);
        if (hasNoWriter()) {
            return future;
        }
        
        MapWriterTask.Add task = new MapWriterTask.Add(key, newValue);
        return mapWriterFuture(future, task, Function.identity());
    }

    protected RFuture replaceOperationAsync(K key, V oldValue, V newValue) {
        String name = getRawName(key);
        return commandExecutor.evalWriteAsync(name, codec, RedisCommands.EVAL_BOOLEAN,
                "if redis.call('hget', KEYS[1], ARGV[1]) == ARGV[2] then "
                    + "redis.call('hset', KEYS[1], ARGV[1], ARGV[3]); "
                    + "return 1; "
                + "else "
                    + "return 0; "
                + "end",
                Collections.singletonList(name), encodeMapKey(key), encodeMapValue(oldValue), encodeMapValue(newValue));
    }

    @Override
    public V replace(K key, V value) {
        return get(replaceAsync(key, value));
    }

    @Override
    public RFuture replaceAsync(K key, V value) {
        checkKey(key);
        checkValue(value);
        
        RFuture future = replaceOperationAsync(key, value);
        if (hasNoWriter()) {
            return future;
        }
        
        MapWriterTask.Add task = new MapWriterTask.Add(key, value);
        return mapWriterFuture(future, task, r -> r != null);
    }

    protected RFuture replaceOperationAsync(K key, V value) {
        String name = getRawName(key);
        return commandExecutor.evalWriteAsync(name, codec, RedisCommands.EVAL_MAP_VALUE,
                "if redis.call('hexists', KEYS[1], ARGV[1]) == 1 then "
                    + "local v = redis.call('hget', KEYS[1], ARGV[1]); "
                    + "redis.call('hset', KEYS[1], ARGV[1], ARGV[2]); "
                    + "return v; "
                + "else "
                    + "return nil; "
                + "end",
            Collections.singletonList(name), encodeMapKey(key), encodeMapValue(value));
    }
    
    @Override
    public boolean fastReplace(K key, V value) {
        return get(fastReplaceAsync(key, value));
    }

    @Override
    public RFuture fastReplaceAsync(K key, V value) {
        checkKey(key);
        checkValue(value);
        
        RFuture future = fastReplaceOperationAsync(key, value);
        if (hasNoWriter()) {
            return future;
        }
        
        MapWriterTask.Add task = new MapWriterTask.Add(key, value);
        return mapWriterFuture(future, task, Function.identity());
    }

    protected RFuture fastReplaceOperationAsync(K key, V value) {
        String name = getRawName(key);
        return commandExecutor.evalWriteAsync(name, codec, RedisCommands.EVAL_BOOLEAN,
                "if redis.call('hexists', KEYS[1], ARGV[1]) == 1 then "
                    + "redis.call('hset', KEYS[1], ARGV[1], ARGV[2]); "
                    + "return 1; "
                + "else "
                    + "return 0; "
                + "end",
            Collections.singletonList(name), encodeMapKey(key), encodeMapValue(value));
    }
    

    public RFuture getOperationAsync(K key) {
        String name = getRawName(key);
        return commandExecutor.readAsync(name, codec, RedisCommands.HGET, name, encodeMapKey(key));
    }
    
    @Override
    public final RFuture getAsync(K key) {
        long threadId = Thread.currentThread().getId();
        return getAsync(key, threadId);
    }

    protected RFuture getAsync(K key, long threadId) {
        checkKey(key);

        RFuture future = getOperationAsync(key);
        if (hasNoLoader()) {
            return future;
        }

        CompletionStage f = future.thenCompose(res -> {
            if (res == null) {
                return loadValue(key, false, threadId);
            }
            return CompletableFuture.completedFuture(res);
        });
        return new CompletableFutureWrapper<>(f);
    }
    
    @Override
    public void loadAll(boolean replaceExistingValues, int parallelism) {
        get(loadAllAsync(replaceExistingValues, parallelism));
    }
    
    @Override
    public RFuture loadAllAsync(boolean replaceExistingValues, int parallelism) {
        if (hasNoLoader()) {
            throw new NullPointerException("MapLoader isn't defined");
        }

        if (options.getLoaderAsync() != null) {
            return loadAllAsync(options.getLoaderAsync().loadAllKeys(), replaceExistingValues, parallelism);
        }

        return loadAllAsync(() -> options.getLoader().loadAllKeys().spliterator(), replaceExistingValues, parallelism);
    }

    RFuture loadAllAsync(AsyncIterator iterator, boolean replaceExistingValues, int parallelism) {
        CompletionStage> f = loadAllAsync(iterator, new ArrayList<>(), new AtomicInteger(parallelism));
        CompletionStage ff = f.thenCompose(elements -> {
            List> futures = new ArrayList<>(elements.size());
            for (K k : elements) {
                CompletableFuture vFuture = loadValue(k, replaceExistingValues);
                futures.add(vFuture);
            }

            CompletableFuture finalFuture = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));

            if (elements.size() < parallelism) {
                return finalFuture;
            }

            return finalFuture
                        .thenCompose(v -> loadAllAsync(iterator, replaceExistingValues, parallelism));
        });
        return new CompletableFutureWrapper<>(ff);
    }

    CompletionStage> loadAllAsync(AsyncIterator iterator, List elements, AtomicInteger workers) {
        return iterator.hasNext()
                .thenCompose(v -> {
                    int s = workers.decrementAndGet();
                    if (v) {
                        return iterator.next().thenCompose(k -> {
                            if (k != null) {
                                elements.add(k);
                            }
                            if (s > 0) {
                                return loadAllAsync(iterator, elements, workers);
                            }
                            return CompletableFuture.completedFuture(elements);
                        });
                    }
                    return CompletableFuture.completedFuture(elements);
                });

    }

    private RFuture loadAllAsync(Supplier> supplier, boolean replaceExistingValues, int parallelism) {
        ForkJoinPool customThreadPool = new ForkJoinPool(parallelism);
        CompletableFuture result = new CompletableFuture<>();
        customThreadPool.submit(() -> {
            try {
                Stream s = StreamSupport.stream(supplier.get(), true);
                List> r = s.filter(k -> k != null)
                        .map(k -> {
                            return loadValue(k, replaceExistingValues).thenApply(v -> null);
                        }).collect(Collectors.toList());

                CompletableFuture ff = CompletableFuture.allOf(r.toArray(new CompletableFuture[0]));
                ff.thenApply(v -> {
                    customThreadPool.shutdown();
                    return result.complete(v);
                });
            } catch (Exception e) {
                result.completeExceptionally(e);
            }
        });

        return new CompletableFutureWrapper<>(result);
    }

    protected CompletionStage> loadAllMapAsync(Spliterator spliterator, boolean replaceExistingValues, int parallelism) {
        ForkJoinPool customThreadPool = new ForkJoinPool(parallelism);
        ConcurrentMap map = new ConcurrentHashMap<>();
        CompletableFuture> result = new CompletableFuture<>();
        customThreadPool.submit(() -> {
            try {
                Stream s = StreamSupport.stream(spliterator, true);
                List> r = s.filter(k -> k != null)
                        .map(k -> {
                            return loadValue(k, replaceExistingValues)
                                    .thenAccept(v -> {
                                        if (v != null) {
                                            map.put(k, v);
                                        }
                                    });
                        }).collect(Collectors.toList());

                CompletableFuture ff = CompletableFuture.allOf(r.toArray(new CompletableFuture[0]));
                ff.whenComplete((v, e) -> {
                    customThreadPool.shutdown();

                    if (e != null) {
                        result.completeExceptionally(e);
                        return;
                    }

                    result.complete(map);
                });
            } catch (Exception e) {
                result.completeExceptionally(e);
            }
        });

        return result;
    }

    @Override
    public void loadAll(Set keys, boolean replaceExistingValues, int parallelism) {
        get(loadAllAsync(keys, replaceExistingValues, parallelism));
    }
    
    @Override
    public RFuture loadAllAsync(Set keys, boolean replaceExistingValues, int parallelism) {
        return loadAllAsync(() -> (Spliterator) keys.spliterator(), replaceExistingValues, parallelism);
    }

    @Override
    public RFuture putAsync(K key, V value) {
        checkKey(key);
        checkValue(value);
        
        RFuture future = putOperationAsync(key, value);
        if (hasNoWriter()) {
            return future;
        }
        
        return mapWriterFuture(future, new MapWriterTask.Add(key, value));
    }

    protected RFuture putOperationAsync(K key, V value) {
        String name = getRawName(key);
        return commandExecutor.evalWriteAsync(name, codec, RedisCommands.EVAL_MAP_VALUE,
                "local v = redis.call('hget', KEYS[1], ARGV[1]); "
                + "redis.call('hset', KEYS[1], ARGV[1], ARGV[2]); "
                + "return v",
                Collections.singletonList(name), encodeMapKey(key), encodeMapValue(value));
    }

    @Override
    public RFuture removeAsync(K key) {
        checkKey(key);

        RFuture future = removeOperationAsync(key);
        if (hasNoWriter()) {
            return future;
        }
        
        return mapWriterFuture(future, new MapWriterTask.Remove(key));
    }

    protected RFuture removeOperationAsync(K key) {
        String name = getRawName(key);
        return commandExecutor.evalWriteAsync(name, codec, RedisCommands.EVAL_MAP_VALUE,
                "local v = redis.call('hget', KEYS[1], ARGV[1]); "
                + "redis.call('hdel', KEYS[1], ARGV[1]); "
                + "return v",
                Collections.singletonList(name), encodeMapKey(key));
    }

    @Override
    public RFuture fastPutAsync(K key, V value) {
        checkKey(key);
        checkValue(value);
        
        RFuture future = fastPutOperationAsync(key, value);
        if (hasNoWriter()) {
            return future;
        }
        
        return mapWriterFuture(future, new MapWriterTask.Add(key, value));
    }

    protected RFuture fastPutOperationAsync(K key, V value) {
        String name = getRawName(key);
        return commandExecutor.writeAsync(name, codec, RedisCommands.HSET, name, encodeMapKey(key), encodeMapValue(value));
    }

    @Override
    public boolean fastPut(K key, V value) {
        return get(fastPutAsync(key, value));
    }

    @Override
    public RFuture fastRemoveAsync(K... keys) {
        if (keys == null) {
            throw new NullPointerException();
        }

        if (keys.length == 0) {
            return new CompletableFutureWrapper<>(0L);
        }

        if (hasNoWriter()) {
            return fastRemoveOperationAsync(keys);
        }

        RFuture> removeFuture = fastRemoveOperationBatchAsync(keys);
        CompletionStage f = removeFuture.thenCompose(res -> {
            if (res.isEmpty()) {
                return CompletableFuture.completedFuture(0L);
            }

            List deletedKeys = new ArrayList();
            for (int i = 0; i < res.size(); i++) {
                if (res.get(i) == 1) {
                    deletedKeys.add(keys[i]);
                }
            }

            if (options.getWriteMode() == WriteMode.WRITE_BEHIND) {
                MapWriterTask.Remove task = new MapWriterTask.Remove(deletedKeys);
                writeBehindTask.addTask(task);

                return CompletableFuture.completedFuture((long) deletedKeys.size());
            } else {
                if (options.getWriter() != null) {
                    return CompletableFuture.runAsync(() -> {
                        options.getWriter().delete(deletedKeys);
                    }, getServiceManager().getExecutor())
                            .thenApply(r -> (long) deletedKeys.size());
                }

                return options.getWriterAsync().delete(deletedKeys)
                                                .thenApply(r -> (long) deletedKeys.size());
            }
        });
        return new CompletableFutureWrapper<>(f);
    }

    protected RFuture> fastRemoveOperationBatchAsync(K... keys) {
        List args = new ArrayList<>(keys.length);
        encodeMapKeys(args, Arrays.asList(keys));

        RFuture> future = commandExecutor.evalWriteAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_LIST,
                        "local result = {}; " + 
                        "for i = 1, #ARGV, 1 do " 
                        + "local val = redis.call('hdel', KEYS[1], ARGV[i]); "
                        + "table.insert(result, val); "
                      + "end;"
                      + "return result;",
                        Arrays.asList(getRawName()),
                        args.toArray());
        return future;
    }

    protected RFuture fastRemoveOperationAsync(K... keys) {
        List args = new ArrayList<>(keys.length + 1);
        args.add(getRawName());
        encodeMapKeys(args, Arrays.asList(keys));
        return commandExecutor.writeAsync(getRawName(), codec, RedisCommands.HDEL, args.toArray());
    }

    @Override
    public long fastRemove(K... keys) {
        return get(fastRemoveAsync(keys));
    }

    public ScanResult> scanIterator(String name, RedisClient client, String startPos, String pattern, int count) {
        RFuture>> f = scanIteratorAsync(name, client, startPos, pattern, count);
        return get(f);
    }

    public ScanResult scanKeyIterator(String name, RedisClient client, String startPos, String pattern, int count) {
        RFuture> f = scanKeyIteratorAsync(name, client, startPos, pattern, count);
        return get(f);
    }

    public RFuture> scanKeyIteratorAsync(String name, RedisClient client, String startPos, String pattern, int count) {
        List params = new ArrayList<>();
        params.add(startPos);
        if (pattern != null) {
            params.add(pattern);
        }
        params.add(count);

        RedisCommand> evalScan = new RedisCommand>("EVAL",
                new ListMultiDecoder2(new ListScanResultReplayDecoder(), new ObjectDecoder<>(codec.getMapKeyDecoder())));

        return commandExecutor.evalReadAsync(client, name, codec, evalScan,
                "local result = {}; "
                + "local res; "
                + "if (#ARGV == 3) then "
                    + " res = redis.call('hscan', KEYS[1], ARGV[1], 'match', ARGV[2], 'count', ARGV[3]); "
                + "else "
                    + " res = redis.call('hscan', KEYS[1], ARGV[1], 'count', ARGV[2]); "
                + "end;"
                + "for i, value in ipairs(res[2]) do "
                    + "if i % 2 ~= 0 then "
                      + "local key = res[2][i]; "
                      + "table.insert(result, key); "
                    + "end; "
                + "end;"
                + "return {res[1], result};",
                Arrays.asList(name),
                params.toArray());
    }
    
    public RFuture>> scanIteratorAsync(String name, RedisClient client, String startPos, String pattern, int count) {
        if (pattern == null) {
            RFuture>> f
                                    = commandExecutor.readAsync(client, name, codec, RedisCommands.HSCAN, name, startPos, "COUNT", count);
            return f;
        }
        RFuture>> f
                                    = commandExecutor.readAsync(client, name, codec, RedisCommands.HSCAN, name, startPos, "MATCH", pattern, "COUNT", count);
        return f;
    }

    @Override
    public V addAndGet(K key, Number value) {
        return get(addAndGetAsync(key, value));
    }

    @Override
    public RFuture addAndGetAsync(K key, Number value) {
        checkKey(key);
        checkValue(value);
        
        RFuture future = addAndGetOperationAsync(key, value);
        if (hasNoWriter()) {
            return future;
        }

        return mapWriterFuture(future, new MapWriterTask.Add() {
            @Override
            public Map getMap() {
                return Collections.singletonMap(key, commandExecutor.getNow(future.toCompletableFuture()));
            }
        });
    }

    protected RFuture addAndGetOperationAsync(K key, Number value) {
        ByteBuf keyState = encodeMapKey(key);
        String name = getRawName(key);
        RFuture future = commandExecutor.writeAsync(name, StringCodec.INSTANCE,
                new RedisCommand<>("HINCRBYFLOAT", new NumberConvertor(value.getClass())),
                name, keyState, new BigDecimal(value.toString()).toPlainString());
        return future;
    }

    @Override
    public boolean equals(Object o) {
        if (o == this)
            return true;

        if (!(o instanceof Map))
            return false;
        Map m = (Map) o;
        if (m.size() != size())
            return false;

        try {
            Iterator> i = entrySet().iterator();
            while (i.hasNext()) {
                Entry e = i.next();
                K key = e.getKey();
                V value = e.getValue();
                if (value == null) {
                    if (!(m.get(key)==null && m.containsKey(key)))
                        return false;
                } else {
                    if (!value.equals(m.get(key)))
                        return false;
                }
            }
        } catch (ClassCastException unused) {
            return false;
        } catch (NullPointerException unused) {
            return false;
        }

        return true;
    }

    @Override
    public int hashCode() {
        int h = 0;
        Iterator> i = entrySet().iterator();
        while (i.hasNext()) {
            h += i.next().hashCode();
        }
        return h;
    }

    protected Iterator keyIterator(String pattern, int count) {
        return new RedissonMapKeyIterator(RedissonMap.this, pattern, count);
    }
    
    final class KeySet extends AbstractSet {

        private final String pattern;
        private final int count;
        
        KeySet(String pattern, int count) {
            this.pattern = pattern;
            this.count = count;
        }

        @Override
        public boolean isEmpty() {
            return !iterator().hasNext();
        }

        @Override
        public Iterator iterator() {
            return keyIterator(pattern, count);
        }

        @Override
        public boolean contains(Object o) {
            return RedissonMap.this.containsKey(o);
        }

        @Override
        public boolean remove(Object o) {
            return RedissonMap.this.fastRemove((K) o) == 1;
        }

        @Override
        public int size() {
            if (pattern != null) {
                int size = 0;
                for (K val : this) {
                    size++;
                }
                return size;
            }
            return RedissonMap.this.size();
        }

        @Override
        public void clear() {
            RedissonMap.this.clear();
        }

    }

    protected Iterator valueIterator(String pattern, int count) {
        return new RedissonMapIterator(RedissonMap.this, pattern, count) {
            @Override
            protected V getValue(java.util.Map.Entry entry) {
                return (V) entry.getValue();
            }
        };
    }

    final class Values extends AbstractCollection {

        private final String keyPattern;
        private final int count;
        
        Values(String keyPattern, int count) {
            this.keyPattern = keyPattern;
            this.count = count;
        }

        @Override
        public boolean isEmpty() {
            return !iterator().hasNext();
        }

        @Override
        public Iterator iterator() {
            return valueIterator(keyPattern, count);
        }

        @Override
        public boolean contains(Object o) {
            return RedissonMap.this.containsValue(o);
        }

        @Override
        public int size() {
            if (keyPattern != null) {
                int size = 0;
                for (V val : this) {
                    size++;
                }
                return size;
            }

            return RedissonMap.this.size();
        }

        @Override
        public void clear() {
            RedissonMap.this.clear();
        }

    }

    protected Iterator> entryIterator(String pattern, int count) {
        return new RedissonMapIterator<>(RedissonMap.this, pattern, count);
    }

    protected CompletableFuture loadValue(K key, boolean replaceValue) {
        return loadValue(key, replaceValue, Thread.currentThread().getId());
    }

    protected CompletableFuture loadValue(K key, boolean replaceValue, long threadId) {
        RLock lock = getLock(key);
        return lock.lockAsync(threadId).thenCompose(res -> {
            if (replaceValue) {
                return loadValue(key, lock, threadId);
            }
            
            return getOperationAsync(key).thenCompose(r -> {
                if (r != null) {
                    return lock.unlockAsync(threadId).thenApply(v -> r);
                }
                
                return loadValue(key, lock, threadId);
            });
        }).whenComplete((r, e) -> {
            if (e != null) {
                lock.unlockAsync(threadId);
            }
        }).toCompletableFuture();
    }
    
    private CompletableFuture loadValue(K key, RLock lock, long threadId) {
        if (options.getLoader() != null) {
            return CompletableFuture
                    .supplyAsync(() -> options.getLoader().load(key), getServiceManager().getExecutor())
                    .thenCompose(value -> {
                        if (value != null) {
                            return putOperationAsync(key, value)
                                    .thenApply(r -> value);
                        }
                        return CompletableFuture.completedFuture(null);
                    })
                    .whenComplete((r, e) -> {
                        lock.unlockAsync(threadId);
                    }).exceptionally(e -> null);
        }

        CompletionStage valueFuture = options.getLoaderAsync().load(key);
        return valueFuture.handle((r, ex) -> {
            if (r == null) {
                return lock.unlockAsync(threadId);
            }
            if (ex != null) {
                log.error("Unable to load value by key {} for map {}", key, getRawName(), ex);
                return lock.unlockAsync(threadId);
            }

            return valueFuture;
        }).thenCompose(f -> f)
          .thenCompose(value -> {
            if (value != null) {
                return (CompletionStage) putOperationAsync(key, (V) value).handle((r, ex) -> {
                    RFuture f = lock.unlockAsync(threadId);
                    if (ex != null) {
                        log.error("Unable to store value by key {} for map {}", key, getRawName(), ex);
                        return f;
                    }
                    return f.thenApply(res -> value);
                }).thenCompose(f -> f);
            }
            return CompletableFuture.completedFuture((V) value);
        }).toCompletableFuture();
    }

    final class EntrySet extends AbstractSet> {

        private final String keyPattern;
        private final int count;
        
        EntrySet(String keyPattern, int count) {
            this.keyPattern = keyPattern;
            this.count = count;
        }

        @Override
        public Iterator> iterator() {
            return entryIterator(keyPattern, count);
        }

        @Override
        public boolean contains(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry e = (Map.Entry) o;
            Object key = e.getKey();
            V value = get(key);
            return value != null && value.equals(e);
        }

        @Override
        public boolean remove(Object o) {
            if (o instanceof Map.Entry) {
                Map.Entry e = (Map.Entry) o;
                Object key = e.getKey();
                Object value = e.getValue();
                return RedissonMap.this.remove(key, value);
            }
            return false;
        }

        @Override
        public int size() {
            if (keyPattern != null) {
                int size = 0;
                for (Entry val : this) {
                    size++;
                }
                return size;
            }
            
            return RedissonMap.this.size();
        }

        @Override
        public void clear() {
            RedissonMap.this.clear();
        }

    }

    @Override
    public RFuture clearAsync() {
        return deleteAsync();
    }

    @Override
    public void destroy() {
        if (writeBehindService != null) {
            writeBehindService.stop(getRawName());
        }
        removeListeners();
    }

    @Override
    public int addListener(ObjectListener listener) {
        if (listener instanceof MapPutListener) {
            return addListener("__keyevent@*:hset", (MapPutListener) listener, MapPutListener::onPut);
        }
        if (listener instanceof MapRemoveListener) {
            return addListener("__keyevent@*:hdel", (MapRemoveListener) listener, MapRemoveListener::onRemove);
        }
        if (listener instanceof TrackingListener) {
            return addTrackingListener((TrackingListener) listener);
        }

        return super.addListener(listener);
    }

    @Override
    public RFuture addListenerAsync(ObjectListener listener) {
        if (listener instanceof MapPutListener) {
            return addListenerAsync("__keyevent@*:hset", (MapPutListener) listener, MapPutListener::onPut);
        }
        if (listener instanceof MapRemoveListener) {
            return addListenerAsync("__keyevent@*:hdel", (MapRemoveListener) listener, MapRemoveListener::onRemove);
        }
        if (listener instanceof TrackingListener) {
            return addTrackingListenerAsync((TrackingListener) listener);
        }

        return super.addListenerAsync(listener);
    }

    @Override
    public void removeListener(int listenerId) {
        removeTrackingListener(listenerId);
        removeListener(listenerId, "__keyevent@*:hset", "__keyevent@*:hdel");
        super.removeListener(listenerId);
    }

    @Override
    public RFuture removeListenerAsync(int listenerId) {
        RFuture f1 = removeTrackingListenerAsync(listenerId);
        RFuture f2 = removeListenerAsync(listenerId, "__keyevent@*:hset", "__keyevent@*:hdel");
        return new CompletableFutureWrapper<>(CompletableFuture.allOf(f1.toCompletableFuture(), f2.toCompletableFuture()));
    }

}