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

ben-manes / caffeine / #4048

19 Sep 2023 08:20AM CUT coverage: 99.016%. Remained the same
#4048

push

github-actions

web-flow
Bump crate-ci/typos from 1.16.11 to 1.16.12 (#1203)

Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.16.11 to 1.16.12.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/7c89b528f...509ec3211)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

7547 of 7622 relevant lines covered (99.02%)

0.99 hits per line

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

97.69
/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.Objects.requireNonNull;
21

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

48
import org.checkerframework.checker.nullness.qual.Nullable;
49

50
import com.github.benmanes.caffeine.cache.LocalAsyncCache.AsyncBulkCompleter.NullMapCompletionException;
51
import com.github.benmanes.caffeine.cache.stats.CacheStats;
52

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

62
  /** Returns the backing {@link LocalCache} data store. */
63
  LocalCache<K, CompletableFuture<V>> cache();
64

65
  /** Returns the policy supported by this implementation and its configuration. */
66
  Policy<K, V> policy();
67

68
  @Override
69
  default @Nullable CompletableFuture<V> getIfPresent(K key) {
70
    return cache().getIfPresent(key, /* recordStats */ true);
1✔
71
  }
72

73
  @Override
74
  default CompletableFuture<V> get(K key, Function<? super K, ? extends V> mappingFunction) {
75
    requireNonNull(mappingFunction);
1✔
76
    return get(key, (k1, executor) -> CompletableFuture.supplyAsync(
1✔
77
        () -> mappingFunction.apply(key), executor));
1✔
78
  }
79

80
  @Override
81
  default CompletableFuture<V> get(K key, BiFunction<? super K, ? super Executor,
82
      ? extends CompletableFuture<? extends V>> mappingFunction) {
83
    return get(key, mappingFunction, /* recordStats */ true);
1✔
84
  }
85

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

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

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

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

144
    var completer = new AsyncBulkCompleter<>(cache(), proxies);
1✔
145
    try {
146
      var loader = mappingFunction.apply(
1✔
147
          Collections.unmodifiableSet(proxies.keySet()), cache().executor());
1✔
148
      loader.whenComplete(completer);
1✔
149
      return composeResult(futures);
1✔
150
    } catch (Throwable t) {
1✔
151
      completer.accept(/* result */ null, t);
1✔
152
      throw t;
1✔
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()) {
1✔
163
      @SuppressWarnings("ImmutableMapOf")
164
      Map<K, V> emptyMap = Collections.unmodifiableMap(Collections.emptyMap());
1✔
165
      return CompletableFuture.completedFuture(emptyMap);
1✔
166
    }
167
    @SuppressWarnings("rawtypes")
168
    CompletableFuture<?>[] array = futures.values().toArray(new CompletableFuture[0]);
1✔
169
    return CompletableFuture.allOf(array).thenApply(ignored -> {
1✔
170
      var result = new LinkedHashMap<K, V>(calculateHashMapCapacity(futures.size()));
1✔
171
      futures.forEach((key, future) -> {
1✔
172
        V value = future.getNow(null);
1✔
173
        if (value != null) {
1✔
174
          result.put(key, value);
1✔
175
        }
176
      });
1✔
177
      return Collections.unmodifiableMap(result);
1✔
178
    });
179
  }
180

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

192
    @SuppressWarnings("unchecked")
193
    var castedFuture = (CompletableFuture<V>) valueFuture;
1✔
194
    cache().put(key, castedFuture);
1✔
195
    handleCompletion(key, valueFuture, startTime, /* recordMiss */ false);
1✔
196
  }
1✔
197

198
  @SuppressWarnings("FutureReturnValueIgnored")
199
  default void handleCompletion(K key, CompletableFuture<? extends V> valueFuture,
200
      long startTime, boolean recordMiss) {
201
    var completed = new AtomicBoolean();
1✔
202
    valueFuture.whenComplete((value, error) -> {
1✔
203
      if (!completed.compareAndSet(false, true)) {
1✔
204
        // Ignore multiple invocations due to ForkJoinPool retrying on delays
205
        return;
×
206
      }
207
      long loadTime = cache().statsTicker().read() - startTime;
1✔
208
      if (value == null) {
1✔
209
        if ((error != null) && !(error instanceof CancellationException)
1✔
210
            && !(error instanceof TimeoutException)) {
211
          logger.log(Level.WARNING, "Exception thrown during asynchronous load", error);
1✔
212
        }
213
        cache().statsCounter().recordLoadFailure(loadTime);
1✔
214
        cache().remove(key, valueFuture);
1✔
215
      } else {
216
        @SuppressWarnings("unchecked")
217
        var castedFuture = (CompletableFuture<V>) valueFuture;
1✔
218

219
        // update the weight and expiration timestamps
220
        cache().statsCounter().recordLoadSuccess(loadTime);
1✔
221
        cache().replace(key, castedFuture, castedFuture, /* shouldDiscardRefresh */ false);
1✔
222
      }
223
      if (recordMiss) {
1✔
224
        cache().statsCounter().recordMisses(1);
1✔
225
      }
226
    });
1✔
227
  }
1✔
228

229
  /** A function executed asynchronously after a bulk load completes. */
230
  final class AsyncBulkCompleter<K, V>
231
      implements BiConsumer<Map<? extends K, ? extends V>, Throwable> {
232
    private final LocalCache<K, CompletableFuture<V>> cache;
233
    private final Map<K, CompletableFuture<V>> proxies;
234
    private final long startTime;
235

236
    AsyncBulkCompleter(LocalCache<K, CompletableFuture<V>> cache,
237
        Map<K, CompletableFuture<V>> proxies) {
1✔
238
      this.startTime = cache.statsTicker().read();
1✔
239
      this.proxies = proxies;
1✔
240
      this.cache = cache;
1✔
241
    }
1✔
242

243
    @Override
244
    public void accept(@Nullable Map<? extends K, ? extends V> result, @Nullable Throwable error) {
245
      long loadTime = cache.statsTicker().read() - startTime;
1✔
246

247
      if (result == null) {
1✔
248
        if (error == null) {
1✔
249
          error = new NullMapCompletionException();
1✔
250
        }
251
        for (var entry : proxies.entrySet()) {
1✔
252
          cache.remove(entry.getKey(), entry.getValue());
1✔
253
          entry.getValue().obtrudeException(error);
1✔
254
        }
1✔
255
        cache.statsCounter().recordLoadFailure(loadTime);
1✔
256
        if (!(error instanceof CancellationException) && !(error instanceof TimeoutException)) {
1✔
257
          logger.log(Level.WARNING, "Exception thrown during asynchronous load", error);
1✔
258
        }
259
      } else {
260
        fillProxies(result);
1✔
261
        addNewEntries(result);
1✔
262
        cache.statsCounter().recordLoadSuccess(loadTime);
1✔
263
      }
264
    }
1✔
265

266
    /** Populates the proxies with the computed result. */
267
    private void fillProxies(Map<? extends K, ? extends V> result) {
268
      proxies.forEach((key, future) -> {
1✔
269
        V value = result.get(key);
1✔
270
        future.obtrudeValue(value);
1✔
271
        if (value == null) {
1✔
272
          cache.remove(key, future);
1✔
273
        } else {
274
          // update the weight and expiration timestamps
275
          cache.replace(key, future, future);
1✔
276
        }
277
      });
1✔
278
    }
1✔
279

280
    /** Adds to the cache any extra entries computed that were not requested. */
281
    private void addNewEntries(Map<? extends K, ? extends V> result) {
282
      result.forEach((key, value) -> {
1✔
283
        if (!proxies.containsKey(key)) {
1✔
284
          cache.put(key, CompletableFuture.completedFuture(value));
1✔
285
        }
286
      });
1✔
287
    }
1✔
288

289
    static final class NullMapCompletionException extends CompletionException {
1✔
290
      private static final long serialVersionUID = 1L;
291
    }
292
  }
293

294
  /* --------------- Asynchronous view --------------- */
295
  final class AsyncAsMapView<K, V> implements ConcurrentMap<K, CompletableFuture<V>> {
296
    final LocalAsyncCache<K, V> asyncCache;
297

298
    AsyncAsMapView(LocalAsyncCache<K, V> asyncCache) {
1✔
299
      this.asyncCache = requireNonNull(asyncCache);
1✔
300
    }
1✔
301
    @Override public boolean isEmpty() {
302
      return asyncCache.cache().isEmpty();
1✔
303
    }
304
    @Override public int size() {
305
      return asyncCache.cache().size();
1✔
306
    }
307
    @Override public void clear() {
308
      asyncCache.cache().clear();
1✔
309
    }
1✔
310
    @Override public boolean containsKey(Object key) {
311
      return asyncCache.cache().containsKey(key);
1✔
312
    }
313
    @Override public boolean containsValue(Object value) {
314
      return asyncCache.cache().containsValue(value);
1✔
315
    }
316
    @Override public @Nullable CompletableFuture<V> get(Object key) {
317
      return asyncCache.cache().get(key);
1✔
318
    }
319
    @Override public CompletableFuture<V> putIfAbsent(K key, CompletableFuture<V> value) {
320
      CompletableFuture<V> prior = asyncCache.cache().putIfAbsent(key, value);
1✔
321
      long startTime = asyncCache.cache().statsTicker().read();
1✔
322
      if (prior == null) {
1✔
323
        asyncCache.handleCompletion(key, value, startTime, /* recordMiss */ false);
1✔
324
      }
325
      return prior;
1✔
326
    }
327
    @Override public CompletableFuture<V> put(K key, CompletableFuture<V> value) {
328
      CompletableFuture<V> prior = asyncCache.cache().put(key, value);
1✔
329
      long startTime = asyncCache.cache().statsTicker().read();
1✔
330
      asyncCache.handleCompletion(key, value, startTime, /* recordMiss */ false);
1✔
331
      return prior;
1✔
332
    }
333
    @SuppressWarnings("FutureReturnValueIgnored")
334
    @Override public void putAll(Map<? extends K, ? extends CompletableFuture<V>> map) {
335
      map.forEach(this::put);
1✔
336
    }
1✔
337
    @Override public CompletableFuture<V> replace(K key, CompletableFuture<V> value) {
338
      CompletableFuture<V> prior = asyncCache.cache().replace(key, value);
1✔
339
      long startTime = asyncCache.cache().statsTicker().read();
1✔
340
      if (prior != null) {
1✔
341
        asyncCache.handleCompletion(key, value, startTime, /* recordMiss */ false);
1✔
342
      }
343
      return prior;
1✔
344
    }
345
    @Override
346
    public boolean replace(K key, CompletableFuture<V> oldValue, CompletableFuture<V> newValue) {
347
      boolean replaced = asyncCache.cache().replace(key, oldValue, newValue);
1✔
348
      long startTime = asyncCache.cache().statsTicker().read();
1✔
349
      if (replaced) {
1✔
350
        asyncCache.handleCompletion(key, newValue, startTime, /* recordMiss */ false);
1✔
351
      }
352
      return replaced;
1✔
353
    }
354
    @Override public CompletableFuture<V> remove(Object key) {
355
      return asyncCache.cache().remove(key);
1✔
356
    }
357
    @Override public boolean remove(Object key, Object value) {
358
      return asyncCache.cache().remove(key, value);
1✔
359
    }
360
    @SuppressWarnings("FutureReturnValueIgnored")
361
    @Override public @Nullable CompletableFuture<V> computeIfAbsent(K key,
362
        Function<? super K, ? extends CompletableFuture<V>> mappingFunction) {
363
      requireNonNull(mappingFunction);
1✔
364
      @SuppressWarnings({"rawtypes", "unchecked"})
365
      CompletableFuture<V>[] result = new CompletableFuture[1];
1✔
366
      long startTime = asyncCache.cache().statsTicker().read();
1✔
367
      CompletableFuture<V> future = asyncCache.cache().computeIfAbsent(key, k -> {
1✔
368
        result[0] = mappingFunction.apply(k);
1✔
369
        return result[0];
1✔
370
      }, /* recordStats */ false, /* recordLoad */ false);
371

372
      if (result[0] == null) {
1✔
373
        if ((future != null) && asyncCache.cache().isRecordingStats()) {
1✔
374
          future.whenComplete((r, e) -> {
1✔
375
            if ((r != null) || (e == null)) {
1✔
376
              asyncCache.cache().statsCounter().recordHits(1);
1✔
377
            }
378
          });
1✔
379
        }
380
      } else {
381
        asyncCache.handleCompletion(key, result[0], startTime, /* recordMiss */ true);
1✔
382
      }
383
      return future;
1✔
384
    }
385
    @Override public CompletableFuture<V> computeIfPresent(K key, BiFunction<? super K,
386
        ? super CompletableFuture<V>, ? extends CompletableFuture<V>> remappingFunction) {
387
      requireNonNull(remappingFunction);
1✔
388

389
      @SuppressWarnings({"rawtypes", "unchecked"})
390
      CompletableFuture<V>[] result = new CompletableFuture[1];
1✔
391
      long startTime = asyncCache.cache().statsTicker().read();
1✔
392
      asyncCache.cache().compute(key, (k, oldValue) -> {
1✔
393
        result[0] = (oldValue == null) ? null : remappingFunction.apply(k, oldValue);
1✔
394
        return result[0];
1✔
395
      }, asyncCache.cache().expiry(), /* recordLoad */ false, /* recordLoadFailure */ false);
1✔
396

397
      if (result[0] != null) {
1✔
398
        asyncCache.handleCompletion(key, result[0], startTime, /* recordMiss */ false);
1✔
399
      }
400
      return result[0];
1✔
401
    }
402
    @Override public CompletableFuture<V> compute(K key, BiFunction<? super K,
403
        ? super CompletableFuture<V>, ? extends CompletableFuture<V>> remappingFunction) {
404
      requireNonNull(remappingFunction);
1✔
405

406
      @SuppressWarnings({"rawtypes", "unchecked"})
407
      CompletableFuture<V>[] result = new CompletableFuture[1];
1✔
408
      long startTime = asyncCache.cache().statsTicker().read();
1✔
409
      asyncCache.cache().compute(key, (k, oldValue) -> {
1✔
410
        result[0] = remappingFunction.apply(k, oldValue);
1✔
411
        return result[0];
1✔
412
      }, asyncCache.cache().expiry(), /* recordLoad */ false, /* recordLoadFailure */ false);
1✔
413

414
      if (result[0] != null) {
1✔
415
        asyncCache.handleCompletion(key, result[0], startTime, /* recordMiss */ false);
1✔
416
      }
417
      return result[0];
1✔
418
    }
419
    @Override public CompletableFuture<V> merge(K key, CompletableFuture<V> value,
420
        BiFunction<? super CompletableFuture<V>, ? super CompletableFuture<V>,
421
            ? extends CompletableFuture<V>> remappingFunction) {
422
      requireNonNull(value);
1✔
423
      requireNonNull(remappingFunction);
1✔
424

425
      @SuppressWarnings({"rawtypes", "unchecked"})
426
      CompletableFuture<V>[] result = new CompletableFuture[1];
1✔
427
      long startTime = asyncCache.cache().statsTicker().read();
1✔
428
      asyncCache.cache().compute(key, (k, oldValue) -> {
1✔
429
        result[0] = (oldValue == null) ? value : remappingFunction.apply(oldValue, value);
1✔
430
        return result[0];
1✔
431
      }, asyncCache.cache().expiry(), /* recordLoad */ false, /* recordLoadFailure */ false);
1✔
432

433
      if (result[0] != null) {
1✔
434
        asyncCache.handleCompletion(key, result[0], startTime, /* recordMiss */ false);
1✔
435
      }
436
      return result[0];
1✔
437
    }
438
    @Override public void forEach(BiConsumer<? super K, ? super CompletableFuture<V>> action) {
439
      asyncCache.cache().forEach(action);
1✔
440
    }
1✔
441
    @Override public Set<K> keySet() {
442
      return asyncCache.cache().keySet();
1✔
443
    }
444
    @Override public Collection<CompletableFuture<V>> values() {
445
      return asyncCache.cache().values();
1✔
446
    }
447
    @Override public Set<Entry<K, CompletableFuture<V>>> entrySet() {
448
      return asyncCache.cache().entrySet();
1✔
449
    }
450
    @Override public boolean equals(Object o) {
451
      return asyncCache.cache().equals(o);
1✔
452
    }
453
    @Override public int hashCode() {
454
      return asyncCache.cache().hashCode();
1✔
455
    }
456
    @Override public String toString() {
457
      return asyncCache.cache().toString();
1✔
458
    }
459
  }
460

461
  /* --------------- Synchronous view --------------- */
462
  final class CacheView<K, V> extends AbstractCacheView<K, V> {
463
    private static final long serialVersionUID = 1L;
464

465
    @SuppressWarnings("serial")
466
    final LocalAsyncCache<K, V> asyncCache;
467

468
    CacheView(LocalAsyncCache<K, V> asyncCache) {
1✔
469
      this.asyncCache = requireNonNull(asyncCache);
1✔
470
    }
1✔
471
    @Override LocalAsyncCache<K, V> asyncCache() {
472
      return asyncCache;
1✔
473
    }
474
  }
475

476
  abstract class AbstractCacheView<K, V> implements Cache<K, V>, Serializable {
1✔
477
    private static final long serialVersionUID = 1L;
478

479
    transient @Nullable ConcurrentMap<K, V> asMapView;
480

481
    abstract LocalAsyncCache<K, V> asyncCache();
482

483
    @Override
484
    public @Nullable V getIfPresent(K key) {
485
      CompletableFuture<V> future = asyncCache().cache().getIfPresent(key, /* recordStats */ true);
1✔
486
      return Async.getIfReady(future);
1✔
487
    }
488

489
    @Override
490
    public Map<K, V> getAllPresent(Iterable<? extends K> keys) {
491
      var result = new LinkedHashMap<K, V>(calculateHashMapCapacity(keys));
1✔
492
      for (K key : keys) {
1✔
493
        result.put(key, null);
1✔
494
      }
1✔
495

496
      int uniqueKeys = result.size();
1✔
497
      for (var iter = result.entrySet().iterator(); iter.hasNext();) {
1✔
498
        Map.Entry<K, V> entry = iter.next();
1✔
499

500
        CompletableFuture<V> future = asyncCache().cache().get(entry.getKey());
1✔
501
        V value = Async.getIfReady(future);
1✔
502
        if (value == null) {
1✔
503
          iter.remove();
1✔
504
        } else {
505
          entry.setValue(value);
1✔
506
        }
507
      }
1✔
508
      asyncCache().cache().statsCounter().recordHits(result.size());
1✔
509
      asyncCache().cache().statsCounter().recordMisses(uniqueKeys - result.size());
1✔
510

511
      return Collections.unmodifiableMap(result);
1✔
512
    }
513

514
    @Override
515
    public V get(K key, Function<? super K, ? extends V> mappingFunction) {
516
      return resolve(asyncCache().get(key, mappingFunction));
1✔
517
    }
518

519
    @Override
520
    public Map<K, V> getAll(Iterable<? extends K> keys,
521
        Function<? super Set<? extends K>, ? extends Map<? extends K, ? extends V>> mappingFunction) {
522
      return resolve(asyncCache().getAll(keys, mappingFunction));
1✔
523
    }
524

525
    @SuppressWarnings({"PMD.AvoidThrowingNullPointerException", "PMD.PreserveStackTrace"})
526
    protected static <T> T resolve(CompletableFuture<T> future) {
527
      try {
528
        return future.join();
1✔
529
      } catch (NullMapCompletionException e) {
1✔
530
        throw new NullPointerException("null map");
1✔
531
      } catch (CompletionException e) {
1✔
532
        if (e.getCause() instanceof RuntimeException) {
1✔
533
          throw (RuntimeException) e.getCause();
1✔
534
        } else if (e.getCause() instanceof Error) {
1✔
535
          throw (Error) e.getCause();
1✔
536
        }
537
        throw e;
1✔
538
      }
539
    }
540

541
    @Override
542
    public void put(K key, V value) {
543
      requireNonNull(value);
1✔
544
      asyncCache().cache().put(key, CompletableFuture.completedFuture(value));
1✔
545
    }
1✔
546

547
    @Override
548
    public void putAll(Map<? extends K, ? extends V> map) {
549
      map.forEach(this::put);
1✔
550
    }
1✔
551

552
    @Override
553
    public void invalidate(K key) {
554
      asyncCache().cache().remove(key);
1✔
555
    }
1✔
556

557
    @Override
558
    public void invalidateAll(Iterable<? extends K> keys) {
559
      asyncCache().cache().invalidateAll(keys);
1✔
560
    }
1✔
561

562
    @Override
563
    public void invalidateAll() {
564
      asyncCache().cache().clear();
1✔
565
    }
1✔
566

567
    @Override
568
    public long estimatedSize() {
569
      return asyncCache().cache().estimatedSize();
1✔
570
    }
571

572
    @Override
573
    public CacheStats stats() {
574
      return asyncCache().cache().statsCounter().snapshot();
1✔
575
    }
576

577
    @Override
578
    public void cleanUp() {
579
      asyncCache().cache().cleanUp();
1✔
580
    }
1✔
581

582
    @Override
583
    public Policy<K, V> policy() {
584
      return asyncCache().policy();
1✔
585
    }
586

587
    @Override
588
    public ConcurrentMap<K, V> asMap() {
589
      return (asMapView == null) ? (asMapView = new AsMapView<>(asyncCache().cache())) : asMapView;
1✔
590
    }
591
  }
592

593
  final class AsMapView<K, V> implements ConcurrentMap<K, V> {
594
    final LocalCache<K, CompletableFuture<V>> delegate;
595

596
    @Nullable Collection<V> values;
597
    @Nullable Set<Entry<K, V>> entries;
598

599
    AsMapView(LocalCache<K, CompletableFuture<V>> delegate) {
1✔
600
      this.delegate = delegate;
1✔
601
    }
1✔
602

603
    @Override
604
    public boolean isEmpty() {
605
      return delegate.isEmpty();
1✔
606
    }
607

608
    @Override
609
    public int size() {
610
      return delegate.size();
1✔
611
    }
612

613
    @Override
614
    public void clear() {
615
      delegate.clear();
1✔
616
    }
1✔
617

618
    @Override
619
    public boolean containsKey(Object key) {
620
      return delegate.containsKey(key);
1✔
621
    }
622

623
    @Override
624
    public boolean containsValue(Object value) {
625
      requireNonNull(value);
1✔
626

627
      for (CompletableFuture<V> valueFuture : delegate.values()) {
1✔
628
        if (value.equals(Async.getIfReady(valueFuture))) {
1✔
629
          return true;
1✔
630
        }
631
      }
1✔
632
      return false;
1✔
633
    }
634

635
    @Override
636
    public @Nullable V get(Object key) {
637
      return Async.getIfReady(delegate.get(key));
1✔
638
    }
639

640
    @Override
641
    public @Nullable V putIfAbsent(K key, V value) {
642
      requireNonNull(value);
1✔
643

644
      // Keep in sync with BoundedVarExpiration.putIfAbsentAsync(key, value, duration, unit)
645
      CompletableFuture<V> priorFuture = null;
1✔
646
      for (;;) {
647
        priorFuture = (priorFuture == null)
1✔
648
            ? delegate.get(key)
1✔
649
            : delegate.getIfPresentQuietly(key);
1✔
650
        if (priorFuture != null) {
1✔
651
          if (!priorFuture.isDone()) {
1✔
652
            Async.getWhenSuccessful(priorFuture);
1✔
653
            continue;
1✔
654
          }
655

656
          V prior = Async.getWhenSuccessful(priorFuture);
1✔
657
          if (prior != null) {
1✔
658
            return prior;
1✔
659
          }
660
        }
661

662
        boolean[] added = { false };
1✔
663
        CompletableFuture<V> computed = delegate.compute(key, (k, valueFuture) -> {
1✔
664
          added[0] = (valueFuture == null)
1✔
665
              || (valueFuture.isDone() && (Async.getIfReady(valueFuture) == null));
1✔
666
          return added[0] ? CompletableFuture.completedFuture(value) : valueFuture;
1✔
667
        }, delegate.expiry(), /* recordLoad */ false, /* recordLoadFailure */ false);
1✔
668

669
        if (added[0]) {
1✔
670
          return null;
1✔
671
        } else {
672
          V prior = Async.getWhenSuccessful(computed);
1✔
673
          if (prior != null) {
1✔
674
            return prior;
1✔
675
          }
676
        }
677
      }
×
678
    }
679

680
    @Override
681
    public void putAll(Map<? extends K, ? extends V> map) {
682
      map.forEach(this::put);
1✔
683
    }
1✔
684

685
    @Override
686
    public @Nullable V put(K key, V value) {
687
      requireNonNull(value);
1✔
688
      CompletableFuture<V> oldValueFuture =
1✔
689
          delegate.put(key, CompletableFuture.completedFuture(value));
1✔
690
      return Async.getWhenSuccessful(oldValueFuture);
1✔
691
    }
692

693
    @Override
694
    public @Nullable V remove(Object key) {
695
      CompletableFuture<V> oldValueFuture = delegate.remove(key);
1✔
696
      return Async.getWhenSuccessful(oldValueFuture);
1✔
697
    }
698

699
    @Override
700
    public boolean remove(Object key, Object value) {
701
      requireNonNull(key);
1✔
702
      if (value == null) {
1✔
703
        return false;
1✔
704
      }
705

706
      @SuppressWarnings("unchecked")
707
      K castedKey = (K) key;
1✔
708
      boolean[] done = { false };
1✔
709
      boolean[] removed = { false };
1✔
710
      CompletableFuture<V> future = null;
1✔
711
      for (;;) {
712
        future = (future == null)
1✔
713
            ? delegate.get(castedKey)
1✔
714
            : delegate.getIfPresentQuietly(castedKey);
1✔
715
        if ((future == null) || future.isCompletedExceptionally()) {
1✔
716
          return false;
1✔
717
        }
718

719
        Async.getWhenSuccessful(future);
1✔
720
        delegate.compute(castedKey, (k, oldValueFuture) -> {
1✔
721
          if (oldValueFuture == null) {
1✔
722
            done[0] = true;
1✔
723
            return null;
1✔
724
          } else if (!oldValueFuture.isDone()) {
1✔
725
            return oldValueFuture;
×
726
          }
727

728
          done[0] = true;
1✔
729
          V oldValue = Async.getIfReady(oldValueFuture);
1✔
730
          removed[0] = value.equals(oldValue);
1✔
731
          return (oldValue == null) || removed[0] ? null : oldValueFuture;
1✔
732
        }, delegate.expiry(), /* recordLoad */ false, /* recordLoadFailure */ true);
1✔
733

734
        if (done[0]) {
1✔
735
          return removed[0];
1✔
736
        }
737
      }
738
    }
739

740
    @Override
741
    public @Nullable V replace(K key, V value) {
742
      requireNonNull(value);
1✔
743

744
      @SuppressWarnings({"rawtypes", "unchecked"})
745
      V[] oldValue = (V[]) new Object[1];
1✔
746
      boolean[] done = { false };
1✔
747
      for (;;) {
748
        CompletableFuture<V> future = delegate.getIfPresentQuietly(key);
1✔
749
        if ((future == null) || future.isCompletedExceptionally()) {
1✔
750
          return null;
1✔
751
        }
752

753
        Async.getWhenSuccessful(future);
1✔
754
        delegate.compute(key, (k, oldValueFuture) -> {
1✔
755
          if (oldValueFuture == null) {
1✔
756
            done[0] = true;
1✔
757
            return null;
1✔
758
          } else if (!oldValueFuture.isDone()) {
1✔
759
            return oldValueFuture;
×
760
          }
761

762
          done[0] = true;
1✔
763
          oldValue[0] = Async.getIfReady(oldValueFuture);
1✔
764
          return (oldValue[0] == null) ? null : CompletableFuture.completedFuture(value);
1✔
765
        }, delegate.expiry(), /* recordLoad */ false, /* recordLoadFailure */ false);
1✔
766

767
        if (done[0]) {
1✔
768
          return oldValue[0];
1✔
769
        }
770
      }
×
771
    }
772

773
    @Override
774
    public boolean replace(K key, V oldValue, V newValue) {
775
      requireNonNull(oldValue);
1✔
776
      requireNonNull(newValue);
1✔
777

778
      boolean[] done = { false };
1✔
779
      boolean[] replaced = { false };
1✔
780
      for (;;) {
781
        CompletableFuture<V> future = delegate.getIfPresentQuietly(key);
1✔
782
        if ((future == null) || future.isCompletedExceptionally()) {
1✔
783
          return false;
1✔
784
        }
785

786
        Async.getWhenSuccessful(future);
1✔
787
        delegate.compute(key, (k, oldValueFuture) -> {
1✔
788
          if (oldValueFuture == null) {
1✔
789
            done[0] = true;
1✔
790
            return null;
1✔
791
          } else if (!oldValueFuture.isDone()) {
1✔
792
            return oldValueFuture;
×
793
          }
794

795
          done[0] = true;
1✔
796
          replaced[0] = oldValue.equals(Async.getIfReady(oldValueFuture));
1✔
797
          return replaced[0] ? CompletableFuture.completedFuture(newValue) : oldValueFuture;
1✔
798
        }, delegate.expiry(), /* recordLoad */ false, /* recordLoadFailure */ false);
1✔
799

800
        if (done[0]) {
1✔
801
          return replaced[0];
1✔
802
        }
803
      }
×
804
    }
805

806
    @Override
807
    public @Nullable V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
808
      requireNonNull(mappingFunction);
1✔
809

810
      CompletableFuture<V> priorFuture = null;
1✔
811
      for (;;) {
812
        priorFuture = (priorFuture == null)
1✔
813
            ? delegate.get(key)
1✔
814
            : delegate.getIfPresentQuietly(key);
1✔
815
        if (priorFuture != null) {
1✔
816
          if (!priorFuture.isDone()) {
1✔
817
            Async.getWhenSuccessful(priorFuture);
1✔
818
            continue;
1✔
819
          }
820

821
          V prior = Async.getWhenSuccessful(priorFuture);
1✔
822
          if (prior != null) {
1✔
823
            delegate.statsCounter().recordHits(1);
1✔
824
            return prior;
1✔
825
          }
826
        }
827

828
        @SuppressWarnings({"rawtypes", "unchecked"})
829
        CompletableFuture<V>[] future = new CompletableFuture[1];
1✔
830
        CompletableFuture<V> computed = delegate.compute(key, (k, valueFuture) -> {
1✔
831
          if ((valueFuture != null) && valueFuture.isDone()
1✔
832
              && (Async.getIfReady(valueFuture) != null)) {
1✔
833
            return valueFuture;
1✔
834
          }
835

836
          V newValue = delegate.statsAware(mappingFunction, /* recordLoad */ true).apply(key);
1✔
837
          if (newValue == null) {
1✔
838
            return null;
1✔
839
          }
840
          future[0] = CompletableFuture.completedFuture(newValue);
1✔
841
          return future[0];
1✔
842
        }, delegate.expiry(), /* recordLoad */ false, /* recordLoadFailure */ false);
1✔
843

844
        V result = Async.getWhenSuccessful(computed);
1✔
845
        if ((computed == future[0]) || (result != null)) {
1✔
846
          return result;
1✔
847
        }
848
      }
×
849
    }
850

851
    @Override
852
    public @Nullable V computeIfPresent(K key,
853
        BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
854
      requireNonNull(remappingFunction);
1✔
855

856
      @SuppressWarnings({"rawtypes", "unchecked"})
857
      V[] newValue = (V[]) new Object[1];
1✔
858
      for (;;) {
859
        Async.getWhenSuccessful(delegate.getIfPresentQuietly(key));
1✔
860

861
        CompletableFuture<V> valueFuture = delegate.computeIfPresent(key, (k, oldValueFuture) -> {
1✔
862
          if (!oldValueFuture.isDone()) {
1✔
863
            return oldValueFuture;
×
864
          }
865

866
          V oldValue = Async.getIfReady(oldValueFuture);
1✔
867
          if (oldValue == null) {
1✔
868
            return null;
1✔
869
          }
870

871
          newValue[0] = remappingFunction.apply(key, oldValue);
1✔
872
          return (newValue[0] == null) ? null : CompletableFuture.completedFuture(newValue[0]);
1✔
873
        });
874

875
        if (newValue[0] != null) {
1✔
876
          return newValue[0];
1✔
877
        } else if (valueFuture == null) {
1✔
878
          return null;
1✔
879
        }
880
      }
×
881
    }
882

883
    @Override
884
    public @Nullable V compute(K key,
885
        BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
886
      // Keep in sync with BoundedVarExpiration.computeAsync(key, remappingFunction, expiry)
887
      requireNonNull(remappingFunction);
1✔
888

889
      @SuppressWarnings({"rawtypes", "unchecked"})
890
      V[] newValue = (V[]) new Object[1];
1✔
891
      for (;;) {
892
        Async.getWhenSuccessful(delegate.getIfPresentQuietly(key));
1✔
893

894
        CompletableFuture<V> valueFuture = delegate.compute(key, (k, oldValueFuture) -> {
1✔
895
          if ((oldValueFuture != null) && !oldValueFuture.isDone()) {
1✔
896
            return oldValueFuture;
×
897
          }
898

899
          V oldValue = Async.getIfReady(oldValueFuture);
1✔
900
          BiFunction<? super K, ? super V, ? extends V> function = delegate.statsAware(
1✔
901
              remappingFunction, /* recordLoad */ true, /* recordLoadFailure */ true);
902
          newValue[0] = function.apply(key, oldValue);
1✔
903
          return (newValue[0] == null) ? null : CompletableFuture.completedFuture(newValue[0]);
1✔
904
        }, delegate.expiry(), /* recordLoad */ false, /* recordLoadFailure */ false);
1✔
905

906
        if (newValue[0] != null) {
1✔
907
          return newValue[0];
1✔
908
        } else if (valueFuture == null) {
1✔
909
          return null;
1✔
910
        }
911
      }
×
912
    }
913

914
    @Override
915
    public @Nullable V merge(K key, V value,
916
        BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
917
      requireNonNull(value);
1✔
918
      requireNonNull(remappingFunction);
1✔
919

920
      CompletableFuture<V> newValueFuture = CompletableFuture.completedFuture(value);
1✔
921
      boolean[] merged = { false };
1✔
922
      for (;;) {
923
        Async.getWhenSuccessful(delegate.getIfPresentQuietly(key));
1✔
924

925
        CompletableFuture<V> mergedValueFuture = delegate.merge(
1✔
926
            key, newValueFuture, (oldValueFuture, valueFuture) -> {
927
          if ((oldValueFuture != null) && !oldValueFuture.isDone()) {
1✔
928
            return oldValueFuture;
×
929
          }
930

931
          merged[0] = true;
1✔
932
          V oldValue = Async.getIfReady(oldValueFuture);
1✔
933
          if (oldValue == null) {
1✔
934
            return valueFuture;
1✔
935
          }
936
          V mergedValue = remappingFunction.apply(oldValue, value);
1✔
937
          if (mergedValue == null) {
1✔
938
            return null;
1✔
939
          } else if (mergedValue == oldValue) {
1✔
940
            return oldValueFuture;
1✔
941
          } else if (mergedValue == value) {
1✔
942
            return valueFuture;
1✔
943
          }
944
          return CompletableFuture.completedFuture(mergedValue);
1✔
945
        });
946

947
        if (merged[0] || (mergedValueFuture == newValueFuture)) {
1✔
948
          return Async.getWhenSuccessful(mergedValueFuture);
1✔
949
        }
950
      }
×
951
    }
