• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ben-manes / caffeine / #5594

06 Jul 2026 06:08PM UTC coverage: 99.713% (-0.2%) from 99.892%
#5594

push

github

ben-manes
Match ConcurrentHashMap for containsAll on the map views

keySet()/values().containsAll inherited AbstractCollection's loop,
which threw NullPointerException on a null element and iterated even
for a self-check. CHM and Guava both skip a null element (false) and
short-circuit containsAll(self) to true; Caffeine's removeAll and
entrySet were already lenient, leaving these two views the holdout.
Override containsAll across the bounded, unbounded, and async views.
The CaffeinatedGuava facade's override (a workaround for the old NPE)
is now redundant, so drop it and delegate to the view.

4106 of 4132 branches covered (99.37%)

42 of 42 new or added lines in 3 files covered. (100.0%)

17 existing lines in 5 files now uncovered.

8350 of 8374 relevant lines covered (99.71%)

1.0 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.44
/caffeine/src/main/java/com/github/benmanes/caffeine/cache/LocalLoadingCache.java
1
/*
2
 * Copyright 2015 Ben Manes. All Rights Reserved.
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
package com.github.benmanes.caffeine.cache;
17

18
import static com.github.benmanes.caffeine.cache.Caffeine.calculateHashMapCapacity;
19
import static com.github.benmanes.caffeine.cache.Caffeine.hasMethodOverride;
20
import static com.github.benmanes.caffeine.cache.LocalAsyncCache.composeResult;
21
import static java.util.Objects.requireNonNull;
22

23
import java.lang.System.Logger;
24
import java.lang.System.Logger.Level;
25
import java.util.Collections;
26
import java.util.LinkedHashMap;
27
import java.util.Map;
28
import java.util.Set;
29
import java.util.concurrent.CancellationException;
30
import java.util.concurrent.CompletableFuture;
31
import java.util.concurrent.CompletionException;
32
import java.util.concurrent.TimeoutException;
33
import java.util.function.Function;
34

35
import org.jspecify.annotations.Nullable;
36

37
import com.google.errorprone.annotations.Var;
38

39
/**
40
 * This class provides a skeletal implementation of the {@link LoadingCache} interface to minimize
41
 * the effort required to implement a {@link LocalCache}.
42
 *
43
 * @author ben.manes@gmail.com (Ben Manes)
44
 */
