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

ben-manes / caffeine / #5612

11 Jul 2026 03:47PM UTC coverage: 71.292% (-28.7%) from 100.0%
#5612

push

github

ben-manes
Preserve timestamps on a raced same-instance refresh

The automatic refreshIfNeeded path checked for an equal-value reload
before the ABA/ownership check and returned without preserving the
timestamps, so a reload completing after a concurrent put(k,
sameInstance) did a full update — shifting the entry's expiration
forward by the reload's in-flight duration and stomping the write.
Mirror the explicit-refresh fix: check ownership first. An owned
reload installs the value (refreshing metadata even for a same
instance), while a raced reload preserves the write's timestamps and
only notifies REPLACED when the value differs.

2957 of 4134 branches covered (71.53%)

3 of 3 new or added lines in 1 file covered. (100.0%)

2408 existing lines in 53 files now uncovered.

5980 of 8388 relevant lines covered (71.29%)

0.71 hits per line

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

94.91
/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);
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, @Nullable CompletableFuture<@Nullable V>>(initialCapacity);
1✔
135
    for (K key : keys) {
1✔
136
      futures.put(requireNonNull(key), null);
1✔
137
    }
1✔
138

139
    var proxies = new LinkedHashMap<K, CompletableFuture<@Nullable V>>(initialCapacity);
1✔
140
    for (var entry : futures.entrySet()) {
1✔
141
      K key = entry.getKey();
1✔
142
      @Var CompletableFuture<V> future = cache().getIfPresent(key, /* recordStats= */ false);
1✔
143
      if (future == null) {
1✔
144
        var proxy = new CompletableFuture<V>();
1✔
145
        future = cache().putIfAbsent(key, proxy);
1✔
146
        if (future == null) {
1✔
147
          future = proxy;
1✔
148
          proxies.put(key, proxy);
1✔
149
        }
150
      }
151
      entry.setValue(future);
1✔
152
    }
1✔
153
    cache().statsCounter().recordMisses(proxies.size());
1✔
154
    cache().statsCounter().recordHits(futures.size() - proxies.size());
1✔
155

156
    @SuppressWarnings("NullAway")
157
    Map<K, CompletableFuture<@Nullable V>> results = futures;
1✔
158
    if (proxies.isEmpty()) {
1✔
159
      return composeResult(results);
1✔
160
    }
161

162
    var completer = new AsyncBulkCompleter<>(cache(), proxies);
1✔
163
    try {
164
      var loader = mappingFunction.apply(
1✔
165
          Collections.unmodifiableSet(proxies.keySet()), cache().executor());
1✔
166
      return loader.handle(completer).thenCompose(ignored -> composeResult(results));
1✔
167
    } catch (Throwable t) {
1✔
168
      throw completer.error(t);
1✔
169
    }
170
  }
171

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

198
  @Override
199
  @SuppressWarnings("FutureReturnValueIgnored")
200
  default void put(K key, CompletableFuture<? extends @Nullable V> valueFuture) {
201
    long startTime = cache().statsTicker().read();
1✔
202

203
    @SuppressWarnings("unchecked")
204
    var castedFuture = (CompletableFuture<V>) valueFuture;
1✔
205
    CompletableFuture<V> prior = cache().put(key, castedFuture);
1✔
206
    if (prior != castedFuture) {
1!
207
      handleCompletion(key, valueFuture, startTime);
1✔
208
    }
209
  }
1✔
210

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

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

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

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

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

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

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

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

302
    @SuppressWarnings("ImmutableMapCopyOf")
303
    private @Nullable Throwable handleResponse(
304
        @Nullable Map<? extends K, ? extends V> result, @Nullable Throwable error) {
305
      if (result == null) {
1✔
306
        var failure = (error == null) ? new NullMapCompletionException() : error;
1✔
307
        failProxies(failure);
1✔
308
        return failure;
1✔
309
      }
310

311
      Map<K, V> snapshot;
312
      try {
313
        snapshot = Map.copyOf(result);
1✔
314
      } catch (Throwable t) {
1✔
315
        failProxies(t);
1✔
316
        return t;
1✔
317
      }
1✔
318

319
      var failure = fillProxies(snapshot);
1✔
320
      return addNewEntries(snapshot, failure);
1✔
321
    }
322

323
    /** Removes and exceptionally completes every requested proxy. */
324
    private void failProxies(Throwable failure) {
325
      for (var entry : proxies.entrySet()) {
1✔
326
        cache.remove(entry.getKey(), entry.getValue());
1✔
327
        entry.getValue().obtrudeException(failure);
1✔
328
      }
1✔
329
      if (!(failure instanceof CancellationException) && !(failure instanceof TimeoutException)) {
1!
330
        logger.log(Level.WARNING, "Exception thrown during asynchronous load", failure);
1✔
331
      }
332
    }
1✔
333

334
    /** Populates the proxies with the computed result. */
335
    private @Nullable Throwable fillProxies(Map<? extends K, ? extends V> result) {
336
      @Var Throwable error = null;
1✔
337
      for (var entry : proxies.entrySet()) {
1✔
338
        var key = entry.getKey();
1✔
339
        var value = result.get(key);
1✔
340
        var future = entry.getValue();
1✔
341
        future.obtrudeValue(value);
1✔
342

343
        if (value == null) {
1✔
344
          cache.remove(key, future);
1✔
345
        } else {
346
          try {
347
            // update the weight and expiration timestamps
348
            cache.replace(key, future, future);
1✔
349
          } catch (Throwable t) {
1✔
350
            logger.log(Level.WARNING, "Exception thrown during asynchronous load", t);
1✔
351
            cache.remove(key, future);
1✔
352
            if (error == null) {
1✔
353
              error = t;
1✔
354
            } else if (error != t) {
1✔
355
              error.addSuppressed(t);
1✔
356
            }
357
          }
1✔
358
        }
359
      }
1✔
360
      return error;
1✔
361
    }
362

363
    /** Adds to the cache any extra entries computed that were not requested. */
364
    private @Nullable Throwable addNewEntries(
365
        Map<? extends K, ? extends V> result, @Nullable Throwable failure) {
366
      @Var Throwable error = failure;
1✔
367
      for (var entry : result.entrySet()) {
1✔
368
        var key = entry.getKey();
1✔
369
        if (!proxies.containsKey(key)) {
1✔
370
          V value = entry.getValue();
1✔
371
          try {
372
            cache.put(key, CompletableFuture.completedFuture(value));
1✔
373
          } catch (Throwable t) {
1✔
374
            logger.log(Level.WARNING, "Exception thrown during asynchronous load", t);
1✔
375
            if (error == null) {
1!
UNCOV
376
              error = t;
×
377
            } else if (error != t) {
1!
UNCOV
378
              error.addSuppressed(t);
×
379
            }
380
          }
1✔
381
        }
382
      }
1✔
383
      return error;
1✔
384
    }
385

386
    static final class NullMapCompletionException extends CompletionException {
1✔
387
      private static final long serialVersionUID = 1L;
388
    }
389
  }
390

391
  /* --------------- Asynchronous view --------------- */
392
  final class AsyncAsMapView<K, V> implements ConcurrentMap<K, CompletableFuture<V>> {
393
    final LocalAsyncCache<K, V> asyncCache;
394

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

482
      @SuppressWarnings({"rawtypes", "unchecked"})
483
      @Nullable CompletableFuture<V>[] result = new CompletableFuture[1];
1✔
484
      @SuppressWarnings({"rawtypes", "unchecked"})
485
      @Nullable CompletableFuture<V>[] prior = new CompletableFuture[1];
1✔
486
      long startTime = asyncCache.cache().statsTicker().read();
1✔
487
      asyncCache.cache().compute(key, (K k, @Nullable CompletableFuture<V> oldValue) -> {
1✔
488
        result[0] = (oldValue == null) ? null : remappingFunction.apply(k, oldValue);
1!
489
        prior[0] = oldValue;
1✔
490
        return result[0];
1✔
491
      }, asyncCache.cache().expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false);
1✔
492

493
      if ((result[0] != null) && (result[0] != prior[0])) {
1!
494
        asyncCache.handleCompletion(key, result[0], startTime);
1✔
495
      }
496
      return result[0];
1✔
497
    }
