• 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

99.88
/caffeine/src/main/java/com/github/benmanes/caffeine/cache/LocalAsyncCache.java
1
/*
2
 * Copyright 2018 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.requireState;
20
import static java.util.Locale.US;
21
import static java.util.Objects.requireNonNull;
22

23
import java.io.IOException;
24
import java.io.InvalidObjectException;
25
import java.io.ObjectInputStream;
26
import java.io.Serializable;
27
import java.lang.System.Logger;
28
import java.lang.System.Logger.Level;
29
import java.util.AbstractCollection;
30
import java.util.AbstractSet;
31
import java.util.Collection;
32
import java.util.Collections;
33
import java.util.Iterator;
34
import java.util.LinkedHashMap;
35
import java.util.Map;
36
import java.util.NoSuchElementException;
37
import java.util.Objects;
38
import java.util.Set;
39
import java.util.Spliterator;
40
import java.util.concurrent.CancellationException;
41
import java.util.concurrent.CompletableFuture;
42
import java.util.concurrent.CompletionException;
43
import java.util.concurrent.ConcurrentMap;
44
import java.util.concurrent.Executor;
45
import java.util.concurrent.TimeoutException;
46
import java.util.function.BiConsumer;
47
import java.util.function.BiFunction;
48
import java.util.function.Consumer;
49
import java.util.function.Function;
50
import java.util.function.Predicate;
51

52
import org.jspecify.annotations.Nullable;
53

54
import com.github.benmanes.caffeine.cache.LocalAsyncCache.AsyncBulkCompleter.NullMapCompletionException;
55
import com.github.benmanes.caffeine.cache.stats.CacheStats;
56
import com.google.errorprone.annotations.CanIgnoreReturnValue;
57
import com.google.errorprone.annotations.Var;
58

59
/**
60
 * This class provides a skeletal implementation of the {@link AsyncCache} interface to minimize the
61
 * effort required to implement a {@link LocalCache}.
62
 *
63
 * @author ben.manes@gmail.com (Ben Manes)
64
 */
