org.redisson.cache.AbstractCacheMap Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of redisson-all Show documentation
Show all versions of redisson-all Show documentation
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
/**
* 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 extends K, ? extends V> m) {
removeExpiredEntries();
for (Map.Entry extends K, ? extends V> 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 super K, ? extends V> 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 super K, ? super V, ? extends V> 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