DZone Snippets is a public source code repository. Easily build up your personal collection of code snippets, categorize them with tags / keywords, and share them with the world

Snippets has posted 5883 posts at DZone. View Full User Profile

Time Based Cache

08.03.2006
| 8352 views |
  • submit to reddit
        A simple time based cache build around a map store.

import java.util.Map;
import java.util.WeakHashMap;

/**
 * Simple time-based cache.
 */
public class SimpleCache {
	private long maxAge;
	private Map store;

	/**
	 * Instanciate a cache with max age of 1 hour and a WeakHashMap as store.
	 * @see java.util.WeakHashMap
	 */
	public SimpleCache() {
		this.maxAge = 1000 * 60 * 60;
		this.store = new WeakHashMap();
	}
	
	/**
	 * @param maxAge maximum age of an entry in milliseconds
	 * @param store map to hold entries
	 */
	public SimpleCache(long maxAge, Map store) {
		this.maxAge = maxAge;
		this.store = store;
	}
	
	/**
	 * Cache an object.
	 * @param key unique identifier to retrieve object
	 * @param value object to cache
	 */
	public void put(Object key, Object value) {
		store.put(key, new Item(value));
	}
	
	/**
	 * Fetch an object.
	 * @param key unique identifier to retrieve object
	 * @return an object or null in case it isn't stored or it expired
	 */
	public Object get(Object key) {
		Item item = getItem(key);
		return item == null ? null : item.payload;
	}
	
	/**
	 * Fetch an object or store and return output of callback.
	 * @param key unique identifier to retrieve object
	 * @param block code executed when object not in cache
	 * @return an object
	 */
	public synchronized Object get(Object key, Callback block) {
		Item item = getItem(key);
		if (item == null) {
			Object value = block.execute();
			item = new Item(value);
			store.put(key, item);
		}
		return item.payload;
	}
	
	/**
	 * Remove an object from cache.
	 * @param key unique identifier to retrieve object
	 */
	public void remove(Object key) {
		store.remove(key);
	}
	
	/**
	 * Get an item, if it expired remove it from cache and return null.
	 * @param key unique identifier to retrieve object
	 * @return an item or null
	 */
	private Item getItem(Object key) {
		Item item = (Item) store.get(key);
		if (item == null) {
			return null;
		}
		if (System.currentTimeMillis() - item.birth > maxAge) {
			store.remove(key);
			return null;
		}
		return item;		
	}

	/**
	 * Value container.
	 */
	private static class Item {
		long birth;
		Object payload;
		Item(Object payload) {
			this.birth = System.currentTimeMillis();
			this.payload = payload;
		}
	}
	
	/**
	 * A visitor interface.
	 */
	public static interface Callback {
		Object execute();
	}
}

And a couple of junit tests:

import java.util.HashMap;

import junit.framework.TestCase;

public class SimpleCacheTest extends TestCase {
	public void testPutGet () {
		SimpleCache c = new SimpleCache(Long.MAX_VALUE, new HashMap());
		c.put("key1", "value1");
		assertEquals("value1", c.get("key1"));
		c.put("key1", "value1.0");
		assertEquals("value1.0", c.get("key1"));
		c.put("key2", "value2");
		assertEquals("value2", c.get("key2"));
		assertEquals("value1.0", c.get("key1"));
	}
	
	public void testMaxAge () throws InterruptedException {
		SimpleCache c = new SimpleCache(1000, new HashMap());
		c.put("key1", "value1");
		assertEquals("value1", c.get("key1"));
		Thread.sleep(1500);
		assertNull(c.get("key1"));
		
		c.put("key2", "value2");
		Thread.sleep(750);
		c.put("key3", "value3");
		Thread.sleep(750);
		assertNull(c.get("key2"));
		assertNotNull(c.get("key3"));
		Thread.sleep(750);
		assertNull(c.get("key3"));
	}
	
	public void testRemove () {
		SimpleCache c = new SimpleCache(Long.MAX_VALUE, new HashMap());
		c.remove("key");
		assertNull(c.get("key"));
		c.put("key", "value");
		assertNotNull(c.get("key"));
		c.remove("key");
		assertNull(c.get("key"));
	}
	
	public void testCallBack () {
		SimpleCache c = new SimpleCache(Long.MAX_VALUE, new HashMap());
		assertEquals("value1", c.get("key1", new SimpleCache.Callback() {
			public Object execute() {
				return "value1";
			}
		}));
		assertEquals("value1", c.get("key1"));
		
		// again with a new callback (value)
		c.get("key1", new SimpleCache.Callback() {
			public Object execute() {
				return "value2";
			}
		});
		assertEquals("value1", c.get("key1"));
	}
}
    

Comments

Snippets Manager replied on Mon, 2012/05/07 - 2:26pm

Consider instead of using System.currentTimeMillis() using a TimeStampProvider interface. For the unit test one can set the time manually instead of waiting.