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

ben-manes / caffeine / #5173

29 Dec 2025 05:27AM UTC coverage: 0.0% (-100.0%) from 100.0%
#5173

push

github

ben-manes
speed up development ci build

0 of 3838 branches covered (0.0%)

0 of 7869 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
    @Nullable 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<@Nullable V>>(initialCapacity);
×
124
    var proxies = new LinkedHashMap<K, CompletableFuture<@Nullable 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(
162
      Map<K, CompletableFuture<@Nullable V>> futures) {
163
    if (futures.isEmpty()) {
×
164
      @SuppressWarnings({"ImmutableMapOf", "RedundantUnmodifiable"})
165
      Map<K, V> emptyMap = Collections.unmodifiableMap(Collections.emptyMap());
×
166
      return CompletableFuture.completedFuture(emptyMap);
×
167
    }
168
    @SuppressWarnings("rawtypes")
169
    CompletableFuture<?>[] array = futures.values().toArray(new CompletableFuture[0]);
×
170
    return CompletableFuture.allOf(array).thenApply(ignored -> {
×
171
      var result = new LinkedHashMap<K, V>(calculateHashMapCapacity(futures.size()));
×
172
      futures.forEach((key, future) -> {
×
173
        @Nullable V value = future.getNow(null);
×
174
        if (value != null) {
×
175
          result.put(key, value);
×
176
        }
177
      });
×
178
      return Collections.unmodifiableMap(result);
×
179
    });
180
  }
181

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

567
    transient @Nullable ConcurrentMap<K, V> asMapView;
568

569
    abstract LocalAsyncCache<K, V> asyncCache();
570

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

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

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

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

599
      @SuppressWarnings("NullableProblems")
600
      Map<K, V> unmodifiable = Collections.unmodifiableMap(result);
×
601
      return unmodifiable;
×
602
    }
603

604
    @Override
605
    public V get(K key, Function<? super K, ? extends V> mappingFunction) {
606
      return resolve(asyncCache().get(key, mappingFunction));
×
607
    }
608

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

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

635
    @Override
636
    public void put(K key, V value) {
637
      requireNonNull(value);
×
638
      asyncCache().cache().put(key, CompletableFuture.completedFuture(value));
×
639
    }
×
640

641
    @Override
642
    public void putAll(Map<? extends K, ? extends V> map) {
643
      map.forEach(this::put);
×
644
    }
×
645

646
    @Override
647
    public void invalidate(K key) {
648
      asyncCache().cache().remove(key);
×
649
    }
×
650

651
    @Override
652
    public void invalidateAll(Iterable<? extends K> keys) {
653
      asyncCache().cache().invalidateAll(keys);
×
654
    }
×
655

656
    @Override
657
    public void invalidateAll() {
658
      asyncCache().cache().clear();
×
659
    }
×
660

661
    @Override
662
    public long estimatedSize() {
663
      return asyncCache().cache().estimatedSize();
×
664
    }
665

666
    @Override
667
    public CacheStats stats() {
668
      return asyncCache().cache().statsCounter().snapshot();
×
669
    }
670

671
    @Override
672
    public void cleanUp() {
673
      asyncCache().cache().cleanUp();
×
674
    }
×
675

676
    @Override
677
    public Policy<K, V> policy() {
678
      return asyncCache().policy();
×
679
    }
680

681
    @Override
682
    public ConcurrentMap<K, V> asMap() {
683
      return (asMapView == null) ? (asMapView = new AsMapView<>(asyncCache().cache())) : asMapView;
×
684
    }
685
  }
686