498
    @SuppressWarnings("ResultOfMethodCallIgnored")
499
    @Override public @Nullable CompletableFuture<V> compute(K key, BiFunction<? super K,
500
        ? super CompletableFuture<V>, ? extends CompletableFuture<V>> remappingFunction) {
501
      requireNonNull(remappingFunction);
1✔
502

503
      @SuppressWarnings({"rawtypes", "unchecked"})
504
      @Nullable CompletableFuture<V>[] result = new CompletableFuture[1];
1✔
505
      @SuppressWarnings({"rawtypes", "unchecked"})
506
      @Nullable CompletableFuture<V>[] prior = new CompletableFuture[1];
1✔
507
      long startTime = asyncCache.cache().statsTicker().read();
1✔
508
      asyncCache.cache().compute(key, (k, oldValue) -> {
1✔
509
        result[0] = remappingFunction.apply(k, oldValue);
1✔
510
        prior[0] = oldValue;
1✔
511
        return result[0];
1✔
512
      }, asyncCache.cache().expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false);
1✔
513

514
      if ((result[0] != null) && (result[0] != prior[0])) {
1!
515
        asyncCache.handleCompletion(key, result[0], startTime);
1✔
516
      }
517
      return result[0];
1✔
518
    }
519
    @SuppressWarnings("ResultOfMethodCallIgnored")
520
    @Override public @Nullable CompletableFuture<V> merge(K key, CompletableFuture<V> value,
521
        BiFunction<? super CompletableFuture<V>, ? super CompletableFuture<V>,
522
            ? extends CompletableFuture<V>> remappingFunction) {
523
      requireNonNull(value);
1✔
524
      requireNonNull(remappingFunction);
1✔
525

526
      @SuppressWarnings({"rawtypes", "unchecked"})
527
      @Nullable  CompletableFuture<V>[] result = new CompletableFuture[1];
1✔
528
      @SuppressWarnings({"rawtypes", "unchecked"})
529
      @Nullable  CompletableFuture<V>[] prior = new CompletableFuture[1];
1✔
530
      long startTime = asyncCache.cache().statsTicker().read();
1✔
531
      asyncCache.cache().compute(key, (K k, @Nullable CompletableFuture<V> oldValue) -> {
1✔
532
        result[0] = (oldValue == null) ? value : remappingFunction.apply(oldValue, value);
1✔
533
        prior[0] = oldValue;
1✔
534
        return result[0];
1✔
535
      }, asyncCache.cache().expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false);
1✔
536

537
      if ((result[0] != null) && (result[0] != prior[0])) {
1!
538
        asyncCache.handleCompletion(key, result[0], startTime);
1✔
539
      }
540
      return result[0];
1✔
541
    }
542
    @Override public void forEach(BiConsumer<? super K, ? super CompletableFuture<V>> action) {
543
      asyncCache.cache().forEach(action);
1✔
544
    }
1✔
545
    @Override public Set<K> keySet() {
546
      return asyncCache.cache().keySet();
1✔
547
    }
548
    @Override public Collection<CompletableFuture<V>> values() {
549
      return asyncCache.cache().values();
1✔
550
    }
551
    @Override public Set<Entry<K, CompletableFuture<V>>> entrySet() {
552
      return new AsyncEntrySet();
1✔
553
    }
554
    @SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
555
    @Override public boolean equals(@Nullable Object o) {
556
      return (o == this) || asyncCache.cache().equals(o);
1✔
557
    }
558
    @Override public int hashCode() {
559
      return asyncCache.cache().hashCode();
1✔
560
    }
561
    @Override public String toString() {
562
      return asyncCache.cache().toString();
1✔
563
    }
564

565
    private final class AsyncEntrySet extends AbstractSet<Entry<K, CompletableFuture<V>>> {
1✔
566
      @Override public int size() {
567
        return AsyncAsMapView.this.size();
1✔
568
      }
569
      @Override public boolean isEmpty() {
570
        return AsyncAsMapView.this.isEmpty();
1✔
571
      }
572
      @Override public void clear() {
UNCOV
573
        AsyncAsMapView.this.clear();
×
UNCOV
574
      }
×
575
      @Override public boolean contains(Object o) {
576
        return asyncCache.cache().entrySet().contains(o);
1✔
577
      }
578
      @Override public boolean remove(Object o) {
579
        return asyncCache.cache().entrySet().remove(o);
1✔
580
      }
581
      @Override public boolean removeAll(Collection<?> collection) {
582
        return asyncCache.cache().entrySet().removeAll(collection);
1✔
583
      }
584
      @Override public boolean retainAll(Collection<?> collection) {
585
        return asyncCache.cache().entrySet().retainAll(collection);
1✔
586
      }
587
      @Override public boolean removeIf(Predicate<? super Entry<K, CompletableFuture<V>>> filter) {
588
        return asyncCache.cache().entrySet().removeIf(filter);
1✔
589
      }
590
      @Override public Iterator<Entry<K, CompletableFuture<V>>> iterator() {
591
        return new AsyncEntryIterator();
1✔
592
      }
593
      @Override public Spliterator<Entry<K, CompletableFuture<V>>> spliterator() {
594
        return new AsyncEntrySpliterator();
1✔
595
      }
596
    }
597

598
    private final class AsyncEntryIterator implements Iterator<Entry<K, CompletableFuture<V>>> {
1✔
599
      final Iterator<Entry<K, CompletableFuture<V>>> iterator =
1✔
600
          asyncCache.cache().entrySet().iterator();
1✔
601

602
      @Override public boolean hasNext() {
603
        return iterator.hasNext();
1✔
604
      }
605
      @Override public Entry<K, CompletableFuture<V>> next() {
606
        var entry = iterator.next();
1✔
607
        return new WriteThroughEntry<>(AsyncAsMapView.this, entry.getKey(), entry.getValue());
1✔
608
      }
609
      @Override public void remove() {
UNCOV
610
        iterator.remove();
×
UNCOV
611
      }
×
612
    }
613

614
    private final class AsyncEntrySpliterator
615
        implements Spliterator<Entry<K, CompletableFuture<V>>> {
616
      final Spliterator<Entry<K, CompletableFuture<V>>> spliterator;
617

618
      AsyncEntrySpliterator() {
619
        this(asyncCache.cache().entrySet().spliterator());
1✔
620
      }
1✔
621
      AsyncEntrySpliterator(Spliterator<Entry<K, CompletableFuture<V>>> spliterator) {
1✔
622
        this.spliterator = requireNonNull(spliterator);
1✔
623
      }
1✔
624
      @Override public boolean tryAdvance(Consumer<? super Entry<K, CompletableFuture<V>>> action) {
625
        requireNonNull(action);
1✔
626
        return spliterator.tryAdvance(entry -> action.accept(
1✔
627
            new WriteThroughEntry<>(AsyncAsMapView.this, entry.getKey(), entry.getValue())));
1✔
628
      }
629
      @Override public void forEachRemaining(
630
          Consumer<? super Entry<K, CompletableFuture<V>>> action) {
631
        requireNonNull(action);
1✔
632
        spliterator.forEachRemaining(entry -> action.accept(
1✔
633
            new WriteThroughEntry<>(AsyncAsMapView.this, entry.getKey(), entry.getValue())));
1✔
634
      }
1✔
635
      @Override public @Nullable Spliterator<Entry<K, CompletableFuture<V>>> trySplit() {
UNCOV
636
        var split = spliterator.trySplit();
×
UNCOV
637
        return (split == null) ? null : new AsyncEntrySpliterator(split);
×
638
      }
639
      @Override public long estimateSize() {
UNCOV
640
        return spliterator.estimateSize();
×
641
      }
642
      @Override public int characteristics() {
643
        return spliterator.characteristics();
1✔
644
      }
645
    }
