1 /*
   2  * Copyright (c) 2002, 2020, 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 volatile 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) {
 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 
 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> {
 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 
 207     private NullCache() {
 208         // empty
 209     }
 210 
 211     public int size() {
 212         return 0;
 213     }
 214 
 215     public void clear() {
 216         // empty
 217     }
 218 
 219     public void put(K key, V value) {
 220         // empty
 221     }
 222 
 223     public V get(Object key) {
 224         return null;
 225     }
 226 
 227     public void remove(Object key) {
 228         // empty
 229     }
 230 
 231     public void setCapacity(int size) {
 232         // empty
 233     }
 234 
 235     public void setTimeout(int timeout) {
 236         // empty
 237     }
 238 
 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 
 260     public MemoryCache(boolean soft, int maxSize) {
 261         this(soft, maxSize, 0);
 262     }
 263 
 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         cacheMap = new LinkedHashMap<>(1, LOAD_FACTOR, true);
 273     }
 274 
 275     /**
 276      * Empty the reference queue and remove all corresponding entries
 277      * from the cache.
 278      *
 279      * This method should be called at the beginning of each public
 280      * method.
 281      */
 282     private void emptyQueue() {
 283         if (queue == null) {
 284             return;
 285         }
 286         int startSize = cacheMap.size();
 287         while (true) {
 288             @SuppressWarnings("unchecked")
 289             CacheEntry<K,V> entry = (CacheEntry<K,V>)queue.poll();
 290             if (entry == null) {
 291                 break;
 292             }
 293             K key = entry.getKey();
 294             if (key == null) {
 295                 // key is null, entry has already been removed
 296                 continue;
 297             }
 298             CacheEntry<K,V> currentEntry = cacheMap.remove(key);
 299             // check if the entry in the map corresponds to the expired
 300             // entry. If not, readd the entry
 301             if ((currentEntry != null) && (entry != currentEntry)) {
 302                 cacheMap.put(key, currentEntry);
 303             }
 304         }
 305         if (DEBUG) {
 306             int endSize = cacheMap.size();
 307             if (startSize != endSize) {
 308                 System.out.println("*** Expunged " + (startSize - endSize)
 309                         + " entries, " + endSize + " entries left");
 310             }
 311         }
 312     }
 313 
 314     /**
 315      * Scan all entries and remove all expired ones.
 316      */
 317     private void expungeExpiredEntries() {
 318         emptyQueue();
 319         if (lifetime == 0) {
 320             return;
 321         }
 322         int cnt = 0;
 323         long time = System.currentTimeMillis();
 324         for (Iterator<CacheEntry<K,V>> t = cacheMap.values().iterator();
 325                 t.hasNext(); ) {
 326             CacheEntry<K,V> entry = t.next();
 327             if (entry.isValid(time) == false) {
 328                 t.remove();
 329                 cnt++;
 330             }
 331         }
 332         if (DEBUG) {
 333             if (cnt != 0) {
 334                 System.out.println("Removed " + cnt
 335                         + " expired entries, remaining " + cacheMap.size());
 336             }
 337         }
 338     }
 339 
 340     public synchronized int size() {
 341         expungeExpiredEntries();
 342         return cacheMap.size();
 343     }
 344 
 345     public synchronized void clear() {
 346         if (queue != null) {
 347             // if this is a SoftReference cache, first invalidate() all
 348             // entries so that GC does not have to enqueue them
 349             for (CacheEntry<K,V> entry : cacheMap.values()) {
 350                 entry.invalidate();
 351             }
 352             while (queue.poll() != null) {
 353                 // empty
 354             }
 355         }
 356         cacheMap.clear();
 357     }
 358 
 359     public synchronized void put(K key, V value) {
 360         emptyQueue();
 361         long expirationTime = (lifetime == 0) ? 0 :
 362                                         System.currentTimeMillis() + lifetime;
 363         CacheEntry<K,V> newEntry = newEntry(key, value, expirationTime, queue);
 364         CacheEntry<K,V> oldEntry = cacheMap.put(key, newEntry);
 365         if (oldEntry != null) {
 366             oldEntry.invalidate();
 367             return;
 368         }
 369         if (maxSize > 0 && cacheMap.size() > maxSize) {
 370             expungeExpiredEntries();
 371             if (cacheMap.size() > maxSize) { // still too large?
 372                 Iterator<CacheEntry<K,V>> t = cacheMap.values().iterator();
 373                 CacheEntry<K,V> lruEntry = t.next();
 374                 if (DEBUG) {
 375                     System.out.println("** Overflow removal "
 376                         + lruEntry.getKey() + " | " + lruEntry.getValue());
 377                 }
 378                 t.remove();
 379                 lruEntry.invalidate();
 380             }
 381         }
 382     }
 383 
 384     public synchronized V get(Object key) {
 385         emptyQueue();
 386         CacheEntry<K,V> entry = cacheMap.get(key);
 387         if (entry == null) {
 388             return null;
 389         }
 390         long time = (lifetime == 0) ? 0 : System.currentTimeMillis();
 391         if (entry.isValid(time) == false) {
 392             if (DEBUG) {
 393                 System.out.println("Ignoring expired entry");
 394             }
 395             cacheMap.remove(key);
 396             return null;
 397         }
 398         return entry.getValue();
 399     }
 400 
 401     public synchronized void remove(Object key) {
 402         emptyQueue();
 403         CacheEntry<K,V> entry = cacheMap.remove(key);
 404         if (entry != null) {
 405             entry.invalidate();
 406         }
 407     }
 408 
 409     public synchronized void setCapacity(int size) {
 410         expungeExpiredEntries();
 411         if (size > 0 && cacheMap.size() > size) {
 412             Iterator<CacheEntry<K,V>> t = cacheMap.values().iterator();
 413             for (int i = cacheMap.size() - size; i > 0; i--) {
 414                 CacheEntry<K,V> lruEntry = t.next();
 415                 if (DEBUG) {
 416                     System.out.println("** capacity reset removal "
 417                         + lruEntry.getKey() + " | " + lruEntry.getValue());
 418                 }
 419                 t.remove();
 420                 lruEntry.invalidate();
 421             }
 422         }
 423 
 424         maxSize = size > 0 ? size : 0;
 425 
 426         if (DEBUG) {
 427             System.out.println("** capacity reset to " + size);
 428         }
 429     }
 430 
 431     public synchronized void setTimeout(int timeout) {
 432         emptyQueue();
 433         lifetime = timeout > 0 ? timeout * 1000L : 0L;
 434 
 435         if (DEBUG) {
 436             System.out.println("** lifetime reset to " + timeout);
 437         }
 438     }
 439 
 440     // it is a heavyweight method.
 441     public synchronized void accept(CacheVisitor<K,V> visitor) {
 442         expungeExpiredEntries();
 443         Map<K,V> cached = getCachedEntries();
 444 
 445         visitor.visit(cached);
 446     }
 447 
 448     private Map<K,V> getCachedEntries() {
 449         Map<K,V> kvmap = new HashMap<>(cacheMap.size());
 450 
 451         for (CacheEntry<K,V> entry : cacheMap.values()) {
 452             kvmap.put(entry.getKey(), entry.getValue());
 453         }
 454 
 455         return kvmap;
 456     }
 457 
 458     protected CacheEntry<K,V> newEntry(K key, V value,
 459             long expirationTime, ReferenceQueue<V> queue) {
 460         if (queue != null) {
 461             return new SoftCacheEntry<>(key, value, expirationTime, queue);
 462         } else {
 463             return new HardCacheEntry<>(key, value, expirationTime);
 464         }
 465     }
 466 
 467     private static interface CacheEntry<K,V> {
 468 
 469         boolean isValid(long currentTime);
 470 
 471         void invalidate();
 472 
 473         K getKey();
 474 
 475         V getValue();
 476 
 477     }
 478 
 479     private static class HardCacheEntry<K,V> implements CacheEntry<K,V> {
 480 
 481         private K key;
 482         private V value;
 483         private long expirationTime;
 484 
 485         HardCacheEntry(K key, V value, long expirationTime) {
 486             this.key = key;
 487             this.value = value;
 488             this.expirationTime = expirationTime;
 489         }
 490 
 491         public K getKey() {
 492             return key;
 493         }
 494 
 495         public V getValue() {
 496             return value;
 497         }
 498 
 499         public boolean isValid(long currentTime) {
 500             boolean valid = (currentTime <= expirationTime);
 501             if (valid == false) {
 502                 invalidate();
 503             }
 504             return valid;
 505         }
 506 
 507         public void invalidate() {
 508             key = null;
 509             value = null;
 510             expirationTime = -1;
 511         }
 512     }
 513 
 514     private static class SoftCacheEntry<K,V>
 515             extends SoftReference<V>
 516             implements CacheEntry<K,V> {
 517 
 518         private K key;
 519         private long expirationTime;
 520 
 521         SoftCacheEntry(K key, V value, long expirationTime,
 522                 ReferenceQueue<V> queue) {
 523             super(value, queue);
 524             this.key = key;
 525             this.expirationTime = expirationTime;
 526         }
 527 
 528         public K getKey() {
 529             return key;
 530         }
 531 
 532         public V getValue() {
 533             return get();
 534         }
 535 
 536         public boolean isValid(long currentTime) {
 537             boolean valid = (currentTime <= expirationTime) && (get() != null);
 538             if (valid == false) {
 539                 invalidate();
 540             }
 541             return valid;
 542         }
 543 
 544         public void invalidate() {
 545             clear();
 546             key = null;
 547             expirationTime = -1;
 548         }
 549     }
 550 
 551 }