// $Id: $ package com.nesterovskyBros; import java.lang.ref.WeakReference; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; /** * An object to cache a reference with an expiration. * * Note that the reference is cached until its expiration point. * An expiration point is updated after call to method get(). * * @param an instance type. */ public class CachedReference { /** * Default expiration interval in ms (10 minutes). */ public static final long defaultExpiration = 10 * 60 * 1000; /** * Creates a {@link CachedReference} instance. */ public CachedReference() { this(defaultExpiration, true); } /** * Creates a {@link CachedReference} instance. * @param expiration an expiration interval in ms. * @param sliding true for a sliding, and false for an absolute expiration. */ public CachedReference(long expiration, boolean sliding) { this.expiration = expiration; this.sliding = sliding; } /** * Gets a cached instance. * @return an cached instance, or null if no instance is cached. */ public final V get() { access = System.currentTimeMillis(); return value; } /** * Caches an instance. * Note that the assumption is that the code pattern for set() is like this: *
   * V value = ref.get();
   * 
   * if (value == null)
   * {
   *   value = create value
   *   ref.set(value);
   * }
* @param value a value to cache. */ public final void set(V value) { this.access = System.currentTimeMillis(); this.value = value; if (value != null) { executor.schedule(new Task(this), expiration, TimeUnit.MILLISECONDS); } } /** * Gets a cached value for a specified key from a cache map. * @param cache a key-value cache. * @param key an instance key. * @return an instance for a key, or null if object is not in the cache. */ public static V get(ConcurrentMap> cache, K key) { CachedReference ref = cache.get(key); return ref == null ? null : ref.get(); } /** * Puts an object into the cache with default value of the sliding expiration. * @param cache a key-value cache. * @param key an instance key. * @param value a value to put. If value is null then object corresponding to * the specified key is removed from the cache. */ public static void put( ConcurrentMap> cache, K key, V value) { put(cache, key, value, defaultExpiration, true); } /** * Puts an object into the cache. * @param cache a key-value cache. * @param key an instance key. * @param value a value to put. If value is null then object corresponding to * the specified key is removed from the cache. * @param expiration an instance expiration in ms. * @param sliding true for sliding, and false for absolute expiration. */ public static void put( final ConcurrentMap> cache, final K key, V value, long expiration, boolean sliding) { CachedReference old; if (value == null) { old = cache.remove(key); } else { CachedReference ref = new CachedReference(expiration, sliding) { @Override protected void expired() { cache.remove(key, this); } }; old = cache.put(key, ref); ref.set(value); } if (old != null) { old.set(null); } } /** * Method is called when {@link Task} invalidates the value. */ protected void expired() { } /** * An expiration value in ms. */ private final long expiration; /** * true for sliding, and false for absolute expiration. */ private final boolean sliding; /** * Last access instant in ms. */ private volatile long access; /** * A value itself. */ private volatile V value; /** * A scheduler task. * Note that a {@link Task} does not prevent a {@link CachedReference} to * be collected. */ private static class Task extends WeakReference> implements Runnable { /** * Creates a {@link Task} instance. * @param referent a item reference. */ public Task(CachedReference referent) { super(referent); } /** * Invalidates the reference. */ public void run() { CachedReference ref = get(); if (ref != null) { V value = ref.value; if (value != null) { long delta = ref.access + ref.expiration - System.currentTimeMillis(); if (ref.sliding && (delta > 0)) { executor.schedule(this, delta, TimeUnit.MILLISECONDS); } else { ref.value = null; ref.expired(); } } } } } /** * An executor reference to clean caches. */ private static final ScheduledThreadPoolExecutor executor; /** * Sets up a global executor to clean caches. */ static { executor = new ScheduledThreadPoolExecutor( 1, new ThreadFactory() { public Thread newThread(Runnable target) { Thread thread = new Thread(target, "Cached reference cleaner"); thread.setDaemon(true); thread.setPriority(Thread.MAX_PRIORITY); return thread; } }); executor.setMaximumPoolSize(1); executor.setKeepAliveTime(20, TimeUnit.SECONDS); executor.allowCoreThreadTimeOut(true); } }