65
interface LocalAsyncCache<K, V> extends AsyncCache<K, V> {
66
  Logger logger = System.getLogger(LocalAsyncCache.class.getName());
1✔
67

68
  /** Returns the backing {@link LocalCache} data store. */
69
  LocalCache<K, CompletableFuture<V>> cache();
70

71
  /** Returns the policy supported by this implementation and its configuration. */
72
  Policy<K, V> policy();
73

74
  @Override
75
  default @Nullable CompletableFuture<V> getIfPresent(K key) {
76
    return cache().getIfPresent(key, /* recordStats= */ true);
1✔
77
  }
78

79
  @Override
80
  default CompletableFuture<V> get(K key, Function<? super K, ? extends V> mappingFunction) {
81
    requireNonNull(mappingFunction);
1✔
82
    return get(key, (k, executor) -> CompletableFuture.supplyAsync(
1✔
83
        () -> mappingFunction.apply(k), executor));
1✔
84
  }
85

86
  @Override
87
  default CompletableFuture<V> get(K key, BiFunction<? super K, ? super Executor,
88
      ? extends CompletableFuture<? extends V>> mappingFunction) {
89
    return get(key, mappingFunction, /* recordStats= */ true);
1✔
90
  }
91

92
  default CompletableFuture<V> get(K key, BiFunction<? super K, ? super Executor,
93
      ? extends CompletableFuture<? extends V>> mappingFunction, boolean recordStats) {
94
    CompletableFuture<V> present = cache().getIfPresent(key, /* recordStats= */ false);
1✔
95
    if (present != null) {
1✔
96
      if (recordStats) {
1✔
97
        cache().statsCounter().recordHits(1);
1✔
98
      }
99
      return present;
1✔
100
    }
101

102
    long startTime = cache().statsTicker().read();
1✔
103
    @SuppressWarnings({"rawtypes", "unchecked"})
104
    @Nullable CompletableFuture<? extends V>[] result = new CompletableFuture[1];
1✔
105
    CompletableFuture<V> future = cache().computeIfAbsent(key, k -> {
1✔
106
      @SuppressWarnings("unchecked")
107
      var castedResult = (CompletableFuture<V>) mappingFunction.apply(key, cache().executor());
1✔
108
      result[0] = castedResult;
1✔
109
      return requireNonNull(castedResult);
1✔
110
    }, recordStats, /* recordLoad= */ false);
111
    if (result[0] != null) {
1✔
112
      handleCompletion(key, result[0], startTime, /* recordMiss= */ false);
1✔
113
    }
114
    return requireNonNull(future);
1✔
115
  }
116

117
  @Override
118
  default CompletableFuture<Map<K, V>> getAll(Iterable<? extends K> keys,
119
      Function<? super Set<? extends K>, ? extends Map<? extends K, ? extends V>> mappingFunction) {
120
    requireNonNull(mappingFunction);
1✔
121
    return getAll(keys, (keysToLoad, executor) ->
1✔
122
        CompletableFuture.supplyAsync(() -> mappingFunction.apply(keysToLoad), executor));
1✔
123
  }
124

125
  @Override
126
  @SuppressWarnings("FutureReturnValueIgnored")
127
  default CompletableFuture<Map<K, V>> getAll(Iterable<? extends K> keys,
128
      BiFunction<? super Set<? extends K>, ? super Executor,
129
          ? extends CompletableFuture<? extends Map<? extends K, ? extends V>>> mappingFunction) {
130
    requireNonNull(mappingFunction);
1✔
131
    requireNonNull(keys);
1✔
132

133
    int initialCapacity = calculateHashMapCapacity(keys);
1✔
134
    var futures = new LinkedHashMap<K, CompletableFuture<@Nullable V>>(initialCapacity);
1✔
135
    var proxies = new LinkedHashMap<K, CompletableFuture<@Nullable V>>(initialCapacity);
1✔
136
    for (K key : keys) {
1✔
137
      if (futures.containsKey(key)) {
1✔
138
        continue;
1✔
139
      }
140
      @Var CompletableFuture<V> future = cache().getIfPresent(key, /* recordStats= */ false);
1✔
141
      if (future == null) {
1✔
142
        var proxy = new CompletableFuture<V>();
1✔
143
        future = cache().putIfAbsent(key, proxy);
1✔
144
        if (future == null) {
1✔
145
          future = proxy;
1✔
146
          proxies.put(key, proxy);
1✔
147
        }
148
      }
149
      futures.put(key, future);
1✔
150
    }
1✔
151
    cache().statsCounter().recordMisses(proxies.size());
1✔
152
    cache().statsCounter().recordHits(futures.size() - proxies.size());
1✔
153
    if (proxies.isEmpty()) {
1✔
154
      return composeResult(futures);
1✔
155
    }
156

157
    var completer = new AsyncBulkCompleter<>(cache(), proxies);
1✔
158
    try {
159
      var loader = mappingFunction.apply(
1✔
160
          Collections.unmodifiableSet(proxies.keySet()), cache().executor());
1✔
161
      return loader.handle(completer).thenCompose(ignored -> composeResult(futures));
1✔
162
    } catch (Throwable t) {
1✔
163
      throw completer.error(t);
1✔
164
    }
165
  }
166

167
  /**
168
   * Returns a future that waits for all of the dependent futures to complete and returns the
169
   * combined mapping if successful. If any future fails then it is automatically removed from
170
   * the cache if still present.
171
   */
172
  static <K, V> CompletableFuture<Map<K, V>> composeResult(
173
      Map<K, CompletableFuture<@Nullable V>> futures) {
174
    if (futures.isEmpty()) {
1✔
175
      @SuppressWarnings({"ImmutableMapOf", "RedundantUnmodifiable"})
176
      Map<K, V> emptyMap = Collections.unmodifiableMap(Collections.emptyMap());
1✔
177
      return CompletableFuture.completedFuture(emptyMap);
1✔
178
    }
179
    @SuppressWarnings("rawtypes")
180
    CompletableFuture<?>[] array = futures.values().toArray(new CompletableFuture[0]);
1✔
181
    return CompletableFuture.allOf(array).thenApply(ignored -> {
1✔
182
      var result = new LinkedHashMap<K, V>(calculateHashMapCapacity(futures.size()));
1✔
183
      futures.forEach((key, future) -> {
1✔
184
        @Nullable V value = future.getNow(null);
1✔
185
        if (value != null) {
1✔
186
          result.put(key, value);
1✔
187
        }
188
      });
1✔
189
      return Collections.unmodifiableMap(result);
1✔
190
    });
191
  }
192

193
  @Override
194
  @SuppressWarnings("FutureReturnValueIgnored")
195
  default void put(K key, CompletableFuture<? extends @Nullable V> valueFuture) {
196
    long startTime = cache().statsTicker().read();
1✔
197

198
    @SuppressWarnings("unchecked")
199
    var castedFuture = (CompletableFuture<V>) valueFuture;
1✔
200
    CompletableFuture<V> prior = cache().put(key, castedFuture);
1✔
201
    if (prior != castedFuture) {
1✔
202
      handleCompletion(key, valueFuture, startTime, /* recordMiss= */ false);
1✔
203
    }
204
  }
1✔
205

206
  @SuppressWarnings({"FutureReturnValueIgnored", "ResultOfMethodCallIgnored"})
207
  default void handleCompletion(K key, CompletableFuture<? extends V> valueFuture,
208
      long startTime, boolean recordMiss) {
209
    valueFuture.whenComplete((value, error) -> {
1✔
210
      long loadTime = cache().statsTicker().read() - startTime;
1✔
211
      if (value == null) {
1✔
212
        if ((error != null) && !(error instanceof CancellationException)
1✔
213
            && !(error instanceof TimeoutException)) {
214
          logger.log(Level.WARNING, "Exception thrown during asynchronous load", error);
1✔
215
        }
216
        cache().statsCounter().recordLoadFailure(loadTime);
1✔
217
        cache().remove(key, valueFuture);
1✔
218
      } else if (!Async.isReady(valueFuture)) {
1✔
219
        logger.log(Level.ERROR, String.format(US, "An invalid state was detected, occurring when "
1✔
220
            + "the future's dependent action has completed successfully with a value, but the "
221
            + "future remains either in-flight or in a failed completion state. This may occur "
222
            + "when using a custom future that does not abide by the CompletableFuture contract "
223
            + "(key: %s, key type: %s, value type: %s, future: %s, cache type: %s).",
224
            key, key.getClass().getName(), value.getClass().getName(), valueFuture,
1✔
225
            cache().getClass().getSimpleName()), new IllegalStateException());
1✔
226
        cache().statsCounter().recordLoadFailure(loadTime);
1✔
227
        cache().remove(key, valueFuture);
1✔
228
      } else {
229
        @SuppressWarnings("unchecked")
230
        var castedFuture = (CompletableFuture<V>) valueFuture;
1✔
231

232
        try {
233
          // update the weight and expiration timestamps
234
          cache().replace(key, castedFuture, castedFuture, /* shouldDiscardRefresh= */ false);
1✔
235
          cache().statsCounter().recordLoadSuccess(loadTime);
1✔
236
        } catch (Throwable t) {
1✔
237
          logger.log(Level.WARNING, "Exception thrown during asynchronous load", t);
1✔
238
          cache().statsCounter().recordLoadFailure(loadTime);
1✔
239
          cache().remove(key, valueFuture);
1✔
240
        }
1✔
241
      }
242
      if (recordMiss) {
1!
UNCOV
243
        cache().statsCounter().recordMisses(1);
×
244
      }
245
    });
1✔
246
  }
1✔
247

248
  /** A function executed asynchronously after a bulk load completes. */
249
  final class AsyncBulkCompleter<K, V> implements BiFunction<
250
      Map<? extends K, ? extends V>, Throwable, Map<? extends K, ? extends V>> {
251
    @SuppressWarnings("ImmutableMemberCollection")
252
    private final Map<K, CompletableFuture<@Nullable V>> proxies;
253
    private final LocalCache<K, CompletableFuture<V>> cache;
254
    private final long startTime;
255

256
    AsyncBulkCompleter(LocalCache<K, CompletableFuture<V>> cache,
257
        Map<K, CompletableFuture<@Nullable V>> proxies) {
1✔
258
      this.startTime = cache.statsTicker().read();
1✔
259
      this.proxies = proxies;
1✔
260
      this.cache = cache;
1✔
261
    }
1✔
262

263
    @Override
264
    @CanIgnoreReturnValue
265
    public @Nullable Map<? extends K, ? extends V> apply(
266
        @Nullable Map<? extends K, ? extends V> result, @Nullable Throwable error) {
267
      long loadTime = cache.statsTicker().read() - startTime;
1✔
268
      var failure = handleResponse(result, error);
1✔
269

270
      if (failure == null) {
1✔
271
        if (requireNonNull(result).isEmpty()) {
1✔
272
          cache.statsCounter().recordLoadFailure(loadTime);
1✔
273
        } else {
274
          cache.statsCounter().recordLoadSuccess(loadTime);
1✔
275
        }
276
        return result;
1✔
277
      }
278

279
      cache.statsCounter().recordLoadFailure(loadTime);
1✔
280
      if (failure instanceof RuntimeException) {
1✔
281
        throw (RuntimeException) failure;
1✔
282
      } else if (failure instanceof Error) {
1✔
283
        throw (Error) failure;
1✔
284
      }
285
      throw new CompletionException(failure);
1✔
286
    }
287

288
    public CompletionException error(Throwable error) {
289
      long loadTime = cache.statsTicker().read() - startTime;
1✔
290
      var failure = handleResponse(/* result= */ null, error);
1✔
291
      cache.statsCounter().recordLoadFailure(loadTime);
1✔
292
      if (failure instanceof RuntimeException) {
1✔
293
        throw (RuntimeException) failure;
1✔
294
      } else if (failure instanceof Error) {
1✔
295
        throw (Error) failure;
1✔
296
      }
297
      return new CompletionException(failure);
1✔
298
    }
299

300
    private @Nullable Throwable handleResponse(
301
        @Nullable Map<? extends K, ? extends V> result, @Nullable Throwable error) {
302
      if (result == null) {
1✔
303
        var failure = (error == null) ? new NullMapCompletionException() : error;
1✔
304
        for (var entry : proxies.entrySet()) {
1✔
305
          cache.remove(entry.getKey(), entry.getValue());
1✔
306
          entry.getValue().obtrudeException(failure);
1✔
307
        }
1✔
308
        if (!(failure instanceof CancellationException) && !(failure instanceof TimeoutException)) {
1✔
309
          logger.log(Level.WARNING, "Exception thrown during asynchronous load", failure);
1✔
310
        }
311
        return failure;
1✔
312
      }
313
      var failure = fillProxies(result);
1✔
314
      return addNewEntries(result, failure);
1✔
315
    }
316

317
    /** Populates the proxies with the computed result. */
318
    private @Nullable Throwable fillProxies(Map<? extends K, ? extends V> result) {
319
      @Var Throwable error = null;
1✔
320
      for (var entry : proxies.entrySet()) {
1✔
321
        var key = entry.getKey();
1✔
322
        var value = result.get(key);
1✔
323
        var future = entry.getValue();
1✔
324
        future.obtrudeValue(value);
1✔
325

326
        if (value == null) {
1✔
327
          cache.remove(key, future);
1✔
328
        } else {
329
          try {
330
            // update the weight and expiration timestamps
331
            cache.replace(key, future, future);
1✔
332
          } catch (Throwable t) {
1✔
333
            logger.log(Level.WARNING, "Exception thrown during asynchronous load", t);
1✔
334
            cache.remove(key, future);
1✔
335
            if (error == null) {
1✔
336
              error = t;
1✔
337
            } else if (error != t) {
1✔
338
              error.addSuppressed(t);
1✔
339
            }
340
          }
1✔
341
        }
342
      }
1✔
343
      return error;
1✔
344
    }
345

346
    /** Adds to the cache any extra entries computed that were not requested. */
347
    private @Nullable Throwable addNewEntries(
348
        Map<? extends K, ? extends @Nullable V> result, @Nullable Throwable failure) {
349
      @Var Throwable error = failure;
1✔
350
      for (Map.Entry<? extends K, ? extends @Nullable V> entry : result.entrySet()) {
1✔
351
        var key = entry.getKey();
1✔
352
        if (!proxies.containsKey(key)) {
1✔
353
          @Nullable V value = entry.getValue();
1✔
354
          if (value == null) {
1✔
355
            continue;
1✔
356
          }
357
          try {
358
            cache.put(key, CompletableFuture.completedFuture(value));
1✔
359
          } catch (Throwable t) {
1✔
360
            logger.log(Level.WARNING, "Exception thrown during asynchronous load", t);
1✔
361
            if (error == null) {
1✔
362
              error = t;
1✔
363
            } else if (error != t) {
1✔
364
              error.addSuppressed(t);
1✔
365
            }
366
          }
1✔
367
        }
368
      }
1✔
369
      return error;
1✔
370
    }
371

372
    static final class NullMapCompletionException extends CompletionException {
1✔
373
      private static final long serialVersionUID = 1L;
374
    }
375
  }
376

377
  /* --------------- Asynchronous view --------------- */
378
  final class AsyncAsMapView<K, V> implements ConcurrentMap<K, CompletableFuture<V>> {
379
    final LocalAsyncCache<K, V> asyncCache;
380

381
    AsyncAsMapView(LocalAsyncCache<K, V> asyncCache) {
1✔
382
      this.asyncCache = requireNonNull(asyncCache);
1✔
383
    }
1✔
384
    @Override public boolean isEmpty() {
385
      return asyncCache.cache().isEmpty();
1✔
386
    }
387
    @Override public int size() {
388
      return asyncCache.cache().size();
1✔
389
    }
390
    @Override public void clear() {
391
      asyncCache.cache().clear();
1✔
392
    }
1✔
393
    @Override public boolean containsKey(Object key) {
394
      return asyncCache.cache().containsKey(key);
1✔
395
    }
396
    @SuppressWarnings("CollectionUndefinedEquality")
397
    @Override public boolean containsValue(Object value) {
398
      return asyncCache.cache().containsValue(value);
1✔
399
    }
400
    @Override public @Nullable CompletableFuture<V> get(Object key) {
401
      return asyncCache.cache().get(key);
1✔
402
    }
403
    @Override public @Nullable CompletableFuture<V> putIfAbsent(K key, CompletableFuture<V> value) {
404
      CompletableFuture<V> prior = asyncCache.cache().putIfAbsent(key, value);
1✔
405
      long startTime = asyncCache.cache().statsTicker().read();
1✔
406
      if (prior == null) {
1✔
407
        asyncCache.handleCompletion(key, value, startTime, /* recordMiss= */ false);
1✔
408
      }
409
      return prior;
1✔
410
    }
411
    @Override public @Nullable CompletableFuture<V> put(K key, CompletableFuture<V> value) {
412
      CompletableFuture<V> prior = asyncCache.cache().put(key, value);
1✔
413
      long startTime = asyncCache.cache().statsTicker().read();
1✔
414
      if (prior != value) {
1✔
415
        asyncCache.handleCompletion(key, value, startTime, /* recordMiss= */ false);
1✔
416
      }
417
      return prior;
1✔
418
    }
419
    @SuppressWarnings({"FutureReturnValueIgnored", "ResultOfMethodCallIgnored"})
420
    @Override public void putAll(Map<? extends K, ? extends CompletableFuture<V>> map) {
421
      map.forEach(this::put);
1✔
422
    }
1✔
423
    @Override public @Nullable CompletableFuture<V> replace(K key, CompletableFuture<V> value) {
424
      CompletableFuture<V> prior = asyncCache.cache().replace(key, value);
1✔
425
      long startTime = asyncCache.cache().statsTicker().read();
1✔
426
      if ((prior != null) && (prior != value)) {
1✔
427
        asyncCache.handleCompletion(key, value, startTime, /* recordMiss= */ false);
1✔
428
      }
429
      return prior;
1✔
430
    }
431
    @Override
432
    public boolean replace(K key, CompletableFuture<V> oldValue, CompletableFuture<V> newValue) {
433
      boolean replaced = asyncCache.cache().replace(key, oldValue, newValue);
1✔
434
      long startTime = asyncCache.cache().statsTicker().read();
1✔
435
      if (replaced && (newValue != oldValue)) {
1✔
436
        asyncCache.handleCompletion(key, newValue, startTime, /* recordMiss= */ false);
1✔
437
      }
438
      return replaced;
1✔
439
    }
440
    @Override public CompletableFuture<V> remove(Object key) {
441
      return asyncCache.cache().remove(key);
1✔
442
    }
443
    @Override public boolean remove(Object key, Object value) {
444
      return asyncCache.cache().remove(key, value);
1✔
445
    }
446
    @Override public @Nullable CompletableFuture<V> computeIfAbsent(K key,
447
        Function<? super K, ? extends @Nullable CompletableFuture<V>> mappingFunction) {
448
      requireNonNull(mappingFunction);
1✔
449
      @SuppressWarnings({"rawtypes", "unchecked"})
450
      @Nullable CompletableFuture<V>[] result = new CompletableFuture[1];
1✔
451
      long startTime = asyncCache.cache().statsTicker().read();
1✔
452
      Function<K, @Nullable CompletableFuture<V>> function = k -> {
1✔
453
        result[0] = mappingFunction.apply(k);
1✔
454
        return result[0];
1✔
455
      };
456
      @Nullable CompletableFuture<V> future = asyncCache.cache().computeIfAbsent(
1✔
457
          key, function, /* recordStats= */ true, /* recordLoad= */ false);
458
      if (result[0] != null) {
1✔
459
        asyncCache.handleCompletion(key, result[0], startTime, /* recordMiss= */ false);
1✔
460
      }
461
      return future;
1✔
462
    }
463
    @SuppressWarnings("ResultOfMethodCallIgnored")
464
    @Override public @Nullable CompletableFuture<V> computeIfPresent(K key, BiFunction<? super K,
465
        ? super CompletableFuture<V>, ? extends CompletableFuture<V>> remappingFunction) {
466
      requireNonNull(remappingFunction);
1✔
467

468
      @SuppressWarnings({"rawtypes", "unchecked"})
469
      @Nullable CompletableFuture<V>[] result = new CompletableFuture[1];
1✔
470
      @SuppressWarnings({"rawtypes", "unchecked"})
471
      @Nullable CompletableFuture<V>[] prior = new CompletableFuture[1];
1✔
472
      long startTime = asyncCache.cache().statsTicker().read();
1✔
473
      asyncCache.cache().compute(key, (K k, @Nullable CompletableFuture<V> oldValue) -> {
1✔
474
        result[0] = (oldValue == null) ? null : remappingFunction.apply(k, oldValue);
1✔
475
        prior[0] = oldValue;
1✔
476
        return result[0];
1✔
477
      }, asyncCache.cache().expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false);
1✔
478

479
      if ((result[0] != null) && (result[0] != prior[0])) {
1✔
480
        asyncCache.handleCompletion(key, result[0], startTime, /* recordMiss= */ false);
1✔
481
      }
482
      return result[0];
1✔
483
    }
484
    @SuppressWarnings("ResultOfMethodCallIgnored")
485
    @Override public @Nullable CompletableFuture<V> compute(K key, BiFunction<? super K,
486
        ? super CompletableFuture<V>, ? extends CompletableFuture<V>> remappingFunction) {
487
      requireNonNull(remappingFunction);
1✔
488

489
      @SuppressWarnings({"rawtypes", "unchecked"})
490
      @Nullable CompletableFuture<V>[] result = new CompletableFuture[1];
1✔
491
      @SuppressWarnings({"rawtypes", "unchecked"})
492
      @Nullable CompletableFuture<V>[] prior = new CompletableFuture[1];
1✔
493
      long startTime = asyncCache.cache().statsTicker().read();
1✔
494
      asyncCache.cache().compute(key, (k, oldValue) -> {
1✔
495
        result[0] = remappingFunction.apply(k, oldValue);
1✔
496
        prior[0] = oldValue;
1✔
497
        return result[0];
1✔
498
      }, asyncCache.cache().expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false);
1✔
499

500
      if ((result[0] != null) && (result[0] != prior[0])) {
1✔
501
        asyncCache.handleCompletion(key, result[0], startTime, /* recordMiss= */ false);
1✔
502
      }
503
      return result[0];
1✔
504
    }
505
    @SuppressWarnings("ResultOfMethodCallIgnored")
506
    @Override public @Nullable CompletableFuture<V> merge(K key, CompletableFuture<V> value,
507
        BiFunction<? super CompletableFuture<V>, ? super CompletableFuture<V>,
508
            ? extends CompletableFuture<V>> remappingFunction) {
509
      requireNonNull(value);
1✔
510
      requireNonNull(remappingFunction);
1✔
511

512
      @SuppressWarnings({"rawtypes", "unchecked"})
513
      @Nullable  CompletableFuture<V>[] result = new CompletableFuture[1];
1✔
514
      @SuppressWarnings({"rawtypes", "unchecked"})
515
      @Nullable  CompletableFuture<V>[] prior = new CompletableFuture[1];
1✔
516
      long startTime = asyncCache.cache().statsTicker().read();
1✔
517
      asyncCache.cache().compute(key, (K k, @Nullable CompletableFuture<V> oldValue) -> {
1✔
518
        result[0] = (oldValue == null) ? value : remappingFunction.apply(oldValue, value);
1✔
519
        prior[0] = oldValue;
1✔
520
        return result[0];
1✔
521
      }, asyncCache.cache().expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false);
1✔
522

523
      if ((result[0] != null) && (result[0] != prior[0])) {
1✔
524
        asyncCache.handleCompletion(key, result[0], startTime, /* recordMiss= */ false);
1✔
525
      }
526
      return result[0];
1✔
527
    }
528
    @Override public void forEach(BiConsumer<? super K, ? super CompletableFuture<V>> action) {
529
      asyncCache.cache().forEach(action);
1✔
530
    }
1✔
531
    @Override public Set<K> keySet() {
532
      return asyncCache.cache().keySet();
1✔
533
    }
534
    @Override public Collection<CompletableFuture<V>> values() {
535
      return asyncCache.cache().values();
1✔
536
    }
537
    @Override public Set<Entry<K, CompletableFuture<V>>> entrySet() {
538
      return new AsyncEntrySet();
1✔
539
    }
540
    @SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
541
    @Override public boolean equals(@Nullable Object o) {
542
      return (o == this) || asyncCache.cache().equals(o);
1✔
543
    }
544
    @Override public int hashCode() {
545
      return asyncCache.cache().hashCode();
1✔
546
    }
547
    @Override public String toString() {
548
      return asyncCache.cache().toString();
1✔
549
    }
550

551
    private final class AsyncEntrySet extends AbstractSet<Entry<K, CompletableFuture<V>>> {
1✔
552
      @Override public int size() {
553
        return AsyncAsMapView.this.size();
1✔
554
      }
555
      @Override public boolean isEmpty() {
556
        return AsyncAsMapView.this.isEmpty();
1✔
557
      }
558
      @Override public void clear() {
559
        AsyncAsMapView.this.clear();
1✔
560
      }
1✔
561
      @Override public boolean contains(Object o) {
562
        return asyncCache.cache().entrySet().contains(o);
1✔
563
      }
564
      @Override public boolean remove(Object o) {
565
        return asyncCache.cache().entrySet().remove(o);
1✔
566
      }
567
      @Override public boolean removeAll(Collection<?> collection) {
568
        return asyncCache.cache().entrySet().removeAll(collection);
1✔
569
      }
570
      @Override public boolean retainAll(Collection<?> collection) {
571
        return asyncCache.cache().entrySet().retainAll(collection);
1✔
572
      }
573
      @Override public boolean removeIf(Predicate<? super Entry<K, CompletableFuture<V>>> filter) {
574
        return asyncCache.cache().entrySet().removeIf(filter);
1✔
575
      }
576
      @Override public Iterator<Entry<K, CompletableFuture<V>>> iterator() {
577
        return new AsyncEntryIterator();
1✔
578
      }
579
      @Override public Spliterator<Entry<K, CompletableFuture<V>>> spliterator() {
580
        return new AsyncEntrySpliterator();
1✔
581
      }
582
    }
583

584
    private final class AsyncEntryIterator implements Iterator<Entry<K, CompletableFuture<V>>> {
1✔
585
      final Iterator<Entry<K, CompletableFuture<V>>> iterator =
1✔
586
          asyncCache.cache().entrySet().iterator();
1✔
587

588
      @Override public boolean hasNext() {
589
        return iterator.hasNext();
1✔
590
      }
591
      @Override public Entry<K, CompletableFuture<V>> next() {
592
        var entry = iterator.next();
1✔
593
        return new WriteThroughEntry<>(AsyncAsMapView.this, entry.getKey(), entry.getValue());
1✔
594
      }
595
      @Override public void remove() {
596
        iterator.remove();
1✔
597
      }
1✔
598
    }
599

600
    private final class AsyncEntrySpliterator
601
        implements Spliterator<Entry<K, CompletableFuture<V>>> {
602
      final Spliterator<Entry<K, CompletableFuture<V>>> spliterator;
603

604
      AsyncEntrySpliterator() {
605
        this(asyncCache.cache().entrySet().spliterator());
1✔
606
      }
1✔
607
      AsyncEntrySpliterator(Spliterator<Entry<K, CompletableFuture<V>>> spliterator) {
1✔
608
        this.spliterator = requireNonNull(spliterator);
1✔
609
      }
1✔
610
      @Override public boolean tryAdvance(Consumer<? super Entry<K, CompletableFuture<V>>> action) {
611
        requireNonNull(action);
1✔
612
        return spliterator.tryAdvance(entry -> action.accept(
1✔
613
            new WriteThroughEntry<>(AsyncAsMapView.this, entry.getKey(), entry.getValue())));
1✔
614
      }
615
      @Override public void forEachRemaining(
616
          Consumer<? super Entry<K, CompletableFuture<V>>> action) {
617
        requireNonNull(action);
1✔
618
        spliterator.forEachRemaining(entry -> action.accept(
1✔
619
            new WriteThroughEntry<>(AsyncAsMapView.this, entry.getKey(), entry.getValue())));
1✔
620
      }
1✔
621
      @Override public @Nullable Spliterator<Entry<K, CompletableFuture<V>>> trySplit() {
622
        var split = spliterator.trySplit();
1✔
623
        return (split == null) ? null : new AsyncEntrySpliterator(split);
1✔
624
      }
625
      @Override public long estimateSize() {
626
        return spliterator.estimateSize();
1✔
627
      }
628
      @Override public int characteristics() {
629
        return spliterator.characteristics();
1✔
630
      }
631
    }
632
  }
633

634
  /* --------------- Synchronous view --------------- */
635
  final class CacheView<K, V> extends AbstractCacheView<K, V> {
636
    private static final long serialVersionUID = 1L;
637

638
    @SuppressWarnings("serial")
639
    final LocalAsyncCache<K, V> asyncCache;
640

641
    CacheView(LocalAsyncCache<K, V> asyncCache) {
1✔
642
      this.asyncCache = requireNonNull(asyncCache);
1✔
643
    }
1✔
644
    @Override LocalAsyncCache<K, V> asyncCache() {
645
      return asyncCache;
1✔
646
    }
647
  }
648

649
  abstract class AbstractCacheView<K, V> implements Cache<K, V>, Serializable {
1✔
650
    private static final long serialVersionUID = 1L;
651

652
    transient @Nullable ConcurrentMap<K, V> asMapView;
653

654
    abstract LocalAsyncCache<K, V> asyncCache();
655

656
    @Override
657
    public @Nullable V getIfPresent(K key) {
658
      var future = asyncCache().cache().getIfPresent(key, /* recordStats= */ false);
1✔
659
      var value = Async.getIfReady(future);
1✔
660
      if (value == null) {
1✔
661
        asyncCache().cache().statsCounter().recordMisses(1);
1✔
662
      } else {
663
        asyncCache().cache().statsCounter().recordHits(1);
1✔
664
      }
665
      return value;
1✔
666
    }
667

668
    @Override
669
    public Map<K, V> getAllPresent(Iterable<? extends K> keys) {
670
      var result = new LinkedHashMap<K, @Nullable V>(calculateHashMapCapacity(keys));
1✔
671
      for (K key : keys) {
1✔
672
        result.put(key, null);
1✔
673
      }
1✔
674

675
      int uniqueKeys = result.size();
1✔
676
      for (var iter = result.entrySet().iterator(); iter.hasNext();) {
1✔
677
        Map.Entry<K, @Nullable V> entry = iter.next();
1✔
678

679
        CompletableFuture<V> future = asyncCache().cache().get(entry.getKey());
1✔
680
        V value = Async.getIfReady(future);
1✔
681
        if (value == null) {
1✔
682
          iter.remove();
1✔
683
        } else {
684
          entry.setValue(value);
1✔
685
        }
686
      }
1✔
687
      asyncCache().cache().statsCounter().recordHits(result.size());
1✔
688
      asyncCache().cache().statsCounter().recordMisses(uniqueKeys - result.size());
1✔
689

690
      @SuppressWarnings("NullableProblems")
691
      Map<K, V> unmodifiable = Collections.unmodifiableMap(result);
1✔
692
      return unmodifiable;
1✔
693
    }
694

695
    @Override
696
    public V get(K key, Function<? super K, ? extends V> mappingFunction) {
697
      return resolve(asyncCache().get(key, mappingFunction));
1✔
698
    }
699

700
    @Override
701
    public Map<K, V> getAll(
702
        Iterable<? extends K> keys,
703
        Function<
704
            ? super Set<? extends K>,
705
            ? extends Map<? extends K, ? extends V>> mappingFunction) {
706
      return resolve(asyncCache().getAll(keys, mappingFunction));
1✔
707
    }
708

709
    @SuppressWarnings({"PMD.AvoidThrowingNullPointerException",
710
      "PMD.PreserveStackTrace", "UnusedException"})
711
    protected static <T> T resolve(CompletableFuture<T> future) {
712
      try {
713
        return future.join();
1✔
714
      } catch (NullMapCompletionException e) {
1✔
715
        throw new NullPointerException("null map");
1✔
716
      } catch (CompletionException e) {
1✔
717
        if (e.getCause() instanceof RuntimeException) {
1✔
718
          throw (RuntimeException) e.getCause();
1✔
719
        } else if (e.getCause() instanceof Error) {
1✔
720
          throw (Error) e.getCause();
1✔
721
        }
722
        throw e;
1✔
723
      }
724
    }
725

726
    @Override
727
    public void put(K key, V value) {
728
      requireNonNull(value);
1✔
729
      asyncCache().cache().put(key, CompletableFuture.completedFuture(value));
1✔
730
    }
1✔
731

732
    @Override
733
    public void putAll(Map<? extends K, ? extends V> map) {
734
      map.forEach(this::put);
1✔
735
    }
1✔
736

737
    @Override
738
    public void invalidate(K key) {
739
      asyncCache().cache().remove(key);
1✔
740
    }
1✔
741

742
    @Override
743
    public void invalidateAll(Iterable<? extends K> keys) {
744
      asyncCache().cache().invalidateAll(keys);
1✔
745
    }
1✔
746

747
    @Override
748
    public void invalidateAll() {
749
      asyncCache().cache().clear();
1✔
750
    }
1✔
751

752
    @Override
753
    public long estimatedSize() {
754
      return asyncCache().cache().estimatedSize();
1✔
755
    }
756

757
    @Override
758
    public CacheStats stats() {
759
      return asyncCache().cache().statsCounter().snapshot();
1✔
760
    }
761

762
    @Override
763
    public void cleanUp() {
764
      asyncCache().cache().cleanUp();
1✔
765
    }
1✔
766

767
    @Override
768
    public Policy<K, V> policy() {
769
      return asyncCache().policy();
1✔
770
    }
771

772
    @Override
773
    public ConcurrentMap<K, V> asMap() {
774
      return (asMapView == null) ? (asMapView = new AsMapView<>(asyncCache().cache())) : asMapView;
1✔
775
    }
776

777
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
778
      throw new InvalidObjectException("Proxy required");
1✔
779
    }
780

781
    @SuppressWarnings("unused")
782
    private void readObjectNoData() throws InvalidObjectException {
783
      throw new InvalidObjectException("Proxy required");
1✔
784
    }
785

786
    Object writeReplace() {
787
      return new SyncViewProxy<>(asyncCache());
1✔
788
    }
789
  }
