ShinyCache.java

  1. /*******************************************************************************
  2.  * Copyright (c) 2004, 2013 Steve Flasby
  3.  * All rights reserved.
  4.  * Redistribution and use in source and binary forms, with or without modification,
  5.  * are permitted provided that the following conditions are met:
  6.  * <ul>
  7.  *     <li>Redistributions of source code must retain the above copyright notice,
  8.  *         this list of conditions and the following disclaimer.</li>
  9.  *     <li>Redistributions in binary form must reproduce the above copyright notice,
  10.  *         this list of conditions and the following disclaimer in the documentation
  11.  *         and/or other materials provided with the distribution.</li>
  12.  * </ul>
  13.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  14.  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  15.  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  16.  * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
  17.  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  18.  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  19.  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  20.  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
  21.  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  22.  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  23.  ******************************************************************************/

  24. package org.flasby.util;

  25. import java.util.Iterator;
  26. import java.util.LinkedHashMap;
  27. import java.util.Map;
  28. import java.util.concurrent.Semaphore;
  29. import java.util.concurrent.TimeUnit;

  30. /**
  31.  * is a cache which handles entry expiry and will ensure that multiple simultaneous requests
  32.  * using the same key will only make a single request to get the missing value.
  33.  * Simultaneous requests will be queued until the value is returned.
  34.  *
  35.  * Cached entries may have a NULL value.
  36.  *
  37.  * @author Steve Flasby
  38.  * 21 Sep 2012
  39.  */

  40. public abstract class ShinyCache<Key,Value> implements BlockingCache<Key, Value>{
  41.    
  42.     protected static class ItemHolder<VV> {
  43.         VV value;
  44.         long createdTimestamp = System.nanoTime()/1000000;
  45.         final Semaphore sema = new Semaphore(0, true);
  46.     }

  47.     protected static class ExpiryPolicy {
  48.         protected boolean hasExpired(ItemHolder<?> holder) {
  49.             return false;
  50.         }
  51.     }
  52.    
  53.     private static final class NeverExpires extends ExpiryPolicy {
  54.         public NeverExpires() {
  55.         }}

  56.     private static final class ExpiresAfter extends ExpiryPolicy {
  57.         private final long expiresAfter;
  58.         ExpiresAfter(long expiresAfter) {
  59.             this.expiresAfter = expiresAfter;
  60.         }
  61.         @Override
  62.         protected boolean hasExpired(ItemHolder<?> holder) {
  63.             return (holder.createdTimestamp+expiresAfter <= System.nanoTime()/1000000);
  64.         }
  65.     }

  66.     private static final NeverExpires NEVER = new NeverExpires();
  67.     public static ExpiryPolicy never() {
  68.         return NEVER;
  69.     }
  70.    
  71.     public static ExpiryPolicy expireAfterMillis(long periodInMillis) {
  72.         return new ExpiresAfter(periodInMillis);
  73.     }
  74.    
  75.     public enum EvictionPolicy { LRU, OLDEST }

  76.     final LinkedHashMap<Key, ItemHolder<Value>> cache;
  77.     private final Semaphore entry = new Semaphore(1, true);
  78.     private final ExpiryPolicy expiryPolicy;

  79.     /**
  80.      * simple, ever growing, cache
  81.      */
  82.     public ShinyCache() {
  83.         this(never());
  84.     }
  85.    
  86.     /**
  87.      * cache which expires entries after a fixed lifetime.
  88.      *
  89.      * @param expiryPolicy
  90.      */
  91.     public ShinyCache(ExpiryPolicy expiryPolicy) {
  92.         this(expiryPolicy, 0, EvictionPolicy.OLDEST);
  93.     }
  94.    
  95.     public ShinyCache(int capacity, EvictionPolicy evictionPolicy) {
  96.         this( never(), capacity, evictionPolicy);
  97.     }
  98.    
  99.     public ShinyCache(final ExpiryPolicy expiryPolicy, final int capacity, final EvictionPolicy evictionPolicy) {
  100.         this.expiryPolicy = expiryPolicy;
  101.        
  102.         cache = new LinkedHashMap<Key, ItemHolder<Value>>( 16, 0.75f, evictionPolicy==EvictionPolicy.LRU){
  103.             private static final long serialVersionUID = 1L;
  104.             @Override
  105.             protected boolean removeEldestEntry( Map.Entry<Key, ItemHolder<Value>> eldest) {
  106.                 return ( capacity != 0 && cache.size()>capacity );
  107.             }
  108.         };
  109.     }
  110.    
  111.     /**
  112.      * gets the key form the cache or returns NULL if the Thread was interrupted.
  113.      */
  114.     @Override
  115.     public Value get(Key key) {
  116.         try {
  117.             return getAndWait(key);
  118.         } catch (InterruptedException e) {
  119.             return null;
  120.         }
  121.     }
  122.    
  123.     @Override
  124.     public Value getAndWait(Key key) throws InterruptedException {
  125.         return getAndWait(key, Integer.MAX_VALUE);
  126.     }
  127.    
  128.     @Override
  129.     public Value getAndWait(Key key, long maxWaitMillis) throws InterruptedException {
  130.         long t = System.nanoTime();
  131.         if ( ! entry.tryAcquire(maxWaitMillis, TimeUnit.MILLISECONDS) ) {
  132.             return null;
  133.         }
  134.        
  135.         // See if weve already waited too long & calculate how much longer to wait
  136.         t = maxWaitMillis - (System.nanoTime()-t)/1000000;
  137.         if ( t<=0 ) return null;
  138.        
  139.         ItemHolder<Value> holder = getCache().get(key);
  140.        
  141.        
  142.         if ( holder != null ) {
  143.             boolean acquired = false;
  144.             try {
  145.                 acquired = holder.sema.tryAcquire(t, TimeUnit.MILLISECONDS);
  146.                 if ( expiryPolicy.hasExpired(holder) ) {
  147.                     // Re-acquire the value
  148.                     holder.value = getValueFor(key);
  149.                     holder.createdTimestamp = System.nanoTime()/1000000;
  150.                 }
  151.                 return holder.value;
  152.             } finally {
  153.                 entry.release();
  154.                 if ( acquired ) {
  155.                     holder.sema.release();
  156.                 }
  157.             }
  158.         }
  159.        
  160.         holder = new ItemHolder<Value>();
  161.         getCache().put(key, holder );
  162.        
  163.         holder.value = getValueFor(key);
  164.         holder.sema.release();
  165.        
  166.         entry.release();
  167.        
  168.         return holder.value;
  169.     }

  170.     @Override
  171.     public int size() {
  172.         return getCache().size();
  173.     }
  174.    
  175.     @Override
  176.     public Iterator<Value> iterator() {
  177.         return new Iterator<Value>() {
  178.             private final Iterator<ItemHolder<Value>> iter = getCache().values().iterator();
  179.             @Override
  180.             public boolean hasNext() {
  181.                 return iter.hasNext();
  182.             }

  183.             @Override
  184.             public Value next() {
  185.                 return iter.next().value;
  186.             }

  187.             @Override
  188.             public void remove() {
  189.                 iter.remove();
  190.             }
  191.         };
  192.     }
  193.    
  194.     protected abstract Value getValueFor(Key key);

  195.     LinkedHashMap<Key, ItemHolder<Value>> getCache() {
  196.         return cache;
  197.     }
  198.    
  199. }