687
  final class AsMapView<K, V> implements ConcurrentMap<K, V> {
688
    final LocalCache<K, CompletableFuture<V>> delegate;
689

690
    @Nullable Set<K> keys;
691
    @Nullable Collection<V> values;
692
    @Nullable Set<Entry<K, V>> entries;
693

694
    AsMapView(LocalCache<K, CompletableFuture<V>> delegate) {
×
695
      this.delegate = delegate;
×
696
    }
×
697

698
    @Override
699
    public boolean isEmpty() {
700
      return delegate.isEmpty();
×
701
    }
702

703
    @Override
704
    public int size() {
705
      return delegate.size();
×
706
    }
707

708
    @Override
709
    public void clear() {
710
      delegate.clear();
×
711
    }
×
712

713
    @Override
714
    public boolean containsKey(Object key) {
715
      return Async.isReady(delegate.getIfPresentQuietly(key));
×
716
    }
717

718
    @Override
719
    public boolean containsValue(Object value) {
720
      requireNonNull(value);
×
721

722
      for (CompletableFuture<V> valueFuture : delegate.values()) {
×
723
        if (value.equals(Async.getIfReady(valueFuture))) {
×
724
          return true;
×
725
        }
726
      }
×
727
      return false;
×
728
    }
729

730
    @Override
731
    public @Nullable V get(Object key) {
732
      return Async.getIfReady(delegate.get(key));
×
733
    }
734

735
    @Override
736
    public @Nullable V putIfAbsent(K key, V value) {
737
      requireNonNull(value);
×
738

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

751
          V prior = Async.getWhenSuccessful(priorFuture);
×
752
          if (prior != null) {
×
753
            return prior;
×
754
          }
755
        }
756

757
        boolean[] added = { false };
×
758
        CompletableFuture<V> computed = delegate.compute(
×
759
            key, (K k, @Nullable CompletableFuture<V> valueFuture) -> {
760
              added[0] = (valueFuture == null)
×
761
                  || (valueFuture.isDone() && (Async.getIfReady(valueFuture) == null));
×
762
              return added[0] ? CompletableFuture.completedFuture(value) : valueFuture;
×
763
            }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false);
×
764

765
        if (added[0]) {
×
766
          return null;
×
767
        } else {
768
          V prior = Async.getWhenSuccessful(computed);
×
769
          if (prior != null) {
×
770
            return prior;
×
771
          }
772
        }
773
      }
×
774
    }
775

776
    @Override
777
    public void putAll(Map<? extends K, ? extends V> map) {
778
      map.forEach(this::put);
×
779
    }
×
780

781
    @Override
782
    public @Nullable V put(K key, V value) {
783
      requireNonNull(value);
×
784
      CompletableFuture<V> oldValueFuture =
×
785
          delegate.put(key, CompletableFuture.completedFuture(value));
×
786
      return Async.getWhenSuccessful(oldValueFuture);
×
787
    }
788

789
    @Override
790
    public @Nullable V remove(Object key) {
791
      CompletableFuture<V> oldValueFuture = delegate.remove(key);
×
792
      return Async.getWhenSuccessful(oldValueFuture);
×
793
    }
794

795
    @Override
796
    public boolean remove(Object key, @Nullable Object value) {
797
      requireNonNull(key);
×
798
      if (value == null) {
×
799
        return false;
×
800
      }
801

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

815
        Async.getWhenSuccessful(future);
×
816
        delegate.compute(castedKey, (K k, @Nullable CompletableFuture<V> oldValueFuture) -> {
×
817
          if (oldValueFuture == null) {
×
818
            done[0] = true;
×
819
            return null;
×
820
          } else if (!oldValueFuture.isDone()) {
×
821
            return oldValueFuture;
×
822
          }
823

824
          done[0] = true;
×
825
          V oldValue = Async.getIfReady(oldValueFuture);
×
826
          removed[0] = Objects.equals(value, oldValue);
×
827
          return (oldValue == null) || removed[0] ? null : oldValueFuture;
×
828
        }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ true);
×
829

830
        if (done[0]) {
×
831
          return removed[0];
×
832
        }
833
      }
834
    }
835

836
    @Override
837
    public @Nullable V replace(K key, V value) {
838
      requireNonNull(value);
×
839

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

849
        Async.getWhenSuccessful(future);
×
850
        delegate.compute(key, (K k, @Nullable CompletableFuture<V> oldValueFuture) -> {
×
851
          if (oldValueFuture == null) {
×
852
            done[0] = true;
×
853
            return null;
×
854
          } else if (!oldValueFuture.isDone()) {
×
855
            return oldValueFuture;
×
856
          }
857

858
          done[0] = true;
×
859
          oldValue[0] = Async.getIfReady(oldValueFuture);
×
860
          return (oldValue[0] == null) ? null : CompletableFuture.completedFuture(value);
×
861
        }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false);
×
862

863
        if (done[0]) {
×
864
          return oldValue[0];
×
865
        }
866
      }
×
867
    }
868

869
    @Override
870
    public boolean replace(K key, V oldValue, V newValue) {
871
      requireNonNull(oldValue);
×
872
      requireNonNull(newValue);
×
873

874
      boolean[] done = { false };
×
875
      boolean[] replaced = { false };
×
876
      for (;;) {
877
        CompletableFuture<V> future = delegate.getIfPresentQuietly(key);
×
878
        if ((future == null) || future.isCompletedExceptionally()) {
×
879
          return false;
×
880
        }
881

882
        Async.getWhenSuccessful(future);
×
883
        delegate.compute(key, (K k, @Nullable CompletableFuture<V> oldValueFuture) -> {
×
884
          if (oldValueFuture == null) {
×
885
            done[0] = true;
×
886
            return null;
×
887
          } else if (!oldValueFuture.isDone()) {
×
888
            return oldValueFuture;
×
889
          }
890

891
          done[0] = true;
×
892
          replaced[0] = Objects.equals(oldValue, Async.getIfReady(oldValueFuture));
×
893
          return replaced[0] ? CompletableFuture.completedFuture(newValue) : oldValueFuture;
×
894
        }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false);
×
895

896
        if (done[0]) {
×
897
          return replaced[0];
×
898
        }
899
      }
×
900
    }
901

902
    @Override
903
    public @Nullable V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
904
      requireNonNull(mappingFunction);
×
905

906
      @Var CompletableFuture<V> priorFuture = null;
×
907
      for (;;) {
908
        priorFuture = (priorFuture == null)
×
909
            ? delegate.get(key)
×
910
            : delegate.getIfPresentQuietly(key);
×
911
        if (priorFuture != null) {
×
912
          if (!priorFuture.isDone()) {
×
913
            Async.getWhenSuccessful(priorFuture);
×
914
            continue;
×
915
          }
916

917
          V prior = Async.getWhenSuccessful(priorFuture);
×
918
          if (prior != null) {
×
919
            delegate.statsCounter().recordHits(1);
×
920
            return prior;
×
921
          }
922
        }
923

924
        @SuppressWarnings({"rawtypes", "unchecked"})
925
        CompletableFuture<V>[] future = new CompletableFuture[1];
×
926
        CompletableFuture<V> computed = delegate.compute(
×
927
            key, (K k, @Nullable CompletableFuture<V> valueFuture) -> {
928
              if ((valueFuture != null)
×
929
                  && (!valueFuture.isDone() || (Async.getIfReady(valueFuture) != null))) {
×
930
                return valueFuture;
×
931
              }
932

933
              V newValue = delegate.statsAware(mappingFunction, /* recordLoad= */ true).apply(key);
×
934
              if (newValue == null) {
×
935
                return null;
×
936
              }
937
              future[0] = CompletableFuture.completedFuture(newValue);
×
938
              return future[0];
×
939
            }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false);
×
940

941
        V result = Async.getWhenSuccessful(computed);
×
942
        if ((computed == future[0]) || (result != null)) {
×
943
          return result;
×
944
        }
945
      }
×
946
    }
947

948
    @Override
949
    public @Nullable V computeIfPresent(K key,
950
        BiFunction<? super K, ? super V, ? extends @Nullable V> remappingFunction) {
951
      requireNonNull(remappingFunction);
×
952

953
      @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
954
      @Nullable V[] newValue = (@Nullable V[]) new Object[1];
×
955
      for (;;) {
956
        Async.getWhenSuccessful(delegate.getIfPresentQuietly(key));
×
957

958
        CompletableFuture<V> valueFuture = delegate.computeIfPresent(key, (k, oldValueFuture) -> {
×
959
          if (!oldValueFuture.isDone()) {
×
960
            return oldValueFuture;
×
961
          }
962

963
          V oldValue = Async.getIfReady(oldValueFuture);
×
964
          if (oldValue == null) {
×
965
            return null;
×
966
          }
967

968
          newValue[0] = remappingFunction.apply(key, oldValue);
×
969
          return (newValue[0] == null) ? null : CompletableFuture.completedFuture(newValue[0]);
×
970
        });
971

972
        if (newValue[0] != null) {
×
973
          return newValue[0];
×
974
        } else if (valueFuture == null) {
×
975
          return null;
×
976
        }
977
      }
×
978
    }
979

980
    @Override
981
    public @Nullable V compute(K key,
982
        BiFunction<? super K, ? super @Nullable V, ? extends @Nullable V> remappingFunction) {
983
      // Keep in sync with BoundedVarExpiration.computeAsync(key, remappingFunction, expiry)
984
      requireNonNull(remappingFunction);
×
985

986
      @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
987
      @Nullable V[] newValue = (@Nullable V[]) new Object[1];
×
988
      for (;;) {
989
        Async.getWhenSuccessful(delegate.getIfPresentQuietly(key));
×
990

991
        CompletableFuture<V> valueFuture = delegate.compute(
×
992
            key, (K k, @Nullable CompletableFuture<V> oldValueFuture) -> {
993
              if ((oldValueFuture != null) && !oldValueFuture.isDone()) {
×
994
                return oldValueFuture;
×
995
              }
996

997
              V oldValue = Async.getIfReady(oldValueFuture);
×
998
              BiFunction<? super K, ? super V, ? extends @Nullable V> function =
×
999
                  delegate.statsAware(remappingFunction,
×
1000
                      /* recordLoad= */ true, /* recordLoadFailure= */ true);
1001
              newValue[0] = function.apply(key, oldValue);
×
1002
              return (newValue[0] == null) ? null : CompletableFuture.completedFuture(newValue[0]);
×
1003
            }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false);
×
1004

1005
        if (newValue[0] != null) {
×
1006
          return newValue[0];
×
1007
        } else if (valueFuture == null) {
×
1008
          return null;
×
1009
        }
1010
      }
×
1011
    }
1012

1013
    @Override
1014
    public @Nullable V merge(K key, V value,
1015
        BiFunction<? super V, ? super V, ? extends @Nullable V> remappingFunction) {
1016
      requireNonNull(value);
×
1017
      requireNonNull(remappingFunction);
×
1018

1019
      CompletableFuture<V> newValueFuture = CompletableFuture.completedFuture(value);
×
1020
      boolean[] merged = { false };
×
1021
      for (;;) {
1022
        Async.getWhenSuccessful(delegate.getIfPresentQuietly(key));
×
1023

1024
        CompletableFuture<V> mergedValueFuture = delegate.merge(
×
1025
            key, newValueFuture, (oldValueFuture, valueFuture) -> {
1026
          if (!oldValueFuture.isDone()) {
×
1027
            return oldValueFuture;
×
1028
          }
1029

1030
          merged[0] = true;
×
1031
          V oldValue = Async.getIfReady(oldValueFuture);
×
1032
          if (oldValue == null) {
×
1033
            return valueFuture;
×
1034
          }
1035
          V mergedValue = remappingFunction.apply(oldValue, value);
×
1036
          if (mergedValue == null) {
×
1037
            return null;
×
1038
          } else if (mergedValue == oldValue) {
×
1039
            return oldValueFuture;
×
1040
          } else if (mergedValue == value) {
×
1041
            return valueFuture;
×
1042
          }
1043
          return CompletableFuture.completedFuture(mergedValue);
×
1044
        });
1045

1046
        if (merged[0] || (mergedValueFuture == newValueFuture)) {
×
1047
          return Async.getWhenSuccessful(mergedValueFuture);
×
1048
        }
1049
      }
×
1050
    }
1051

1052
    @Override
1053
    public Set<K> keySet() {
1054
      return (keys == null) ? (keys = new KeySet()) : keys;
×
1055
    }
1056

1057
    @Override
1058
    public Collection<V> values() {
1059
      return (values == null) ? (values = new Values()) : values;
×
1060
    }
1061

1062
    @Override
1063
    public Set<Entry<K, V>> entrySet() {
1064
      return (entries == null) ? (entries = new EntrySet()) : entries;
×
1065
    }
1066

1067
    /** See {@link BoundedLocalCache#equals(Object)} for semantics. */
1068
    @Override
1069
    public boolean equals(@Nullable Object o) {
1070
      if (o == this) {
×
1071
        return true;
×
1072
      } else if (!(o instanceof Map)) {
×
1073
        return false;
×
1074
      }
1075

1076
      var map = (Map<?, ?>) o;
×
1077
      int expectedSize = size();
×
1078
      if (map.size() != expectedSize) {
×
1079
        return false;
×
1080
      }
1081

1082
      @Var int count = 0;
×
1083
      for (var iterator = new EntryIterator(); iterator.hasNext();) {
×
1084
        var entry = iterator.next();
×
1085
        var value = map.get(entry.getKey());
×
1086
        if ((value == null) || ((value != entry.getValue()) && !value.equals(entry.getValue()))) {
×
1087
          return false;
×
1088
        }
1089
        count++;
×
1090
      }
×
1091
      return (count == expectedSize);
×
1092
    }
1093

1094
    @Override
1095
    public int hashCode() {
1096
      @Var int hash = 0;
×
1097
      for (var iterator = new EntryIterator(); iterator.hasNext();) {
×
1098
        var entry = iterator.next();
×
1099
        hash += entry.hashCode();
×
1100
      }
×
1101
      return hash;
×
1102
    }
1103

1104
    @Override
1105
    public String toString() {
1106
      var result = new StringBuilder(50).append('{');
×
1107
      for (var iterator = new EntryIterator(); iterator.hasNext();) {
×
1108
        var entry = iterator.next();
×
1109
        result.append((entry.getKey() == this) ? "(this Map)" : entry.getKey())
×
1110
            .append('=')
×
1111
            .append((entry.getValue() == this) ? "(this Map)" : entry.getValue());
×
1112

1113
        if (iterator.hasNext()) {
×
1114
          result.append(", ");
×
1115
        }
1116
      }
×
1117
      return result.append('}').toString();
×
1118
    }
1119

1120
    private final class KeySet extends AbstractSet<K> {
×
1121

1122
      @Override
1123
      public boolean isEmpty() {
1124
        return AsMapView.this.isEmpty();
×
1125
      }
1126

1127
      @Override
1128
      public int size() {
1129
        return AsMapView.this.size();
×
1130
      }
1131

1132
      @Override
1133
      public void clear() {
1134
        AsMapView.this.clear();
×
1135
      }
×
1136

1137
      @Override
1138
      public boolean contains(Object o) {
1139
        return AsMapView.this.containsKey(o);
×
1140
      }
1141

1142
      @Override
1143
      public boolean removeAll(Collection<?> collection) {
1144
        return delegate.keySet().removeAll(collection);
×
1145
      }
1146

1147
      @Override
1148
      @SuppressWarnings("RedundantCollectionOperation")
1149
      public boolean remove(Object o) {
1150
        return delegate.keySet().remove(o);
×
1151
      }
1152

1153
      @Override
1154
      public boolean removeIf(Predicate<? super K> filter) {
1155
        return delegate.keySet().removeIf(filter);
×
1156
      }
1157

1158
      @Override
1159
      public boolean retainAll(Collection<?> collection) {
1160
        return delegate.keySet().retainAll(collection);
×
1161
      }
1162

1163
      @Override
1164
      public Iterator<K> iterator() {
1165
        return new KeyIterator<>(new EntryIterator());
×
1166
      }
1167

1168
      @Override
1169
      public Spliterator<K> spliterator() {
1170
        return new KeySpliterator<>(new EntrySpliterator());
×
1171
      }
1172
    }
1173

1174
    private static final class KeyIterator<K, V> implements Iterator<K> {
1175
      private final Iterator<Entry<K, V>> iterator;
1176

1177
      KeyIterator(Iterator<Entry<K, V>> iterator) {
×
1178
        this.iterator = requireNonNull(iterator);
×
1179
      }
×
1180

1181
      @Override
1182
      public boolean hasNext() {
1183
        return iterator.hasNext();
×
1184
      }
1185

1186
      @Override
1187
      public K next() {
1188
        return iterator.next().getKey();
×
1189
      }
1190

1191
      @Override
1192
      public void remove() {
1193
        iterator.remove();
×
1194
      }
×
1195
    }
1196

1197
    private static final class KeySpliterator<K, V> implements Spliterator<K> {
1198
      private final Spliterator<Entry<K, V>> spliterator;
1199

1200
      KeySpliterator(Spliterator<Entry<K, V>> iterator) {
×
1201
        this.spliterator = requireNonNull(iterator);
×
1202
      }
×
1203

1204
      @Override
1205
      public boolean tryAdvance(Consumer<? super K> action) {
1206
        requireNonNull(action);
×
1207
        return spliterator.tryAdvance(entry -> {
×
1208
          action.accept(entry.getKey());
×
1209
        });
×
1210
      }
1211

1212
      @Override
1213
      public @Nullable Spliterator<K> trySplit() {
1214
        Spliterator<Entry<K, V>> split = spliterator.trySplit();
×
1215
        return (split == null) ? null : new KeySpliterator<>(split);
×
1216
      }
1217

1218
      @Override
1219
      public long estimateSize() {
1220
        return spliterator.estimateSize();
×
1221
      }
1222

1223
      @Override
1224
      public int characteristics() {
1225
        return DISTINCT | NONNULL | CONCURRENT;
×
1226
      }
1227
    }
1228

1229
    private final class Values extends AbstractCollection<V> {
×
1230

1231
      @Override
1232
      public boolean isEmpty() {
1233
        return AsMapView.this.isEmpty();
×
1234
      }
1235

1236
      @Override
1237
      public int size() {
1238
        return AsMapView.this.size();
×
1239
      }
1240

1241
      @Override
1242
      public void clear() {
1243
        AsMapView.this.clear();
×
1244
      }
×
1245

1246
      @Override
1247
      public boolean contains(Object o) {
1248
        return AsMapView.this.containsValue(o);
×
1249
      }
1250

1251
      @Override
1252
      public boolean removeAll(Collection<?> collection) {
1253
        requireNonNull(collection);
×
1254
        @Var boolean modified = false;
×
1255
        for (var entry : delegate.entrySet()) {
×
1256
          V value = Async.getIfReady(entry.getValue());
×
1257
          if ((value != null) && collection.contains(value)
×
1258
              && AsMapView.this.remove(entry.getKey(), value)) {
×
1259
            modified = true;
×
1260
          }
1261
        }
×
1262
        return modified;
×
1263
      }
1264

1265
      @Override
1266
      public boolean remove(@Nullable Object o) {
1267
        if (o == null) {
×
1268
          return false;
×
1269
        }
1270
        for (var entry : delegate.entrySet()) {
×
1271
          V value = Async.getIfReady(entry.getValue());
×
1272
          if ((value != null) && value.equals(o) && AsMapView.this.remove(entry.getKey(), value)) {
×
1273
            return true;
×
1274
          }
1275
        }
×
1276
        return false;
×
1277
      }
1278

1279
      @Override
1280
      public boolean removeIf(Predicate<? super V> filter) {
1281
        requireNonNull(filter);
×
1282
        return delegate.values().removeIf(future -> {
×
1283
          V value = Async.getIfReady(future);
×
1284
          return (value != null) && filter.test(value);
×
1285
        });
1286
      }
1287

1288
      @Override
1289
      public boolean retainAll(Collection<?> collection) {
1290
        requireNonNull(collection);
×
1291
        @Var boolean modified = false;
×
1292
        for (var entry : delegate.entrySet()) {
×
1293
          V value = Async.getIfReady(entry.getValue());
×
1294
          if ((value != null) && !collection.contains(value)
×
1295
              && AsMapView.this.remove(entry.getKey(), value)) {
×
1296
            modified = true;
×
1297
          }
1298
        }
×
1299
        return modified;
×
1300
      }
1301

1302
      @Override
1303
      public void forEach(Consumer<? super V> action) {
1304
        requireNonNull(action);
×
1305
        delegate.values().forEach(future -> {
×
1306
          V value = Async.getIfReady(future);
×
1307
          if (value != null) {
×
1308
            action.accept(value);
×
1309
          }
1310
        });
×
1311
      }
×
1312

1313
      @Override
1314
      public Iterator<V> iterator() {
1315
        return new ValueIterator<>(new EntryIterator());
×
1316
      }
1317

1318
      @Override
1319
      public Spliterator<V> spliterator() {
1320
        return new ValueSpliterator<>(new EntrySpliterator());
×
1321
      }
1322
    }
1323

1324
    private static final class ValueIterator<K, V> implements Iterator<V> {
1325
      private final Iterator<Entry<K, V>> iterator;
1326

1327
      ValueIterator(Iterator<Entry<K, V>> iterator) {
×
1328
        this.iterator = requireNonNull(iterator);
×
1329
      }
×
1330

1331
      @Override
1332
      public boolean hasNext() {
1333
        return iterator.hasNext();
×
1334
      }
1335

1336
      @Override
1337
      public V next() {
1338
        return iterator.next().getValue();
×
1339
      }
1340

1341
      @Override
1342
      public void remove() {
1343
        iterator.remove();
×
1344
      }
×
1345
    }
1346

1347
    private static final class ValueSpliterator<K, V> implements Spliterator<V> {
1348
      private final Spliterator<Entry<K, V>> spliterator;
1349

1350
      ValueSpliterator(Spliterator<Entry<K, V>> iterator) {
×
1351
        this.spliterator = requireNonNull(iterator);
×
1352
      }
×
1353

1354
      @Override
1355
      public boolean tryAdvance(Consumer<? super V> action) {
1356
        requireNonNull(action);
×
1357
        return spliterator.tryAdvance(entry -> {
×
1358
          action.accept(entry.getValue());
×
1359
        });
×
1360
      }
1361

1362
      @Override
1363
      public @Nullable Spliterator<V> trySplit() {
1364
        Spliterator<Entry<K, V>> split = spliterator.trySplit();
×
1365
        return (split == null) ? null : new ValueSpliterator<>(split);
×
1366
      }
1367

1368
      @Override
1369
      public long estimateSize() {
1370
        return spliterator.estimateSize();
×
1371
      }
1372

1373
      @Override
1374
      public int characteristics() {
1375
        return NONNULL | CONCURRENT;
×
1376
      }
1377
    }
1378

1379
    private final class EntrySet extends AbstractSet<Entry<K, V>> {
×
1380

1381
      @Override
1382
      public boolean isEmpty() {
1383
        return AsMapView.this.isEmpty();
×
1384
      }
1385

1386
      @Override
1387
      public int size() {
1388
        return AsMapView.this.size();
×
1389
      }
1390

1391
      @Override
1392
      public void clear() {
1393
        AsMapView.this.clear();
×
1394
      }
×
1395

1396
      @Override
1397
      public boolean contains(Object o) {
1398
        if (!(o instanceof Entry<?, ?>)) {
×
1399
          return false;
×
1400
        }
1401
        var entry = (Entry<?, ?>) o;
×
1402
        var key = entry.getKey();
×
1403
        var value = entry.getValue();
×
1404
        if ((key == null) || (value == null)) {
×
1405
          return false;
×
1406
        }
1407
        V cachedValue = AsMapView.this.get(key);
×
1408
        return (cachedValue != null) && cachedValue.equals(value);
×
1409
      }
1410

1411
      @Override
1412
      public boolean removeAll(Collection<?> collection) {
1413
        requireNonNull(collection);
×
1414
        @Var boolean modified = false;
×
1415
        if ((collection instanceof Set<?>) && (collection.size() > size())) {
×
1416
          for (var entry : this) {
×
1417
            if (collection.contains(entry)) {
×
1418
              modified |= remove(entry);
×
1419
            }
1420
          }
×
1421
        } else {
1422
          for (var o : collection) {
×
1423
            modified |= remove(o);
×
1424
          }
×
1425
        }
1426
        return modified;
×
1427
      }
1428

1429
      @Override
1430
      public boolean remove(Object obj) {
1431
        if (!(obj instanceof Entry<?, ?>)) {
×
1432
          return false;
×
1433
        }
1434
        var entry = (Entry<?, ?>) obj;
×
1435
        var key = entry.getKey();
×
1436
        return (key != null) && AsMapView.this.remove(key, entry.getValue());
×
1437
      }
1438

1439
      @Override
1440
      public boolean removeIf(Predicate<? super Entry<K, V>> filter) {
1441
        requireNonNull(filter);
×
1442
        @Var boolean modified = false;
×
1443
        for (Entry<K, V> entry : this) {
×
1444
          if (filter.test(entry)) {
×
1445
            modified |= AsMapView.this.remove(entry.getKey(), entry.getValue());
×
1446
          }
1447
        }
×
1448
        return modified;
×
1449
      }
1450

1451
      @Override
1452
      public boolean retainAll(Collection<?> collection) {
1453
        requireNonNull(collection);
×
1454
        @Var boolean modified = false;
×
1455
        for (var entry : this) {
×
1456
          if (!collection.contains(entry) && remove(entry)) {
×
1457
            modified = true;
×
1458
          }
1459
        }
×
1460
        return modified;
×
1461
      }
1462

1463
      @Override
1464
      public Iterator<Entry<K, V>> iterator() {
1465
        return new EntryIterator();
×
1466
      }
1467

1468
      @Override
1469
      public Spliterator<Entry<K, V>> spliterator() {
1470
        return new EntrySpliterator();
×
1471
      }
1472
    }
