Skip to content

Commit ba1b63f

Browse files
committed
Update SdkUri to use BoundedCache
1 parent b58c1e7 commit ba1b63f

File tree

3 files changed

+138
-12
lines changed

3 files changed

+138
-12
lines changed
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. 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+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.utils.cache.bounded;
17+
18+
import java.util.Iterator;
19+
import java.util.concurrent.ConcurrentHashMap;
20+
import java.util.function.Function;
21+
import software.amazon.awssdk.annotations.SdkProtectedApi;
22+
import software.amazon.awssdk.annotations.ThreadSafe;
23+
import software.amazon.awssdk.utils.Logger;
24+
import software.amazon.awssdk.utils.Validate;
25+
26+
/**
27+
* A thread-safe cache implementation that returns the value for a specified key,
28+
* retrieving it by either getting the stored value from the cache or using a supplied function to calculate that value
29+
* and add it to the cache.
30+
* <p>
31+
* When the cache is full, a new value will push out an unspecified value.
32+
* <p>
33+
* The user can configure the maximum size of the cache, which is set to a default of 100.
34+
* <p>
35+
* Null values are not cached.
36+
*/
37+
@SdkProtectedApi
38+
@ThreadSafe
39+
public final class BoundedCache<K, V> {
40+
41+
private static final Logger log = Logger.loggerFor(BoundedCache.class);
42+
43+
private static final int DEFAULT_SIZE = 100;
44+
45+
private final ConcurrentHashMap<K, V> cache;
46+
private final Function<K, V> valueSupplier;
47+
private final int maxCacheSize;
48+
private final Object cacheLock;
49+
50+
private BoundedCache(Builder<K, V> builder) {
51+
this.valueSupplier = builder.supplier;
52+
this.maxCacheSize = builder.maxSize != null ?
53+
Validate.isPositive(builder.maxSize, "maxSize")
54+
: DEFAULT_SIZE;
55+
this.cache = new ConcurrentHashMap<>();
56+
this.cacheLock = new Object();
57+
}
58+
59+
/**
60+
* Get a value based on the key. If the value exists in the cache, it's returned.
61+
* Otherwise, the value is calculated based on the supplied function {@link Builder#builder(Function)}.
62+
*/
63+
public V get(K key) {
64+
V value = cache.get(key);
65+
if (value != null) {
66+
return value;
67+
}
68+
69+
V newValue = valueSupplier.apply(key);
70+
if (newValue == null) {
71+
return null;
72+
}
73+
74+
synchronized (cacheLock) {
75+
value = cache.get(key);
76+
if (value != null) {
77+
return value;
78+
}
79+
80+
if (cache.size() >= maxCacheSize) {
81+
cleanup();
82+
}
83+
84+
cache.put(key, newValue);
85+
return newValue;
86+
}
87+
}
88+
89+
/**
90+
* Clean up the cache by removing an unspecified entry
91+
*/
92+
private void cleanup() {
93+
Iterator<K> iterator = cache.keySet().iterator();
94+
if (iterator.hasNext()) {
95+
K key = iterator.next();
96+
cache.remove(key);
97+
}
98+
}
99+
100+
public int size() {
101+
return cache.size();
102+
}
103+
104+
public static <K, V> BoundedCache.Builder<K, V> builder(Function<K, V> supplier) {
105+
return new Builder<>(supplier);
106+
}
107+
108+
public static final class Builder<K, V> {
109+
110+
private final Function<K, V> supplier;
111+
private Integer maxSize;
112+
113+
private Builder(Function<K, V> supplier) {
114+
this.supplier = supplier;
115+
}
116+
117+
public Builder<K, V> maxSize(Integer maxSize) {
118+
this.maxSize = maxSize;
119+
return this;
120+
}
121+
122+
public BoundedCache<K, V> build() {
123+
return new BoundedCache<>(this);
124+
}
125+
}
126+
}

utils/src/main/java/software/amazon/awssdk/utils/uri/SdkUri.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import software.amazon.awssdk.annotations.SdkProtectedApi;
2222
import software.amazon.awssdk.utils.Lazy;
2323
import software.amazon.awssdk.utils.Logger;
24-
import software.amazon.awssdk.utils.cache.lru.LruCache;
24+
import software.amazon.awssdk.utils.cache.bounded.BoundedCache;
2525
import software.amazon.awssdk.utils.uri.internal.UriConstructorArgs;
2626

2727
/**
@@ -37,19 +37,19 @@ public final class SdkUri {
3737
private static final int MAX_INT_DIGITS_BASE_10 = 10;
3838

3939
/*
40-
* The default LRUCache size is 100, but for a single service call we cache at least 3 different URIs so the cache size is
40+
* The default BoundedCache size is 100, but for a single service call we cache at least 3 different URIs so the cache size is
4141
* increased a bit to account for the different URIs.
4242
*/
4343
private static final int CACHE_SIZE = 150;
4444

4545
private static final Lazy<SdkUri> INSTANCE = new Lazy<>(SdkUri::new);
4646

47-
private final LruCache<UriConstructorArgs, URI> cache;
47+
private final BoundedCache<UriConstructorArgs, URI> cache;
4848

4949
private SdkUri() {
50-
this.cache = LruCache.builder(UriConstructorArgs::newInstance)
51-
.maxSize(CACHE_SIZE)
52-
.build();
50+
this.cache = BoundedCache.builder(UriConstructorArgs::newInstance)
51+
.maxSize(CACHE_SIZE)
52+
.build();
5353
}
5454

5555
public static SdkUri getInstance() {

utils/src/test/java/software/amazon/awssdk/utils/SdkUriTest.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
import org.junit.jupiter.params.provider.ValueSource;
3030
import org.junit.platform.commons.util.ReflectionUtils;
3131
import org.opentest4j.AssertionFailedError;
32-
import software.amazon.awssdk.utils.cache.lru.LruCache;
32+
import software.amazon.awssdk.utils.cache.bounded.BoundedCache;
3333
import software.amazon.awssdk.utils.uri.SdkUri;
3434
import software.amazon.awssdk.utils.uri.internal.UriConstructorArgs;
3535

@@ -39,9 +39,9 @@ class SdkUriTest {
3939
void resetCache() throws IllegalAccessException {
4040
Field cacheField = getCacheField();
4141
cacheField.setAccessible(true);
42-
cacheField.set(SdkUri.getInstance(), LruCache.builder(UriConstructorArgs::newInstance)
43-
.maxSize(100)
44-
.build());
42+
cacheField.set(SdkUri.getInstance(), BoundedCache.builder(UriConstructorArgs::newInstance)
43+
.maxSize(100)
44+
.build());
4545
}
4646

4747
@ParameterizedTest
@@ -276,11 +276,11 @@ void shouldNotCache_whenLeadingDigitsDoNotExceedIntegerMaxValue(String strURI) {
276276
}
277277

278278

279-
private LruCache<UriConstructorArgs, URI> getCache() {
279+
private BoundedCache<UriConstructorArgs, URI> getCache() {
280280
Field field = getCacheField();
281281
field.setAccessible(true);
282282
try {
283-
return (LruCache<UriConstructorArgs, URI>) field.get(SdkUri.getInstance());
283+
return (BoundedCache<UriConstructorArgs, URI>) field.get(SdkUri.getInstance());
284284
} catch (IllegalAccessException e) {
285285
fail(e);
286286
return null;

0 commit comments

Comments
 (0)