646
  }
647

648
  /* --------------- Synchronous view --------------- */
649
  final class CacheView<K, V> extends AbstractCacheView<K, V> {
650
    private static final long serialVersionUID = 1L;
651

652
    @SuppressWarnings("serial")
653
    final LocalAsyncCache<K, V> asyncCache;
654

655
    CacheView(LocalAsyncCache<K, V> asyncCache) {
1✔
656
      this.asyncCache = requireNonNull(asyncCache);
1✔
657
    }
1✔
658
    @Override LocalAsyncCache<K, V> asyncCache() {
659
      return asyncCache;
1✔
660
    }
661
  }
662

663
  abstract class AbstractCacheView<K, V> implements Cache<K, V>, Serializable {
1✔
664
    private static final long serialVersionUID = 1L;
665

666
    transient @Nullable ConcurrentMap<K, V> asMapView;
667

668
    abstract LocalAsyncCache<K, V> asyncCache();
669

670
    @Override
671
    public @Nullable V getIfPresent(K key) {
672
      var future = asyncCache().cache().getIfPresent(key, /* recordStats= */ false);
1✔
673
      var value = Async.getIfReady(future);
1✔
674
      if (value == null) {
1✔
675
        asyncCache().cache().statsCounter().recordMisses(1);
1✔
676
      } else {
677
        asyncCache().cache().statsCounter().recordHits(1);
1✔
678
      }
679
      return value;
1✔
680
    }
681

682
    @Override
683
    public Map<K, V> getAllPresent(Iterable<? extends K> keys) {
684
      var result = new LinkedHashMap<K, @Nullable V>(calculateHashMapCapacity(keys));
1✔
685
      for (K key : keys) {
1✔
686
        result.put(key, null);
1✔
687
      }
1✔
688

689
      int uniqueKeys = result.size();
1✔
690
      for (var iter = result.entrySet().iterator(); iter.hasNext();) {
1✔
691
        Map.Entry<K, @Nullable V> entry = iter.next();
1✔
692

693
        CompletableFuture<V> future = asyncCache().cache().get(entry.getKey());
1✔
694
        V value = Async.getIfReady(future);
1✔
695
        if (value == null) {
1✔
696
          iter.remove();
1✔
697
        } else {
698
          entry.setValue(value);
1✔
699
        }
700
      }
1✔
701
      asyncCache().cache().statsCounter().recordHits(result.size());
1✔
702
      asyncCache().cache().statsCounter().recordMisses(uniqueKeys - result.size());
1✔
703

704
      @SuppressWarnings("NullableProblems")
705
      Map<K, V> unmodifiable = Collections.unmodifiableMap(result);
1✔
706
      return unmodifiable;
1✔
707
    }
708

709
    @Override
710
    public V get(K key, Function<? super K, ? extends V> mappingFunction) {
711
      return resolve(asyncCache().get(key, mappingFunction));
1✔
712
    }
713

714
    @Override
715
    public Map<K, V> getAll(
716
        Iterable<? extends K> keys,
717
        Function<
718
            ? super Set<? extends K>,
719
            ? extends Map<? extends K, ? extends V>> mappingFunction) {
720
      return resolve(asyncCache().getAll(keys, mappingFunction));
1✔
721
    }
722

723
    @SuppressWarnings({"PMD.AvoidThrowingNullPointerException",
724
      "PMD.PreserveStackTrace", "UnusedException"})
725
    protected static <T> T resolve(CompletableFuture<T> future) {
726
      try {
727
        return future.join();
1✔
728
      } catch (NullMapCompletionException e) {
1✔
729
        throw new NullPointerException("null map");
1✔
730
      } catch (CompletionException e) {
1✔
731
        if (e.getCause() instanceof RuntimeException) {
1✔
732
          throw (RuntimeException) e.getCause();
1✔
733
        } else if (e.getCause() instanceof Error) {
1✔
734
          throw (Error) e.getCause();
1✔
735
        }
736
        throw e;
1✔
737
      }
738
    }
739

740
    @Override
741
    public void put(K key, V value) {
742
      requireNonNull(value);
1✔
743
      asyncCache().cache().put(key, CompletableFuture.completedFuture(value));
1✔
744
    }
1✔
745

746
    @Override
747
    public void putAll(Map<? extends K, ? extends V> map) {
748
      map.forEach(this::put);
1✔
749
    }
1✔
750

751
    @Override
752
    public void invalidate(K key) {
753
      asyncCache().cache().remove(key);
1✔
754
    }
1✔
755

756
    @Override
757
    public void invalidateAll(Iterable<? extends K> keys) {
758
      asyncCache().cache().invalidateAll(keys);
1✔
759
    }
1✔
760

761
    @Override
762
    public void invalidateAll() {
763
      asyncCache().cache().clear();
1✔
764
    }
1✔
765

766
    @Override
767
    public long estimatedSize() {
768
      return asyncCache().cache().estimatedSize();
1✔
769
    }
770

771
    @Override
772
    public CacheStats stats() {
773
      return asyncCache().cache().statsCounter().snapshot();
1✔
774
    }
775

776
    @Override
777
    public void cleanUp() {
778
      asyncCache().cache().cleanUp();
1✔
779
    }
1✔
780

781
    @Override
782
    public Policy<K, V> policy() {
783
      return asyncCache().policy();
1✔
784
    }
785

786
    @Override
787
    public ConcurrentMap<K, V> asMap() {
788
      return (asMapView == null) ? (asMapView = new AsMapView<>(asyncCache().cache())) : asMapView;
1✔
789
    }
790

791
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
UNCOV
792
      throw new InvalidObjectException("Proxy required");
×
793
    }
794

795
    @SuppressWarnings("unused")
796
    private void readObjectNoData() throws InvalidObjectException {
797
      throw new InvalidObjectException("Proxy required");
1✔
798
    }
799

800
    Object writeReplace() {
801
      return new SyncViewProxy<>(asyncCache());
1✔
802
    }
803
  }
804

