summaryrefslogtreecommitdiff
path: root/java/src/IceUtil/Cache.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/src/IceUtil/Cache.java')
-rw-r--r--java/src/IceUtil/Cache.java298
1 files changed, 298 insertions, 0 deletions
diff --git a/java/src/IceUtil/Cache.java b/java/src/IceUtil/Cache.java
new file mode 100644
index 00000000000..899a35892fc
--- /dev/null
+++ b/java/src/IceUtil/Cache.java
@@ -0,0 +1,298 @@
+// **********************************************************************
+//
+// Copyright (c) 2003-2011 ZeroC, Inc. All rights reserved.
+//
+// This copy of Ice is licensed to you under the terms described in the
+// ICE_LICENSE file included in this distribution.
+//
+// **********************************************************************
+
+package IceUtil;
+
+/**
+ * An abstraction to efficiently maintain a cache, without holding
+ * a lock on the entire cache while objects are being loaded from
+ * their backing store. This class is useful mainly to implement
+ * evictors, such as used by Freeze.
+ *
+ * @see Store
+ * @see Freeze.Evictor
+ **/
+
+public class Cache
+{
+ /**
+ * Initialize a cache using the specified backing store.
+ **/
+ public Cache(Store store)
+ {
+ _store = store;
+ }
+
+ /**
+ * Return the value stored for the given key from the cache.
+ *
+ * @param key The key for the object to look up in the cache.
+ *
+ * @return If the cache contains an entry for the key, the return value
+ * is the object corresponding to the key; otherwise, the return value
+ * is null. <code>getIfPinned</code> does not call {@link Store#load}.
+ *
+ * @see Store#load
+ **/
+ public Object
+ getIfPinned(Object key)
+ {
+ synchronized(_map)
+ {
+ CacheValue val = (CacheValue)_map.get(key);
+ return val == null ? null : val.obj;
+ }
+ }
+
+ /**
+ * Removes the entry for the given key from the cache.
+ *
+ * @param key The key for the entry to remove.
+ *
+ * @return If the cache contains an entry for the key, the
+ * return value is the corresponding object; otherwise, the
+ * return value is <code>null</code>.
+ **/
+ public Object
+ unpin(Object key)
+ {
+ synchronized(_map)
+ {
+ CacheValue val = (CacheValue)_map.remove(key);
+ return val == null ? null : val.obj;
+ }
+ }
+
+ /**
+ * Removes all entries from the cache.
+ **/
+ public void
+ clear()
+ {
+ synchronized(_map)
+ {
+ _map.clear();
+ }
+ }
+
+ /**
+ * Returns the number of entries in the cache.
+ *
+ * @return The number of entries.
+ **/
+ public int
+ size()
+ {
+ synchronized(_map)
+ {
+ return _map.size();
+ }
+ }
+
+ /**
+ * Adds a key-value pair to the cache.
+ * This version of <code>pin</code> does not call {@link Store#load} to retrieve
+ * an entry from backing store if an entry for the given key is not yet in the cache. This
+ * is useful to add a newly-created object to the cache.
+ *
+ * @param key The key for the entry.
+ * @param o The value for the entry.
+ * @return If the cache already contains an entry with the given key, the entry is
+ * unchanged and <code>pin(Object, Object)</code> returns the original value for the entry; otherwise,
+ * the entry is added and <code>pin(Object, Object)</code> returns <code>null</code>.
+ **/
+ public Object
+ pin(Object key, Object o)
+ {
+ synchronized(_map)
+ {
+ CacheValue existingVal = (CacheValue)_map.put(key, new CacheValue(o));
+ if(existingVal != null)
+ {
+ _map.put(key, existingVal);
+ return existingVal.obj;
+ }
+ else
+ {
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Returns an object from the cache.
+ * If no entry with the given key is in the cache, <code>pin</code> calls
+ * {@link Store#load} to retrieve the corresponding value (if any) from the
+ * backing store.
+ *
+ * @param key The key for the entry to retrieve.
+ * @return Returns the value for the corresponding key if the cache
+ * contains an entry for the key. Otherwise, <code>pin(Object)</code> calls
+ * {@link Store#load} and the return value is whatever is returned by
+ * <code>load</code>; if <code>load</code> throws an exception, that exception
+ * is thrown by <code>pin(Object)</code>.
+ **/
+ public Object
+ pin(Object key)
+ {
+ return pinImpl(key, null);
+ }
+
+ /**
+ * Adds a key-value pair to the cache.
+ * @param key The key for the entry.
+ * @param newObj The value for the entry.
+ * @return If the cache already contains an entry for the given key,
+ * <code>putIfAbsent</code> returns the original value for that key.
+ * If no entry is for the given key is in the cache, <code>putIfAbsent</code>
+ * calls {@link Store#load} to retrieve the corresponding entry (if any) from
+ * the backings store and returns the value returned by <code>load</code>.
+ * If the cache does not contain an entry for the given key and <code>load</code>
+ * does not return a value for the key, <code>putIfAbsent</code> adds the new entry
+ * and returns <code>null</code>.
+ **/
+ public Object
+ putIfAbsent(Object key, Object newObj)
+ {
+ return pinImpl(key, newObj);
+ }
+
+ static private class CacheValue
+ {
+ CacheValue()
+ {
+ }
+
+ CacheValue(Object obj)
+ {
+ this.obj = obj;
+ }
+
+ Object obj = null;
+ java.util.concurrent.CountDownLatch latch = null;
+ }
+
+ private Object
+ pinImpl(Object key, Object newObj)
+ {
+ for(;;)
+ {
+ CacheValue val = null;
+ java.util.concurrent.CountDownLatch latch = null;
+
+ synchronized(_map)
+ {
+ val = (CacheValue)_map.get(key);
+ if(val == null)
+ {
+ val = new CacheValue();
+ _map.put(key, val);
+ }
+ else
+ {
+ if(val.obj != null)
+ {
+ return val.obj;
+ }
+ if(val.latch == null)
+ {
+ //
+ // The first queued thread creates the latch
+ //
+ val.latch = new java.util.concurrent.CountDownLatch(1);
+ }
+ latch = val.latch;
+ }
+ }
+
+ if(latch != null)
+ {
+ try
+ {
+ latch.await();
+ }
+ catch(InterruptedException e)
+ {
+ // Ignored
+ }
+
+ //
+ // val could be stale now, e.g. some other thread pinned and unpinned the
+ // object while we were waiting.
+ // So start over.
+ //
+ continue;
+ }
+ else
+ {
+ Object obj;
+ try
+ {
+ obj = _store.load(key);
+ }
+ catch(RuntimeException e)
+ {
+ synchronized(_map)
+ {
+ _map.remove(key);
+ latch = val.latch;
+ val.latch = null;
+ }
+ if(latch != null)
+ {
+ latch.countDown();
+ assert latch.getCount() == 0;
+ }
+ throw e;
+ }
+
+ synchronized(_map)
+ {
+ if(obj != null)
+ {
+ val.obj = obj;
+ }
+ else
+ {
+ if(newObj == null)
+ {
+ //
+ // pin() did not find the object
+ //
+
+ //
+ // The waiting threads will have to call load() to see by themselves.
+ //
+ _map.remove(key);
+ }
+ else
+ {
+ //
+ // putIfAbsent() inserts key/newObj
+ //
+ val.obj = newObj;
+ }
+ }
+
+ latch = val.latch;
+ val.latch = null;
+ }
+ if(latch != null)
+ {
+ latch.countDown();
+ assert latch.getCount() == 0;
+ }
+ return obj;
+ }
+ }
+ }
+
+ private final java.util.Map<Object, CacheValue> _map = new java.util.HashMap<Object, CacheValue>();
+ private final Store _store;
+}