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

ben-manes / caffeine / #5155

03 Dec 2025 02:00AM UTC coverage: 0.0% (-100.0%) from 100.0%
#5155

push

github

ben-manes
add loading type to parameterized test dimensions to reduce task size

0 of 3834 branches covered (0.0%)

0 of 7848 relevant lines covered (0.0%)

0.0 hits per line

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

0.0
/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.Serializable;
24
import java.lang.System.Logger;
25
import java.lang.System.Logger.Level;
26
import java.util.AbstractCollection;
27
import java.util.AbstractSet;
28
import java.util.Collection;
29
import java.util.Collections;
30
import java.util.Iterator;
31
import java.util.LinkedHashMap;
32
import java.util.Map;
33
import java.util.NoSuchElementException;
34
import java.util.Objects;
35
import java.util.Set;
36
import java.util.Spliterator;
37
import java.util.concurrent.CancellationException;
38
import java.util.concurrent.CompletableFuture;
39
import java.util.concurrent.CompletionException;
40
import java.util.concurrent.ConcurrentMap;
41
import java.util.concurrent.Executor;
42
import java.util.concurrent.TimeoutException;
43
import java.util.function.BiConsumer;
44
import java.util.function.BiFunction;
45
import java.util.function.Consumer;
46
import java.util.function.Function;
47
import java.util.function.Predicate;
48

49
import org.jspecify.annotations.Nullable;
50

51
import com.github.benmanes.caffeine.cache.LocalAsyncCache.AsyncBulkCompleter.NullMapCompletionException;
52
import com.github.benmanes.caffeine.cache.stats.CacheStats;
53
import com.google.errorprone.annotations.CanIgnoreReturnValue;
54
import com.google.errorprone.annotations.Var;
55

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

65
  /** Returns the backing {@link LocalCache} data store. */
66
  LocalCache<K, CompletableFuture<V>> cache();
67

68
  /** Returns the policy supported by this implementation and its configuration. */
69
  Policy<K, V> policy();
70

71
  @Override
72
  default @Nullable CompletableFuture<V> getIfPresent(K key) {
73
    return cache().getIfPresent(key, /* recordStats= */ true);
×
74
  }
75

76
  @Override
77
  default CompletableFuture<V> get(K key, Function<? super K, ? extends V> mappingFunction) {
78
    requireNonNull(mappingFunction);
×
79
    return get(key, (k1, executor) -> CompletableFuture.supplyAsync(
×
80
        () -> mappingFunction.apply(key), executor));
×
81
  }
82

83
  @Override
84
  default CompletableFuture<V> get(K key, BiFunction<? super K, ? super Executor,
85
      ? extends CompletableFuture<? extends V>> mappingFunction) {
86
    return get(key, mappingFunction, /* recordStats= */ true);
×
87
  }
88

89
  default CompletableFuture<V> get(K key, BiFunction<? super K, ? super Executor,
90
      ? extends CompletableFuture<? extends V>> mappingFunction, boolean recordStats) {
91
    long startTime = cache().statsTicker().read();
×
92
    @SuppressWarnings({"rawtypes", "unchecked"})
93
    CompletableFuture<? extends V>[] result = new CompletableFuture[1];
×
94
    CompletableFuture<V> future = cache().computeIfAbsent(key, k -> {
×
95
      @SuppressWarnings("unchecked")
96
      var castedResult = (CompletableFuture<V>) mappingFunction.apply(key, cache().executor());
×
97
      result[0] = castedResult;
×
98
      return requireNonNull(castedResult);
×
99
    }, recordStats, /* recordLoad= */ false);
100
    if (result[0] != null) {
×
101
      handleCompletion(key, result[0], startTime, /* recordMiss= */ false);
×
102
    }
103
    return requireNonNull(future);
×
104
  }
105

106
  @Override
107
  default CompletableFuture<Map<K, V>> getAll(Iterable<? extends K> keys,
108
      Function<? super Set<? extends K>, ? extends Map<? extends K, ? extends V>> mappingFunction) {
109
    requireNonNull(mappingFunction);
×
110
    return getAll(keys, (keysToLoad, executor) ->
×
111
        CompletableFuture.supplyAsync(() -> mappingFunction.apply(keysToLoad), executor));
×
112
  }
113

114
  @Override
115
  @SuppressWarnings("FutureReturnValueIgnored")
116
  default CompletableFuture<Map<K, V>> getAll(Iterable<? extends K> keys,
117
      BiFunction<? super Set<? extends K>, ? super Executor,
118
          ? extends CompletableFuture<? extends Map<? extends K, ? extends V>>> mappingFunction) {
119
    requireNonNull(mappingFunction);
×
120
    requireNonNull(keys);
×
121

122
    int initialCapacity = calculateHashMapCapacity(keys);
×
123
    var futures = new LinkedHashMap<K, CompletableFuture<V>>(initialCapacity);
×
124
    var proxies = new LinkedHashMap<K, CompletableFuture<V>>(initialCapacity);
×
125
    for (K key : keys) {
×
126
      if (futures.containsKey(key)) {
×
127
        continue;
×
128
      }
129
      @Var CompletableFuture<V> future = cache().getIfPresent(key, /* recordStats= */ false);
×
130
      if (future == null) {
×
131
        var proxy = new CompletableFuture<V>();
×
132
        future = cache().putIfAbsent(key, proxy);
×
133
        if (future == null) {
×
134
          future = proxy;
×
135
          proxies.put(key, proxy);
×
136
        }
137
      }
138
      futures.put(key, future);
×
139
    }
×
140
    cache().statsCounter().recordMisses(proxies.size());
×
141
    cache().statsCounter().recordHits(futures.size() - proxies.size());
×
142
    if (proxies.isEmpty()) {
×
143
      return composeResult(futures);
×
144
    }
145

146
    var completer = new AsyncBulkCompleter<>(cache(), proxies);
×
147
    try {
148
      var loader = mappingFunction.apply(
×
149
          Collections.unmodifiableSet(proxies.keySet()), cache().executor());
×
150
      return loader.handle(completer).thenCompose(ignored -> composeResult(futures));
×
151
    } catch (Throwable t) {
×
152
      throw completer.error(t);
×
153
    }
154
  }
155

156
  /**
157
   * Returns a future that waits for all of the dependent futures to complete and returns the
158
   * combined mapping if successful. If any future fails then it is automatically removed from
159
   * the cache if still present.
160
   */
