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