805
  final class AsMapView<K, V> implements ConcurrentMap<K, V> {
806
    final LocalCache<K, CompletableFuture<V>> delegate;
807

808
    @Nullable Set<K> keys;
809
    @Nullable Collection<V> values;
810
    @Nullable Set<Entry<K, V>> entries;
811

812
    AsMapView(LocalCache<K, CompletableFuture<V>> delegate) {
1✔
813
      this.delegate = delegate;
1✔
814
    }
1✔
815

816
    @Override
817
    public boolean isEmpty() {
818
      return delegate.isEmpty();
1✔
819
    }
820

821
    @Override
822
    public int size() {
823
      return delegate.size();
1✔
824
    }
825

826
    @Override
827
    public void clear() {
828
      delegate.clear();
1✔
829
    }
1✔
830

831
    @Override
832
    public boolean containsKey(Object key) {
833
      return Async.isReady(delegate.getIfPresentQuietly(key));
1✔
834
    }
835

836
    @Override
837
    public boolean containsValue(Object value) {
838
      requireNonNull(value);
1✔
839

840
      for (CompletableFuture<V> valueFuture : delegate.values()) {
1✔
841
        if (value.equals(Async.getIfReady(valueFuture))) {
1✔
842
          return true;
1✔
843
        }
844
      }
1✔
845
      return false;
1✔
846
    }
847

848
    @Override
849
    public @Nullable V get(Object key) {
850
      return Async.getIfReady(delegate.get(key));
1✔
851
    }
852

853
    @Override
854
    @SuppressWarnings("ResultOfMethodCallIgnored")
855
    public @Nullable V putIfAbsent(K key, V value) {
856
      requireNonNull(value);
1✔
857

858
      // Keep in sync with BoundedVarExpiration.putIfAbsentAsync(key, value, duration, unit)
859
      @Var CompletableFuture<V> priorFuture = null;
1✔
860
      for (;;) {
861
        priorFuture = (priorFuture == null)
1✔
862
            ? delegate.get(key)
1✔
863
            : delegate.getIfPresentQuietly(key);
1✔
864
        if (priorFuture != null) {
1✔
865
          if (!priorFuture.isDone()) {
1✔
866
            Async.getWhenSuccessful(priorFuture);
1✔
867
            continue;
1✔
868
          }
869

870
          V prior = Async.getWhenSuccessful(priorFuture);
1✔
871
          if (prior != null) {
1✔
872
            return prior;
1✔
873
          }
874
        }
875

876
        boolean[] added = { false };
1✔
877
        var hints = new LocalCache.RemapHints();
1✔
878
        CompletableFuture<V> computed = delegate.compute(
1✔
879
            key, (K k, @Nullable CompletableFuture<V> valueFuture) -> {
880
              added[0] = (valueFuture == null)
1✔
881
                  || (valueFuture.isDone() && (Async.getIfReady(valueFuture) == null));
1!
882
              if (added[0]) {
1!
883
                return CompletableFuture.completedFuture(value);
1✔
884
              }
UNCOV
885
              hints.preserveTimestamps = true;
×
UNCOV
886
              hints.preserveRefresh = true;
×
UNCOV
887
              return valueFuture;
×
888
            }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false, hints);
1✔
889

890
        if (added[0]) {
1!
891
          return null;
1✔
892
        } else {
UNCOV
893
          V prior = Async.getWhenSuccessful(computed);
×
UNCOV
894
          if (prior != null) {
×
UNCOV
895
            return prior;
×
896
          }
897
        }
UNCOV
898
      }
×
899
    }
900

901
    @Override
902
    @SuppressWarnings("ResultOfMethodCallIgnored")
903
    public void putAll(Map<? extends K, ? extends V> map) {
904
      map.forEach(this::put);
1✔
905
    }
1✔
906

907
    @Override
908
    public @Nullable V put(K key, V value) {
909
      requireNonNull(value);
1✔
910
      CompletableFuture<V> oldValueFuture =
1✔
911
          delegate.put(key, CompletableFuture.completedFuture(value));
1✔
912
      return Async.getWhenSuccessful(oldValueFuture);
1✔
913
    }
914

915
    @Override
916
    public @Nullable V remove(Object key) {
917
      CompletableFuture<V> oldValueFuture = delegate.remove(key);
1✔
918
      return Async.getWhenSuccessful(oldValueFuture);
1✔
919
    }
920

921
    @Override
922
    @SuppressWarnings("ResultOfMethodCallIgnored")
923
    public boolean remove(Object key, @Nullable Object value) {
924
      requireNonNull(key);
1✔
925
      if (value == null) {
1✔
926
        return false;
1✔
927
      }
928

929
      @SuppressWarnings("unchecked")
930
      var castedKey = (K) key;
1✔
931
      boolean[] done = { false };
1✔
932
      boolean[] removed = { false };
1✔
933
      for (;;) {
934
        CompletableFuture<V> future = delegate.getIfPresentQuietly(castedKey);
1✔
935
        if ((future == null) || future.isCompletedExceptionally()) {
1✔
936
          return false;
1✔
937
        }
938

939
        Async.getWhenSuccessful(future);
1✔
940
        var hints = new LocalCache.RemapHints();
1✔
941
        delegate.compute(castedKey, (K k, @Nullable CompletableFuture<V> oldValueFuture) -> {
1✔
942
          if (oldValueFuture == null) {
1✔
943
            done[0] = true;
1✔
944
            return null;
1✔
945
          } else if (!oldValueFuture.isDone()) {
1✔
946
            hints.preserveTimestamps = true;
1✔
947
            hints.preserveRefresh = true;
1✔
948
            return oldValueFuture;
1✔
949
          }
950

951
          done[0] = true;
1✔
952
          V oldValue = Async.getIfReady(oldValueFuture);
1✔
953
          removed[0] = Objects.equals(value, oldValue);
1✔
954
          if (!removed[0] && (oldValue != null)) {
1✔
955
            hints.preserveTimestamps = true;
1✔
956
            hints.preserveRefresh = true;
1✔
957
          }
958
          return (oldValue == null) || removed[0] ? null : oldValueFuture;
1✔
959
        }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ true, hints);
1✔
960

961
        if (done[0]) {
1✔
962
          return removed[0];
1✔
963
        }
964
      }
1✔
965
    }
966

967
    @Override
968
    @SuppressWarnings("ResultOfMethodCallIgnored")
969
    public @Nullable V replace(K key, V value) {
970
      requireNonNull(value);
1✔
971

972
      @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
973
      @Nullable V[] oldValue = (V[]) new Object[1];
1✔
974
      boolean[] done = { false };
1✔
975
      for (;;) {
976
        CompletableFuture<V> future = delegate.getIfPresentQuietly(key);
1✔
977
        if ((future == null) || future.isCompletedExceptionally()) {
1!
978
          return null;
1✔
979
        }
980

981
        Async.getWhenSuccessful(future);
1✔
982
        var hints = new LocalCache.RemapHints();
1✔
983
        delegate.compute(key, (K k, @Nullable CompletableFuture<V> oldValueFuture) -> {
1✔
984
          if (oldValueFuture == null) {
1✔
985
            done[0] = true;
1✔
986
            return null;
1✔
987
          } else if (!oldValueFuture.isDone()) {
1!
UNCOV
988
            hints.preserveTimestamps = true;
×
UNCOV
989
            hints.preserveRefresh = true;
×
UNCOV
990
            return oldValueFuture;
×
991
          }
992

993
          done[0] = true;
1✔
994
          oldValue[0] = Async.getIfReady(oldValueFuture);
1✔
995
          return (oldValue[0] == null) ? null : CompletableFuture.completedFuture(value);
1✔
996
        }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false, hints);
1✔
997

998
        if (done[0]) {
1!
999
          return oldValue[0];
1✔
1000
        }
UNCOV
1001
      }
×
1002
    }
1003

1004
    @Override
1005
    @SuppressWarnings("ResultOfMethodCallIgnored")
1006
    public boolean replace(K key, V oldValue, V newValue) {
1007
      requireNonNull(oldValue);
1✔
1008
      requireNonNull(newValue);
1✔
1009

1010
      boolean[] done = { false };
1✔
1011
      boolean[] replaced = { false };
1✔
1012
      for (;;) {
1013
        CompletableFuture<V> future = delegate.getIfPresentQuietly(key);
1✔
1014
        if ((future == null) || future.isCompletedExceptionally()) {
1!
1015
          return false;
1✔
1016
        }
1017

1018
        Async.getWhenSuccessful(future);
1✔
1019
        var hints = new LocalCache.RemapHints();
1✔
1020
        delegate.compute(key, (K k, @Nullable CompletableFuture<V> oldValueFuture) -> {
1✔
1021
          if (oldValueFuture == null) {
1✔
1022
            done[0] = true;
1✔
1023
            return null;
1✔
1024
          } else if (!oldValueFuture.isDone()) {
1!
UNCOV
1025
            hints.preserveTimestamps = true;
×
UNCOV
1026
            hints.preserveRefresh = true;
×
UNCOV
1027
            return oldValueFuture;
×
1028
          }
1029

1030
          done[0] = true;
1✔
1031
          replaced[0] = Objects.equals(oldValue, Async.getIfReady(oldValueFuture));
1✔
1032
          if (!replaced[0]) {
1✔
1033
            hints.preserveTimestamps = true;
1✔
1034
            hints.preserveRefresh = true;
1✔
1035
          }
1036
          return replaced[0] ? CompletableFuture.completedFuture(newValue) : oldValueFuture;
1✔
1037
        }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false, hints);
1✔
1038

1039
        if (done[0]) {
1!
1040
          return replaced[0];
1✔
1041
        }
UNCOV
1042
      }
×
1043
    }
1044

1045
    @Override
1046
    @SuppressWarnings("ResultOfMethodCallIgnored")
1047
    public @Nullable V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
1048
      requireNonNull(mappingFunction);
1✔
1049

1050
      @Var CompletableFuture<V> priorFuture = null;
1✔
1051
      for (;;) {
1052
        priorFuture = (priorFuture == null)
1✔
1053
            ? delegate.get(key)
1✔
1054
            : delegate.getIfPresentQuietly(key);
1✔
1055
        if (priorFuture != null) {
1✔
1056
          if (!priorFuture.isDone()) {
1✔
1057
            Async.getWhenSuccessful(priorFuture);
1✔
1058
            continue;
1✔
1059
          }
1060

1061
          V prior = Async.getWhenSuccessful(priorFuture);
1✔
1062
          if (prior != null) {
1✔
1063
            delegate.statsCounter().recordHits(1);
1✔
1064
            return prior;
1✔
1065
          }
1066
        }
1067

1068
        @SuppressWarnings({"rawtypes", "unchecked"})
1069
        CompletableFuture<V>[] future = new CompletableFuture[1];
1✔
1070
        var hints = new LocalCache.RemapHints();
1✔
1071
        CompletableFuture<V> computed = delegate.compute(
1✔
1072
            key, (K k, @Nullable CompletableFuture<V> valueFuture) -> {
1073
              if ((valueFuture != null)
1✔
1074
                  && (!valueFuture.isDone() || (Async.getIfReady(valueFuture) != null))) {
1✔
1075
                hints.preserveTimestamps = true;
1✔
1076
                hints.preserveRefresh = true;
1✔
1077
                return valueFuture;
1✔
1078
              }
1079

1080
              V newValue = delegate.statsAware(mappingFunction, /* recordLoad= */ true).apply(key);
1✔
1081
              if (newValue == null) {
1!
UNCOV
1082
                return null;
×
1083
              }
1084
              future[0] = CompletableFuture.completedFuture(newValue);
1✔
1085
              return future[0];
1✔
1086
            }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false, hints);
1✔
1087

1088
        V result = Async.getWhenSuccessful(computed);
1✔
1089
        if ((computed == future[0]) || (result != null)) {
1✔
1090
          return result;
1✔
1091
        }
1092
      }
1✔
1093
    }
