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

org.redisson.cache.AbstractCacheMap 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

The 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.cache;

import java.util.*;
import java.util.AbstractMap.SimpleEntry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 *
 * @author Nikita Koksharov
 *
 * @param  key
 * @param  value
 */
public abstract class AbstractCacheMap implements Cache {

    final int size;
    final ConcurrentMap> map = new ConcurrentHashMap<>();
    private final long timeToLiveInMillis;
    private final long maxIdleInMillis;
    private Consumer> removalListener;


    public AbstractCacheMap(int size, long timeToLiveInMillis, long maxIdleInMillis) {
        if (size < 0) {
            throw new IllegalArgumentException("Size can't be " + size);
        }
        this.size = size;
        this.maxIdleInMillis = maxIdleInMillis;
        this.timeToLiveInMillis = timeToLiveInMillis;
    }

    protected void onValueRead(CachedValue value) {

    }

    protected void onValueRemove(CachedValue value) {
        if (removalListener != null) {
            removalListener.accept(value);
        }
    }

    public final void removalListener(Consumer> removalListener) {
        this.removalListener = removalListener;
    }

    /*
     * (non-Javadoc)
     * @see java.util.Map#size()
     */
    @Override
    public int size() {
        return map.size();
    }

    /*
     * (non-Javadoc)
     * @see java.util.Map#isEmpty()
     */
    @Override
    public boolean isEmpty() {
        return map.isEmpty();
    }

    /*
     * (non-Javadoc)
     * @see java.util.Map#containsKey(java.lang.Object)
     */
    @Override
    public boolean containsKey(Object key) {
        if (key == null) {
            throw new NullPointerException();
        }

        CachedValue entry = map.get(key);
        if (entry == null) {
            return false;
        }
        if (isValueExpired(entry)) {
            if (map.remove(key, entry)) {
                onValueRemove(entry);
                return false;
            }
            return containsKey(key);
        }
        return true;
    }

    private boolean isValueExpired(CachedValue entry) {
        if (entry.isExpired()) {
            return true;
        }
        if (entry.getValue() instanceof ExpirableValue) {
            if (((ExpirableValue) entry.getValue()).isExpired()) {
                return true;
            }
        }
        return false;
    }

    /*
     * (non-Javadoc)
     * @see java.util.Map#containsValue(java.lang.Object)
     */
    @Override
    public boolean containsValue(Object value) {
        if (value == null) {
            throw new NullPointerException();
        }

        for (Map.Entry> entry : map.entrySet()) {
            CachedValue cachedValue = entry.getValue();
            if (cachedValue.getValue().equals(value)) {
                if (isValueExpired(cachedValue)) {
                    if (map.remove(cachedValue.getKey(), cachedValue)) {
                        onValueRemove(cachedValue);
                    }
                } else {
                    readValue(cachedValue);
                    return true;
                }
            }
        }
        return false;
    }

    /*
     * (non-Javadoc)
     * @see java.util.Map#get(java.lang.Object)
     */
    @Override
    public V get(Object key) {
        if (key == null) {
            throw new NullPointerException();
        }

        CachedValue entry = map.get(key);
        if (entry == null) {
            return null;
        }
        if (isValueExpired(entry)) {
            if (map.remove(key, entry)) {
                onValueRemove(entry);
                return null;
            }
            return get(key);
        }
        return readValue(entry);
    }

    protected V readValue(CachedValue entry) {
        onValueRead(entry);
        return (V) entry.getValue();
    }

    /*
     * (non-Javadoc)
     * @see java.util.Map#put(java.lang.Object, java.lang.Object)
     */
    @Override
    public V put(K key, V value) {
        return put(key, value, timeToLiveInMillis, TimeUnit.MILLISECONDS, maxIdleInMillis, TimeUnit.MILLISECONDS);
    }

    private V put(K key, V value, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit) {
        CachedValue entry = create(key, value, ttlUnit.toMillis(ttl), maxIdleUnit.toMillis(maxIdleTime));
        if (isFull(key)) {
            if (!removeExpiredEntries()) {
                onMapFull();
            }
        }
        onValueCreate(entry);
        CachedValue prevCachedValue = map.put(key, entry);
        if (prevCachedValue != null) {
            onValueRemove(prevCachedValue);
            if (!isValueExpired(prevCachedValue)) {
                return (V) prevCachedValue.getValue();
            }
        }
        return null;
    }