790

791
  final class AsMapView<K, V> implements ConcurrentMap<K, V> {
792
    final LocalCache<K, CompletableFuture<V>> delegate;
793

794
    @Nullable Set<K> keys;
795
    @Nullable Collection<V> values;
796
    @Nullable Set<Entry<K, V>> entries;
797

798
    AsMapView(LocalCache<K, CompletableFuture<V>> delegate) {
1✔
799
      this.delegate = delegate;
1✔
800
    }
1✔
801

802
    @Override
803
    public boolean isEmpty() {
804
      return delegate.isEmpty();
1✔
805
    }
806

807
    @Override
808
    public int size() {
809
      return delegate.size();
1✔
810
    }
811

812
    @Override
813
    public void clear() {
814
      delegate.clear();
1✔
815
    }
1✔
816

817
    @Override
818
    public boolean containsKey(Object key) {
819
      return Async.isReady(delegate.getIfPresentQuietly(key));
1✔
820
    }
821

822
    @Override
823
    public boolean containsValue(Object value) {
824
      requireNonNull(value);
1✔
825

826
      for (CompletableFuture<V> valueFuture : delegate.values()) {
1✔
827
        if (value.equals(Async.getIfReady(valueFuture))) {
1✔
828
          return true;
1✔
829
        }
830
      }
1✔
831
      return false;
1✔
832
    }
833

834
    @Override
835
    public @Nullable V get(Object key) {
836
      return Async.getIfReady(delegate.get(key));
1✔
837
    }
838

839
    @Override
840
    @SuppressWarnings("ResultOfMethodCallIgnored")
841
    public @Nullable V putIfAbsent(K key, V value) {
842
      requireNonNull(value);
1✔
843

844
      // Keep in sync with BoundedVarExpiration.putIfAbsentAsync(key, value, duration, unit)
845
      @Var CompletableFuture<V> priorFuture = null;
1✔
846
      for (;;) {
847
        priorFuture = (priorFuture == null)
1✔
848
            ? delegate.get(key)
1✔
849
            : delegate.getIfPresentQuietly(key);
1✔
850
        if (priorFuture != null) {
1✔
851
          if (!priorFuture.isDone()) {
1✔
852
            Async.getWhenSuccessful(priorFuture);
1✔
853
            continue;
1✔
854
          }
855

856
          V prior = Async.getWhenSuccessful(priorFuture);
1✔
857
          if (prior != null) {
1✔
858
            return prior;
1✔
859
          }
860
        }
861

862
        boolean[] added = { false };
1✔
863
        var hints = new LocalCache.RemapHints();
1✔
864
        CompletableFuture<V> computed = delegate.compute(
1✔
865
            key, (K k, @Nullable CompletableFuture<V> valueFuture) -> {
866
              added[0] = (valueFuture == null)
1✔
867
                  || (valueFuture.isDone() && (Async.getIfReady(valueFuture) == null));
1✔
868
              if (added[0]) {
1✔
869
                return CompletableFuture.completedFuture(value);
1✔
870
              }
871
              hints.preserveTimestamps = true;
1✔
872
              hints.preserveRefresh = true;
1✔
873
              return valueFuture;
1✔
874
            }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false, hints);
1✔
875

876
        if (added[0]) {
1✔
877
          return null;
1✔
878
        } else {
879
          V prior = Async.getWhenSuccessful(computed);
1✔
880
          if (prior != null) {
1✔
881
            return prior;
1✔
882
          }
883
        }
884
      }
1✔
885
    }
886

887
    @Override
888
    @SuppressWarnings("ResultOfMethodCallIgnored")
889
    public void putAll(Map<? extends K, ? extends V> map) {
890
      map.forEach(this::put);
1✔
891
    }
1✔
892

893
    @Override
894
    public @Nullable V put(K key, V value) {
895
      requireNonNull(value);
1✔
896
      CompletableFuture<V> oldValueFuture =
1✔
897
          delegate.put(key, CompletableFuture.completedFuture(value));
1✔
898
      return Async.getWhenSuccessful(oldValueFuture);
1✔
899
    }
900

901
    @Override
902
    public @Nullable V remove(Object key) {
903
      CompletableFuture<V> oldValueFuture = delegate.remove(key);
1✔
904
      return Async.getWhenSuccessful(oldValueFuture);
1✔
905
    }
906

907
    @Override
908
    @SuppressWarnings("ResultOfMethodCallIgnored")
909
    public boolean remove(Object key, @Nullable Object value) {
910
      requireNonNull(key);
1✔
911
      if (value == null) {
1✔
912
        return false;
1✔
913
      }
914

915
      @SuppressWarnings("unchecked")
916
      var castedKey = (K) key;
1✔
917
      boolean[] done = { false };
1✔
918
      boolean[] removed = { false };
1✔
919
      for (;;) {
920
        CompletableFuture<V> future = delegate.getIfPresentQuietly(castedKey);
1✔
921
        if ((future == null) || future.isCompletedExceptionally()) {
1✔
922
          return false;
1✔
923
        }
924

925
        Async.getWhenSuccessful(future);
1✔
926
        var hints = new LocalCache.RemapHints();
1✔
927
        delegate.compute(castedKey, (K k, @Nullable CompletableFuture<V> oldValueFuture) -> {
1✔
928
          if (oldValueFuture == null) {
1✔
929
            done[0] = true;
1✔
930
            return null;
1✔
931
          } else if (!oldValueFuture.isDone()) {
1✔
932
            hints.preserveTimestamps = true;
1✔
933
            hints.preserveRefresh = true;
1✔
934
            return oldValueFuture;
1✔
935
          }
936

937
          done[0] = true;
1✔
938
          V oldValue = Async.getIfReady(oldValueFuture);
1✔
939
          removed[0] = Objects.equals(value, oldValue);
1✔
940
          if (!removed[0] && (oldValue != null)) {
1✔
941
            hints.preserveTimestamps = true;
1✔
942
            hints.preserveRefresh = true;
1✔
943
          }
944
          return (oldValue == null) || removed[0] ? null : oldValueFuture;
1✔
945
        }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ true, hints);
1✔
946

947
        if (done[0]) {
1✔
948
          return removed[0];
1✔
949
        }
950
      }
1✔
951
    }
952

953
    @Override
954
    @SuppressWarnings("ResultOfMethodCallIgnored")
955
    public @Nullable V replace(K key, V value) {
956
      requireNonNull(value);
1✔
957

958
      @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
959
      @Nullable V[] oldValue = (V[]) new Object[1];
1✔
960
      boolean[] done = { false };
1✔
961
      for (;;) {
962
        CompletableFuture<V> future = delegate.getIfPresentQuietly(key);
1✔
963
        if ((future == null) || future.isCompletedExceptionally()) {
1✔
964
          return null;
1✔
965
        }
966

967
        Async.getWhenSuccessful(future);
1✔
968
        var hints = new LocalCache.RemapHints();
1✔
969
        delegate.compute(key, (K k, @Nullable CompletableFuture<V> oldValueFuture) -> {
1✔
970
          if (oldValueFuture == null) {
1✔
971
            done[0] = true;
1✔
972
            return null;
1✔
973
          } else if (!oldValueFuture.isDone()) {
1✔
974
            hints.preserveTimestamps = true;
1✔
975
            hints.preserveRefresh = true;
1✔
976
            return oldValueFuture;
1✔
977
          }
978

979
          done[0] = true;
1✔
980
          oldValue[0] = Async.getIfReady(oldValueFuture);
1✔
981
          return (oldValue[0] == null) ? null : CompletableFuture.completedFuture(value);
1✔
982
        }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false, hints);
1✔
983

984
        if (done[0]) {
1✔
985
          return oldValue[0];
1✔
986
        }
987
      }
1✔
988
    }
989

990
    @Override
991
    @SuppressWarnings("ResultOfMethodCallIgnored")
992
    public boolean replace(K key, V oldValue, V newValue) {
993
      requireNonNull(oldValue);
1✔
994
      requireNonNull(newValue);
1✔
995

996
      boolean[] done = { false };
1✔
997
      boolean[] replaced = { false };
1✔
998
      for (;;) {
999
        CompletableFuture<V> future = delegate.getIfPresentQuietly(key);
1✔
1000
        if ((future == null) || future.isCompletedExceptionally()) {
1✔
1001
          return false;
1✔
1002
        }
1003

1004
        Async.getWhenSuccessful(future);
1✔
1005
        var hints = new LocalCache.RemapHints();
1✔
1006
        delegate.compute(key, (K k, @Nullable CompletableFuture<V> oldValueFuture) -> {
1✔
1007
          if (oldValueFuture == null) {
1✔
1008
            done[0] = true;
1✔
1009
            return null;
1✔
1010
          } else if (!oldValueFuture.isDone()) {
1✔
1011
            hints.preserveTimestamps = true;
1✔
1012
            hints.preserveRefresh = true;
1✔
1013
            return oldValueFuture;
1✔
1014
          }
1015

1016
          done[0] = true;
1✔
1017
          replaced[0] = Objects.equals(oldValue, Async.getIfReady(oldValueFuture));
1✔
1018
          if (!replaced[0]) {
1✔
1019
            hints.preserveTimestamps = true;
1✔
1020
            hints.preserveRefresh = true;
1✔
1021
          }
1022
          return replaced[0] ? CompletableFuture.completedFuture(newValue) : oldValueFuture;
1✔
1023
        }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false, hints);
1✔
1024

1025
        if (done[0]) {
1✔
1026
          return replaced[0];
1✔
1027
        }
1028
      }
1✔
1029
    }
1030

1031
    @Override
1032
    @SuppressWarnings("ResultOfMethodCallIgnored")
1033
    public @Nullable V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
1034
      requireNonNull(mappingFunction);
1✔
1035

1036
      @Var CompletableFuture<V> priorFuture = null;
1✔
1037
      for (;;) {
1038
        priorFuture = (priorFuture == null)
1✔
1039
            ? delegate.get(key)
1✔
1040
            : delegate.getIfPresentQuietly(key);
1✔
1041
        if (priorFuture != null) {
1✔
1042
          if (!priorFuture.isDone()) {
1✔
1043
            Async.getWhenSuccessful(priorFuture);
1✔
1044
            continue;
1✔
1045
          }
1046

1047
          V prior = Async.getWhenSuccessful(priorFuture);
1✔
1048
          if (prior != null) {
1✔
1049
            delegate.statsCounter().recordHits(1);
1✔
1050
            return prior;
1✔
1051
          }
1052
        }
1053

1054
        @SuppressWarnings({"rawtypes", "unchecked"})
1055
        CompletableFuture<V>[] future = new CompletableFuture[1];
1✔
1056
        var hints = new LocalCache.RemapHints();
1✔
1057
        CompletableFuture<V> computed = delegate.compute(
1✔
1058
            key, (K k, @Nullable CompletableFuture<V> valueFuture) -> {
1059
              if ((valueFuture != null)
1✔
1060
                  && (!valueFuture.isDone() || (Async.getIfReady(valueFuture) != null))) {
1✔
1061
                hints.preserveTimestamps = true;
1✔
1062
                hints.preserveRefresh = true;
1✔
1063
                return valueFuture;
1✔
1064
              }
1065

1066
              V newValue = delegate.statsAware(mappingFunction, /* recordLoad= */ true).apply(key);
1✔
1067
              if (newValue == null) {
1✔
1068
                return null;
1✔
1069
              }
1070
              future[0] = CompletableFuture.completedFuture(newValue);
1✔
1071
              return future[0];
1✔
1072
            }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false, hints);
1✔
1073

1074
        V result = Async.getWhenSuccessful(computed);
1✔
1075
        if ((computed == future[0]) || (result != null)) {
1✔
1076
          return result;
1✔
1077
        }
1078
      }
1✔
1079
    }
