1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.jexl3.internal;
18
19 import java.lang.ref.SoftReference;
20 import java.util.ArrayList;
21 import java.util.Collections;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.Set;
25 import java.util.concurrent.locks.ReadWriteLock;
26 import java.util.concurrent.locks.ReentrantReadWriteLock;
27
28 /**
29 * A soft referenced cache.
30 * <p>
31 * The actual cache is held through a soft reference, allowing it to be GCed
32 * under memory pressure.</p>
33 *
34 * @param <K> the cache key entry type
35 * @param <V> the cache key value type
36 */
37 public class SoftCache<K, V> {
38 /**
39 * The default cache load factor.
40 */
41 private static final float LOAD_FACTOR = 0.75f;
42 /**
43 * The cache size.
44 */
45 private final int size;
46 /**
47 * The soft reference to the cache map.
48 */
49 private SoftReference<Map<K, V>> ref = null;
50 /**
51 * The cache r/w lock.
52 */
53 private final ReadWriteLock lock;
54
55 /**
56 * Creates a new instance of a soft cache.
57 *
58 * @param theSize the cache size
59 */
60 SoftCache(final int theSize) {
61 size = theSize;
62 lock = new ReentrantReadWriteLock();
63 }
64
65 /**
66 * Returns the cache size.
67 *
68 * @return the cache size
69 */
70 public int size() {
71 return size;
72 }
73
74 /**
75 * Clears the cache.
76 */
77 public void clear() {
78 lock.writeLock().lock();
79 try {
80 ref = null;
81 } finally {
82 lock.writeLock().unlock();
83 }
84 }
85
86 /**
87 * Gets a value from cache.
88 *
89 * @param key the cache entry key
90 * @return the cache entry value
91 */
92 public V get(final K key) {
93 lock.readLock().lock();
94 try {
95 final Map<K, V> map = ref != null ? ref.get() : null;
96 return map != null ? map.get(key) : null;
97 } finally {
98 lock.readLock().unlock();
99 }
100 }
101
102 /**
103 * Puts a value in cache.
104 *
105 * @param key the cache entry key
106 * @param script the cache entry value
107 */
108 public void put(final K key, final V script) {
109 lock.writeLock().lock();
110 try {
111 Map<K, V> map = ref != null ? ref.get() : null;
112 if (map == null) {
113 map = createCache(size);
114 ref = new SoftReference<>(map);
115 }
116 map.put(key, script);
117 } finally {
118 lock.writeLock().unlock();
119 }
120 }
121
122 /**
123 * Produces the cache entry set.
124 * <p>
125 * For testing only, perform deep copy of cache entries
126 *
127 * @return the cache entry list
128 */
129 public List<Map.Entry<K, V>> entries() {
130 lock.readLock().lock();
131 try {
132 final Map<K, V> map = ref != null ? ref.get() : null;
133 if (map == null) {
134 return Collections.emptyList();
135 }
136 final Set<Map.Entry<K, V>> set = map.entrySet();
137 final List<Map.Entry<K, V>> entries = new ArrayList<>(set.size());
138 for (final Map.Entry<K, V> e : set) {
139 entries.add(new SoftCacheEntry<>(e));
140 }
141 return entries;
142 } finally {
143 lock.readLock().unlock();
144 }
145 }
146
147 /**
148 * Creates the cache store.
149 *
150 * @param <K> the key type
151 * @param <V> the value type
152 * @param cacheSize the cache size, must be > 0
153 * @return a Map usable as a cache bounded to the given size
154 */
155 public <K, V> Map<K, V> createCache(final int cacheSize) {
156 return new java.util.LinkedHashMap<K, V>(cacheSize, LOAD_FACTOR, true) {
157 /**
158 * Serial version UID.
159 */
160 private static final long serialVersionUID = 1L;
161
162 @Override
163 protected boolean removeEldestEntry(final Map.Entry<K, V> eldest) {
164 return super.size() > cacheSize;
165 }
166 };
167 }
168 }
169
170 /**
171 * A soft cache entry.
172 *
173 * @param <K> key type
174 * @param <V> value type
175 */
176 class SoftCacheEntry<K, V> implements Map.Entry<K, V> {
177 /**
178 * The entry key.
179 */
180 private final K key;
181 /**
182 * The entry value.
183 */
184 private final V value;
185
186 /**
187 * Creates an entry clone.
188 *
189 * @param e the entry to clone
190 */
191 SoftCacheEntry(final Map.Entry<K, V> e) {
192 key = e.getKey();
193 value = e.getValue();
194 }
195
196 @Override
197 public K getKey() {
198 return key;
199 }
200
201 @Override
202 public V getValue() {
203 return value;
204 }
205
206 @Override
207 public V setValue(final V v) {
208 throw new UnsupportedOperationException("Not supported.");
209 }
210 }
211