    protected CachedValue create(K key, V value, long ttl, long maxIdleTime) {
        return new StdCachedValue(key, value, ttl, maxIdleTime);
    }

    protected void onValueCreate(CachedValue entry) {
    }

    protected boolean removeExpiredEntries() {
        if (timeToLiveInMillis == 0 && maxIdleInMillis == 0) {
            return false;
        }

        boolean removed = false;
        // TODO optimize
        for (CachedValue value : map.values()) {
            if (isValueExpired(value)) {
                if (map.remove(value.getKey(), value)) {
                    onValueRemove(value);
                    removed = true;
                }
            }
        }
        return removed;
    }

    protected abstract void onMapFull();

    protected boolean isFull(K key) {
        if (size == 0) {
            return false;
        }
        if (map.size() >= size) {
            return !map.containsKey(key);
        }
        return false;
    }

    /*
     * (non-Javadoc)
     * @see java.util.Map#remove(java.lang.Object)
     */
    @Override
    public V remove(Object key) {
        CachedValue entry = map.remove(key);
        if (entry != null) {
            onValueRemove(entry);
            if (!isValueExpired(entry)) {
                return (V) entry.getValue();
            }
        }
        return null;
    }

    /*
     * (non-Javadoc)
     * @see java.util.Map#putAll(java.util.Map)
     */
    @Override
    public void putAll(Map m) {
        removeExpiredEntries();
        for (Map.Entry entry : m.entrySet()) {
            put(entry.getKey(), entry.getValue());
        }
    }

    /*
     * (non-Javadoc)
     * @see java.util.Map#clear()
     */
    @Override
    public void clear() {
        map.clear();
    }

    /*
     * (non-Javadoc)
     * @see java.util.Map#keySet()
     */
    @Override
    public Set keySet() {
        removeExpiredEntries();
        return new KeySet();
    }

    /*
     * (non-Javadoc)
     * @see java.util.Map#values()
     */
    @Override
    public Collection values() {
        removeExpiredEntries();
        return new Values();
    }

    /*
     * (non-Javadoc)
     * @see java.util.Map#entrySet()
     */
    @Override
    public Set> entrySet() {
        removeExpiredEntries();
        return new EntrySet();
    }

    abstract class MapIterator implements Iterator {

        private final Iterator>> keyIterator = map.entrySet().iterator();

        Map.Entry> mapEntry;

        @Override
        public boolean hasNext() {
            if (mapEntry != null) {
                return true;
            }
            mapEntry = null;
            while (keyIterator.hasNext()) {
                Map.Entry> entry = keyIterator.next();
                if (isValueExpired(entry.getValue())) {
                    continue;
                }
                mapEntry = entry;
                break;
            }
            return mapEntry != null;
        }

        public CachedValue cursorValue() {
            if (mapEntry == null) {
                throw new IllegalStateException();
            }
            return mapEntry.getValue();
        }
    }

    final class KeySet extends AbstractSet {

        @Override
        public Iterator iterator() {
            return new MapIterator() {
                @Override
                public K next() {
                    if (mapEntry == null) {
                        throw new NoSuchElementException();
                    }

                    K key = mapEntry.getKey();
                    mapEntry = null;
                    return key;
                }

                @Override
                public void remove() {
                    if (mapEntry == null) {
                        throw new IllegalStateException();
                    }
                    map.remove(mapEntry.getKey());
                    mapEntry = null;
                }
            };
        }

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

        @Override
        public boolean remove(Object o) {
            return AbstractCacheMap.this.remove(o) != null;
        }

        @Override
        public int size() {
            return AbstractCacheMap.this.size();
        }

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

    }

    final class Values extends AbstractCollection {

        @Override
        public Iterator iterator() {
            return new MapIterator() {
                @Override
                public V next() {
                    if (mapEntry == null) {
                        throw new NoSuchElementException();
                    }

                    V value = readValue(mapEntry.getValue());
                    mapEntry = null;
                    return value;
                }

                @Override
                public void remove() {
                    if (mapEntry == null) {
                        throw new IllegalStateException();
                    }
                    map.remove(mapEntry.getKey(), mapEntry.getValue());
                    mapEntry = null;
                }
            };
        }

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