161
  static <K, V> CompletableFuture<Map<K, V>> composeResult(Map<K, CompletableFuture<V>> futures) {
162
    if (futures.isEmpty()) {
×
163
      @SuppressWarnings({"ImmutableMapOf", "RedundantUnmodifiable"})
164
      Map<K, V> emptyMap = Collections.unmodifiableMap(Collections.emptyMap());
×
165
      return CompletableFuture.completedFuture(emptyMap);
×
166
    }
167
    @SuppressWarnings("rawtypes")
168
    CompletableFuture<?>[] array = futures.values().toArray(new CompletableFuture[0]);
×
169
    return CompletableFuture.allOf(array).thenApply(ignored -> {
×
170
      var result = new LinkedHashMap<K, V>(calculateHashMapCapacity(futures.size()));
×
171
      futures.forEach((key, future) -> {
×
172
        V value = future.getNow(null);
×
173
        if (value != null) {
×
174
          result.put(key, value);
×
175
        }
176
      });
×
177
      return Collections.unmodifiableMap(result);
×
178
    });
179
  }
180

181
  @Override
182
  @SuppressWarnings("FutureReturnValueIgnored")
183
  default void put(K key, CompletableFuture<? extends V> valueFuture) {
184
    if (valueFuture.isCompletedExceptionally()
×
185
        || (valueFuture.isDone() && (valueFuture.join() == null))) {
×
186
      cache().statsCounter().recordLoadFailure(0L);
×
187
      cache().remove(key);
×
188
      return;
×
189
    }
190
    long startTime = cache().statsTicker().read();
×
191

192
    @SuppressWarnings("unchecked")
193
    var castedFuture = (CompletableFuture<V>) valueFuture;
×
194
    CompletableFuture<V> prior = cache().put(key, castedFuture);
×
195
    if (prior != castedFuture) {
×
196
      handleCompletion(key, valueFuture, startTime, /* recordMiss= */ false);
×
197
    }
198
  }
×
199

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

226
        try {
227
          // update the weight and expiration timestamps
228
          cache().replace(key, castedFuture, castedFuture, /* shouldDiscardRefresh= */ false);
×
229
          cache().statsCounter().recordLoadSuccess(loadTime);
×
230
        } catch (Throwable t) {
×
231
          logger.log(Level.WARNING, "Exception thrown during asynchronous load", t);
×
232
          cache().statsCounter().recordLoadFailure(loadTime);
×
233
          cache().remove(key, valueFuture);
×
234
        }
×
235
      }
236
      if (recordMiss) {
×
237
        cache().statsCounter().recordMisses(1);
×
238
      }
239
    });
×
240
  }
×
241

242
  /** A function executed asynchronously after a bulk load completes. */
243
  final class AsyncBulkCompleter<K, V> implements BiFunction<
244
      Map<? extends K, ? extends V>, Throwable, Map<? extends K, ? extends V>> {
245
    private final LocalCache<K, CompletableFuture<V>> cache;
246
    @SuppressWarnings("ImmutableMemberCollection")
247
    private final Map<K, CompletableFuture<V>> proxies;
248
    private final long startTime;
249

250
    AsyncBulkCompleter(LocalCache<K, CompletableFuture<V>> cache,
251
        Map<K, CompletableFuture<V>> proxies) {
×
252
      this.startTime = cache.statsTicker().read();
×
253
      this.proxies = proxies;
×
254
      this.cache = cache;
×
255
    }
×
256

257
    @Override
258
    @CanIgnoreReturnValue
259
    public @Nullable Map<? extends K, ? extends V> apply(
260
        @Nullable Map<? extends K, ? extends V> result, @Nullable Throwable error) {
261
      long loadTime = cache.statsTicker().read() - startTime;
×
262
      var failure = handleResponse(result, error);
×
263

264
      if (failure == null) {
×
265
        cache.statsCounter().recordLoadSuccess(loadTime);
×
266
        return result;
×
267
      }
268

269
      cache.statsCounter().recordLoadFailure(loadTime);
×
270
      if (failure instanceof RuntimeException) {
×
271
        throw (RuntimeException) failure;
×
272
      } else if (failure instanceof Error) {
×
273
        throw (Error) failure;
×
274
      }
275
      throw new CompletionException(failure);
×
276
    }
277

278
    public CompletionException error(Throwable error) {
279
      long loadTime = cache.statsTicker().read() - startTime;
×
280
      var failure = handleResponse(/* result= */ null, error);
×
281
      cache.statsCounter().recordLoadFailure(loadTime);
×
282
      if (failure instanceof RuntimeException) {
×
283
        throw (RuntimeException) failure;
×
284
      } else if (failure instanceof Error) {
×
285
        throw (Error) failure;
×
286
      }
287
      return new CompletionException(failure);
×
288
    }
289

290
    private @Nullable Throwable handleResponse(
291
        @Nullable Map<? extends K, ? extends V> result, @Nullable Throwable error) {
292
      if (result == null) {
×
293
        var failure = (error == null) ? new NullMapCompletionException() : error;
×
294
        for (var entry : proxies.entrySet()) {
×
295
          cache.remove(entry.getKey(), entry.getValue());
×
296
          entry.getValue().obtrudeException(failure);
×
297
        }
×
298
        if (!(failure instanceof CancellationException) && !(failure instanceof TimeoutException)) {
×
299
          logger.log(Level.WARNING, "Exception thrown during asynchronous load", failure);
×
300
        }
301
        return failure;
×
302
      }
303
      var failure = fillProxies(result);
×
304
      return addNewEntries(result, failure);
×
305
    }
306

307
    /** Populates the proxies with the computed result. */
308
    private @Nullable Throwable fillProxies(Map<? extends K, ? extends V> result) {
309
      @Var Throwable error = null;
×
310
      for (var entry : proxies.entrySet()) {
×
311
        var key = entry.getKey();
×
312
        var value = result.get(key);
×
313
        var future = entry.getValue();
×
314
        future.obtrudeValue(value);
×
315

316
        if (value == null) {
×
317
          cache.remove(key, future);
×
318
        } else {
319
          try {
320
            // update the weight and expiration timestamps
321
            cache.replace(key, future, future);
×
322
          } catch (Throwable t) {
×
323
            logger.log(Level.WARNING, "Exception thrown during asynchronous load", t);
×
324
            cache.remove(key, future);
×
325
            if (error == null) {
×
326
              error = t;
×
327
            } else {
328
              error.addSuppressed(t);
×
329
            }
330
          }
×
331
        }
332
      }
×
333
      return error;
×
334
    }
335

336
    /** Adds to the cache any extra entries computed that were not requested. */
337
    private @Nullable Throwable addNewEntries(
338
        Map<? extends K, ? extends V> result, @Nullable Throwable failure) {
339
      @Var Throwable error = failure;
×
340
      for (var entry : result.entrySet()) {
×
341
        var key = entry.getKey();
×
342
        var value = entry.getValue();
×
343
        if (!proxies.containsKey(key)) {
×
344
          if (value == null) {
×
345
            continue;
×
346
          }
347
          try {
348
            cache.put(key, CompletableFuture.completedFuture(value));
×
349
          } catch (Throwable t) {
×
350
            logger.log(Level.WARNING, "Exception thrown during asynchronous load", t);
×
351
            if (error == null) {
×
352
              error = t;
×
353
            } else {
354
              error.addSuppressed(t);
×
355
            }
356
          }
×
357
        }
358
      }
×
359
      return error;
×
360
    }
361

362
    static final class NullMapCompletionException extends CompletionException {
×
363
      private static final long serialVersionUID = 1L;
364
    }
365
  }
