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
+ }
0 commit comments