1080

1081
    @Override
1082
    @SuppressWarnings("ResultOfMethodCallIgnored")
1083
    public @Nullable V computeIfPresent(K key,
1084
        BiFunction<? super K, ? super V, ? extends @Nullable V> remappingFunction) {
1085
      requireNonNull(remappingFunction);
1✔
1086

1087
      @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
1088
      @Nullable V[] newValue = (@Nullable V[]) new Object[1];
1✔
1089
      for (;;) {
1090
        Async.getWhenSuccessful(delegate.getIfPresentQuietly(key));
1✔
1091

1092
        var hints = new LocalCache.RemapHints();
1✔
1093
        CompletableFuture<V> valueFuture = delegate.compute(
1✔
1094
            key, (K k, @Nullable CompletableFuture<V> oldValueFuture) -> {
1095
              if (oldValueFuture == null) {
1✔
1096
                return null;
1✔
1097
              } else if (!oldValueFuture.isDone()) {
1✔
1098
                hints.preserveTimestamps = true;
1✔
1099
                hints.preserveRefresh = true;
1✔
1100
                return oldValueFuture;
1✔
1101
              }
1102

1103
              V oldValue = Async.getIfReady(oldValueFuture);
1✔
1104
              if (oldValue == null) {
1✔
1105
                return null;
1✔
1106
              }
1107

1108
              BiFunction<? super K, ? super V, ? extends @Nullable V> function =
1✔
1109
                  delegate.statsAware(remappingFunction);
1✔
1110
              newValue[0] = function.apply(key, oldValue);
1✔
1111
              return (newValue[0] == null) ? null : CompletableFuture.completedFuture(newValue[0]);
1✔
1112
            }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false, hints);
1✔
1113

1114
        if (newValue[0] != null) {
1✔
1115
          return newValue[0];
1✔
1116
        } else if (valueFuture == null) {
1✔
1117
          return null;
1✔
1118
        }
1119
      }
1✔
1120
    }