366

367
  /* --------------- Asynchronous view --------------- */
368
  final class AsyncAsMapView<K, V> implements ConcurrentMap<K, CompletableFuture<V>> {
369
    final LocalAsyncCache<K, V> asyncCache;
370

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

448
      if (result[0] == null) {
×
449
        if ((future != null) && asyncCache.cache().isRecordingStats()) {
×
450
          future.whenComplete((r, e) -> {
×
451
            if ((r != null) || (e == null)) {
×
452
              asyncCache.cache().statsCounter().recordHits(1);
×
453
            }
454
          });
×
455
        }
456
      } else {
457
        asyncCache.handleCompletion(key, result[0], startTime, /* recordMiss= */ true);
×
458
      }
459
      return future;
×
460
    }
461
    @Override public @Nullable CompletableFuture<V> computeIfPresent(K key, BiFunction<? super K,
462
        ? super CompletableFuture<V>, ? extends CompletableFuture<V>> remappingFunction) {
463
      requireNonNull(remappingFunction);
×
464

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

476
      if ((result[0] != null) && (result[0] != prior[0])) {
×
477
        asyncCache.handleCompletion(key, result[0], startTime, /* recordMiss= */ false);
×
478
      }
479
      return result[0];
×
480
    }
481
    @Override public CompletableFuture<V> compute(K key, BiFunction<? super K,
482
        ? super CompletableFuture<V>, ? extends CompletableFuture<V>> remappingFunction) {
483
      requireNonNull(remappingFunction);
×
484

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

496
      if ((result[0] != null) && (result[0] != prior[0])) {
×
497
        asyncCache.handleCompletion(key, result[0], startTime, /* recordMiss= */ false);
×
498
      }
499
      return result[0];
×
500
    }
