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 
Cache()74     protected Cache() {
75         // empty
76     }
77 
78     /**
79      * Return the number of currently valid entries in the cache.
80      */
size()81     public abstract int size();
82 
83     /**
84      * Remove all entries from the cache.
85      */
clear()86     public abstract void clear();
87 
88     /**
89      * Add an entry to the cache.
90      */
put(K key, V value)91     public abstract void put(K key, V value);
92 
93     /**
94      * Get a value from the cache.
95      */
get(Object key)96     public abstract V get(Object key);
97 
98     /**
99      * Remove an entry from the cache.
100      */
remove(Object key)101     public abstract void remove(Object key);
102 
103     /**
104      * Set the maximum size.
105      */
setCapacity(int size)106     public abstract void setCapacity(int size);
107 
108     /**
109      * Set the timeout(in seconds).
110      */
setTimeout(int timeout)111     public abstract void setTimeout(int timeout);
112 
113     /**
114      * accept a visitor
115      */
accept(CacheVisitor<K,V> visitor)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      */
newSoftMemoryCache(int size)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      */
newSoftMemoryCache(int size, int timeout)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      */
newHardMemoryCache(int size)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")
newNullCache()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      */
newHardMemoryCache(int size, int timeout)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 volatile int hash;
168 
EqualByteArray(byte[] b)169         public EqualByteArray(byte[] b) {
170             this.b = b;
171         }
172 
hashCode()173         public int hashCode() {
174             int h = hash;
175             if (h == 0) {
176                 h = b.length + 1;
177                 for (int i = 0; i < b.length; i++) {
178                     h += (b[i] & 0xff) * 37;
179                 }
180                 hash = h;
181             }
182             return h;
183         }
184 
equals(Object obj)185         public boolean equals(Object obj) {
186             if (this == obj) {
187                 return true;
188             }
189             if (obj instanceof EqualByteArray == false) {
190                 return false;
191             }
192             EqualByteArray other = (EqualByteArray)obj;
193             return Arrays.equals(this.b, other.b);
194         }
195     }
196 
197     public interface CacheVisitor<K,V> {
visit(Map<K,V> map)198         public void visit(Map<K,V> map);
199     }
200 
201 }
202 
203 class NullCache<K,V> extends Cache<K,V> {
204 
205     final static Cache<Object,Object> INSTANCE = new NullCache<>();
206 
NullCache()207     private NullCache() {
208         // empty
209     }
210 
size()211     public int size() {
212         return 0;
213     }
214 
clear()215     public void clear() {
216         // empty
217     }
218 
put(K key, V value)219     public void put(K key, V value) {
220         // empty
221     }
222 
get(Object key)223     public V get(Object key) {
224         return null;
225     }
226 
remove(Object key)227     public void remove(Object key) {
228         // empty
229     }
230 
setCapacity(int size)231     public void setCapacity(int size) {
232         // empty
233     }
234 
setTimeout(int timeout)235     public void setTimeout(int timeout) {
236         // empty
237     }
238 
accept(CacheVisitor<K,V> visitor)239     public void accept(CacheVisitor<K,V> visitor) {
240         // empty
241     }
242 
243 }
244 
245 class MemoryCache<K,V> extends Cache<K,V> {
246 
247     private final static float LOAD_FACTOR = 0.75f;
248 
249     // XXXX
250     private final static boolean DEBUG = false;
251 
252     private final Map<K, CacheEntry<K,V>> cacheMap;
253     private int maxSize;
254     private long lifetime;
255 
256     // ReferenceQueue is of type V instead of Cache<K,V>
257     // to allow SoftCacheEntry to extend SoftReference<V>
258     private final ReferenceQueue<V> queue;
259 
MemoryCache(boolean soft, int maxSize)260     public MemoryCache(boolean soft, int maxSize) {
261         this(soft, maxSize, 0);
262     }
263 
MemoryCache(boolean soft, int maxSize, int lifetime)264     public MemoryCache(boolean soft, int maxSize, int lifetime) {
265         this.maxSize = maxSize;
266         this.lifetime = lifetime * 1000;
267         if (soft)
268             this.queue = new ReferenceQueue<>();
269         else
270             this.queue = null;
271 
272         int buckets = (int)(maxSize / LOAD_FACTOR) + 1;
273         cacheMap = new LinkedHashMap<>(buckets, LOAD_FACTOR, true);
274     }
275 
276     /**
277      * Empty the reference queue and remove all corresponding entries
278      * from the cache.
279      *
280      * This method should be called at the beginning of each public
281      * method.
282      */
emptyQueue()283     private void emptyQueue() {
284         if (queue == null) {
285             return;
286         }
287         int startSize = cacheMap.size();
288         while (true) {
289             @SuppressWarnings("unchecked")
290             CacheEntry<K,V> entry = (CacheEntry<K,V>)queue.poll();
291             if (entry == null) {
292                 break;
293             }
294             K key = entry.getKey();
295             if (key == null) {
296                 // key is null, entry has already been removed
297                 continue;
298             }
299             CacheEntry<K,V> currentEntry = cacheMap.remove(key);
300             // check if the entry in the map corresponds to the expired
301             // entry. If not, readd the entry
302             if ((currentEntry != null) && (entry != currentEntry)) {
303                 cacheMap.put(key, currentEntry);
304             }
305         }
306         if (DEBUG) {
307             int endSize = cacheMap.size();
308             if (startSize != endSize) {
309                 System.out.println("*** Expunged " + (startSize - endSize)
310                         + " entries, " + endSize + " entries left");
311             }
312         }
313     }
314 
315     /**
316      * Scan all entries and remove all expired ones.
317      */
expungeExpiredEntries()318     private void expungeExpiredEntries() {
319         emptyQueue();
320         if (lifetime == 0) {
321             return;
322         }
323         int cnt = 0;
324         long time = System.currentTimeMillis();
325         for (Iterator<CacheEntry<K,V>> t = cacheMap.values().iterator();
326                 t.hasNext(); ) {
327             CacheEntry<K,V> entry = t.next();
328             if (entry.isValid(time) == false) {
329                 t.remove();
330                 cnt++;
331             }
332         }
333         if (DEBUG) {
334             if (cnt != 0) {
335                 System.out.println("Removed " + cnt
336                         + " expired entries, remaining " + cacheMap.size());
337             }
338         }
339     }
340 
size()341     public synchronized int size() {
342         expungeExpiredEntries();
343         return cacheMap.size();
344     }
345 
clear()346     public synchronized void clear() {
347         if (queue != null) {
348             // if this is a SoftReference cache, first invalidate() all
349             // entries so that GC does not have to enqueue them
350             for (CacheEntry<K,V> entry : cacheMap.values()) {
351                 entry.invalidate();
352             }
353             while (queue.poll() != null) {
354                 // empty
355             }
356         }
357         cacheMap.clear();
358     }
359 
put(K key, V value)360     public synchronized void put(K key, V value) {
361         emptyQueue();
362         long expirationTime = (lifetime == 0) ? 0 :
363                                         System.currentTimeMillis() + lifetime;
364         CacheEntry<K,V> newEntry = newEntry(key, value, expirationTime, queue);
365         CacheEntry<K,V> oldEntry = cacheMap.put(key, newEntry);
366         if (oldEntry != null) {
367             oldEntry.invalidate();
368             return;
369         }
370         if (maxSize > 0 && cacheMap.size() > maxSize) {
371             expungeExpiredEntries();
372             if (cacheMap.size() > maxSize) { // still too large?
373                 Iterator<CacheEntry<K,V>> t = cacheMap.values().iterator();
374                 CacheEntry<K,V> lruEntry = t.next();
375                 if (DEBUG) {
376                     System.out.println("** Overflow removal "
377                         + lruEntry.getKey() + " | " + lruEntry.getValue());
378                 }
379                 t.remove();
380                 lruEntry.invalidate();
381             }
382         }
383     }
384 
get(Object key)385     public synchronized V get(Object key) {
386         emptyQueue();
387         CacheEntry<K,V> entry = cacheMap.get(key);
388         if (entry == null) {
389             return null;
390         }
391         long time = (lifetime == 0) ? 0 : System.currentTimeMillis();
392         if (entry.isValid(time) == false) {
393             if (DEBUG) {
394                 System.out.println("Ignoring expired entry");
395             }
396             cacheMap.remove(key);
397             return null;
398         }
399         return entry.getValue();
400     }
401 
remove(Object key)402     public synchronized void remove(Object key) {
403         emptyQueue();
404         CacheEntry<K,V> entry = cacheMap.remove(key);
405         if (entry != null) {
406             entry.invalidate();
407         }
408     }
409 
setCapacity(int size)410     public synchronized void setCapacity(int size) {
411         expungeExpiredEntries();
412         if (size > 0 && cacheMap.size() > size) {
413             Iterator<CacheEntry<K,V>> t = cacheMap.values().iterator();
414             for (int i = cacheMap.size() - size; i > 0; i--) {
415                 CacheEntry<K,V> lruEntry = t.next();
416                 if (DEBUG) {
417                     System.out.println("** capacity reset removal "
418                         + lruEntry.getKey() + " | " + lruEntry.getValue());
419                 }
420                 t.remove();
421                 lruEntry.invalidate();
422             }
423         }
424 
425         maxSize = size > 0 ? size : 0;
426 
427         if (DEBUG) {
428             System.out.println("** capacity reset to " + size);
429         }
430     }
431 
setTimeout(int timeout)432     public synchronized void setTimeout(int timeout) {
433         emptyQueue();
434         lifetime = timeout > 0 ? timeout * 1000L : 0L;
435 
436         if (DEBUG) {
437             System.out.println("** lifetime reset to " + timeout);
438         }
439     }
440 
441     // it is a heavyweight method.
accept(CacheVisitor<K,V> visitor)442     public synchronized void accept(CacheVisitor<K,V> visitor) {
443         expungeExpiredEntries();
444         Map<K,V> cached = getCachedEntries();
445 
446         visitor.visit(cached);
447     }
448 
getCachedEntries()449     private Map<K,V> getCachedEntries() {
450         Map<K,V> kvmap = new HashMap<>(cacheMap.size());
451 
452         for (CacheEntry<K,V> entry : cacheMap.values()) {
453             kvmap.put(entry.getKey(), entry.getValue());
454         }
455 
456         return kvmap;
457     }
458 
newEntry(K key, V value, long expirationTime, ReferenceQueue<V> queue)459     protected CacheEntry<K,V> newEntry(K key, V value,
460             long expirationTime, ReferenceQueue<V> queue) {
461         if (queue != null) {
462             return new SoftCacheEntry<>(key, value, expirationTime, queue);
463         } else {
464             return new HardCacheEntry<>(key, value, expirationTime);
465         }
466     }
467 
468     private static interface CacheEntry<K,V> {
469 
isValid(long currentTime)470         boolean isValid(long currentTime);
471 
invalidate()472         void invalidate();
473 
getKey()474         K getKey();
475 
getValue()476         V getValue();
477 
478     }
479 
480     private static class HardCacheEntry<K,V> implements CacheEntry<K,V> {
481 
482         private K key;
483         private V value;
484         private long expirationTime;
485 
HardCacheEntry(K key, V value, long expirationTime)486         HardCacheEntry(K key, V value, long expirationTime) {
487             this.key = key;
488             this.value = value;
489             this.expirationTime = expirationTime;
490         }
491 
getKey()492         public K getKey() {
493             return key;
494         }
495 
getValue()496         public V getValue() {
497             return value;
498         }
499 
isValid(long currentTime)500         public boolean isValid(long currentTime) {
501             boolean valid = (currentTime <= expirationTime);
502             if (valid == false) {
503                 invalidate();
504             }
505             return valid;
506         }
507 
invalidate()508         public void invalidate() {
509             key = null;
510             value = null;
511             expirationTime = -1;
512         }
513     }
514 
515     private static class SoftCacheEntry<K,V>
516             extends SoftReference<V>
517             implements CacheEntry<K,V> {
518 
519         private K key;
520         private long expirationTime;
521 
SoftCacheEntry(K key, V value, long expirationTime, ReferenceQueue<V> queue)522         SoftCacheEntry(K key, V value, long expirationTime,
523                 ReferenceQueue<V> queue) {
524             super(value, queue);
525             this.key = key;
526             this.expirationTime = expirationTime;
527         }
528 
getKey()529         public K getKey() {
530             return key;
531         }
532 
getValue()533         public V getValue() {
534             return get();
535         }
536 
isValid(long currentTime)537         public boolean isValid(long currentTime) {
538             boolean valid = (currentTime <= expirationTime) && (get() != null);
539             if (valid == false) {
540                 invalidate();
541             }
542             return valid;
543         }
544 
invalidate()545         public void invalidate() {
546             clear();
547             key = null;
548             expirationTime = -1;
549         }
550     }
551 
552 }
553