// **********************************************************************
//
// Copyright (c) 2003-2010 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;
}