1094

1095
    @Override
1096
    @SuppressWarnings("ResultOfMethodCallIgnored")
1097
    public @Nullable V computeIfPresent(K key,
1098
        BiFunction<? super K, ? super V, ? extends @Nullable V> remappingFunction) {
1099
      requireNonNull(remappingFunction);
1✔
1100

1101
      @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
1102
      @Nullable V[] newValue = (@Nullable V[]) new Object[1];
1✔
1103
      for (;;) {
1104
        Async.getWhenSuccessful(delegate.getIfPresentQuietly(key));
1✔
1105

1106
        var hints = new LocalCache.RemapHints();
1✔
1107
        CompletableFuture<V> valueFuture = delegate.compute(
1✔
1108
            key, (K k, @Nullable CompletableFuture<V> oldValueFuture) -> {
1109
              if (oldValueFuture == null) {
1✔
1110
                return null;
1✔
1111
              } else if (!oldValueFuture.isDone()) {
1✔
1112
                hints.preserveTimestamps = true;
1✔
1113
                hints.preserveRefresh = true;
1✔
1114
                return oldValueFuture;
1✔
1115
              }
1116

1117
              V oldValue = Async.getIfReady(oldValueFuture);
1✔
1118
              if (oldValue == null) {
1!
UNCOV
1119
                return null;
×
1120
              }
1121

1122
              BiFunction<? super K, ? super V, ? extends @Nullable V> function =
1✔
1123
                  delegate.statsAware(remappingFunction);
1✔
1124
              newValue[0] = function.apply(key, oldValue);
1✔
1125
              return (newValue[0] == null) ? null : CompletableFuture.completedFuture(newValue[0]);
1!
1126
            }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false, hints);
1✔
1127

1128
        if (newValue[0] != null) {
1✔
1129
          return newValue[0];
1✔
1130
        } else if (valueFuture == null) {
1✔
1131
          return null;
1✔
1132
        }
1133
      }
1✔
1134
    }
1135

1136
    @Override
1137
    @SuppressWarnings("ResultOfMethodCallIgnored")
1138
    public @Nullable V compute(K key,
1139
        BiFunction<? super K, ? super @Nullable V, ? extends @Nullable V> remappingFunction) {
1140
      // Keep in sync with BoundedVarExpiration.computeAsync(key, remappingFunction, expiry)
1141
      requireNonNull(remappingFunction);
1✔
1142

1143
      @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
1144
      @Nullable V[] newValue = (@Nullable V[]) new Object[1];
1✔
1145
      for (;;) {
1146
        Async.getWhenSuccessful(delegate.getIfPresentQuietly(key));
1✔
1147

1148
        var hints = new LocalCache.RemapHints();
1✔
1149
        CompletableFuture<V> valueFuture = delegate.compute(
1✔
1150
            key, (K k, @Nullable CompletableFuture<V> oldValueFuture) -> {
1151
              if ((oldValueFuture != null) && !oldValueFuture.isDone()) {
1✔
1152
                hints.preserveTimestamps = true;
1✔
1153
                hints.preserveRefresh = true;
1✔
1154
                return oldValueFuture;
1✔
1155
              }
1156

1157
              V oldValue = Async.getIfReady(oldValueFuture);
1✔
1158
              BiFunction<? super K, ? super V, ? extends @Nullable V> function =
1✔
1159
                  delegate.statsAware(remappingFunction,
1✔
1160
                      /* recordLoad= */ true, /* recordLoadFailure= */ true);
1161
              newValue[0] = function.apply(key, oldValue);
1✔
1162
              return (newValue[0] == null) ? null : CompletableFuture.completedFuture(newValue[0]);
1✔
1163
            }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false, hints);
1✔
1164

1165
        if (newValue[0] != null) {
1✔
1166
          return newValue[0];
1✔
1167
        } else if (valueFuture == null) {
1✔
1168
          return null;
1✔
1169
        }
1170
      }
1✔
1171
    }
1172

1173
    @Override
1174
    @SuppressWarnings("ResultOfMethodCallIgnored")
