View Javadoc

1   package com.atlassian.vcache.internal.core.service;
2   
3   import java.util.Map;
4   import java.util.Objects;
5   import java.util.Optional;
6   import java.util.Set;
7   import java.util.function.Function;
8   import java.util.stream.Collectors;
9   import java.util.stream.StreamSupport;
10  
11  import static java.util.Objects.requireNonNull;
12  
13  /**
14   * Provides common functionality for local cache operations.
15   *
16   * @since 1.6.2
17   */
18  public class LocalCacheUtils {
19      /**
20       * Arguments for a put operation
21       *
22       * @param <K> the key type
23       * @param <V> the value type
24       */
25      public static class PutArgs<K, V> {
26          public final K key;
27          public final V value;
28  
29          private PutArgs(K key, V value) {
30              this.key = requireNonNull(key);
31              this.value = requireNonNull(value);
32          }
33      }
34  
35      /**
36       * Performs a bulk get.
37       *
38       * @param factory       for creating missing values
39       * @param keys          the keys to return
40       * @param getFn         function for getting an existing value
41       * @param putIfAbsentFn function for putting a new value
42       * @param lock          the lock for the cache
43       * @param <K>           the key type
44       * @param <V>           the value type
45       * @return the map of all key/value pairs.
46       */
47      public static <K, V> Map<K, V> getBulk(
48              Function<Set<K>, Map<K, V>> factory,
49              Iterable<K> keys,
50              Function<K, Optional<V>> getFn,
51              Function<PutArgs<K, V>, Optional<V>> putIfAbsentFn,
52              VCacheLock lock) {
53          // Get the existing values
54          final Map<K, Optional<V>> existingValues =
55                  lock.withLock(() ->
56                          StreamSupport.stream(keys.spliterator(), false)
57                                  .distinct()
58                                  .collect(Collectors.toMap(
59                                          Objects::requireNonNull,
60                                          getFn)));
61  
62          // Add known values to the grand result
63          @SuppressWarnings("OptionalGetWithoutIsPresent")
64          final Map<K, V> grandResult = existingValues.entrySet().stream()
65                  .filter(e -> e.getValue().isPresent())
66                  .collect(Collectors.toMap(
67                          Map.Entry::getKey,
68                          e -> e.getValue().get()));
69  
70          // Bail out if we have all the values
71          if (grandResult.size() == existingValues.size()) {
72              return grandResult;
73          }
74  
75          // Sadly we now need to call the factory to create the missing values and then merge into the grand result.
76          final Set<K> missingKeys = existingValues.entrySet().stream()
77                  .filter(e -> !e.getValue().isPresent())
78                  .map(Map.Entry::getKey)
79                  .collect(Collectors.toSet());
80  
81          final Map<K, V> missingValues = factory.apply(missingKeys);
82          FactoryUtils.verifyFactoryResult(missingValues, missingKeys);
83  
84          lock.withLock(() ->
85                  missingValues.entrySet().forEach(e -> {
86                      // Handle that another thread may have beaten us to the punch.
87                      final Optional<V> existing = putIfAbsentFn.apply(new PutArgs<>(e.getKey(), e.getValue()));
88                      grandResult.put(e.getKey(), existing.orElse(e.getValue()));
89                  }));
90  
91          return grandResult;
92      }
93  }