Skip to content

Commit

Permalink
Allow caches to have nullable type arguments for their value types.
Browse files Browse the repository at this point in the history
This does not mean that the cache will "contain" `null`, since `null` is
never cached. However, it does allow users to declare whether they want
"a cache whose loads might return `null`" (which thus can return `null`
from cache operations) or "a cache whose loads will never return `null`"
(which thus doesn't require users to handle `null` when they request
that the cache return or load a value).

This PR follows up on #1805.

Ideally I will come back to write some Kotlin tests for this. For the
moment, I can only say that the results look good in my testing inside
Google.
  • Loading branch information
cpovirk committed Dec 5, 2024
1 parent 047021c commit 461f020
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 81 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@

import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.NullUnmarked;
import org.jspecify.annotations.Nullable;

/**
Expand All @@ -41,19 +40,18 @@
* @param <V> the type of mapped values
*/
@NullMarked
public interface AsyncCache<K, V> {
public interface AsyncCache<K, V extends @Nullable Object> {

/**
* Returns the future associated with the {@code key} in this cache, or {@code null} if there is
* no cached future for the {@code key}.
*
* @param key the key whose associated value is to be returned
* @return the future value to which the specified key is mapped, or {@code null} if this cache
* does not contain a mapping for the key
* does not contain a mapping for the key
* @throws NullPointerException if the specified key is null
*/
@Nullable
CompletableFuture<V> getIfPresent(K key);
@Nullable CompletableFuture<@NonNull V> getIfPresent(K key);

/**
* Returns the future associated with the {@code key} in this cache, obtaining that value from
Expand All @@ -73,9 +71,7 @@ public interface AsyncCache<K, V> {
* @return the current (existing or computed) future value associated with the specified key
* @throws NullPointerException if the specified key or mappingFunction is null
*/
@NullUnmarked
@NonNull CompletableFuture<V> get(
@NonNull K key, @NonNull Function<? super @NonNull K, ? extends @Nullable V> mappingFunction);
CompletableFuture<V> get(K key, Function<? super @NonNull K, ? extends V> mappingFunction);

/**
* Returns the future associated with the {@code key} in this cache, obtaining that value from
Expand All @@ -93,21 +89,16 @@ public interface AsyncCache<K, V> {
*
* @param key the key with which the specified value is to be associated
* @param mappingFunction the function to asynchronously compute a value, optionally using the
* given executor
* given executor
* @return the current (existing or computed) future value associated with the specified key
* @throws NullPointerException if the specified key or mappingFunction is null, or if the
* future returned by the mappingFunction is null
* @throws RuntimeException or Error if the mappingFunction does when constructing the future,
* in which case the mapping is left unestablished
*/
@NullUnmarked
@NonNull CompletableFuture<V> get(
@NonNull K key,
@NonNull
BiFunction<
? super @NonNull K,
? super @NonNull Executor,
? extends @NonNull CompletableFuture<? extends @Nullable V>>
CompletableFuture<V> get(
K key,
BiFunction<? super K, ? super Executor, ? extends CompletableFuture<? extends V>>
mappingFunction);

/**
Expand All @@ -129,14 +120,16 @@ public interface AsyncCache<K, V> {
* @param keys the keys whose associated values are to be returned
* @param mappingFunction the function to asynchronously compute the values
* @return a future containing an unmodifiable mapping of keys to values for the specified keys in
* this cache
* this cache
* @throws NullPointerException if the specified collection is null or contains a null element, or
* if the future returned by the mappingFunction is null
* if the future returned by the mappingFunction is null
* @throws RuntimeException or Error if the mappingFunction does so, in which case the mapping is
* left unestablished
* left unestablished
*/
CompletableFuture<Map<K, V>> getAll(Iterable<? extends K> keys,
Function<? super Set<? extends K>, ? extends Map<? extends K, ? extends V>> mappingFunction);
CompletableFuture<Map<K, @NonNull V>> getAll(
Iterable<? extends K> keys,
Function<? super Set<? extends K>, ? extends Map<? extends K, ? extends @NonNull V>>
mappingFunction);

/**
* Returns the future of a map of the values associated with the {@code keys}, creating or
Expand All @@ -157,17 +150,21 @@ CompletableFuture<Map<K, V>> getAll(Iterable<? extends K> keys,
*
* @param keys the keys whose associated values are to be returned
* @param mappingFunction the function to asynchronously compute the values, optionally using the
* given executor
* given executor
* @return a future containing an unmodifiable mapping of keys to values for the specified keys in
* this cache
* this cache
* @throws NullPointerException if the specified collection is null or contains a null element, or
* if the future returned by the mappingFunction is null
* if the future returned by the mappingFunction is null
* @throws RuntimeException or Error if the mappingFunction does so, in which case the mapping is
* left unestablished
* left unestablished
*/
CompletableFuture<Map<K, V>> getAll(Iterable<? extends K> keys,
BiFunction<? super Set<? extends K>, ? super Executor,
? extends CompletableFuture<? extends Map<? extends K, ? extends V>>> mappingFunction);
CompletableFuture<Map<K, @NonNull V>> getAll(
Iterable<? extends K> keys,
BiFunction<
? super Set<? extends K>,
? super Executor,
? extends CompletableFuture<? extends Map<? extends K, ? extends @NonNull V>>>
mappingFunction);

/**
* Associates {@code value} with {@code key} in this cache. If the cache previously contained a
Expand All @@ -181,7 +178,7 @@ CompletableFuture<Map<K, V>> getAll(Iterable<? extends K> keys,
* @param valueFuture the value to be associated with the specified key
* @throws NullPointerException if the specified key or value is null
*/
void put(K key, CompletableFuture<? extends @Nullable V> valueFuture);
void put(K key, CompletableFuture<? extends V> valueFuture);

/**
* Returns a view of the entries stored in this cache as a thread-safe map. Modifications made to
Expand All @@ -198,8 +195,7 @@ CompletableFuture<Map<K, V>> getAll(Iterable<? extends K> keys,
*
* @return a thread-safe view of this cache supporting all of the optional {@link Map} operations
*/
@NullUnmarked
@NonNull ConcurrentMap<@NonNull K, @NonNull CompletableFuture<V>> asMap();
ConcurrentMap<K, CompletableFuture<V>> asMap();

/**
* Returns a view of the entries stored in this cache as a synchronous {@link Cache}. A mapping is
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.util.function.BiFunction;
import java.util.function.Function;

import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;

Expand All @@ -48,7 +49,7 @@
@NullMarked
@FunctionalInterface
@SuppressWarnings("PMD.SignatureDeclareThrowsException")
public interface AsyncCacheLoader<K, V> {
public interface AsyncCacheLoader<K, V extends @Nullable Object> {

/**
* Asynchronously computes or retrieves the value corresponding to {@code key}.
Expand All @@ -63,7 +64,7 @@ public interface AsyncCacheLoader<K, V> {
* treated like any other {@code Exception} in all respects except that, when it is
* caught, the thread's interrupt status is set
*/
CompletableFuture<? extends @Nullable V> asyncLoad(K key, Executor executor) throws Exception;
CompletableFuture<? extends V> asyncLoad(K key, Executor executor) throws Exception;

/**
* Asynchronously computes or retrieves the values corresponding to {@code keys}. This method is
Expand All @@ -89,7 +90,7 @@ public interface AsyncCacheLoader<K, V> {
* treated like any other {@code Exception} in all respects except that, when it is
* caught, the thread's interrupt status is set
*/
default CompletableFuture<? extends Map<? extends K, ? extends V>> asyncLoadAll(
default CompletableFuture<? extends Map<? extends K, ? extends @NonNull V>> asyncLoadAll(
Set<? extends K> keys, Executor executor) throws Exception {
throw new UnsupportedOperationException();
}
Expand All @@ -115,8 +116,8 @@ public interface AsyncCacheLoader<K, V> {
* treated like any other {@code Exception} in all respects except that, when it is
* caught, the thread's interrupt status is set
*/
default CompletableFuture<? extends @Nullable V> asyncReload(
K key, V oldValue, Executor executor) throws Exception {
default CompletableFuture<? extends V> asyncReload(
K key, @NonNull V oldValue, Executor executor) throws Exception {
return asyncLoad(key, executor);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.NullUnmarked;
import org.jspecify.annotations.Nullable;

/**
* A semi-persistent mapping from keys to values. Values are automatically loaded by the cache
Expand All @@ -34,7 +34,7 @@
* @param <V> the type of mapped values
*/
@NullMarked
public interface AsyncLoadingCache<K, V> extends AsyncCache<K, V> {
public interface AsyncLoadingCache<K, V extends @Nullable Object> extends AsyncCache<K, V> {

/**
* Returns the future associated with the {@code key} in this cache, obtaining that value from
Expand All @@ -52,8 +52,7 @@ public interface AsyncLoadingCache<K, V> extends AsyncCache<K, V> {
* @throws RuntimeException or Error if the {@link AsyncCacheLoader} does when constructing the
* future, in which case the mapping is left unestablished
*/
@NullUnmarked
@NonNull CompletableFuture<V> get(@NonNull K key);
CompletableFuture<V> get(K key);

/**
* Returns the future of a map of the values associated with {@code keys}, creating or retrieving
Expand Down Expand Up @@ -81,7 +80,7 @@ public interface AsyncLoadingCache<K, V> extends AsyncCache<K, V> {
* {@link AsyncCacheLoader#asyncLoadAll} returns {@code null}, or fails when constructing
* the future, in which case the mapping is left unestablished
*/
CompletableFuture<Map<K, V>> getAll(Iterable<? extends K> keys);
CompletableFuture<Map<K, @NonNull V>> getAll(Iterable<? extends K> keys);

/**
* Returns a view of the entries stored in this cache as a synchronous {@link LoadingCache}. A
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@

import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.NullUnmarked;
import org.jspecify.annotations.Nullable;

import com.github.benmanes.caffeine.cache.stats.CacheStats;
Expand All @@ -40,15 +39,15 @@
* @param <V> the type of mapped values
*/
@NullMarked
public interface Cache<K, V> {
public interface Cache<K, V extends @Nullable Object> {

/**
* Returns the value associated with the {@code key} in this cache, or {@code null} if there is no
* cached value for the {@code key}.
*
* @param key the key whose associated value is to be returned
* @return the value to which the specified key is mapped, or {@code null} if this cache does not
* contain a mapping for the key
* contain a mapping for the key
* @throws NullPointerException if the specified key is null
*/
@Nullable
Expand All @@ -72,15 +71,14 @@ public interface Cache<K, V> {
* @param key the key with which the specified value is to be associated
* @param mappingFunction the function to compute a value
* @return the current (existing or computed) value associated with the specified key, or null if
* the computed value is null
* the computed value is null
* @throws NullPointerException if the specified key or mappingFunction is null
* @throws IllegalStateException if the computation detectably attempts a recursive update to this
* cache that would otherwise never complete
* cache that would otherwise never complete
* @throws RuntimeException or Error if the mappingFunction does so, in which case the mapping is
* left unestablished
* left unestablished
*/
@NullUnmarked
V get(@NonNull K key, Function<? super @NonNull K, ? extends @Nullable V> mappingFunction);
V get(K key, Function<? super K, ? extends V> mappingFunction);

/**
* Returns a map of the values associated with the {@code keys} in this cache. The returned map
Expand All @@ -93,7 +91,7 @@ public interface Cache<K, V> {
* @return an unmodifiable mapping of keys to values for the specified keys found in this cache
* @throws NullPointerException if the specified collection is null or contains a null element
*/
Map<K, V> getAllPresent(Iterable<? extends K> keys);
Map<K, @NonNull V> getAllPresent(Iterable<? extends K> keys);

/**
* Returns a map of the values associated with the {@code keys}, creating or retrieving those
Expand All @@ -117,12 +115,14 @@ public interface Cache<K, V> {
* @param mappingFunction the function to compute the values
* @return an unmodifiable mapping of keys to values for the specified keys in this cache
* @throws NullPointerException if the specified collection is null or contains a null element, or
* if the map returned by the mappingFunction is null
* if the map returned by the mappingFunction is null
* @throws RuntimeException or Error if the mappingFunction does so, in which case the mapping is
* left unestablished
* left unestablished
*/
Map<K, V> getAll(Iterable<? extends K> keys,
Function<? super Set<? extends K>, ? extends Map<? extends K, ? extends V>> mappingFunction);
Map<K, @NonNull V> getAll(
Iterable<? extends K> keys,
Function<? super Set<? extends K>, ? extends Map<? extends K, ? extends @NonNull V>>
mappingFunction);

/**
* Associates the {@code value} with the {@code key} in this cache. If the cache previously
Expand All @@ -136,7 +136,7 @@ Map<K, V> getAll(Iterable<? extends K> keys,
* @param value value to be associated with the specified key
* @throws NullPointerException if the specified key or value is null
*/
void put(K key, V value);
void put(K key, @NonNull V value);

/**
* Copies all of the mappings from the specified map to the cache. The effect of this call is
Expand All @@ -146,9 +146,9 @@ Map<K, V> getAll(Iterable<? extends K> keys,
*
* @param map the mappings to be stored in this cache
* @throws NullPointerException if the specified map is null or the specified map contains null
* keys or values
* keys or values
*/
void putAll(Map<? extends K, ? extends V> map);
void putAll(Map<? extends K, ? extends @NonNull V> map);

/**
* Discards any cached value for the {@code key}. The behavior of this operation is undefined for
Expand Down Expand Up @@ -210,7 +210,7 @@ Map<K, V> getAll(Iterable<? extends K> keys,
*
* @return a thread-safe view of this cache supporting all of the optional {@link Map} operations
*/
ConcurrentMap<K, V> asMap();
ConcurrentMap<K, @NonNull V> asMap();

/**
* Performs any pending maintenance operations needed by the cache. Exactly which activities are
Expand All @@ -228,5 +228,5 @@ Map<K, V> getAll(Iterable<? extends K> keys,
*
* @return access to inspect and perform advanced operations based on the cache's characteristics
*/
Policy<K, V> policy();
Policy<K, @NonNull V> policy();
}
Loading

0 comments on commit 461f020

Please sign in to comment.