        @Override
        public int size() {
            return AbstractCacheMap.this.size();
        }

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

    }

    final class EntrySet extends AbstractSet> {

        public Iterator> iterator() {
            return new MapIterator>() {
                @Override
                public Map.Entry next() {
                    if (mapEntry == null) {
                        throw new NoSuchElementException();
                    }

                    SimpleEntry result = new SimpleEntry(mapEntry.getKey(), readValue(mapEntry.getValue()));
                    mapEntry = null;
                    return result;
                }

                @Override
                public void remove() {
                    if (mapEntry == null) {
                        throw new IllegalStateException();
                    }
                    map.remove(mapEntry.getKey(), mapEntry.getValue());
                    mapEntry = null;
                }
            };
        }

        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);
        }

        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 AbstractCacheMap.this.map.remove(key, value);
            }
            return false;
        }

        public int size() {
            return AbstractCacheMap.this.size();
        }

        public void clear() {
            AbstractCacheMap.this.clear();
        }

    }

    @Override
    public V putIfAbsent(K key, V value) {
        CachedValue entry = create(key, value, timeToLiveInMillis, maxIdleInMillis);
        CachedValue prevCachedValue = map.putIfAbsent(key, entry);
        if (prevCachedValue != null) {
            return prevCachedValue.getValue();
        }

        if (isFull(key)) {
            if (!removeExpiredEntries()) {
                onMapFull();
            }
        }
        onValueCreate(entry);
        return null;
    }

    @Override
    public boolean remove(Object key, Object value) {
        AtomicBoolean result = new AtomicBoolean();
        map.computeIfPresent((K) key, (k, entry) -> {
            if (entry.getValue().equals(value)
                    && !isValueExpired(entry)) {
                onValueRemove(entry);
                result.set(true);
                return null;
            }
            return entry;
        });
        return result.get();
    }

    @Override
    public boolean replace(K key, V oldValue, V newValue) {
        AtomicBoolean result = new AtomicBoolean();
        map.computeIfPresent(key, (k, entry) -> {
            if (entry.getValue().equals(oldValue)
                    && !isValueExpired(entry)) {
                onValueRemove(entry);
                result.set(true);
                CachedValue newEntry = create(key, newValue, timeToLiveInMillis, maxIdleInMillis);
                onValueCreate(newEntry);
                return newEntry;
            }
            return entry;
        });
        return result.get();
    }

    @Override
    public V replace(K key, V value) {
        AtomicReference result = new AtomicReference<>();
        map.computeIfPresent(key, (k, entry) -> {
            if (!isValueExpired(entry)) {
                onValueRemove(entry);
                result.set(entry.getValue());
                CachedValue newEntry = create(key, value, timeToLiveInMillis, maxIdleInMillis);
                onValueCreate(newEntry);
                return newEntry;
            }
            return entry;
        });
        return result.get();
    }

    @Override
    public V computeIfAbsent(K key, Function mappingFunction) {
        if (isFull(key)) {
            if (!removeExpiredEntries()) {
                onMapFull();
            }
        }

        CachedValue v = map.computeIfAbsent(key, k -> {
            V value = mappingFunction.apply(k);
            if (value == null) {
                return null;
            }
            CachedValue entry = create(key, value, timeToLiveInMillis, maxIdleInMillis);
            onValueCreate(entry);
            return entry;
        });
        if (v != null) {
            return v.getValue();
        }
        return null;
    }

    @Override
    public V computeIfPresent(K key, BiFunction remappingFunction) {
        CachedValue v = map.computeIfPresent(key, (k, e) -> {
            if (!isValueExpired(e)) {
                V value = remappingFunction.apply(k, e.getValue());
                if (value == null) {
                    return null;
                }
                CachedValue entry = create(key, value, timeToLiveInMillis, maxIdleInMillis);
                onValueCreate(entry);
                return entry;
            }
            return null;
        });
        if (v != null) {
            return v.getValue();
        }
        return null;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy