AbstractSimpleCache.java
package space.sunqian.common.cache;
import space.sunqian.annotations.Nonnull;
import space.sunqian.annotations.Nullable;
import space.sunqian.common.Fs;
import space.sunqian.common.base.value.Val;
import space.sunqian.common.base.value.Var;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;
/**
* This is a skeletal implementation of the {@link SimpleCache} to minimize the effort required to implement the
* interface. To implement a cache, just need to override the {@link #generate(Object, Object)} and {@link #clean()}.
* <p>
* This implementation is based on an underlying {@link ConcurrentMap}, which is a protected field: {@link #cacheMap}.
* The type of {@link #cacheMap}'s value is {@link Value}, which wraps the actual value to be cached. Every time this
* cache is invoked, {@link #clean()} is executed to clear expired values. Therefore, it is necessary to correctly
* implement the storage and invalidation behavior of cached data in {@link #generate(Object, Object)} and
* {@link #clean()}.
*
* @param <K> the key type
* @param <V> the value type
* @author sunqian
*/
public abstract class AbstractSimpleCache<K, V> implements SimpleCache<K, V> {
private static final @Nonnull Object NULL_VAL = "(╯‵□′)╯︵┻━┻";
/**
* The underlying cache map, which is used to store the cache value.
*/
protected final @Nonnull ConcurrentMap<K, Value<K>> cacheMap = new ConcurrentHashMap<>();
/**
* Generates a cache value wrapper with the given cache key and cache value. Note the value is masked and non-null.
*
* @param key the given cache key
* @param value the given cache value which is masked and non-null
* @return the cache value wrapper
*/
protected abstract @Nonnull Value<K> generate(@Nonnull K key, @Nonnull Object value);
@Override
public @Nullable V get(@Nonnull K key) {
clean();
@Nullable Value<K> cv = cacheMap.get(key);
if (cv == null) {
return null;
}
@Nullable Object raw = cv.refValue();
if (raw == null) {
return null;
}
return unmaskRawValue(raw);
}
@Override
public @Nullable Val<@Nullable V> getVal(@Nonnull K key) {
clean();
@Nullable Value<K> cv = cacheMap.get(key);
if (cv == null) {
return null;
}
@Nullable Object raw = cv.refValue();
if (raw == null) {
return null;
}
return Val.of(unmaskRawValue(raw));
}
@Override
public @Nullable V get(@Nonnull K key, @Nonnull Function<? super @Nonnull K, ? extends @Nullable V> producer) {
clean();
@Nullable Value<K> cv = cacheMap.get(key);
if (cv != null) {
@Nullable Object raw = cv.refValue();
if (raw != null) {
return unmaskRawValue(raw);
}
}
Var<V> value = Var.of(null);
cacheMap.compute(key, (k, old) -> {
if (old != null) {
@Nullable Object raw = old.refValue();
if (raw != null) {
value.set(unmaskRawValue(raw));
return old;
}
}
@Nullable V newV = producer.apply(key);
value.set(newV);
return generate(key, maskValue(newV));
});
return Fs.as(value.get());
}
@Override
public @Nullable Val<V> getVal(
@Nonnull K key,
@Nonnull Function<? super @Nonnull K, ? extends @Nullable Val<? extends @Nullable V>> producer
) {
clean();
@Nullable Value<K> cv = cacheMap.get(key);
if (cv != null) {
@Nullable Object raw = cv.refValue();
if (raw != null) {
return Val.of(unmaskRawValue(raw));
}
}
Var<Object> value = Var.of(null);
cacheMap.compute(key, (k, old) -> {
if (old != null) {
@Nullable Object raw = old.refValue();
if (raw != null) {
value.set(unmaskRawValue(raw));
return old;
}
}
@Nullable Val<? extends V> newV = producer.apply(key);
if (newV == null) {
value.set(NULL_VAL);
return null;
}
@Nullable V nv = newV.get();
value.set(nv);
return generate(key, maskValue(nv));
});
@Nullable Object v = value.get();
// if (v == NULL_VAL) {
if (NULL_VAL.equals(v)) {
return null;
}
return Val.of(Fs.as(v));
}
@Override
public void put(@Nonnull K key, @Nullable V value) {
clean();
Value<K> newValue = generate(key, maskValue(value));
@Nullable Value<K> old = cacheMap.put(key, newValue);
if (old != null) {
old.invalid();
}
}
@Override
public void remove(@Nonnull K key) {
clean();
@Nullable Value<K> old = cacheMap.remove(key);
if (old != null) {
old.invalid();
}
}
@Override
public int size() {
clean();
return cacheMap.size();
}
@Override
public void clear() {
cacheMap.forEach((k, v) -> {
v.invalid();
});
clean();
}
private @Nonnull Object maskValue(@Nullable V value) {
return value == null ? new Null() : value;
}
private @Nullable V unmaskRawValue(@Nonnull Object raw) {
if (raw instanceof Null) {
return null;
}
return Fs.as(raw);
}
private static final class Null {
private Null() {
}
}
/**
* This interface represents the cache value, which is used to wrap the actual value to be cached.
*
* @param <K> the key type
*/
public interface Value<K> {
/**
* Returns the cache key.
*
* @return the cache key
*/
@Nonnull
K key();
/**
* Returns the actual value to be cached, may be {@code null} if the value is invalid.
*
* @return the actual value to be cached, may be {@code null} if the value is invalid
*/
@Nullable
Object refValue();
/**
* Invalidates the cache value, the actual cached value should be cleared.
*/
void invalid();
}
}