1121

1122
    @Override
1123
    @SuppressWarnings("ResultOfMethodCallIgnored")
1124
    public @Nullable V compute(K key,
1125
        BiFunction<? super K, ? super @Nullable V, ? extends @Nullable V> remappingFunction) {
1126
      // Keep in sync with BoundedVarExpiration.computeAsync(key, remappingFunction, expiry)
1127
      requireNonNull(remappingFunction);
1✔
1128

1129
      @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
1130
      @Nullable V[] newValue = (@Nullable V[]) new Object[1];
1✔
1131
      for (;;) {
1132
        Async.getWhenSuccessful(delegate.getIfPresentQuietly(key));
1✔
1133

1134
        var hints = new LocalCache.RemapHints();
1✔
1135
        CompletableFuture<V> valueFuture = delegate.compute(
1✔
1136
            key, (K k, @Nullable CompletableFuture<V> oldValueFuture) -> {
1137
              if ((oldValueFuture != null) && !oldValueFuture.isDone()) {
1✔
1138
                hints.preserveTimestamps = true;
1✔
1139
                hints.preserveRefresh = true;
1✔
1140
                return oldValueFuture;
1✔
1141
              }
1142

1143
              V oldValue = Async.getIfReady(oldValueFuture);
1✔
1144
              BiFunction<? super K, ? super V, ? extends @Nullable V> function =
1✔
1145
                  delegate.statsAware(remappingFunction,
1✔
1146
                      /* recordLoad= */ true, /* recordLoadFailure= */ true);
1147
              newValue[0] = function.apply(key, oldValue);
1✔
1148
              return (newValue[0] == null) ? null : CompletableFuture.completedFuture(newValue[0]);
1✔
1149
            }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false, hints);
1✔
1150

1151
        if (newValue[0] != null) {
1✔
1152
          return newValue[0];
1✔
1153
        } else if (valueFuture == null) {
1✔
1154
          return null;
1✔
1155
        }
1156
      }
1✔
1157
    }