1175
    public @Nullable V merge(K key, V value,
1176
        BiFunction<? super V, ? super V, ? extends @Nullable V> remappingFunction) {
1177
      requireNonNull(value);
1✔
1178
      requireNonNull(remappingFunction);
1✔
1179

1180
      CompletableFuture<V> newValueFuture = CompletableFuture.completedFuture(value);
1✔
1181
      boolean[] merged = { false };
1✔
1182
      for (;;) {
1183
        Async.getWhenSuccessful(delegate.getIfPresentQuietly(key));
1✔
1184

1185
        var hints = new LocalCache.RemapHints();
1✔
1186
        CompletableFuture<V> mergedValueFuture = delegate.compute(
1✔
1187
            key, (K k, @Nullable CompletableFuture<V> oldValueFuture) -> {
1188
              if (oldValueFuture == null) {
1✔
1189
                merged[0] = true;
1✔
1190
                return newValueFuture;
1✔
1191
              } else if (!oldValueFuture.isDone()) {
1!
UNCOV
1192
                hints.preserveTimestamps = true;
×
UNCOV
1193
                hints.preserveRefresh = true;
×
UNCOV
1194
                return oldValueFuture;
×
1195
              }
1196

1197
              merged[0] = true;
1✔
1198
              V oldValue = Async.getIfReady(oldValueFuture);
1✔
1199
              if (oldValue == null) {
1!
UNCOV
1200
                return newValueFuture;
×
1201
              }
1202
              V mergedValue = delegate.statsAware(remappingFunction).apply(oldValue, value);
1✔
1203
              if (mergedValue == null) {
1✔
1204
                return null;
1✔
1205
              } else if (mergedValue == oldValue) {
1✔
1206
                hints.preserveTimestamps = true;
1✔
1207
                hints.preserveRefresh = true;
1✔
1208
                return oldValueFuture;
1✔
1209
              } else if (mergedValue == value) {
1✔
1210
                return newValueFuture;
1✔
1211
              }
1212
              return CompletableFuture.completedFuture(mergedValue);
1✔
1213
            }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false, hints);
1✔
1214

1215
        if (merged[0]) {
1!
1216
          return Async.getWhenSuccessful(mergedValueFuture);
1✔
1217
        }
UNCOV
1218
      }
×
1219
    }
1220

1221
    @Override
1222
    public Set<K> keySet() {
1223
      return (keys == null) ? (keys = new KeySet()) : keys;
1✔
1224
    }
1225

1226
    @Override
1227
    public Collection<V> values() {
1228
      return (values == null) ? (values = new Values()) : values;
1✔
1229
    }
1230

1231
    @Override
1232
    public Set<Entry<K, V>> entrySet() {
1233
      return (entries == null) ? (entries = new EntrySet()) : entries;
1✔
1234
    }
1235

1236
    /** See {@link BoundedLocalCache#equals(Object)} for semantics. */
1237
    @Override
1238
    public boolean equals(@Nullable Object o) {
1239
      if (o == this) {
1!
UNCOV
1240
        return true;
×
1241
      } else if (!(o instanceof Map)) {
1✔
1242
        return false;
1✔
1243
      }
1244

1245
      var map = (Map<?, ?>) o;
1✔
1246
      int expectedSize = size();
1✔
1247
      if (map.size() != expectedSize) {
1✔
1248
        return false;
1✔
1249
      }
1250

1251
      @Var int count = 0;
1✔
1252
      try {
1253
        for (var iterator = new EntryIterator(); iterator.hasNext();) {
1✔
1254
          var entry = iterator.next();
1✔
1255
          var value = map.get(entry.getKey());
1✔
1256
          if ((value == null) || ((value != entry.getValue()) && !value.equals(entry.getValue()))) {
1✔
1257
            return false;
1✔
1258
          }
1259
          count++;
1✔
1260
        }
1✔
UNCOV
1261
      } catch (ClassCastException | NullPointerException ignored) {
×
UNCOV
1262
        return false;
×
1263
      }
1✔
1264
      return (count == expectedSize);
1✔
1265
    }
1266

1267
    @Override
1268
    public int hashCode() {
1269
      @Var int hash = 0;
1✔
1270
      for (var iterator = new EntryIterator(); iterator.hasNext();) {
1✔
1271
        var entry = iterator.next();
1✔
1272
        hash += entry.hashCode();
1✔
1273
      }
1✔
1274
      return hash;
1✔
1275
    }
1276

1277
    @Override
1278
    public String toString() {
1279
      var result = new StringBuilder(50).append('{');
1✔
1280
      for (var iterator = new EntryIterator(); iterator.hasNext();) {
1✔
1281
        var entry = iterator.next();
1✔
1282
        result.append((entry.getKey() == this) ? "(this Map)" : entry.getKey())
1✔
1283
            .append('=')
1✔
1284
            .append((entry.getValue() == this) ? "(this Map)" : entry.getValue());
1✔
1285

1286
        if (iterator.hasNext()) {
1✔
1287
          result.append(", ");
1✔
1288
        }
1289
      }
1✔
1290
      return result.append('}').toString();
1✔
1291
    }
1292

1293
    private final class KeySet extends AbstractSet<K> {
1✔
1294

1295
      @Override
1296
      public boolean isEmpty() {
1297
        return AsMapView.this.isEmpty();
1✔
1298
      }
1299

1300
      @Override
1301
      public int size() {
1302
        return AsMapView.this.size();
1✔
1303
      }
1304

1305
      @Override
1306
      public void clear() {
1307
        AsMapView.this.clear();
1✔
1308
      }
1✔
1309

1310
      @Override
1311
      public boolean contains(Object o) {
1312
        return AsMapView.this.containsKey(o);
1✔
1313
      }
1314

1315
      @Override
1316
      public boolean containsAll(Collection<?> collection) {
1317
        requireNonNull(collection);
1✔
1318
        if (collection != this) {
1!
1319
          for (Object o : collection) {
1✔
1320
            if ((o == null) || !contains(o)) {
1✔
1321
              return false;
1✔
1322
            }
1323
          }
1✔
1324
        }
1325
        return true;
1✔
1326
      }
1327

1328
      @Override
1329
      public boolean removeAll(Collection<?> collection) {
1330
        return delegate.keySet().removeAll(collection);
1✔
1331
      }
1332

1333
      @Override
1334
      @SuppressWarnings("RedundantCollectionOperation")
1335
      public boolean remove(Object o) {
1336
        return delegate.keySet().remove(o);
1✔
1337
      }
1338

1339
      @Override
1340
      public boolean removeIf(Predicate<? super K> filter) {
UNCOV
1341
        return delegate.keySet().removeIf(filter);
×
1342
      }
1343

1344
      @Override
1345
      public boolean retainAll(Collection<?> collection) {
1346
        return delegate.keySet().retainAll(collection);
1✔
1347
      }
1348

1349
      @Override
1350
      public Iterator<K> iterator() {
1351
        return new KeyIterator<>(new EntryIterator());
1✔
1352
      }
1353

1354
      @Override
1355
      public Spliterator<K> spliterator() {
1356
        return new KeySpliterator<>(new EntrySpliterator());
1✔
1357
      }
1358
    }
1359

1360
    private static final class KeyIterator<K, V> implements Iterator<K> {
1361
      private final Iterator<Entry<K, V>> iterator;
1362

1363
      KeyIterator(Iterator<Entry<K, V>> iterator) {
1✔
1364
        this.iterator = requireNonNull(iterator);
1✔
1365
      }
1✔
1366

1367
      @Override
1368
      public boolean hasNext() {
1369
        return iterator.hasNext();
1✔
1370
      }
1371

1372
      @Override
1373
      public K next() {
1374
        return iterator.next().getKey();
1✔
1375
      }
1376

1377
      @Override
1378
      public void remove() {
1379
        iterator.remove();
1✔
1380
      }
1✔
1381
    }
1382

