// ********************************************************************** // // 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; }