1158

1159
    @Override
1160
    @SuppressWarnings("ResultOfMethodCallIgnored")
1161
    public @Nullable V merge(K key, V value,
1162
        BiFunction<? super V, ? super V, ? extends @Nullable V> remappingFunction) {
1163
      requireNonNull(value);
1✔
1164
      requireNonNull(remappingFunction);
1✔
1165

1166
      CompletableFuture<V> newValueFuture = CompletableFuture.completedFuture(value);
1✔
1167
      boolean[] merged = { false };
1✔
1168
      for (;;) {
1169
        Async.getWhenSuccessful(delegate.getIfPresentQuietly(key));
1✔
1170

1171
        var hints = new LocalCache.RemapHints();
1✔
1172
        CompletableFuture<V> mergedValueFuture = delegate.compute(
1✔
1173
            key, (K k, @Nullable CompletableFuture<V> oldValueFuture) -> {
1174
              if (oldValueFuture == null) {
1✔
1175
                merged[0] = true;
1✔
1176
                return newValueFuture;
1✔
1177
              } else if (!oldValueFuture.isDone()) {
1✔
1178
                hints.preserveTimestamps = true;
1✔
1179
                hints.preserveRefresh = true;
1✔
1180
                return oldValueFuture;
1✔
1181
              }
1182

1183
              merged[0] = true;
1✔
1184
              V oldValue = Async.getIfReady(oldValueFuture);
1✔
1185
              if (oldValue == null) {
1✔
1186
                return newValueFuture;
1✔
1187
              }
1188
              V mergedValue = delegate.statsAware(remappingFunction).apply(oldValue, value);
1✔
1189
              if (mergedValue == null) {
1✔
1190
                return null;
1✔
1191
              } else if (mergedValue == oldValue) {
1✔
1192
                hints.preserveTimestamps = true;
1✔
1193
                hints.preserveRefresh = true;
1✔
1194
                return oldValueFuture;
1✔
1195
              } else if (mergedValue == value) {
1✔
1196
                return newValueFuture;
1✔
1197
              }
1198
              return CompletableFuture.completedFuture(mergedValue);
1✔
1199
            }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false, hints);
1✔
1200

1201
        if (merged[0]) {
1✔
1202
          return Async.getWhenSuccessful(mergedValueFuture);
1✔
1203
        }
1204
      }
1✔
1205
    }
1206

1207
    @Override
1208
    public Set<K> keySet() {
1209
      return (keys == null) ? (keys = new KeySet()) : keys;
1✔
1210
    }
1211

1212
    @Override
1213
    public Collection<V> values() {
1214
      return (values == null) ? (values = new Values()) : values;
1✔
1215
    }
1216

1217
    @Override
1218
    public Set<Entry<K, V>> entrySet() {
1219
      return (entries == null) ? (entries = new EntrySet()) : entries;
1✔
1220
    }
1221

1222
    /** See {@link BoundedLocalCache#equals(Object)} for semantics. */
1223
    @Override
1224
    public boolean equals(@Nullable Object o) {
1225
      if (o == this) {
1✔
1226
        return true;
1✔
1227
      } else if (!(o instanceof Map)) {
1✔
1228
        return false;
1✔
1229
      }
1230

1231
      var map = (Map<?, ?>) o;
1✔
1232
      int expectedSize = size();
1✔
1233
      if (map.size() != expectedSize) {
1✔
1234
        return false;
1✔
1235
      }
1236

1237
      @Var int count = 0;
1✔
1238
      try {
1239
        for (var iterator = new EntryIterator(); iterator.hasNext();) {
1✔
1240
          var entry = iterator.next();
1✔
1241
          var value = map.get(entry.getKey());
1✔
1242
          if ((value == null) || ((value != entry.getValue()) && !value.equals(entry.getValue()))) {
1✔
1243
            return false;
1✔
1244
          }
1245
          count++;
1✔
1246
        }
1✔
1247
      } catch (ClassCastException | NullPointerException ignored) {
1✔
1248
        return false;
1✔
1249
      }
1✔
1250
      return (count == expectedSize);
1✔
1251
    }
1252

1253
    @Override
1254
    public int hashCode() {
1255
      @Var int hash = 0;
1✔
1256
      for (var iterator = new EntryIterator(); iterator.hasNext();) {
1✔
1257
        var entry = iterator.next();
1✔
1258
        hash += entry.hashCode();
1✔
1259
      }
1✔
1260
      return hash;
1✔
1261
    }