952

953
    @Override
954
    public Set<K> keySet() {
955
      return delegate.keySet();
1✔
956
    }
957

958
    @Override
959
    public Collection<V> values() {
960
      return (values == null) ? (values = new Values()) : values;
1✔
961
    }
962

963
    @Override
964
    public Set<Entry<K, V>> entrySet() {
965
      return (entries == null) ? (entries = new EntrySet()) : entries;
1✔
966
    }
967

968
    /** See {@link BoundedLocalCache#equals(Object)} for semantics. */
969
    @Override
970
    public boolean equals(Object o) {
971
      if (o == this) {
1✔
972
        return true;
1✔
973
      } else if (!(o instanceof Map)) {
1✔
974
        return false;
1✔
975
      }
976

977
      var map = (Map<?, ?>) o;
1✔
978
      int expectedSize = size();
1✔
979
      if (map.size() != expectedSize) {
1✔
980
        return false;
1✔
981
      }
982

983
      int count = 0;
1✔
984
      for (var iterator = new EntryIterator(); iterator.hasNext();) {
1✔
985
        var entry = iterator.next();
1✔
986
        var value = map.get(entry.getKey());
1✔
987
        if ((value == null) || ((value != entry.getValue()) && !value.equals(entry.getValue()))) {
1✔
988
          return false;
1✔
989
        }
990
        count++;
1✔
991
      }
1✔
992
      return (count == expectedSize);
1✔
993
    }
994

995
    @Override
996
    public int hashCode() {
997
      int hash = 0;
1✔
998
      for (var iterator = new EntryIterator(); iterator.hasNext();) {
1✔
999
        var entry = iterator.next();
1✔
1000
        hash += entry.hashCode();
1✔
1001
      }
1✔
1002
      return hash;
1✔
1003
    }
1004

1005
    @Override
1006
    public String toString() {
1007
      var result = new StringBuilder(50).append('{');
1✔
1008
      for (var iterator = new EntryIterator(); iterator.hasNext();) {
1✔
1009
        var entry = iterator.next();
1✔
1010
        result.append((entry.getKey() == this) ? "(this Map)" : entry.getKey())
1✔
1011
            .append('=')
1✔
1012
            .append((entry.getValue() == this) ? "(this Map)" : entry.getValue());
1✔
1013

1014
        if (iterator.hasNext()) {
1✔
1015
          result.append(", ");
1✔
1016
        }
1017
      }
1✔
1018
      return result.append('}').toString();
1✔
1019
    }
1020

1021
    private final class Values extends AbstractCollection<V> {
1✔
1022

1023
      @Override
1024
      public boolean isEmpty() {
1025
        return AsMapView.this.isEmpty();
1✔
1026
      }
1027

1028
      @Override
1029
      public int size() {
1030
        return AsMapView.this.size();
1✔
1031
      }
1032

1033
      @Override
1034
      public void clear() {
1035
        AsMapView.this.clear();
1✔
1036
      }
1✔
1037

1038
      @Override
1039
      public boolean contains(Object o) {
1040
        return AsMapView.this.containsValue(o);
1✔
1041
      }
1042

1043
      @Override
1044
      public boolean removeAll(Collection<?> collection) {
1045
        requireNonNull(collection);
1✔
1046
        boolean modified = false;
1✔
1047
        for (var entry : delegate.entrySet()) {
1✔
1048
          V value = Async.getIfReady(entry.getValue());
1✔
1049
          if ((value != null) && collection.contains(value)
1✔
1050
              && AsMapView.this.remove(entry.getKey(), value)) {
1✔
1051
            modified = true;
1✔
1052
          }
1053
        }
1✔
1054
        return modified;
1✔
1055
      }
1056

1057
      @Override
1058
      public boolean remove(Object o) {
1059
        if (o == null) {
1✔
1060
          return false;
1✔
1061
        }
1062
        for (var entry : delegate.entrySet()) {
1✔
1063
          V value = Async.getIfReady(entry.getValue());
1✔
1064
          if ((value != null) && value.equals(o) && AsMapView.this.remove(entry.getKey(), value)) {
1✔
1065
            return true;
1✔
1066
          }
1067
        }
1✔
1068
        return false;
1✔
1069
      }
1070

1071
      @Override
1072
      public boolean removeIf(Predicate<? super V> filter) {
1073
        requireNonNull(filter);
1✔
1074
        return delegate.values().removeIf(future -> {
1✔
1075
          V value = Async.getIfReady(future);
1✔
1076
          return (value != null) && filter.test(value);
1✔
1077
        });
1078
      }
1079

1080
      @Override
1081
      public boolean retainAll(Collection<?> collection) {
1082
        requireNonNull(collection);
1✔
1083
        boolean modified = false;
1✔
1084
        for (var entry : delegate.entrySet()) {
1✔
1085
          V value = Async.getIfReady(entry.getValue());
1✔
1086
          if ((value != null) && !collection.contains(value)
1✔
1087
              && AsMapView.this.remove(entry.getKey(), value)) {
1✔
1088
            modified = true;
1✔
1089
          }
1090
        }
1✔
1091
        return modified;
1✔
1092
      }
1093

1094
      @Override
1095
      public void forEach(Consumer<? super V> action) {
1096
        requireNonNull(action);
1✔
1097
        delegate.values().forEach(future -> {
1✔
1098
          V value = Async.getIfReady(future);
1✔
1099
          if (value != null) {
1✔
1100
            action.accept(value);
1✔
1101
          }
1102
        });
1✔
1103
      }
1✔
1104

1105
      @Override
1106
      public Iterator<V> iterator() {
1107
        return new Iterator<V>() {
1✔
1108
          final Iterator<Entry<K, V>> iterator = entrySet().iterator();
1✔
1109

1110
          @Override
1111
          public boolean hasNext() {
1112
            return iterator.hasNext();
1✔
1113
          }
1114

1115
          @Override
1116
          public V next() {
1117
            return iterator.next().getValue();
1✔
1118
          }
1119

1120
          @Override
1121
          public void remove() {
1122
            iterator.remove();
1✔
1123
          }
1✔
1124
        };
1125
      }
1126
    }
