Last active
July 23, 2024 14:16
-
-
Save dhsrocha/970eae1988cdc78787022b004ad103c8 to your computer and use it in GitHub Desktop.
Generic cache interface and default implementation for JDK 11+
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.time.LocalDateTime; | |
import java.time.temporal.ChronoUnit; | |
import java.util.Map; | |
import java.util.Optional; | |
import java.util.Set; | |
import java.util.concurrent.ConcurrentHashMap; | |
import java.util.concurrent.locks.ReadWriteLock; | |
import java.util.concurrent.locks.ReentrantReadWriteLock; | |
import java.util.function.Supplier; | |
import java.util.stream.Collectors; | |
/** | |
* A simple cache interface that allows storing key-value pairs with an optional timeout. | |
* | |
* @param <K> The type of the keys in the cache. | |
* @param <V> The type of the values in the cache. | |
* @author <a href="mailto:[email protected]">Diego Rocha</a> | |
*/ | |
public interface Cache<K, V> { | |
/** | |
* Creates a new Cache instance with the default timeout. | |
* | |
* @param <K> The type of the keys in the cache. | |
* @param <V> The type of the values in the cache. | |
* @return A new Cache instance with the default timeout. | |
*/ | |
static <K, V> Cache<K, V> create() { | |
return create(Default.DEFAULT_CACHE_TIMEOUT); | |
} | |
/** | |
* Creates a new Cache instance with a specified timeout. | |
* | |
* @param <K> The type of the keys in the cache. | |
* @param <V> The type of the values in the cache. | |
* @param timeout The timeout value in milliseconds. | |
* @return A new Cache instance with the specified timeout. | |
*/ | |
static <K, V> Cache<K, V> create(final long timeout) { | |
return new Default<>(timeout); | |
} | |
/** Cleans up the cache by removing expired entries. */ | |
void clean(); | |
/** Clears the cache, removing all entries. */ | |
void clear(); | |
/** | |
* Checks if the cache contains a specific key. | |
* | |
* @param key The key to check for existence in the cache. | |
* @return true if the cache contains the key, false otherwise. | |
*/ | |
boolean containsKey(final K key); | |
/** | |
* Retrieves the value associated with a specific key in the cache. | |
* | |
* @param key The key whose associated value is to be retrieved. | |
* @return An Optional containing the value if present, otherwise an empty Optional. | |
*/ | |
Optional<V> get(final K key); | |
/** | |
* Adds a key-value pair to the cache. | |
* | |
* @param key The key to be added to the cache. | |
* @param value The value to be associated with the key in the cache. | |
*/ | |
void put(final K key, final V value); | |
/** | |
* Removes a key and its associated value from the cache. | |
* | |
* @param key The key to be removed from the cache. | |
*/ | |
void remove(final K key); | |
} | |
/** | |
* The default implementation of the Cache interface. | |
* | |
* @param <K> The type of the keys in the cache. | |
* @param <V> The type of the values in the cache. | |
* @author <a href="mailto:[email protected]">Diego Rocha</a> | |
*/ | |
final class Default<K, V> implements Cache<K, V> { | |
/** The default cache timeout value in milliseconds. */ | |
public static final long DEFAULT_CACHE_TIMEOUT = 60000L; | |
private final ReadWriteLock lock = new ReentrantReadWriteLock(); | |
private final long timeout; | |
private final Map<K, Value<V>> store = new ConcurrentHashMap<>(); | |
/** | |
* Constructs a new Default cache instance with the specified timeout. | |
* | |
* @param timeout The timeout value in milliseconds. | |
*/ | |
Default(final long timeout) { | |
this.timeout = timeout; | |
this.clear(); | |
} | |
@Override | |
public void clean() { | |
for (final K key : this.expiredKeys()) { | |
this.remove(key); | |
} | |
} | |
@Override | |
public boolean containsKey(final K key) { | |
return this.store.containsKey(key); | |
} | |
@Override | |
public void clear() { | |
this.lock.writeLock().lock(); | |
try { | |
this.store.clear(); | |
} finally { | |
this.lock.writeLock().unlock(); | |
} | |
} | |
@Override | |
public Optional<V> get(final K key) { | |
this.lock.readLock().lock(); | |
try { | |
this.clean(); | |
return Optional.ofNullable(this.store.get(key)).map(Value::get); | |
} finally { | |
this.lock.readLock().unlock(); | |
} | |
} | |
@Override | |
public void put(final K key, final V value) { | |
try { | |
this.lock.writeLock().lock(); | |
this.store.put(key, this.valueOf(value)); | |
} finally { | |
lock.writeLock().unlock(); | |
} | |
} | |
@Override | |
public void remove(final K key) { | |
this.store.remove(key); | |
} | |
private Set<K> expiredKeys() { | |
return this.store.keySet().parallelStream().filter(this::isExpired).collect(Collectors.toSet()); | |
} | |
private boolean isExpired(final K key) { | |
var expirationDateTime = this.store.get(key).createdAt().plus(this.timeout, ChronoUnit.MILLIS); | |
return LocalDateTime.now().isAfter(expirationDateTime); | |
} | |
private Value<V> valueOf(final V val) { | |
return new Value<>() { | |
@Override | |
public V get() { | |
return val; | |
} | |
@Override | |
public LocalDateTime createdAt() { | |
return LocalDateTime.now(); | |
} | |
}; | |
} | |
/** | |
* Represents the value associated with a key in the cache. | |
* | |
* @param <V> The type of the value. | |
* @author <a href="mailto:[email protected]">Diego Rocha</a> | |
*/ | |
interface Value<V> extends Supplier<V> { | |
/** | |
* Gets the LocalDateTime when the value was created. | |
* | |
* @return The LocalDateTime when the value was created. | |
*/ | |
LocalDateTime createdAt(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment