Skip to content

Instantly share code, notes, and snippets.

@dhsrocha
Last active January 23, 2024 17:35
Show Gist options
  • Save dhsrocha/970eae1988cdc78787022b004ad103c8 to your computer and use it in GitHub Desktop.
Save dhsrocha/970eae1988cdc78787022b004ad103c8 to your computer and use it in GitHub Desktop.
Generic cache interface and default implementation for JDK 11+
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
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:dhsrocha.dev@gmail.com">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:dhsrocha.dev@gmail.com">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 Map<K, Value<V>> store;
private final long timeout;
/**
* 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.store = new HashMap<>();
}
@Override
public Optional<V> get(final K key) {
this.clean();
return Optional.ofNullable(this.store.get(key)).map(Value::get);
}
@Override
public void put(final K key, final V value) {
this.store.put(key, this.valueOf(value));
}
@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:dhsrocha.dev@gmail.com">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