45
interface LocalLoadingCache<K, V> extends LocalManualCache<K, V>, LoadingCache<K, V> {
46
  Logger logger = System.getLogger(LocalLoadingCache.class.getName());
1✔
47

48
  /** Returns the {@link AsyncCacheLoader} used by this cache. */
49
  AsyncCacheLoader<? super K, V> cacheLoader();
50

51
  /** Returns the {@link CacheLoader#load} as a mapping function. */
52
  Function<K, @Nullable V> mappingFunction();
53

54
  /** Returns the {@link CacheLoader#loadAll} as a mapping function, if implemented. */
55
  @Nullable Function<Set<? extends K>, Map<K, V>> bulkMappingFunction();
56

57
  @Override
58
  @SuppressWarnings("NullAway")
59
  default V get(K key) {
60
    return cache().computeIfAbsent(key, mappingFunction());
1✔
61
  }
62

63
  @Override
64
  default Map<K, V> getAll(Iterable<? extends K> keys) {
65
    Function<Set<? extends K>, Map<K, V>> mappingFunction = bulkMappingFunction();
1✔
66
    return (mappingFunction == null)
1✔
67
        ? loadSequentially(keys)
1✔
68
        : getAll(keys, mappingFunction);
1✔
69
  }
70

71
  /** Sequentially loads each missing entry. */
72
  default Map<K, V> loadSequentially(Iterable<? extends K> keys) {
73
    var result = new LinkedHashMap<K, @Nullable V>(calculateHashMapCapacity(keys));
1✔
74
    for (K key : keys) {
1✔
75
      result.put(key, null);
1✔
76
    }
1✔
77

78
    @Var int count = 0;
1✔
79
    int size = result.size();
1✔
80
    try {
81
      for (var iter = result.entrySet().iterator(); iter.hasNext();) {
1✔
82
        Map.Entry<K, @Nullable V> entry = iter.next();
1✔
83
        count++;
1✔
84

85
        V value = get(entry.getKey());
1✔
86
        if (value == null) {
1✔
87
          iter.remove();
1✔
88
        } else {
89
          entry.setValue(value);
1✔
90
        }
91
      }
1✔
92
    } catch (Throwable t) {
1✔
93
      cache().statsCounter().recordMisses(size - count);
1✔
94
      throw t;
1✔
95
    }
1✔
96
    @SuppressWarnings("NullableProblems")
97
    Map<K, V> unmodifiable = Collections.unmodifiableMap(result);
1✔
98
    return unmodifiable;
1✔
99
  }
100

101
  @Override
102
  @SuppressWarnings("FutureReturnValueIgnored")
103
  default CompletableFuture<V> refresh(K key) {
104
    requireNonNull(key);
1✔
105

106
    var startTime = new long[1];
1✔
107
    @SuppressWarnings({"unchecked", "Varifier"})
108
    @Nullable V[] oldValue = (V[]) new Object[1];
1✔
109
    @SuppressWarnings({"rawtypes", "unchecked"})
110
    @Nullable CompletableFuture<? extends V>[] reloading = new CompletableFuture[1];
1✔
111
    Object keyReference = cache().referenceKey(key);
1✔
112

113
    var future = cache().refreshes().compute(keyReference, (k, existing) -> {
1✔
114
      if ((existing != null) && !Async.isReady(existing) && !cache().isPendingEviction(key)) {
1✔
115
        return existing;
1✔
116
      }
117

118
      try {
119
        startTime[0] = cache().statsTicker().read();
1✔
120
        oldValue[0] = cache().getIfPresentQuietly(key);
1✔
121
        var refreshFuture = (oldValue[0] == null)
1✔
122
            ? cacheLoader().asyncLoad(key, cache().executor())
1✔
123
            : cacheLoader().asyncReload(key, oldValue[0], cache().executor());
1✔
124
        reloading[0] = requireNonNull(refreshFuture, "Null future");
1✔
125
        return refreshFuture;
1✔
126
      } catch (RuntimeException e) {
1✔
127
        throw e;
1✔
128
      } catch (InterruptedException e) {
1✔
129
        Thread.currentThread().interrupt();
1✔
130
        throw new CompletionException(e);
1✔
131
      } catch (Exception e) {
1✔
132
        throw new CompletionException(e);
1✔
133
      }
134
    });
135

136
    if (reloading[0] != null) {
1✔
137
      reloading[0].whenComplete((newValue, error) -> {
1✔
138
        long loadTime = cache().statsTicker().read() - startTime[0];
1✔
139
        if (error != null) {
1✔
140
          if (!(error instanceof CancellationException) && !(error instanceof TimeoutException)) {
1✔
141
            logger.log(Level.WARNING, "Exception thrown during refresh", error);
1✔
142
          }
143
          cache().refreshes().remove(keyReference, reloading[0]);
1✔
144
          cache().statsCounter().recordLoadFailure(loadTime);
1✔
145
          return;
1✔
146
        }
147

148
        try {
149
          var discard = new boolean[1];
1✔
150
          var hints = new LocalCache.RemapHints();
1✔
151
          @Nullable V value = cache().compute(key, (K k, @Nullable V currentValue) -> {
1✔
152
            // Keep the refresh registered until the write clears it to avoid refreshAfterWrite
153
            // readers from prematurely scheduling another reload
154
            boolean owned = (cache().refreshes().get(keyReference) == reloading[0]);
1✔
155
            if (owned && (currentValue == oldValue[0])) {
1✔
156
              return (currentValue == null) && (newValue == null) ? null : newValue;
1✔
157
            }
158
            discard[0] = (currentValue != newValue);
1✔
159
            hints.preserveTimestamps = true;
1✔
160
            return currentValue;
1✔
161
          }, cache().expiry(), /* recordLoad= */ false,
1✔
162
              /* recordLoadFailure= */ true, hints);
163

164
          if (discard[0] && (newValue != null)) {
1✔
165
            var cause = (value == null) ? RemovalCause.EXPLICIT : RemovalCause.REPLACED;
1✔
166
            cache().notifyRemoval(key, newValue, cause);
1✔
167
          }
168
          if (newValue == null) {
1✔
169
            cache().statsCounter().recordLoadFailure(loadTime);
1✔
170
          } else {
171
            cache().statsCounter().recordLoadSuccess(loadTime);
1✔
172
          }
173
        } catch (Throwable t) {
1✔
174
          logger.log(Level.WARNING, "Exception thrown during refresh", t);
1✔
175
          cache().statsCounter().recordLoadFailure(loadTime);
1✔
176
        }
1✔
177
      });
1✔
178
    }
179

180
    @SuppressWarnings("unchecked")
181
    var castedFuture = (CompletableFuture<V>) future;
1✔
182
    return castedFuture;
1✔
183
  }
184

185
  @Override
186
  default CompletableFuture<Map<K, V>> refreshAll(Iterable<? extends K> keys) {
187
    var result = new LinkedHashMap<K, CompletableFuture<@Nullable V>>(
1✔
188
        calculateHashMapCapacity(keys));
1✔
189
    for (K key : keys) {
1✔
190
      result.computeIfAbsent(key, this::refresh);
1✔
191
    }
1✔
192
    return composeResult(result);
1✔
193
  }
194

195
  /** Returns a mapping function that adapts to {@link CacheLoader#load}. */
196
  static <K, V> Function<K, @Nullable V> newMappingFunction(CacheLoader<? super K, V> cacheLoader) {
197
    return key -> {
1✔
198
      try {
199
        return cacheLoader.load(key);
1✔
200
      } catch (RuntimeException e) {
1✔
201
        throw e;
1✔
UNCOV
202
      } catch (InterruptedException e) {
×
UNCOV
203
        Thread.currentThread().interrupt();
×
UNCOV
204
        throw new CompletionException(e);
×
205
      } catch (Exception e) {
1✔
206
        throw new CompletionException(e);
1✔
207
      }
208
    };
209
  }
210

211
  /** Returns a mapping function that adapts to {@link CacheLoader#loadAll}, if implemented. */
212
  static <K, V> @Nullable Function<Set<? extends K>, Map<K, V>> newBulkMappingFunction(
213
      CacheLoader<? super K, V> cacheLoader) {
214
    if (!hasLoadAll(cacheLoader)) {
1✔
215
      return null;
1✔
216
    }
217
    return keysToLoad -> {
1✔
218
      try {
219
        @SuppressWarnings("unchecked")
220
        var loaded = (Map<K, V>) cacheLoader.loadAll(keysToLoad);
1✔
221
        return loaded;
1✔
222
      } catch (RuntimeException e) {
1✔
223
        throw e;
1✔
UNCOV
224
      } catch (InterruptedException e) {
×
UNCOV
225
        Thread.currentThread().interrupt();
×
UNCOV
226
        throw new CompletionException(e);
×
227
      } catch (Exception e) {
1✔
228
        throw new CompletionException(e);
1✔
229
      }
230
    };
231
  }
232

233
  /** Returns whether the supplied cache loader has bulk load functionality. */
234
  static boolean hasLoadAll(CacheLoader<?, ?> cacheLoader) {
235
    return hasMethodOverride(CacheLoader.class, cacheLoader, "loadAll", Set.class);
1✔
236
  }
237
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc