/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2007-2008, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.util; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; //import java.util.concurrent.locks.ReentrantWriterPreferenceReadWriteLock; /** * Caching implementation for {@link ObjectCache}. This instance is used when * actual caching is desired. * * @since 2.5 * @version $Id$ * @source $URL$ * @author Cory Horner (Refractions Research) */ final class DefaultObjectCache implements ObjectCache { /** * The cached values for each key. */ private final Map/*<Object,ObjectCacheEntry>*/ cache; /** * An entry in the {@link DefaultObjectCache}. * * To use as a reader: * <blockquote><pre> * entry.get(); * </pre></blockquote> * * To use as a writer: * <blockquote><pre> * try { * entry.writeLock(); * entry.set(value); * } finally { * entry.writeUnLock(); * } * </pre></blockquote> * Tip: The use of try/finally is more than just a good idea - it is the law. * * @todo change from edu.oswego to java.concurrent */ static final class ObjectCacheEntry { /** * Value of this cache entry, managed by the {@linkplain #lock}. * * @todo According {@link java.util.concurrent.locks.ReentrantReadWriteLock} documentation, * we don't need to declare this field as volatile. Revisit when we will be allowed to * compile for J2SE 1.5. */ private volatile Object value; /** * The lock used to manage the {@linkplain #value}. */ private final ReadWriteLock lock = new ReentrantReadWriteLock(); // formally ReentrantReadWriteLock /** * Creates an entry with no initial value. */ public ObjectCacheEntry() { } /** * Creates an entry with the specified initial value. */ public ObjectCacheEntry(final Object value) { this.value = value; } /** * Acquires a write lock, obtains the value, unlocks, and returns the value. * If another thread already has the read or write lock, this method will block. * * <blockquote><pre> * try { * entry.writeLock(); * value = entry.peek(); * } * finally { * entry.writeUnLock(); * } * </pre></blockquote> */ public Object peek() { try { lock.writeLock().lock(); return value; } // catch (RuntimeException e) { // return null; // } finally { lock.writeLock().unlock(); } } /** * Acquires a read lock, obtains the value, unlocks, and returns the value. * If another thread already has the write lock, this method will block. * * @return cached value or null if empty */ public Object getValue() { try { lock.readLock().lock(); return value; } // catch (InterruptedException e) { // //TODO: add logger, or is this too performance constrained? // return null; // } finally { lock.readLock().unlock(); } } /** * Stores the value in the entry, using the write lock. * It is common to use this method while already holding the writeLock (since writeLock * is re-entrant). * * @param value */ public void setValue(Object value) { try { lock.writeLock().lock(); this.value = value; } // catch (InterruptedException e) { // } finally { lock.writeLock().unlock(); } } /** * Acquires a write lock. This will block other readers and writers (on this * entry only), and other readers and writers will need to be cleared before * the write lock can be acquired, unless it is the same thread attempting * to read or write. */ public boolean writeLock() { lock.writeLock().lock(); return true; } /** * Releases a write lock. */ public void writeUnLock() { lock.writeLock().unlock(); } } /** * Creates a new cache. */ public DefaultObjectCache() { cache = new HashMap(); } /** * Creates a new cache using the indicated initialSize. */ public DefaultObjectCache(final int initialSize) { cache = new HashMap(initialSize); } /** * Removes all entries from this map. */ public void clear() { synchronized (cache) { cache.clear(); } } /** * Check if an entry exists in the cache. * * @param key * @return boolean */ public boolean containsKey(final Object key) { return cache.containsKey(key); } /** * Returns the object from the cache. * <p> * Please note that a read lock is maintained on the cache contents; you * may be stuck waiting for a writer to produce the result over the * course of calling this method. * </p> * The contents (of course) may be null. * * @param key * The authority code. * * @todo Consider logging a message here to the finer or finest level. */ public Object get(final Object key) { return getEntry(key).getValue(); } public Object peek(final Object key) { synchronized (cache) { if (!cache.containsKey(key)) { // no entry for this key - so no value return null; } return getEntry(key).peek(); } } public void writeLock(final Object key) { getEntry(key).writeLock(); } public void writeUnLock(final Object key) { synchronized (cache) { if (!cache.containsKey(key)) { throw new IllegalStateException("Cannot unlock prior to locking"); } getEntry(key).writeUnLock(); } } /** * Stores a value */ public void put(final Object key, final Object object) { getEntry(key).setValue(object); } /** * Retrieve cache entry, will create one if needed. * * @param key * @return ObjectCacheEntry */ private ObjectCacheEntry getEntry(Object key) { synchronized (cache) { ObjectCacheEntry entry = (ObjectCacheEntry) cache.get(key); if (entry == null) { entry = new ObjectCacheEntry(); cache.put(key, entry); } return entry; } } /** * Retrieves all keys currently in the cache. * * @return Set of keys */ public Set<Object> getKeys(){ Set<Object> ret = null; synchronized (cache) { ret = new HashSet<Object>( cache.keySet() ); } return ret; } /** * Removes this item from the object cache. */ public void remove(Object key) { synchronized (cache) { cache.remove(key); } } }