1127

1128
    private final class EntrySet extends AbstractSet<Entry<K, V>> {
1✔
1129

1130
      @Override
1131
      public boolean isEmpty() {
1132
        return AsMapView.this.isEmpty();
1✔
1133
      }
1134

1135
      @Override
1136
      public int size() {
1137
        return AsMapView.this.size();
1✔
1138
      }
1139

1140
      @Override
1141
      public void clear() {
1142
        AsMapView.this.clear();
1✔
1143
      }
1✔
1144

1145
      @Override
1146
      public boolean contains(Object o) {
1147
        if (!(o instanceof Entry<?, ?>)) {
1✔
1148
          return false;
1✔
1149
        }
1150
        Entry<?, ?> entry = (Entry<?, ?>) o;
1✔
1151
        var key = entry.getKey();
1✔
1152
        var value = entry.getValue();
1✔
1153
        if ((key == null) || (value == null)) {
1✔
1154
          return false;
1✔
1155
        }
1156
        V cachedValue = AsMapView.this.get(key);
1✔
1157
        return (cachedValue != null) && cachedValue.equals(value);
1✔
1158
      }
1159

1160
      @Override
1161
      public boolean removeAll(Collection<?> collection) {
1162
        requireNonNull(collection);
1✔
1163
        boolean modified = false;
1✔
1164
        if ((collection instanceof Set<?>) && (collection.size() > size())) {
1✔
1165
          for (var entry : this) {
1✔
1166
            if (collection.contains(entry)) {
1✔
1167
              modified |= remove(entry);
1✔
1168
            }
1169
          }
1✔
1170
        } else {
1171
          for (var o : collection) {
1✔
1172
            modified |= remove(o);
1✔
1173
          }
1✔
1174
        }
1175
        return modified;
1✔
1176
      }
1177

1178
      @Override
1179
      public boolean remove(Object obj) {
1180
        if (!(obj instanceof Entry<?, ?>)) {
1✔
1181
          return false;
1✔
1182
        }
1183
        Entry<?, ?> entry = (Entry<?, ?>) obj;
1✔
1184
        var key = entry.getKey();
1✔
1185
        return (key != null) && AsMapView.this.remove(key, entry.getValue());
1✔
1186
      }
1187

1188
      @Override
1189
      public boolean removeIf(Predicate<? super Entry<K, V>> filter) {
1190
        requireNonNull(filter);
1✔
1191
        boolean modified = false;
1✔
1192
        for (Entry<K, V> entry : this) {
1✔
1193
          if (filter.test(entry)) {
1✔
1194
            modified |= AsMapView.this.remove(entry.getKey(), entry.getValue());
1✔
1195
          }
1196
        }
1✔
1197
        return modified;
1✔
1198
      }
1199

1200
      @Override
1201
      public boolean retainAll(Collection<?> collection) {
1202
        requireNonNull(collection);
1✔
1203
        boolean modified = false;
1✔
1204
        for (var entry : this) {
1✔
1205
          if (!collection.contains(entry) && remove(entry)) {
1✔
1206
            modified = true;
1✔
1207
          }
1208
        }
1✔
1209
        return modified;
1✔
1210
      }
1211

1212
      @Override
1213
      public Iterator<Entry<K, V>> iterator() {
1214
        return new EntryIterator();
1✔
1215
      }
1216
    }
1217

1218
    private final class EntryIterator implements Iterator<Entry<K, V>> {
1219
      final Iterator<Entry<K, CompletableFuture<V>>> iterator;
1220
      @Nullable Entry<K, V> cursor;
1221
      @Nullable K removalKey;
1222

1223
      EntryIterator() {
1✔
1224
        iterator = delegate.entrySet().iterator();
1✔
1225
      }
1✔
1226

1227
      @Override
1228
      public boolean hasNext() {
1229
        while ((cursor == null) && iterator.hasNext()) {
1✔
1230
          Entry<K, CompletableFuture<V>> entry = iterator.next();
1✔
1231
          V value = Async.getIfReady(entry.getValue());
1✔
1232
          if (value != null) {
1✔
1233
            cursor = new WriteThroughEntry<>(AsMapView.this, entry.getKey(), value);
1✔
1234
          }
1235
        }
1✔
1236
        return (cursor != null);
1✔
1237
      }
1238

1239
      @Override
1240
      public Entry<K, V> next() {
1241
        if (!hasNext()) {
1✔
1242
          throw new NoSuchElementException();
1✔
1243
        }
1244
        @SuppressWarnings("NullAway")
1245
        K key = cursor.getKey();
1✔
1246
        Entry<K, V> entry = cursor;
1✔
1247
        removalKey = key;
1✔
1248
        cursor = null;
1✔
1249
        return entry;
1✔
1250
      }
1251

1252
      @Override
1253
      public void remove() {
1254
        requireState(removalKey != null);
1✔
1255
        delegate.remove(removalKey);
1✔
1256
        removalKey = null;
1✔
1257
      }
1✔
1258
    }
1259
  }
1260
}
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

© 2025 Coveralls, Inc