1262

1263
    @Override
1264
    public String toString() {
1265
      var result = new StringBuilder(50).append('{');
1✔
1266
      for (var iterator = new EntryIterator(); iterator.hasNext();) {
1✔
1267
        var entry = iterator.next();
1✔
1268
        result.append((entry.getKey() == this) ? "(this Map)" : entry.getKey())
1✔
1269
            .append('=')
1✔
1270
            .append((entry.getValue() == this) ? "(this Map)" : entry.getValue());
1✔
1271

1272
        if (iterator.hasNext()) {
1✔
1273
          result.append(", ");
1✔
1274
        }
1275
      }
1✔
1276
      return result.append('}').toString();
1✔
1277
    }
1278

1279
    private final class KeySet extends AbstractSet<K> {
1✔
1280

1281
      @Override
1282
      public boolean isEmpty() {
1283
        return AsMapView.this.isEmpty();
1✔
1284
      }
1285

1286
      @Override
1287
      public int size() {
1288
        return AsMapView.this.size();
1✔
1289
      }
1290

1291
      @Override
1292
      public void clear() {
1293
        AsMapView.this.clear();
1✔
1294
      }
1✔
1295

1296
      @Override
1297
      public boolean contains(Object o) {
1298
        return AsMapView.this.containsKey(o);
1✔
1299
      }
1300

1301
      @Override
1302
      public boolean containsAll(Collection<?> collection) {
1303
        requireNonNull(collection);
1✔
1304
        if (collection != this) {
1✔
1305
          for (Object o : collection) {
1✔
1306
            if ((o == null) || !contains(o)) {
1✔
1307
              return false;
1✔
1308
            }
1309
          }
1✔
1310
        }
1311
        return true;
1✔
1312
      }
1313

1314
      @Override
1315
      public boolean removeAll(Collection<?> collection) {
1316
        return delegate.keySet().removeAll(collection);
1✔
1317
      }
1318

1319
      @Override
1320
      @SuppressWarnings("RedundantCollectionOperation")
1321
      public boolean remove(Object o) {
1322
        return delegate.keySet().remove(o);
1✔
1323
      }
1324

1325
      @Override
1326
      public boolean removeIf(Predicate<? super K> filter) {
1327
        return delegate.keySet().removeIf(filter);
1✔
1328
      }
1329

1330
      @Override
1331
      public boolean retainAll(Collection<?> collection) {
1332
        return delegate.keySet().retainAll(collection);
1✔
1333
      }
1334

1335
      @Override
1336
      public Iterator<K> iterator() {
1337
        return new KeyIterator<>(new EntryIterator());
1✔
1338
      }
1339

1340
      @Override
1341
      public Spliterator<K> spliterator() {
1342
        return new KeySpliterator<>(new EntrySpliterator());
1✔
1343
      }
1344
    }
1345

1346
    private static final class KeyIterator<K, V> implements Iterator<K> {
1347
      private final Iterator<Entry<K, V>> iterator;
1348

1349
      KeyIterator(Iterator<Entry<K, V>> iterator) {
1✔
1350
        this.iterator = requireNonNull(iterator);
1✔
1351
      }
1✔
1352

1353
      @Override
1354
      public boolean hasNext() {
1355
        return iterator.hasNext();
1✔
1356
      }
1357

1358
      @Override
1359
      public K next() {
1360
        return iterator.next().getKey();
1✔
1361
      }
1362

1363
      @Override
1364
      public void remove() {
1365
        iterator.remove();
1✔
1366
      }
1✔
1367
    }
1368

1369
    private static final class KeySpliterator<K, V> implements Spliterator<K> {
1370
      private final Spliterator<Entry<K, V>> spliterator;
1371

1372
      KeySpliterator(Spliterator<Entry<K, V>> iterator) {
1✔
1373
        this.spliterator = requireNonNull(iterator);
1✔
1374
      }
1✔
1375

1376
      @Override
1377
      public boolean tryAdvance(Consumer<? super K> action) {
1378
        requireNonNull(action);
1✔
1379
        return spliterator.tryAdvance(entry -> {
1✔
1380
          action.accept(entry.getKey());
1✔
1381
        });
1✔
1382
      }
1383

1384
      @Override
1385
      public @Nullable Spliterator<K> trySplit() {
1386
        Spliterator<Entry<K, V>> split = spliterator.trySplit();
1✔
1387
        return (split == null) ? null : new KeySpliterator<>(split);
1✔
1388
      }
1389

1390
      @Override
1391
      public long estimateSize() {
1392
        return spliterator.estimateSize();
1✔
1393
      }
1394

1395
      @Override
1396
      public int characteristics() {
1397
        return DISTINCT | NONNULL | CONCURRENT;
1✔
1398
      }
1399
    }
1400

1401
    private final class Values extends AbstractCollection<V> {
1✔
1402

1403
      @Override
1404
      public boolean isEmpty() {
1405
        return AsMapView.this.isEmpty();
1✔
1406
      }
1407

1408
      @Override
1409
      public int size() {
1410
        return AsMapView.this.size();
1✔
1411
      }
1412

1413
      @Override
1414
      public void clear() {
1415
        AsMapView.this.clear();
1✔
1416
      }
1✔
1417

1418
      @Override
1419
      public boolean contains(Object o) {
1420
        return AsMapView.this.containsValue(o);
1✔
1421
      }
1422

1423
      @Override
1424
      public boolean containsAll(Collection<?> collection) {
1425
        requireNonNull(collection);
1✔
1426
        if (collection != this) {
1✔
1427
          for (Object o : collection) {
1✔
1428
            if ((o == null) || !contains(o)) {
1✔
1429
              return false;
1✔
1430
            }
1431
          }
1✔
1432
        }
1433
        return true;
1✔
1434
      }
1435

1436
      @Override
1437
      public boolean removeAll(Collection<?> collection) {
1438
        requireNonNull(collection);
1✔
1439
        @Var boolean modified = false;
1✔
1440
        for (var entry : delegate.entrySet()) {
1✔
1441
          V value = Async.getIfReady(entry.getValue());
1✔
1442
          if ((value != null) && collection.contains(value)
1✔
1443
              && AsMapView.this.remove(entry.getKey(), value)) {
1✔
1444
            modified = true;
1✔
1445
          }
1446
        }
1✔
1447
        return modified;
1✔
1448
      }
1449

1450
      @Override
1451
      public boolean remove(@Nullable Object o) {
1452
        if (o == null) {
1✔
1453
          return false;
1✔
1454
        }
1455
        for (var entry : delegate.entrySet()) {
1✔
1456
          V value = Async.getIfReady(entry.getValue());
1✔
1457
          if (o.equals(value) && AsMapView.this.remove(entry.getKey(), value)) {
1✔
1458
            return true;
1✔
1459
          }
1460
        }
1✔
1461
        return false;
1✔
1462
      }
1463

1464
      @Override
1465
      public boolean removeIf(Predicate<? super V> filter) {
1466
        requireNonNull(filter);
1✔
1467
        return delegate.values().removeIf(future -> {
1✔
1468
          V value = Async.getIfReady(future);
1✔
1469
          return (value != null) && filter.test(value);
1!
1470
        });
1471
      }
1472

1473
      @Override
1474
      public boolean retainAll(Collection<?> collection) {
1475
        requireNonNull(collection);
1✔
1476
        @Var boolean modified = false;
1✔
1477
        for (var entry : delegate.entrySet()) {
1✔
1478
          V value = Async.getIfReady(entry.getValue());
1✔
1479
          if ((value != null) && !collection.contains(value)
1✔
1480
              && AsMapView.this.remove(entry.getKey(), value)) {
1✔
1481
            modified = true;
1✔
1482
          }
1483
        }
1✔
1484
        return modified;
1✔
1485
      }
1486

1487
      @Override
1488
      public void forEach(Consumer<? super V> action) {
1489
        requireNonNull(action);
1✔
1490
        delegate.values().forEach(future -> {
1✔
1491
          V value = Async.getIfReady(future);
1✔
1492
          if (value != null) {
1!
1493
            action.accept(value);
1✔
1494
          }
1495
        });
1✔
1496
      }
1✔
1497

1498
      @Override
1499
      public Iterator<V> iterator() {
1500
        return new ValueIterator<>(new EntryIterator());
1✔
1501
      }
1502

1503
      @Override
1504
      public Spliterator<V> spliterator() {
1505
        return new ValueSpliterator<>(new EntrySpliterator());
1✔
1506
      }
1507
    }
1508

1509
    private static final class ValueIterator<K, V> implements Iterator<V> {
1510
      private final Iterator<Entry<K, V>> iterator;
1511

1512
      ValueIterator(Iterator<Entry<K, V>> iterator) {
1✔
1513
        this.iterator = requireNonNull(iterator);
1✔
1514
      }
1✔
1515

1516
      @Override
1517
      public boolean hasNext() {
1518
        return iterator.hasNext();
1✔
1519
      }
1520

1521
      @Override
1522
      public V next() {
1523
        return iterator.next().getValue();
1✔
1524
      }
1525

1526
      @Override
1527
      public void remove() {
1528
        iterator.remove();
1✔
1529
      }
1✔
1530
    }
1531

