View Javadoc

1   package com.atlassian.vcache.internal.core.service;
2   
3   import com.atlassian.vcache.internal.core.RecursionDetector;
4   
5   import java.util.Map;
6   import java.util.Objects;
7   import java.util.Optional;
8   import java.util.Set;
9   import java.util.function.Consumer;
10  import java.util.function.Function;
11  import java.util.stream.Collectors;
12  import java.util.stream.StreamSupport;
13  
14  import static java.util.Objects.requireNonNull;
15  
16  /**
17   * Provides common functionality for local cache operations.
18   * @since 1.6.2
19   */
20  public class LocalCacheUtils {
21      /**
22       * Arguments for a put operation
23       * @param <K> the key type
24       * @param <V> the value type
25       */
26      public static class PutArgs<K, V> {
27          public final K key;
28          public final V value;
29  
30          private PutArgs(K key, V value) {
31              this.key = requireNonNull(key);
32              this.value = requireNonNull(value);
33          }
34      }
35  
36      /**
37       * Performs a bulk get.
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 putFn function for putting a new value
42       * @param recursionDetector used for detecting recursive calls
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              Consumer<PutArgs<K, V>> putFn,
52              RecursionDetector<K> recursionDetector) {
53          // Get the existing values
54          final Map<K, Optional<V>> existingValues = StreamSupport.stream(keys.spliterator(), false)
55                  .distinct()
56                  .collect(Collectors.toMap(
57                          Objects::requireNonNull,
58                          getFn));
59  
60          // Add known values to the grand result
61          @SuppressWarnings("OptionalGetWithoutIsPresent")
62          final Map<K, V> grandResult = existingValues.entrySet().stream()
63                  .filter(e -> e.getValue().isPresent())
64                  .collect(Collectors.toMap(
65                          Map.Entry::getKey,
66                          e -> e.getValue().get()));
67  
68          // Bail out if we have all the values
69          if (grandResult.size() == existingValues.size()) {
70              return grandResult;
71          }
72  
73          // Sadly we now need to call the factory to create the missing values and then merge into the grand result.
74          final Set<K> missingKeys = existingValues.entrySet().stream()
75                  .filter(e -> !e.getValue().isPresent())
76                  .map(Map.Entry::getKey)
77                  .collect(Collectors.toSet());
78  
79          try (RecursionDetector.Guard<K> ignored = recursionDetector.guardOn(missingKeys)) {
80              final Map<K, V> missingValues = factory.apply(missingKeys);
81  
82              missingValues.entrySet().forEach(e -> {
83                  putFn.accept(new PutArgs<>(e.getKey(), e.getValue()));
84                  grandResult.put(e.getKey(), e.getValue());
85              });
86          }
87  
88          return grandResult;
89      }
90  }