1383
    private static final class KeySpliterator<K, V> implements Spliterator<K> {
1384
      private final Spliterator<Entry<K, V>> spliterator;
1385

1386
      KeySpliterator(Spliterator<Entry<K, V>> iterator) {
1✔
1387
        this.spliterator = requireNonNull(iterator);
1✔
1388
      }
1✔
1389

1390
      @Override
1391
      public boolean tryAdvance(Consumer<? super K> action) {
1392
        requireNonNull(action);
1✔
1393
        return spliterator.tryAdvance(entry -> {
1✔
1394
          action.accept(entry.getKey());
1✔
1395
        });
1✔
1396
      }
1397

1398
      @Override
1399
      public @Nullable Spliterator<K> trySplit() {
UNCOV
1400
        Spliterator<Entry<K, V>> split = spliterator.trySplit();
×
UNCOV
1401
        return (split == null) ? null : new KeySpliterator<>(split);
×
1402
      }
1403

1404
      @Override
1405
      public long estimateSize() {
UNCOV
1406
        return spliterator.estimateSize();
×
1407
      }
1408

1409
      @Override
1410
      public int characteristics() {
1411
        return DISTINCT | NONNULL | CONCURRENT;
1✔
1412
      }
1413
    }
1414

1415
    private final class Values extends AbstractCollection<V> {
1✔
1416

1417
      @Override
1418
      public boolean isEmpty() {
1419
        return AsMapView.this.isEmpty();
1✔
1420
      }
1421

1422
      @Override
1423
      public int size() {
1424
        return AsMapView.this.size();
1✔
1425
      }
1426

1427
      @Override
1428
      public void clear() {
1429
        AsMapView.this.clear();
1✔
1430
      }
1✔
1431

1432
      @Override
1433
      public boolean contains(Object o) {
1434
        return AsMapView.this.containsValue(o);
1✔
1435
      }
1436

1437
      @Override
1438
      public boolean containsAll(Collection<?> collection) {
1439
        requireNonNull(collection);
1✔
1440
        if (collection != this) {
1!
1441
          for (Object o : collection) {
1✔
1442
            if ((o == null) || !contains(o)) {
1✔
1443
              return false;
1✔
1444
            }
1445
          }
1✔
1446
        }
1447
        return true;
1✔
1448
      }
1449

1450
      @Override
1451
      public boolean removeAll(Collection<?> collection) {
1452
        requireNonNull(collection);
1✔
1453
        @Var boolean modified = false;
1✔
1454
        for (var entry : delegate.entrySet()) {
1✔
1455
          V value = Async.getIfReady(entry.getValue());
1✔
1456
          if ((value != null) && collection.contains(value)
1!
1457
              && AsMapView.this.remove(entry.getKey(), value)) {
1!
1458
            modified = true;
1✔
1459
          }
1460
        }
1✔
1461
        return modified;
1✔
1462
      }
1463

1464
      @Override
1465
      public boolean remove(@Nullable Object o) {
1466
        if (o == null) {
1✔
1467
          return false;
1✔
1468
        }
1469
        for (var entry : delegate.entrySet()) {
1✔
1470
          V value = Async.getIfReady(entry.getValue());
1✔
1471
          if (o.equals(value) && AsMapView.this.remove(entry.getKey(), value)) {
1!
1472
            return true;
1✔
1473
          }
1474
        }
1✔
1475
        return false;
1✔
1476
      }
1477

1478
      @Override
1479
      public boolean removeIf(Predicate<? super V> filter) {
1480
        requireNonNull(filter);
1✔
1481
        return delegate.values().removeIf(future -> {
1✔
1482
          V value = Async.getIfReady(future);
1✔
1483
          return (value != null) && filter.test(value);
1!
1484
        });
1485
      }
1486

1487
      @Override
1488
      public boolean retainAll(Collection<?> collection) {
1489
        requireNonNull(collection);
1✔
1490
        @Var boolean modified = false;
1✔
1491
        for (var entry : delegate.entrySet()) {
1✔
1492
          V value = Async.getIfReady(entry.getValue());
1✔
1493
          if ((value != null) && !collection.contains(value)
1!
1494
              && AsMapView.this.remove(entry.getKey(), value)) {
1!
1495
            modified = true;
1✔
1496
          }
1497
        }
1✔
1498
        return modified;
1✔
1499
      }
1500

1501
      @Override
1502
      public void forEach(Consumer<? super V> action) {
1503
        requireNonNull(action);
1✔
1504
        delegate.values().forEach(future -> {
1✔
1505
          V value = Async.getIfReady(future);
1✔
1506
          if (value != null) {
1!
1507
            action.accept(value);
1✔
1508
          }
1509
        });
1✔
1510
      }
1✔
1511

1512
      @Override
1513
      public Iterator<V> iterator() {
1514
        return new ValueIterator<>(new EntryIterator());
1✔
1515
      }
1516

1517
      @Override
1518
      public Spliterator<V> spliterator() {
1519
        return new ValueSpliterator<>(new EntrySpliterator());
1✔
1520
      }
1521
    }
1522

1523
    private static final class ValueIterator<K, V> implements Iterator<V> {
1524
      private final Iterator<Entry<K, V>> iterator;
1525

1526
      ValueIterator(Iterator<Entry<K, V>> iterator) {
1✔
1527
        this.iterator = requireNonNull(iterator);
1✔
1528
      }
1✔
1529

1530
      @Override
1531
      public boolean hasNext() {
1532
        return iterator.hasNext();
1✔
1533
      }
1534

1535
      @Override
1536
      public V next() {
1537
        return iterator.next().getValue();
1✔
1538
      }
1539

1540
      @Override
1541
      public void remove() {
1542
        iterator.remove();
1✔
1543
      }
1✔
1544
    }
1545

1546
    private static final class ValueSpliterator<K, V> implements Spliterator<V> {
1547
      private final Spliterator<Entry<K, V>> spliterator;
1548

1549
      ValueSpliterator(Spliterator<Entry<K, V>> iterator) {
1✔
1550
        this.spliterator = requireNonNull(iterator);
1✔
1551
      }
1✔
1552

1553
      @Override
1554
      public boolean tryAdvance(Consumer<? super V> action) {
1555
        requireNonNull(action);
1✔
1556
        return spliterator.tryAdvance(entry -> {
1✔
1557
          action.accept(entry.getValue());
1✔
1558
        });
1✔
1559
      }
1560

1561
      @Override
1562
      public @Nullable Spliterator<V> trySplit() {
1563
        Spliterator<Entry<K, V>> split = spliterator.trySplit();
1✔
1564
        return (split == null) ? null : new ValueSpliterator<>(split);
1✔
1565
      }
1566

1567
      @Override
1568
      public long estimateSize() {
UNCOV
1569
        return spliterator.estimateSize();
×
1570
      }
1571

1572
      @Override
1573
      public int characteristics() {
1574
        return NONNULL | CONCURRENT;
1✔
1575
      }
1576
    }
1577