1532
    private static final class ValueSpliterator<K, V> implements Spliterator<V> {
1533
      private final Spliterator<Entry<K, V>> spliterator;
1534

1535
      ValueSpliterator(Spliterator<Entry<K, V>> iterator) {
1✔
1536
        this.spliterator = requireNonNull(iterator);
1✔
1537
      }
1✔
1538

1539
      @Override
1540
      public boolean tryAdvance(Consumer<? super V> action) {
1541
        requireNonNull(action);
1✔
1542
        return spliterator.tryAdvance(entry -> {
1✔
1543
          action.accept(entry.getValue());
1✔
1544
        });
1✔
1545
      }
1546

1547
      @Override
1548
      public @Nullable Spliterator<V> trySplit() {
1549
        Spliterator<Entry<K, V>> split = spliterator.trySplit();
1✔
1550
        return (split == null) ? null : new ValueSpliterator<>(split);
1✔
1551
      }
1552

1553
      @Override
1554
      public long estimateSize() {
1555
        return spliterator.estimateSize();
1✔
1556
      }
1557

1558
      @Override
1559
      public int characteristics() {
1560
        return NONNULL | CONCURRENT;
1✔
1561
      }
1562
    }
1563

1564
    private final class EntrySet extends AbstractSet<Entry<K, V>> {
1✔
1565

1566
      @Override
1567
      public boolean isEmpty() {
1568
        return AsMapView.this.isEmpty();
1✔
1569
      }
1570

1571
      @Override
1572
      public int size() {
1573
        return AsMapView.this.size();
1✔
1574
      }
1575

1576
      @Override
1577
      public void clear() {
1578
        AsMapView.this.clear();
1✔
1579
      }
1✔
1580

1581
      @Override
1582
      public boolean contains(Object o) {
1583
        if (!(o instanceof Entry<?, ?>)) {
1✔
1584
          return false;
1✔
1585
        }
1586
        var entry = (Entry<?, ?>) o;
1✔
1587
        var key = entry.getKey();
1✔
1588
        var value = entry.getValue();
1✔
1589
        if ((key == null) || (value == null)) {
1✔
1590
          return false;
1✔
1591
        }
1592
        V cachedValue = Async.getIfReady(delegate.getIfPresentQuietly(key));
1✔
1593
        return value.equals(cachedValue);
1✔
1594
      }
1595

1596
      @Override
1597
      public boolean removeAll(Collection<?> collection) {
1598
        requireNonNull(collection);
1✔
1599
        @Var boolean modified = false;
1✔
1600
        if (delegate.collectKeys()
1✔
1601
            || ((collection instanceof Set<?>) && (collection.size() > size()))) {
1✔
1602
          for (var entry : this) {
1✔
1603
            if (collection.contains(entry)) {
1✔
1604
              modified |= remove(entry);
1✔
1605
            }
1606
          }
1✔
1607
        } else {
1608
          for (var o : collection) {
1✔
1609
            modified |= remove(o);
1✔
1610
          }
1✔
1611
        }
1612
        return modified;
1✔
1613
      }
1614

1615
      @Override
1616
      public boolean remove(Object obj) {
1617
        if (!(obj instanceof Entry<?, ?>)) {
1✔
1618
          return false;
1✔
1619
        }
1620
        var entry = (Entry<?, ?>) obj;
1✔
1621
        var key = entry.getKey();
1✔
1622
        return (key != null) && AsMapView.this.remove(key, entry.getValue());
1✔
1623
      }
1624

1625
      @Override
1626
      public boolean removeIf(Predicate<? super Entry<K, V>> filter) {
1627
        requireNonNull(filter);
1✔
1628
        return delegate.entrySet().removeIf(entry -> {
1✔
1629
          V value = Async.getIfReady(entry.getValue());
1✔
1630
          return (value != null) && filter.test(Map.entry(entry.getKey(), value));
1!
1631
        });
1632
      }
1633

1634
      @Override
1635
      public boolean retainAll(Collection<?> collection) {
1636
        requireNonNull(collection);
1✔
1637
        @Var boolean modified = false;
1✔
1638
        for (var entry : this) {
1✔
1639
          if (!collection.contains(entry) && remove(entry)) {
1✔
1640
            modified = true;
1✔
1641
          }
1642
        }
1✔
1643
        return modified;
1✔
1644
      }
1645

1646
      @Override
1647
      public Iterator<Entry<K, V>> iterator() {
1648
        return new EntryIterator();
1✔
1649
      }
1650

1651
      @Override
1652
      public Spliterator<Entry<K, V>> spliterator() {
1653
        return new EntrySpliterator();
1✔
1654
      }
1655
    }
1656

1657
    private final class EntryIterator implements Iterator<Entry<K, V>> {
1658
      final Iterator<Entry<K, CompletableFuture<V>>> iterator;
1659
      @Nullable Entry<K, V> cursor;
1660
      @Nullable K removalKey;
1661

1662
      EntryIterator() {
1✔
1663
        this.iterator = delegate.entrySet().iterator();
1✔
1664
      }
1✔
1665

1666
      @Override
1667
      public boolean hasNext() {
1668
        while ((cursor == null) && iterator.hasNext()) {
1✔
1669
          Entry<K, CompletableFuture<V>> entry = iterator.next();
1✔
1670
          V value = Async.getIfReady(entry.getValue());
1✔
1671
          if (value != null) {
1✔
1672
            cursor = new WriteThroughEntry<>(AsMapView.this, entry.getKey(), value);
1✔
1673
          }
1674
        }
1✔
1675
        return (cursor != null);
1✔
1676
      }
1677

1678
      @Override
1679
      public Entry<K, V> next() {
1680
        if (!hasNext()) {
1✔
1681
          throw new NoSuchElementException();
1✔
1682
        }
1683
        K key = requireNonNull(cursor).getKey();
1✔
1684
        Entry<K, V> entry = cursor;
1✔
1685
        removalKey = key;
1✔
1686
        cursor = null;
1✔
1687
        return entry;
1✔
1688
      }
1689

1690
      @Override
1691
      public void remove() {
1692
        requireState(removalKey != null);
1✔
1693
        delegate.remove(removalKey);
1✔
1694
        removalKey = null;
1✔
1695
      }
1✔
1696
    }
1697

1698
    private final class EntrySpliterator implements Spliterator<Entry<K, V>> {
1699
      final Spliterator<Entry<K, CompletableFuture<V>>> spliterator;
1700

1701
      EntrySpliterator() {
1702
        this(delegate.entrySet().spliterator());
1✔
1703
      }
1✔
1704

1705
      EntrySpliterator(Spliterator<Entry<K, CompletableFuture<V>>> spliterator) {
1✔
1706
        this.spliterator = requireNonNull(spliterator);
1✔
1707
      }
1✔
1708

1709
      @Override
1710
      public void forEachRemaining(Consumer<? super Entry<K, V>> action) {
1711
        requireNonNull(action);
1✔
1712
        spliterator.forEachRemaining(entry -> {
1✔
1713
          V value = Async.getIfReady(entry.getValue());
1✔
1714
          if (value != null) {
1✔
1715
            var e = new WriteThroughEntry<>(AsMapView.this, entry.getKey(), value);
1✔
1716
            action.accept(e);
1✔
1717
          }
1718
        });
1✔
1719
      }
1✔
1720

1721
      @Override
1722
      public boolean tryAdvance(Consumer<? super Entry<K, V>> action) {
1723
        requireNonNull(action);
1✔
1724
        boolean[] advanced = { false };
1✔
1725
        Consumer<? super Entry<K, CompletableFuture<V>>> consumer = entry -> {
1✔
1726
          V value = Async.getIfReady(entry.getValue());
1✔
1727
          if (value != null) {
1✔
1728
            var e = new WriteThroughEntry<>(AsMapView.this, entry.getKey(), value);
1✔
1729
            action.accept(e);
1✔
1730
            advanced[0] = true;
1✔
1731
          }
1732
        };
1✔
1733
        while (spliterator.tryAdvance(consumer)) {
1✔
1734
          if (advanced[0]) {
1✔
1735
            return true;
1✔
1736
          }
1737
        }
1738
        return false;
1✔
1739
      }
1740

1741
      @Override
1742
      public @Nullable Spliterator<Entry<K, V>> trySplit() {
1743
        Spliterator<Entry<K, CompletableFuture<V>>> split = spliterator.trySplit();
1✔
1744
        return (split == null) ? null : new EntrySpliterator(split);
1✔
1745
      }
1746

1747
      @Override
1748
      public long estimateSize() {
1749
        return spliterator.estimateSize();
1✔
1750
      }
1751

1752
      @Override
1753
      public int characteristics() {
1754
        return DISTINCT | CONCURRENT | NONNULL;
1✔
1755
      }
1756
    }
1757
  }
1758

1759
  @SuppressWarnings("serial")
1760
  final class SyncViewProxy<K, V> implements Serializable {
1761
    private static final long serialVersionUID = 1;
1762

1763
    final AsyncCache<K, V> asyncCache;
1764

1765
    SyncViewProxy(AsyncCache<K, V> asyncCache) {
1✔
1766
      this.asyncCache = requireNonNull(asyncCache);
1✔
1767
    }
1✔
1768

1769
    @SuppressWarnings("unused")
1770
    private void readObject(ObjectInputStream stream)
1771
        throws IOException, ClassNotFoundException {
1772
      stream.defaultReadObject();
1✔
1773
      if (asyncCache == null) {
1✔
1774
        throw new InvalidObjectException("asyncCache required");
1✔
1775
      }
1776
    }
1✔
1777

1778
    Object readResolve() {
1779
      return asyncCache.synchronous();
1✔
1780
    }
1781
  }
1782
}
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