1 /*
2 * Copyright (c) 2002, 2011, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package sun.security.util;
27
28 import java.util.*;
29 import java.lang.ref.*;
30
31 /**
32 * Abstract base class and factory for caches. A cache is a key-value mapping.
33 * It has properties that make it more suitable for caching than a Map.
34 *
35 * The factory methods can be used to obtain two different implementations.
36 * They have the following properties:
37 *
38 * . keys and values reside in memory
39 *
40 * . keys and values must be non-null
41 *
42 * . maximum size. Replacements are made in LRU order.
43 *
44 * . optional lifetime, specified in seconds.
45 *
46 * . safe for concurrent use by multiple threads
47 *
48 * . values are held by either standard references or via SoftReferences.
49 * SoftReferences have the advantage that they are automatically cleared
50 * by the garbage collector in response to memory demand. This makes it
51 * possible to simple set the maximum size to a very large value and let
52 * the GC automatically size the cache dynamically depending on the
53 * amount of available memory.
54 *
55 * However, note that because of the way SoftReferences are implemented in
56 * HotSpot at the moment, this may not work perfectly as it clears them fairly
57 * eagerly. Performance may be improved if the Java heap size is set to larger
58 * value using e.g. java -ms64M -mx128M foo.Test
59 *
60 * Cache sizing: the memory cache is implemented on top of a LinkedHashMap.
61 * In its current implementation, the number of buckets (NOT entries) in
62 * (Linked)HashMaps is always a power of two. It is recommended to set the
63 * maximum cache size to value that uses those buckets fully. For example,
64 * if a cache with somewhere between 500 and 1000 entries is desired, a
65 * maximum size of 750 would be a good choice: try 1024 buckets, with a
66 * load factor of 0.75f, the number of entries can be calculated as
67 * buckets / 4 * 3. As mentioned above, with a SoftReference cache, it is
68 * generally reasonable to set the size to a fairly large value.
69 *
70 * @author Andreas Sterbenz
71 */
72 public abstract class Cache<K,V> {
73
74 protected Cache() {
75 // empty
76 }
77
78 /**
79 * Return the number of currently valid entries in the cache.
80 */
81 public abstract int size();
82
83 /**
84 * Remove all entries from the cache.
85 */
86 public abstract void clear();
87
88 /**
89 * Add an entry to the cache.
90 */
91 public abstract void put(K key, V value);
92
93 /**
94 * Get a value from the cache.
95 */
96 public abstract V get(Object key);
97
98 /**
99 * Remove an entry from the cache.
100 */
101 public abstract void remove(Object key);
102
103 /**
104 * Set the maximum size.
105 */
106 public abstract void setCapacity(int size);
107
108 /**
109 * Set the timeout(in seconds).
110 */
111 public abstract void setTimeout(int timeout);
112
113 /**
114 * accept a visitor
115 */
116 public abstract void accept(CacheVisitor<K,V> visitor);
117
118 /**
119 * Return a new memory cache with the specified maximum size, unlimited
120 * lifetime for entries, with the values held by SoftReferences.
121 */
122 public static <K,V> Cache<K,V> newSoftMemoryCache(int size) {
123 return new MemoryCache<>(true, size);
124 }
125
126 /**
127 * Return a new memory cache with the specified maximum size, the
128 * specified maximum lifetime (in seconds), with the values held
129 * by SoftReferences.
130 */
131 public static <K,V> Cache<K,V> newSoftMemoryCache(int size, int timeout) {
132 return new MemoryCache<>(true, size, timeout);
133 }
134
135 /**
136 * Return a new memory cache with the specified maximum size, unlimited
137 * lifetime for entries, with the values held by standard references.
138 */
139 public static <K,V> Cache<K,V> newHardMemoryCache(int size) {
140 return new MemoryCache<>(false, size);
141 }
142
143 /**
144 * Return a dummy cache that does nothing.
145 */
146 @SuppressWarnings("unchecked")
147 public static <K,V> Cache<K,V> newNullCache() {
148 return (Cache<K,V>) NullCache.INSTANCE;
149 }
150
151 /**
152 * Return a new memory cache with the specified maximum size, the
153 * specified maximum lifetime (in seconds), with the values held
154 * by standard references.
155 */
156 public static <K,V> Cache<K,V> newHardMemoryCache(int size, int timeout) {
157 return new MemoryCache<>(false, size, timeout);
158 }
159
160 /**
161 * Utility class that wraps a byte array and implements the equals()
162 * and hashCode() contract in a way suitable for Maps and caches.
163 */
164 public static class EqualByteArray {
165
166 private final byte[] b;
167 private int hash;
168
169 public EqualByteArray(byte[] b) {
170 this.b = b;
171 }
172
173 public int hashCode() {
174 int h = hash;
175 if (h == 0 && b.length > 0) {
176 hash = h = Arrays.hashCode(b);
177 }
178 return h;
179 }
180
181 public boolean equals(Object obj) {
182 if (this == obj) {
183 return true;
184 }
185 if (obj instanceof EqualByteArray == false) {
186 return false;
187 }
188 EqualByteArray other = (EqualByteArray)obj;
189 return Arrays.equals(this.b, other.b);
190 }
191 }
192
193 public interface CacheVisitor<K,V> {
194 public void visit(Map<K,V> map);
195 }
196
197 }
198
199 class NullCache<K,V> extends Cache<K,V> {
200
201 static final Cache<Object,Object> INSTANCE = new NullCache<>();
202
203 private NullCache() {
204 // empty
205 }
206
207 public int size() {
208 return 0;
209 }
210
211 public void clear() {
212 // empty
213 }
214
215 public void put(K key, V value) {
216 // empty
217 }
218
219 public V get(Object key) {
220 return null;
221 }
222
223 public void remove(Object key) {
224 // empty
225 }
226
227 public void setCapacity(int size) {
228 // empty
229 }
230
231 public void setTimeout(int timeout) {
232 // empty
233 }
234
235 public void accept(CacheVisitor<K,V> visitor) {
236 // empty
237 }
238
239 }
240
241 class MemoryCache<K,V> extends Cache<K,V> {
242
243 private static final float LOAD_FACTOR = 0.75f;
244
245 // XXXX
246 private static final boolean DEBUG = false;
247
248 private final Map<K, CacheEntry<K,V>> cacheMap;
249 private int maxSize;
250 private long lifetime;
251
252 // ReferenceQueue is of type V instead of Cache<K,V>
253 // to allow SoftCacheEntry to extend SoftReference<V>
254 private final ReferenceQueue<V> queue;
255
256 public MemoryCache(boolean soft, int maxSize) {
257 this(soft, maxSize, 0);
258 }
259
260 public MemoryCache(boolean soft, int maxSize, int lifetime) {
261 this.maxSize = maxSize;
262 this.lifetime = lifetime * 1000;
263 if (soft)
264 this.queue = new ReferenceQueue<>();
265 else
266 this.queue = null;
267
268 int buckets = (int)(maxSize / LOAD_FACTOR) + 1;
269 cacheMap = new LinkedHashMap<>(buckets, LOAD_FACTOR, true);
270 }
271
272 /**
273 * Empty the reference queue and remove all corresponding entries
274 * from the cache.
275 *
276 * This method should be called at the beginning of each public
277 * method.
278 */
279 private void emptyQueue() {
280 if (queue == null) {
281 return;
282 }
283 int startSize = cacheMap.size();
284 while (true) {
285 @SuppressWarnings("unchecked")
286 CacheEntry<K,V> entry = (CacheEntry<K,V>)queue.poll();
287 if (entry == null) {
288 break;
289 }
290 K key = entry.getKey();
291 if (key == null) {
292 // key is null, entry has already been removed
293 continue;
294 }
295 CacheEntry<K,V> currentEntry = cacheMap.remove(key);
296 // check if the entry in the map corresponds to the expired
297 // entry. If not, readd the entry
298 if ((currentEntry != null) && (entry != currentEntry)) {
299 cacheMap.put(key, currentEntry);
300 }
301 }
302 if (DEBUG) {
303 int endSize = cacheMap.size();
304 if (startSize != endSize) {
305 System.out.println("*** Expunged " + (startSize - endSize)
306 + " entries, " + endSize + " entries left");
307 }
308 }
309 }
310
311 /**
312 * Scan all entries and remove all expired ones.
313 */
314 private void expungeExpiredEntries() {
315 emptyQueue();
316 if (lifetime == 0) {
317 return;
318 }
319 int cnt = 0;
320 long time = System.currentTimeMillis();
321 for (Iterator<CacheEntry<K,V>> t = cacheMap.values().iterator();
322 t.hasNext(); ) {
323 CacheEntry<K,V> entry = t.next();
324 if (entry.isValid(time) == false) {
325 t.remove();
326 cnt++;
327 }
328 }
329 if (DEBUG) {
330 if (cnt != 0) {
331 System.out.println("Removed " + cnt
332 + " expired entries, remaining " + cacheMap.size());
333 }
334 }
335 }
336
337 public synchronized int size() {
338 expungeExpiredEntries();
339 return cacheMap.size();
340 }
341
342 public synchronized void clear() {
343 if (queue != null) {
344 // if this is a SoftReference cache, first invalidate() all
345 // entries so that GC does not have to enqueue them
346 for (CacheEntry<K,V> entry : cacheMap.values()) {
347 entry.invalidate();
348 }
349 while (queue.poll() != null) {
350 // empty
351 }
352 }
353 cacheMap.clear();
354 }
355
356 public synchronized void put(K key, V value) {
357 emptyQueue();
358 long expirationTime = (lifetime == 0) ? 0 :
359 System.currentTimeMillis() + lifetime;
360 CacheEntry<K,V> newEntry = newEntry(key, value, expirationTime, queue);
361 CacheEntry<K,V> oldEntry = cacheMap.put(key, newEntry);
362 if (oldEntry != null) {
363 oldEntry.invalidate();
364 return;
365 }
366 if (maxSize > 0 && cacheMap.size() > maxSize) {
367 expungeExpiredEntries();
368 if (cacheMap.size() > maxSize) { // still too large?
369 Iterator<CacheEntry<K,V>> t = cacheMap.values().iterator();
370 CacheEntry<K,V> lruEntry = t.next();
371 if (DEBUG) {
372 System.out.println("** Overflow removal "
373 + lruEntry.getKey() + " | " + lruEntry.getValue());
374 }
375 t.remove();
376 lruEntry.invalidate();
377 }
378 }
379 }
380
381 public synchronized V get(Object key) {
382 emptyQueue();
383 CacheEntry<K,V> entry = cacheMap.get(key);
384 if (entry == null) {
385 return null;
386 }
387 long time = (lifetime == 0) ? 0 : System.currentTimeMillis();
388 if (entry.isValid(time) == false) {
389 if (DEBUG) {
390 System.out.println("Ignoring expired entry");
391 }
392 cacheMap.remove(key);
393 return null;
394 }
395 return entry.getValue();
396 }
397
398 public synchronized void remove(Object key) {
399 emptyQueue();
400 CacheEntry<K,V> entry = cacheMap.remove(key);
401 if (entry != null) {
402 entry.invalidate();
403 }
404 }
405
406 public synchronized void setCapacity(int size) {
407 expungeExpiredEntries();
408 if (size > 0 && cacheMap.size() > size) {
409 Iterator<CacheEntry<K,V>> t = cacheMap.values().iterator();
410 for (int i = cacheMap.size() - size; i > 0; i--) {
411 CacheEntry<K,V> lruEntry = t.next();
412 if (DEBUG) {
413 System.out.println("** capacity reset removal "
414 + lruEntry.getKey() + " | " + lruEntry.getValue());
415 }
416 t.remove();
417 lruEntry.invalidate();
418 }
419 }
420
421 maxSize = size > 0 ? size : 0;
422
423 if (DEBUG) {
424 System.out.println("** capacity reset to " + size);
425 }
426 }
427
428 public synchronized void setTimeout(int timeout) {
429 emptyQueue();
430 lifetime = timeout > 0 ? timeout * 1000L : 0L;
431
432 if (DEBUG) {
433 System.out.println("** lifetime reset to " + timeout);
434 }
435 }
436
437 // it is a heavyweight method.
438 public synchronized void accept(CacheVisitor<K,V> visitor) {
439 expungeExpiredEntries();
440 Map<K,V> cached = getCachedEntries();
441
442 visitor.visit(cached);
443 }
444
445 private Map<K,V> getCachedEntries() {
446 Map<K,V> kvmap = new HashMap<>(cacheMap.size());
447
448 for (CacheEntry<K,V> entry : cacheMap.values()) {
449 kvmap.put(entry.getKey(), entry.getValue());
450 }
451
452 return kvmap;
453 }
454
455 protected CacheEntry<K,V> newEntry(K key, V value,
456 long expirationTime, ReferenceQueue<V> queue) {
457 if (queue != null) {
458 return new SoftCacheEntry<>(key, value, expirationTime, queue);
459 } else {
460 return new HardCacheEntry<>(key, value, expirationTime);
461 }
462 }
463
464 private static interface CacheEntry<K,V> {
465
466 boolean isValid(long currentTime);
467
468 void invalidate();
469
470 K getKey();
471
472 V getValue();
473
474 }
475
476 private static class HardCacheEntry<K,V> implements CacheEntry<K,V> {
477
478 private K key;
479 private V value;
480 private long expirationTime;
481
482 HardCacheEntry(K key, V value, long expirationTime) {
483 this.key = key;
484 this.value = value;
485 this.expirationTime = expirationTime;
486 }
487
488 public K getKey() {
489 return key;
490 }
491
492 public V getValue() {
493 return value;
494 }
495
496 public boolean isValid(long currentTime) {
497 boolean valid = (currentTime <= expirationTime);
498 if (valid == false) {
499 invalidate();
500 }
501 return valid;
502 }
503
504 public void invalidate() {
505 key = null;
506 value = null;
507 expirationTime = -1;
508 }
509 }
510
511 private static class SoftCacheEntry<K,V>
512 extends SoftReference<V>
513 implements CacheEntry<K,V> {
514
515 private K key;
516 private long expirationTime;
517
518 SoftCacheEntry(K key, V value, long expirationTime,
519 ReferenceQueue<V> queue) {
520 super(value, queue);
521 this.key = key;
522 this.expirationTime = expirationTime;
523 }
524
525 public K getKey() {
526 return key;
527 }
528
529 public V getValue() {
530 return get();
531 }
532
533 public boolean isValid(long currentTime) {
534 boolean valid = (currentTime <= expirationTime) && (get() != null);
535 if (valid == false) {
536 invalidate();
537 }
538 return valid;
539 }
540
541 public void invalidate() {
542 clear();
543 key = null;
544 expirationTime = -1;
545 }
546 }
547
548 }
--- EOF ---