1473

1474
    private final class EntryIterator implements Iterator<Entry<K, V>> {
1475
      final Iterator<Entry<K, CompletableFuture<V>>> iterator;
1476
      @Nullable Entry<K, V> cursor;
1477
      @Nullable K removalKey;
1478

1479
      EntryIterator() {
×
1480
        this.iterator = delegate.entrySet().iterator();
×
1481
      }
×
1482

1483
      @Override
1484
      public boolean hasNext() {
1485
        while ((cursor == null) && iterator.hasNext()) {
×
1486
          Entry<K, CompletableFuture<V>> entry = iterator.next();
×
1487
          V value = Async.getIfReady(entry.getValue());
×
1488
          if (value != null) {
×
1489
            cursor = new WriteThroughEntry<>(AsMapView.this, entry.getKey(), value);
×
1490
          }
1491
        }
×
1492
        return (cursor != null);
×
1493
      }
1494

1495
      @Override
1496
      public Entry<K, V> next() {
1497
        if (!hasNext()) {
×
1498
          throw new NoSuchElementException();
×
1499
        }
1500
        K key = requireNonNull(cursor).getKey();
×
1501
        Entry<K, V> entry = cursor;
×
1502
        removalKey = key;
×
1503
        cursor = null;
×
1504
        return entry;
×
1505
      }
1506

1507
      @Override
1508
      public void remove() {
1509
        requireState(removalKey != null);
×
1510
        delegate.remove(removalKey);
×
1511
        removalKey = null;
×
1512
      }
×
1513
    }
1514

1515
    private final class EntrySpliterator implements Spliterator<Entry<K, V>> {
1516
      final Spliterator<Entry<K, CompletableFuture<V>>> spliterator;
1517

1518
      EntrySpliterator() {
1519
        this(delegate.entrySet().spliterator());
×
1520
      }
×
1521

1522
      EntrySpliterator(Spliterator<Entry<K, CompletableFuture<V>>> spliterator) {
×
1523
        this.spliterator = requireNonNull(spliterator);
×
1524
      }
×
1525

1526
      @Override
1527
      public void forEachRemaining(Consumer<? super Entry<K, V>> action) {
1528
        requireNonNull(action);
×
1529
        spliterator.forEachRemaining(entry -> {
×
1530
          V value = Async.getIfReady(entry.getValue());
×
1531
          if (value != null) {
×
1532
            var e = new WriteThroughEntry<>(AsMapView.this, entry.getKey(), value);
×
1533
            action.accept(e);
×
1534
          }
1535
        });
×
1536
      }
×
1537

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

1558
      @Override
1559
      public @Nullable Spliterator<Entry<K, V>> trySplit() {
1560
        Spliterator<Entry<K, CompletableFuture<V>>> split = spliterator.trySplit();
×
1561
        return (split == null) ? null : new EntrySpliterator(split);
×
1562
      }
1563

1564
      @Override
1565
      public long estimateSize() {
1566
        return spliterator.estimateSize();
×
1567
      }
1568

1569
      @Override
1570
      public int characteristics() {
1571
        return DISTINCT | CONCURRENT | NONNULL;
×
1572
      }
1573
    }
1574
  }
1575
}
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