1578
    private final class EntrySet extends AbstractSet<Entry<K, V>> {
1✔
1579

1580
      @Override
1581
      public boolean isEmpty() {
1582
        return AsMapView.this.isEmpty();
1✔
1583
      }
1584

1585
      @Override
1586
      public int size() {
1587
        return AsMapView.this.size();
1✔
1588
      }
1589

1590
      @Override
1591
      public void clear() {
1592
        AsMapView.this.clear();
1✔
1593
      }
1✔
1594

1595
      @Override
1596
      public boolean contains(Object o) {
1597
        if (!(o instanceof Entry<?, ?>)) {
1✔
1598
          return false;
1✔
1599
        }
1600
        var entry = (Entry<?, ?>) o;
1✔
1601
        var key = entry.getKey();
1✔
1602
        var value = entry.getValue();
1✔
1603
        if ((key == null) || (value == null)) {
1✔
1604
          return false;
1✔
1605
        }
1606
        V cachedValue = Async.getIfReady(delegate.getIfPresentQuietly(key));
1✔
1607
        return value.equals(cachedValue);
1✔
1608
      }
1609

1610
      @Override
1611
      public boolean removeAll(Collection<?> collection) {
1612
        requireNonNull(collection);
1✔
1613
        @Var boolean modified = false;
1✔
1614
        if (delegate.collectKeys()
1✔
1615
            || ((collection instanceof Set<?>) && (collection.size() > size()))) {
1✔
1616
          for (var entry : this) {
1✔
1617
            if (collection.contains(entry)) {
1✔
1618
              modified |= remove(entry);
1✔
1619
            }
1620
          }
1✔
1621
        } else {
1622
          for (var o : collection) {
1✔
1623
            modified |= remove(o);
1✔
1624
          }
1✔
1625
        }
1626
        return modified;
1✔
1627
      }
1628

1629
      @Override
1630
      public boolean remove(Object obj) {
1631
        if (!(obj instanceof Entry<?, ?>)) {
1✔
1632
          return false;
1✔
1633
        }
1634
        var entry = (Entry<?, ?>) obj;
1✔
1635
        var key = entry.getKey();
1✔
1636
        return (key != null) && AsMapView.this.remove(key, entry.getValue());
1✔
1637
      }
1638

1639
      @Override
1640
      public boolean removeIf(Predicate<? super Entry<K, V>> filter) {
1641
        requireNonNull(filter);
1✔
1642
        return delegate.entrySet().removeIf(entry -> {
1✔
1643
          V value = Async.getIfReady(entry.getValue());
1✔
1644
          return (value != null) && filter.test(Map.entry(entry.getKey(), value));
1✔
1645
        });
1646
      }
1647

1648
      @Override
1649
      public boolean retainAll(Collection<?> collection) {
1650
        requireNonNull(collection);
1✔
1651
        @Var boolean modified = false;
1✔
1652
        for (var entry : this) {
1✔
1653
          if (!collection.contains(entry) && remove(entry)) {
1!
1654
            modified = true;
1✔
1655
          }
1656
        }
1✔
1657
        return modified;
1✔
1658
      }
1659

1660
      @Override
1661
      public Iterator<Entry<K, V>> iterator() {
1662
        return new EntryIterator();
1✔
1663
      }
1664

1665
      @Override
1666
      public Spliterator<Entry<K, V>> spliterator() {
1667
        return new EntrySpliterator();
1✔
1668
      }
1669
    }
1670

1671
    private final class EntryIterator implements Iterator<Entry<K, V>> {
1672
      final Iterator<Entry<K, CompletableFuture<V>>> iterator;
1673
      @Nullable Entry<K, V> cursor;
1674
      @Nullable K removalKey;
1675

1676
      EntryIterator() {
1✔
1677
        this.iterator = delegate.entrySet().iterator();
1✔
1678
      }
1✔
1679

1680
      @Override
1681
      public boolean hasNext() {
1682
        while ((cursor == null) && iterator.hasNext()) {
1✔
1683
          Entry<K, CompletableFuture<V>> entry = iterator.next();
1✔
1684
          V value = Async.getIfReady(entry.getValue());
1✔
1685
          if (value != null) {
1!
1686
            cursor = new WriteThroughEntry<>(AsMapView.this, entry.getKey(), value);
1✔
1687
          }
1688
        }
1✔
1689
        return (cursor != null);
1✔
1690
      }
1691

1692
      @Override
1693
      public Entry<K, V> next() {
1694
        if (!hasNext()) {
1✔
1695
          throw new NoSuchElementException();
1✔
1696
        }
1697
        K key = requireNonNull(cursor).getKey();
1✔
1698
        Entry<K, V> entry = cursor;
1✔
1699
        removalKey = key;
1✔
1700
        cursor = null;
1✔
1701
        return entry;
1✔
1702
      }
1703

1704
      @Override
1705
      public void remove() {
1706
        requireState(removalKey != null);
1✔
1707
        delegate.remove(removalKey);
1✔
1708
        removalKey = null;
1✔
1709
      }
1✔
1710
    }
1711

1712
    private final class EntrySpliterator implements Spliterator<Entry<K, V>> {
1713
      final Spliterator<Entry<K, CompletableFuture<V>>> spliterator;
1714

1715
      EntrySpliterator() {
1716
        this(delegate.entrySet().spliterator());
1✔
1717
      }
1✔
1718

1719
      EntrySpliterator(Spliterator<Entry<K, CompletableFuture<V>>> spliterator) {
1✔
1720
        this.spliterator = requireNonNull(spliterator);
1✔
1721
      }
1✔
1722

1723
      @Override
1724
      public void forEachRemaining(Consumer<? super Entry<K, V>> action) {
1725
        requireNonNull(action);
1✔
1726
        spliterator.forEachRemaining(entry -> {
1✔
1727
          V value = Async.getIfReady(entry.getValue());
1✔
1728
          if (value != null) {
1✔
1729
            var e = new WriteThroughEntry<>(AsMapView.this, entry.getKey(), value);
1✔
1730
            action.accept(e);
1✔
1731
          }
1732
        });
1✔
1733
      }
1✔
1734

1735
      @Override
1736
      public boolean tryAdvance(Consumer<? super Entry<K, V>> action) {
1737
        requireNonNull(action);
1✔
1738
        boolean[] advanced = { false };
1✔
1739
        Consumer<? super Entry<K, CompletableFuture<V>>> consumer = entry -> {
1✔
1740
          V value = Async.getIfReady(entry.getValue());
1✔
1741
          if (value != null) {
1✔
1742
            var e = new WriteThroughEntry<>(AsMapView.this, entry.getKey(), value);
1✔
1743
            action.accept(e);
1✔
1744
            advanced[0] = true;
1✔
1745
          }
1746
        };
1✔
1747
        while (spliterator.tryAdvance(consumer)) {
1✔
1748
          if (advanced[0]) {
1✔
1749
            return true;
1✔
1750
          }
1751
        }
1752
        return false;
1✔
1753
      }
1754

1755
      @Override
1756
      public @Nullable Spliterator<Entry<K, V>> trySplit() {
1757
        Spliterator<Entry<K, CompletableFuture<V>>> split = spliterator.trySplit();
1✔
1758
        return (split == null) ? null : new EntrySpliterator(split);
1✔
1759
      }
1760

1761
      @Override
1762
      public long estimateSize() {
1763
        return spliterator.estimateSize();
1✔
1764
      }
1765

1766
      @Override
1767
      public int characteristics() {
1768
        return DISTINCT | CONCURRENT | NONNULL;
1✔
1769
      }
1770
    }
1771
  }
1772

1773
  @SuppressWarnings("serial")
1774
  final class SyncViewProxy<K, V> implements Serializable {
1775
    private static final long serialVersionUID = 1;
1776

1777
    final AsyncCache<K, V> asyncCache;
1778

1779
    SyncViewProxy(AsyncCache<K, V> asyncCache) {
1✔
1780
      this.asyncCache = requireNonNull(asyncCache);
1✔
1781
    }
1✔
1782

1783
    @SuppressWarnings("unused")
1784
    private void readObject(ObjectInputStream stream)
1785
        throws IOException, ClassNotFoundException {
1786
      stream.defaultReadObject();
1✔
1787
      if (asyncCache == null) {
1✔
1788
        throw new InvalidObjectException("asyncCache required");
1✔
1789
      }
1790
    }
1✔
1791

1792
    Object readResolve() {
1793
      return asyncCache.synchronous();
1✔
1794
    }
1795
  }
1796
}
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