501
    @Override public CompletableFuture<V> merge(K key, CompletableFuture<V> value,
502
        BiFunction<? super CompletableFuture<V>, ? super CompletableFuture<V>,
503
            ? extends CompletableFuture<V>> remappingFunction) {
504
      requireNonNull(value);
×
505
      requireNonNull(remappingFunction);
×
506

507
      @SuppressWarnings({"rawtypes", "unchecked"})
508
      CompletableFuture<V>[] result = new CompletableFuture[1];
×
509
      @SuppressWarnings({"rawtypes", "unchecked"})
510
      CompletableFuture<V>[] prior = new CompletableFuture[1];
×
511
      long startTime = asyncCache.cache().statsTicker().read();
×
512
      asyncCache.cache().compute(key, (k, oldValue) -> {
×
513
        result[0] = (oldValue == null) ? value : remappingFunction.apply(oldValue, value);
×
514
        prior[0] = oldValue;
×
515
        return result[0];
×
516
      }, asyncCache.cache().expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false);
×
517

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

547
  /* --------------- Synchronous view --------------- */
548
  final class CacheView<K, V> extends AbstractCacheView<K, V> {
549
    private static final long serialVersionUID = 1L;
550

551
    @SuppressWarnings("serial")
552
    final LocalAsyncCache<K, V> asyncCache;
553

554
    CacheView(LocalAsyncCache<K, V> asyncCache) {
×
555
      this.asyncCache = requireNonNull(asyncCache);
×
556
    }
×
557
    @Override LocalAsyncCache<K, V> asyncCache() {
558
      return asyncCache;
×
559
    }
560
  }
561

562
  abstract class AbstractCacheView<K, V> implements Cache<K, V>, Serializable {
×
563
    private static final long serialVersionUID = 1L;
564

565
    transient @Nullable ConcurrentMap<K, V> asMapView;
566

567
    abstract LocalAsyncCache<K, V> asyncCache();
568

569
    @Override
570
    public @Nullable V getIfPresent(K key) {
571
      CompletableFuture<V> future = asyncCache().cache().getIfPresent(key, /* recordStats= */ true);
×
572
      return Async.getIfReady(future);
×
573
    }
574

575
    @Override
576
    public Map<K, V> getAllPresent(Iterable<? extends K> keys) {
577
      var result = new LinkedHashMap<K, V>(calculateHashMapCapacity(keys));
×
578
      for (K key : keys) {
×
579
        result.put(key, null);
×
580
      }
×
581

582
      int uniqueKeys = result.size();
×
583
      for (var iter = result.entrySet().iterator(); iter.hasNext();) {
×
584
        Map.Entry<K, V> entry = iter.next();
×
585

586
        CompletableFuture<V> future = asyncCache().cache().get(entry.getKey());
×
587
        V value = Async.getIfReady(future);
×
588
        if (value == null) {
×
589
          iter.remove();
×
590
        } else {
591
          entry.setValue(value);
×
592
        }
593
      }
×
594
      asyncCache().cache().statsCounter().recordHits(result.size());
×
595
      asyncCache().cache().statsCounter().recordMisses(uniqueKeys - result.size());
×
596

597
      return Collections.unmodifiableMap(result);
×
598
    }
599

600
    @Override
601
    public V get(K key, Function<? super K, ? extends V> mappingFunction) {
602
      return resolve(asyncCache().get(key, mappingFunction));
×
603
    }
604

605
    @Override
606
    public Map<K, V> getAll(
607
        Iterable<? extends K> keys,
608
        Function<
609
            ? super Set<? extends K>,
610
            ? extends Map<? extends K, ? extends V>> mappingFunction) {
611
      return resolve(asyncCache().getAll(keys, mappingFunction));
×
612
    }
613

614
    @SuppressWarnings({"PMD.AvoidThrowingNullPointerException",
615
      "PMD.PreserveStackTrace", "UnusedException"})
616
    protected static <T> T resolve(CompletableFuture<T> future) {
617
      try {
618
        return future.join();
×
619
      } catch (NullMapCompletionException e) {
×
620
        throw new NullPointerException("null map");
×
621
      } catch (CompletionException e) {
×
622
        if (e.getCause() instanceof RuntimeException) {
×
623
          throw (RuntimeException) e.getCause();
×
624
        } else if (e.getCause() instanceof Error) {
×
625
          throw (Error) e.getCause();
×
626
        }
627
        throw e;
×
628
      }
629
    }
630

631
    @Override
632
    public void put(K key, V value) {
633
      requireNonNull(value);
×
634
      asyncCache().cache().put(key, CompletableFuture.completedFuture(value));
×
635
    }
×
636

637
    @Override
638
    public void putAll(Map<? extends K, ? extends V> map) {
639
      map.forEach(this::put);
×
640
    }
×
641

642
    @Override
643
    public void invalidate(K key) {
644
      asyncCache().cache().remove(key);
×
645
    }
×
646

647
    @Override
648
    public void invalidateAll(Iterable<? extends K> keys) {
649
      asyncCache().cache().invalidateAll(keys);
×
650
    }
×
651

652
    @Override
653
    public void invalidateAll() {
654
      asyncCache().cache().clear();
×
655
    }
×
656

657
    @Override
658
    public long estimatedSize() {
659
      return asyncCache().cache().estimatedSize();
×
660
    }
661

662
    @Override
663
    public CacheStats stats() {
664
      return asyncCache().cache().statsCounter().snapshot();
×
665
    }
666

667
    @Override
668
    public void cleanUp() {
669
      asyncCache().cache().cleanUp();
×
670
    }
×
671

672
    @Override
673
    public Policy<K, V> policy() {
674
      return asyncCache().policy();
×
675
    }
676

677
    @Override
678
    public ConcurrentMap<K, V> asMap() {
679
      return (asMapView == null) ? (asMapView = new AsMapView<>(asyncCache().cache())) : asMapView;
×
680
    }
681
  }
682

683
  final class AsMapView<K, V> implements ConcurrentMap<K, V> {
684
    final LocalCache<K, CompletableFuture<V>> delegate;
685

686
    @Nullable Set<K> keys;
687
    @Nullable Collection<V> values;
688
    @Nullable Set<Entry<K, V>> entries;
689

690
    AsMapView(LocalCache<K, CompletableFuture<V>> delegate) {
×
691
      this.delegate = delegate;
×
692
    }
×
693

694
    @Override
695
    public boolean isEmpty() {
696
      return delegate.isEmpty();
×
697
    }
698

699
    @Override
700
    public int size() {
701
      return delegate.size();
×
702
    }
703

704
    @Override
705
    public void clear() {
706
      delegate.clear();
×
707
    }
×
708

709
    @Override
710
    public boolean containsKey(Object key) {
711
      return Async.isReady(delegate.getIfPresentQuietly(key));
×
712
    }
713

714
    @Override
715
    public boolean containsValue(Object value) {
716
      requireNonNull(value);
×
717

718
      for (CompletableFuture<V> valueFuture : delegate.values()) {
×
719
        if (value.equals(Async.getIfReady(valueFuture))) {
×
720
          return true;
×
721
        }
722
      }
×
723
      return false;
×
724
    }
725

726
    @Override
727
    public @Nullable V get(Object key) {
728
      return Async.getIfReady(delegate.get(key));
×
729
    }
730

731
    @Override
732
    public @Nullable V putIfAbsent(K key, V value) {
733
      requireNonNull(value);
×
734

735
      // Keep in sync with BoundedVarExpiration.putIfAbsentAsync(key, value, duration, unit)
736
      @Var CompletableFuture<V> priorFuture = null;
×
737
      for (;;) {
738
        priorFuture = (priorFuture == null)
×
739
            ? delegate.get(key)
×
740
            : delegate.getIfPresentQuietly(key);
×
741
        if (priorFuture != null) {
×
742
          if (!priorFuture.isDone()) {
×
743
            Async.getWhenSuccessful(priorFuture);
×
744
            continue;
×
745
          }
746

747
          V prior = Async.getWhenSuccessful(priorFuture);
×
748
          if (prior != null) {
×
749
            return prior;
×
750
          }
751
        }
752

753
        boolean[] added = { false };
×
754
        CompletableFuture<V> computed = delegate.compute(key, (k, valueFuture) -> {
×
755
          added[0] = (valueFuture == null)
×
756
              || (valueFuture.isDone() && (Async.getIfReady(valueFuture) == null));
×
757
          return added[0] ? CompletableFuture.completedFuture(value) : valueFuture;
×
758
        }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false);
×
759

760
        if (added[0]) {
×
761
          return null;
×
762
        } else {
763
          V prior = Async.getWhenSuccessful(computed);
×
764
          if (prior != null) {
×
765
            return prior;
×
766
          }
767
        }
768
      }
×
769
    }
770

771
    @Override
772
    public void putAll(Map<? extends K, ? extends V> map) {
773
      map.forEach(this::put);
×
774
    }
×
775

776
    @Override
777
    public @Nullable V put(K key, V value) {
778
      requireNonNull(value);
×
779
      CompletableFuture<V> oldValueFuture =
×
780
          delegate.put(key, CompletableFuture.completedFuture(value));
×
781
      return Async.getWhenSuccessful(oldValueFuture);
×
782
    }
783

784
    @Override
785
    public @Nullable V remove(Object key) {
786
      CompletableFuture<V> oldValueFuture = delegate.remove(key);
×
787
      return Async.getWhenSuccessful(oldValueFuture);
×
788
    }
789

790
    @Override
791
    public boolean remove(Object key, Object value) {
792
      requireNonNull(key);
×
793
      if (value == null) {
×
794
        return false;
×
795
      }
796

797
      @SuppressWarnings("unchecked")
798
      var castedKey = (K) key;
×
799
      boolean[] done = { false };
×
800
      boolean[] removed = { false };
×
801
      @Var CompletableFuture<V> future = null;
×
802
      for (;;) {
803
        future = (future == null)
×
804
            ? delegate.get(castedKey)
×
805
            : delegate.getIfPresentQuietly(castedKey);
×
806
        if (!Async.isReady(future)) {
×
807
          return false;
×
808
        }
809

810
        Async.getWhenSuccessful(future);
×
811
        delegate.compute(castedKey, (k, oldValueFuture) -> {
×
812
          if (oldValueFuture == null) {
×
813
            done[0] = true;
×
814
            return null;
×
815
          } else if (!oldValueFuture.isDone()) {
×
816
            return oldValueFuture;
×
817
          }
818

819
          done[0] = true;
×
820
          V oldValue = Async.getIfReady(oldValueFuture);
×
821
          removed[0] = Objects.equals(value, oldValue);
×
822
          return (oldValue == null) || removed[0] ? null : oldValueFuture;
×
823
        }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ true);
×
824

825
        if (done[0]) {
×
826
          return removed[0];
×
827
        }
828
      }
829
    }
830

831
    @Override
832
    public @Nullable V replace(K key, V value) {
833
      requireNonNull(value);
×
834

835
      @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
836
      @Nullable V[] oldValue = (V[]) new Object[1];
×
837
      boolean[] done = { false };
×
838
      for (;;) {
839
        CompletableFuture<V> future = delegate.getIfPresentQuietly(key);
×
840
        if ((future == null) || future.isCompletedExceptionally()) {
×
841
          return null;
×
842
        }
843

844
        Async.getWhenSuccessful(future);
×
845
        delegate.compute(key, (k, oldValueFuture) -> {
×
846
          if (oldValueFuture == null) {
×
847
            done[0] = true;
×
848
            return null;
×
849
          } else if (!oldValueFuture.isDone()) {
×
850
            return oldValueFuture;
×
851
          }
852

853
          done[0] = true;
×
854
          oldValue[0] = Async.getIfReady(oldValueFuture);
×
855
          return (oldValue[0] == null) ? null : CompletableFuture.completedFuture(value);
×
856
        }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false);
×
857

858
        if (done[0]) {
×
859
          return oldValue[0];
×
860
        }
861
      }
×
862
    }
863

864
    @Override
865
    public boolean replace(K key, V oldValue, V newValue) {
866
      requireNonNull(oldValue);
×
867
      requireNonNull(newValue);
×
868

869
      boolean[] done = { false };
×
870
      boolean[] replaced = { false };
×
871
      for (;;) {
872
        CompletableFuture<V> future = delegate.getIfPresentQuietly(key);
×
873
        if ((future == null) || future.isCompletedExceptionally()) {
×
874
          return false;
×
875
        }
876

877
        Async.getWhenSuccessful(future);
×
878
        delegate.compute(key, (k, oldValueFuture) -> {
×
879
          if (oldValueFuture == null) {
×
880
            done[0] = true;
×
881
            return null;
×
882
          } else if (!oldValueFuture.isDone()) {
×
883
            return oldValueFuture;
×
884
          }
885

886
          done[0] = true;
×
887
          replaced[0] = Objects.equals(oldValue, Async.getIfReady(oldValueFuture));
×
888
          return replaced[0] ? CompletableFuture.completedFuture(newValue) : oldValueFuture;
×
889
        }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false);
×
890

891
        if (done[0]) {
×
892
          return replaced[0];
×
893
        }
894
      }
×
895
    }
896

897
    @Override
898
    public @Nullable V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
899
      requireNonNull(mappingFunction);
×
900

901
      @Var CompletableFuture<V> priorFuture = null;
×
902
      for (;;) {
903
        priorFuture = (priorFuture == null)
×
904
            ? delegate.get(key)
×
905
            : delegate.getIfPresentQuietly(key);
×
906
        if (priorFuture != null) {
×
907
          if (!priorFuture.isDone()) {
×
908
            Async.getWhenSuccessful(priorFuture);
×
909
            continue;
×
910
          }
911

912
          V prior = Async.getWhenSuccessful(priorFuture);
×
913
          if (prior != null) {
×
914
            delegate.statsCounter().recordHits(1);
×
915
            return prior;
×
916
          }
917
        }
918

919
        @SuppressWarnings({"rawtypes", "unchecked"})
920
        CompletableFuture<V>[] future = new CompletableFuture[1];
×
921
        CompletableFuture<V> computed = delegate.compute(key, (k, valueFuture) -> {
×
922
          if ((valueFuture != null)
×
923
              && (!valueFuture.isDone() || (Async.getIfReady(valueFuture) != null))) {
×
924
            return valueFuture;
×
925
          }
926

927
          V newValue = delegate.statsAware(mappingFunction, /* recordLoad= */ true).apply(key);
×
928
          if (newValue == null) {
×
929
            return null;
×
930
          }
931
          future[0] = CompletableFuture.completedFuture(newValue);
×
932
          return future[0];
×
933
        }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false);
×
934

935
        V result = Async.getWhenSuccessful(computed);
×
936
        if ((computed == future[0]) || (result != null)) {
×
937
          return result;
×
938
        }
939
      }
×
940
    }
941

942
    @Override
943
    public @Nullable V computeIfPresent(K key,
944
        BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
945
      requireNonNull(remappingFunction);
×
946

947
      @SuppressWarnings({"rawtypes", "unchecked"})
948
      var newValue = (V[]) new Object[1];
×
949
      for (;;) {
950
        Async.getWhenSuccessful(delegate.getIfPresentQuietly(key));
×
951

952
        CompletableFuture<V> valueFuture = delegate.computeIfPresent(key, (k, oldValueFuture) -> {
×
953
          if (!oldValueFuture.isDone()) {
×
954
            return oldValueFuture;
×
955
          }
956

957
          V oldValue = Async.getIfReady(oldValueFuture);
×
958
          if (oldValue == null) {
×
959
            return null;
×
960
          }
961

962
          newValue[0] = remappingFunction.apply(key, oldValue);
×
963
          return (newValue[0] == null) ? null : CompletableFuture.completedFuture(newValue[0]);
×
964
        });
965

966
        if (newValue[0] != null) {
×
967
          return newValue[0];
×
968
        } else if (valueFuture == null) {
×
969
          return null;
×
970
        }
971
      }
×
972
    }
973

974
    @Override
975
    public @Nullable V compute(K key,
976
        BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
977
      // Keep in sync with BoundedVarExpiration.computeAsync(key, remappingFunction, expiry)
978
      requireNonNull(remappingFunction);
×
979

980
      @SuppressWarnings({"rawtypes", "unchecked"})
981
      var newValue = (V[]) new Object[1];
×
982
      for (;;) {
983
        Async.getWhenSuccessful(delegate.getIfPresentQuietly(key));
×
984

985
        CompletableFuture<V> valueFuture = delegate.compute(key, (k, oldValueFuture) -> {
×
986
          if ((oldValueFuture != null) && !oldValueFuture.isDone()) {
×
987
            return oldValueFuture;
×
988
          }
989

990
          V oldValue = Async.getIfReady(oldValueFuture);
×
991
          BiFunction<? super K, ? super V, ? extends V> function = delegate.statsAware(
×
992
              remappingFunction, /* recordLoad= */ true, /* recordLoadFailure= */ true);
993
          newValue[0] = function.apply(key, oldValue);
×
994
          return (newValue[0] == null) ? null : CompletableFuture.completedFuture(newValue[0]);
×
995
        }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false);
×
996

997
        if (newValue[0] != null) {
×
998
          return newValue[0];
×
999
        } else if (valueFuture == null) {
×
1000
          return null;
×
1001
        }
1002
      }
×
1003
    }
1004

1005
    @Override
1006
    public @Nullable V merge(K key, V value,
1007
        BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
1008
      requireNonNull(value);
×
1009
      requireNonNull(remappingFunction);
×
1010

1011
      CompletableFuture<V> newValueFuture = CompletableFuture.completedFuture(value);
×
1012
      boolean[] merged = { false };
×
1013
      for (;;) {
1014
        Async.getWhenSuccessful(delegate.getIfPresentQuietly(key));
×
1015

1016
        CompletableFuture<V> mergedValueFuture = delegate.merge(
×
1017
            key, newValueFuture, (oldValueFuture, valueFuture) -> {
1018
          if (!oldValueFuture.isDone()) {
×
1019
            return oldValueFuture;
×
1020
          }
1021

1022
          merged[0] = true;
×
1023
          V oldValue = Async.getIfReady(oldValueFuture);
×
1024
          if (oldValue == null) {
×
1025
            return valueFuture;
×
1026
          }
1027
          V mergedValue = remappingFunction.apply(oldValue, value);
×
1028
          if (mergedValue == null) {
×
1029
            return null;
×
1030
          } else if (mergedValue == oldValue) {
×
1031
            return oldValueFuture;
×
1032
          } else if (mergedValue == value) {
×
1033
            return valueFuture;
×
1034
          }
1035
          return CompletableFuture.completedFuture(mergedValue);
×
1036
        });
1037

1038
        if (merged[0] || (mergedValueFuture == newValueFuture)) {
×
1039
          return Async.getWhenSuccessful(mergedValueFuture);
×
1040
        }
1041
      }
×
1042
    }
1043

1044
    @Override
1045
    public Set<K> keySet() {
1046
      return (keys == null) ? (keys = new KeySet()) : keys;
×
1047
    }
1048

1049
    @Override
1050
    public Collection<V> values() {
1051
      return (values == null) ? (values = new Values()) : values;
×
1052
    }
1053

1054
    @Override
1055
    public Set<Entry<K, V>> entrySet() {
1056
      return (entries == null) ? (entries = new EntrySet()) : entries;
×
1057
    }
1058

1059
    /** See {@link BoundedLocalCache#equals(Object)} for semantics. */
1060
    @Override
1061
    public boolean equals(@Nullable Object o) {
1062
      if (o == this) {
×
1063
        return true;
×
1064
      } else if (!(o instanceof Map)) {
×
1065
        return false;
×
1066
      }
1067

1068
      var map = (Map<?, ?>) o;
×
1069
      int expectedSize = size();
×
1070
      if (map.size() != expectedSize) {
×
1071
        return false;
×
1072
      }
1073

1074
      @Var int count = 0;
×
1075
      for (var iterator = new EntryIterator(); iterator.hasNext();) {
×
1076
        var entry = iterator.next();
×
1077
        var value = map.get(entry.getKey());
×
1078
        if ((value == null) || ((value != entry.getValue()) && !value.equals(entry.getValue()))) {
×
1079
          return false;
×
1080
        }
1081
        count++;
×
1082
      }
×
1083
      return (count == expectedSize);
×
1084
    }
1085

1086
    @Override
1087
    public int hashCode() {
1088
      @Var int hash = 0;
×
1089
      for (var iterator = new EntryIterator(); iterator.hasNext();) {
×
1090
        var entry = iterator.next();
×
1091
        hash += entry.hashCode();
×
1092
      }
×
1093
      return hash;
×
1094
    }
1095

1096
    @Override
1097
    public String toString() {
1098
      var result = new StringBuilder(50).append('{');
×
1099
      for (var iterator = new EntryIterator(); iterator.hasNext();) {
×
1100
        var entry = iterator.next();
×
1101
        result.append((entry.getKey() == this) ? "(this Map)" : entry.getKey())
×
1102
            .append('=')
×
1103
            .append((entry.getValue() == this) ? "(this Map)" : entry.getValue());
×
1104

1105
        if (iterator.hasNext()) {
×
1106
          result.append(", ");
×
1107
        }
1108
      }
×
1109
      return result.append('}').toString();
×
1110
    }
1111

1112
    private final class KeySet extends AbstractSet<K> {
×
1113

1114
      @Override
1115
      public boolean isEmpty() {
1116
        return AsMapView.this.isEmpty();
×
1117
      }
1118

1119
      @Override
1120
      public int size() {
1121
        return AsMapView.this.size();
×
1122
      }
1123

1124
      @Override
1125
      public void clear() {
1126
        AsMapView.this.clear();
×
1127
      }
×
1128

1129
      @Override
1130
      public boolean contains(Object o) {
1131
        return AsMapView.this.containsKey(o);
×
1132
      }
1133

1134
      @Override
1135
      public boolean removeAll(Collection<?> collection) {
1136
        return delegate.keySet().removeAll(collection);
×
1137
      }
1138

1139
      @Override
1140
      @SuppressWarnings("RedundantCollectionOperation")
1141
      public boolean remove(Object o) {
1142
        return delegate.keySet().remove(o);
×
1143
      }
1144

1145
      @Override
1146
      public boolean removeIf(Predicate<? super K> filter) {
1147
        return delegate.keySet().removeIf(filter);
×
1148
      }
1149

1150
      @Override
1151
      public boolean retainAll(Collection<?> collection) {
1152
        return delegate.keySet().retainAll(collection);
×
1153
      }
1154

1155
      @Override
1156
      public Iterator<K> iterator() {
1157
        return new KeyIterator<>(new EntryIterator());
×
1158
      }
1159

1160
      @Override
1161
      public Spliterator<K> spliterator() {
1162
        return new KeySpliterator<>(new EntrySpliterator());
×
1163
      }
1164
    }
1165

1166
    private static final class KeyIterator<K, V> implements Iterator<K> {
1167
      private final Iterator<Entry<K, V>> iterator;
1168

1169
      KeyIterator(Iterator<Entry<K, V>> iterator) {
×
1170
        this.iterator = requireNonNull(iterator);
×
1171
      }
×
1172

1173
      @Override
1174
      public boolean hasNext() {
1175
        return iterator.hasNext();
×
1176
      }
1177

1178
      @Override
1179
      public K next() {
1180
        return iterator.next().getKey();
×
1181
      }
1182

1183
      @Override
1184
      public void remove() {
1185
        iterator.remove();
×
1186
      }
×
1187
    }
1188

1189
    private static final class KeySpliterator<K, V> implements Spliterator<K> {
1190
      private final Spliterator<Entry<K, V>> spliterator;
1191

1192
      KeySpliterator(Spliterator<Entry<K, V>> iterator) {
×
1193
        this.spliterator = requireNonNull(iterator);
×
1194
      }
×
1195

1196
      @Override
1197
      public boolean tryAdvance(Consumer<? super K> action) {
1198
        requireNonNull(action);
×
1199
        return spliterator.tryAdvance(entry -> {
×
1200
          action.accept(entry.getKey());
×
1201
        });
×
1202
      }
1203

1204
      @Override
1205
      public @Nullable Spliterator<K> trySplit() {
1206
        Spliterator<Entry<K, V>> split = spliterator.trySplit();
×
1207
        return (split == null) ? null : new KeySpliterator<>(split);
×
1208
      }
1209

1210
      @Override
1211
      public long estimateSize() {
1212
        return spliterator.estimateSize();
×
1213
      }
1214

1215
      @Override
1216
      public int characteristics() {
1217
        return DISTINCT | NONNULL | CONCURRENT;
×
1218
      }
1219
    }
1220

1221
    private final class Values extends AbstractCollection<V> {
×
1222

1223
      @Override
1224
      public boolean isEmpty() {
1225
        return AsMapView.this.isEmpty();
×
1226
      }
1227

1228
      @Override
1229
      public int size() {
1230
        return AsMapView.this.size();
×
1231
      }
1232

1233
      @Override
1234
      public void clear() {
1235
        AsMapView.this.clear();
×
1236
      }
×
1237

1238
      @Override
1239
      public boolean contains(Object o) {
1240
        return AsMapView.this.containsValue(o);
×
1241
      }
1242

1243
      @Override
1244
      public boolean removeAll(Collection<?> collection) {
1245
        requireNonNull(collection);
×
1246
        @Var boolean modified = false;
×
1247
        for (var entry : delegate.entrySet()) {
×
1248
          V value = Async.getIfReady(entry.getValue());
×
1249
          if ((value != null) && collection.contains(value)
×
1250
              && AsMapView.this.remove(entry.getKey(), value)) {
×
1251
            modified = true;
×
1252
          }
1253
        }
×
1254
        return modified;
×
1255
      }
1256

1257
      @Override
1258
      public boolean remove(Object o) {
1259
        if (o == null) {
×
1260
          return false;
×
1261
        }
1262
        for (var entry : delegate.entrySet()) {
×
1263
          V value = Async.getIfReady(entry.getValue());
×
1264
          if ((value != null) && value.equals(o) && AsMapView.this.remove(entry.getKey(), value)) {
×
1265
            return true;
×
1266
          }
1267
        }
×
1268
        return false;
×
1269
      }
1270

1271
      @Override
1272
      public boolean removeIf(Predicate<? super V> filter) {
1273
        requireNonNull(filter);
×
1274
        return delegate.values().removeIf(future -> {
×
1275
          V value = Async.getIfReady(future);
×
1276
          return (value != null) && filter.test(value);
×
1277
        });
1278
      }
1279

1280
      @Override
1281
      public boolean retainAll(Collection<?> collection) {
1282
        requireNonNull(collection);
×
1283
        @Var boolean modified = false;
×
1284
        for (var entry : delegate.entrySet()) {
×
1285
          V value = Async.getIfReady(entry.getValue());
×
1286
          if ((value != null) && !collection.contains(value)
×
1287
              && AsMapView.this.remove(entry.getKey(), value)) {
×
1288
            modified = true;
×
1289
          }
1290
        }
×
1291
        return modified;
×
1292
      }
1293

1294
      @Override
1295
      public void forEach(Consumer<? super V> action) {
1296
        requireNonNull(action);
×
1297
        delegate.values().forEach(future -> {
×
1298
          V value = Async.getIfReady(future);
×
1299
          if (value != null) {
×
1300
            action.accept(value);
×
1301
          }
1302
        });
×
1303
      }
×
1304

1305
      @Override
1306
      public Iterator<V> iterator() {
1307
        return new ValueIterator<>(new EntryIterator());
×
1308
      }
1309

1310
      @Override
1311
      public Spliterator<V> spliterator() {
1312
        return new ValueSpliterator<>(new EntrySpliterator());
×
1313
      }
1314
    }
1315

1316
    private static final class ValueIterator<K, V> implements Iterator<V> {
1317
      private final Iterator<Entry<K, V>> iterator;
1318

1319
      ValueIterator(Iterator<Entry<K, V>> iterator) {
×
1320
        this.iterator = requireNonNull(iterator);
×
1321
      }
×
1322

1323
      @Override
1324
      public boolean hasNext() {
1325
        return iterator.hasNext();
×
1326
      }
1327

1328
      @Override
1329
      public V next() {
1330
        return iterator.next().getValue();
×
1331
      }
1332

1333
      @Override
1334
      public void remove() {
1335
        iterator.remove();
×
1336
      }
×
1337
    }
1338

1339
    private static final class ValueSpliterator<K, V> implements Spliterator<V> {
1340
      private final Spliterator<Entry<K, V>> spliterator;
1341

1342
      ValueSpliterator(Spliterator<Entry<K, V>> iterator) {
×
1343
        this.spliterator = requireNonNull(iterator);
×
1344
      }
×
1345

1346
      @Override
1347
      public boolean tryAdvance(Consumer<? super V> action) {
1348
        requireNonNull(action);
×
1349
        return spliterator.tryAdvance(entry -> {
×
1350
          action.accept(entry.getValue());
×
1351
        });
×
1352
      }
1353

1354
      @Override
1355
      public @Nullable Spliterator<V> trySplit() {
1356
        Spliterator<Entry<K, V>> split = spliterator.trySplit();
×
1357
        return (split == null) ? null : new ValueSpliterator<>(split);
×
1358
      }
1359

1360
      @Override
1361
      public long estimateSize() {
1362
        return spliterator.estimateSize();
×
1363
      }
1364

1365
      @Override
1366
      public int characteristics() {
1367
        return NONNULL | CONCURRENT;
×
1368
      }
1369
    }
1370

1371
    private final class EntrySet extends AbstractSet<Entry<K, V>> {
×
1372

1373
      @Override
1374
      public boolean isEmpty() {
1375
        return AsMapView.this.isEmpty();
×
1376
      }
1377

1378
      @Override
1379
      public int size() {
1380
        return AsMapView.this.size();
×
1381
      }
1382

1383
      @Override
1384
      public void clear() {
1385
        AsMapView.this.clear();
×
1386
      }
×
1387

1388
      @Override
1389
      public boolean contains(Object o) {
1390
        if (!(o instanceof Entry<?, ?>)) {
×
1391
          return false;
×
1392
        }
1393
        var entry = (Entry<?, ?>) o;
×
1394
        var key = entry.getKey();
×
1395
        var value = entry.getValue();
×
1396
        if ((key == null) || (value == null)) {
×
1397
          return false;
×
1398
        }
1399
        V cachedValue = AsMapView.this.get(key);
×
1400
        return (cachedValue != null) && cachedValue.equals(value);
×
1401
      }
1402

1403
      @Override
1404
      public boolean removeAll(Collection<?> collection) {
1405
        requireNonNull(collection);
×
1406
        @Var boolean modified = false;
×
1407
        if ((collection instanceof Set<?>) && (collection.size() > size())) {
×
1408
          for (var entry : this) {
×
1409
            if (collection.contains(entry)) {
×
1410
              modified |= remove(entry);
×
1411
            }
1412
          }
×
1413
        } else {
1414
          for (var o : collection) {
×
1415
            modified |= remove(o);
×
1416
          }
×
1417
        }
1418
        return modified;
×
1419
      }
1420

1421
      @Override
1422
      public boolean remove(Object obj) {
1423
        if (!(obj instanceof Entry<?, ?>)) {
×
1424
          return false;
×
1425
        }
1426
        var entry = (Entry<?, ?>) obj;
×
1427
        var key = entry.getKey();
×
1428
        return (key != null) && AsMapView.this.remove(key, entry.getValue());
×
1429
      }
1430

1431
      @Override
1432
      public boolean removeIf(Predicate<? super Entry<K, V>> filter) {
1433
        requireNonNull(filter);
×
1434
        @Var boolean modified = false;
×
1435
        for (Entry<K, V> entry : this) {
×
1436
          if (filter.test(entry)) {
×
1437
            modified |= AsMapView.this.remove(entry.getKey(), entry.getValue());
×
1438
          }
1439
        }
×
1440
        return modified;
×
1441
      }
1442

1443
      @Override
1444
      public boolean retainAll(Collection<?> collection) {
1445
        requireNonNull(collection);
×
1446
        @Var boolean modified = false;
×
1447
        for (var entry : this) {
×
1448
          if (!collection.contains(entry) && remove(entry)) {
×
1449
            modified = true;
×
1450
          }
1451
        }
×
1452
        return modified;
×
1453
      }
1454

1455
      @Override
1456
      public Iterator<Entry<K, V>> iterator() {
1457
        return new EntryIterator();
×
1458
      }
1459

1460
      @Override
1461
      public Spliterator<Entry<K, V>> spliterator() {
1462
        return new EntrySpliterator();
×
1463
      }
1464
    }
1465

1466
    private final class EntryIterator implements Iterator<Entry<K, V>> {
1467
      final Iterator<Entry<K, CompletableFuture<V>>> iterator;
1468
      @Nullable Entry<K, V> cursor;
1469
      @Nullable K removalKey;
1470

1471
      EntryIterator() {
×
1472
        this.iterator = delegate.entrySet().iterator();
×
1473
      }
×
1474

1475
      @Override
1476
      public boolean hasNext() {
1477
        while ((cursor == null) && iterator.hasNext()) {
×
1478
          Entry<K, CompletableFuture<V>> entry = iterator.next();
×
1479
          V value = Async.getIfReady(entry.getValue());
×
1480
          if (value != null) {
×
1481
            cursor = new WriteThroughEntry<>(AsMapView.this, entry.getKey(), value);
×
1482
          }
1483
        }
×
1484
        return (cursor != null);
×
1485
      }
1486

1487
      @Override
1488
      public Entry<K, V> next() {
1489
        if (!hasNext()) {
×
1490
          throw new NoSuchElementException();
×
1491
        }
1492
        K key = requireNonNull(cursor).getKey();
×
1493
        Entry<K, V> entry = cursor;
×
1494
        removalKey = key;
×
1495
        cursor = null;
×
1496
        return entry;
×
1497
      }
1498

1499
      @Override
1500
      public void remove() {
1501
        requireState(removalKey != null);
×
1502
        delegate.remove(removalKey);
×
1503
        removalKey = null;
×
1504
      }
×
1505
    }
1506

1507
    private final class EntrySpliterator implements Spliterator<Entry<K, V>> {
1508
      final Spliterator<Entry<K, CompletableFuture<V>>> spliterator;
1509

1510
      EntrySpliterator() {
1511
        this(delegate.entrySet().spliterator());
×
1512
      }
×
1513

1514
      EntrySpliterator(Spliterator<Entry<K, CompletableFuture<V>>> spliterator) {
×
1515
        this.spliterator = requireNonNull(spliterator);
×
1516
      }
×
1517

1518
      @Override
1519
      public void forEachRemaining(Consumer<? super Entry<K, V>> action) {
1520
        requireNonNull(action);
×
1521
        spliterator.forEachRemaining(entry -> {
×
1522
          V value = Async.getIfReady(entry.getValue());
×
1523
          if (value != null) {
×
1524
            var e = new WriteThroughEntry<>(AsMapView.this, entry.getKey(), value);
×
1525
            action.accept(e);
×
1526
          }
1527
        });
×
1528
      }
×
1529

1530
      @Override
1531
      public boolean tryAdvance(Consumer<? super Entry<K, V>> action) {
1532
        requireNonNull(action);
×
1533
        boolean[] advanced = { false };
×
1534
        Consumer<? super Entry<K, CompletableFuture<V>>> consumer = entry -> {
×
1535
          V value = Async.getIfReady(entry.getValue());
×
1536
          if (value != null) {
×
1537
            var e = new WriteThroughEntry<>(AsMapView.this, entry.getKey(), value);
×
1538
            action.accept(e);
×
1539
            advanced[0] = true;
×
1540
          }
1541
        };
×
1542
        while (spliterator.tryAdvance(consumer)) {
×
1543
          if (advanced[0]) {
×
1544
            return true;
×
1545
          }
1546
        }
1547
        return false;
×
1548
      }
1549

1550
      @Override
1551
      public @Nullable Spliterator<Entry<K, V>> trySplit() {
1552
        Spliterator<Entry<K, CompletableFuture<V>>> split = spliterator.trySplit();
×
1553
        return (split == null) ? null : new EntrySpliterator(split);
×
1554
      }
1555

1556
      @Override
1557
      public long estimateSize() {
1558
        return spliterator.estimateSize();
×
1559
      }
1560

1561
      @Override
1562
      public int characteristics() {
1563
        return DISTINCT | CONCURRENT | NONNULL;
×
1564
      }
1565
    }
1566
  }
1567
}
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