Clover coverage report -
Coverage timestamp: Sun Mar 5 2006 06:05:21 CST
file stats: LOC: 2,084   Methods: 89
NCLOC: 922   Classes: 5
 
 Source file Conditionals Statements Methods TOTAL
AbstractConcurrentReadCache.java 60.2% 63.1% 52.8% 61.2%
coverage coverage
 1    /*
 2    * Copyright (c) 2002-2003 by OpenSymphony
 3    * All rights reserved.
 4    */
 5    /*
 6    File: AbstractConcurrentReadCache
 7   
 8    Written by Doug Lea. Adapted from JDK1.2 HashMap.java and Hashtable.java
 9    which carries the following copyright:
 10   
 11    * Copyright 1997 by Sun Microsystems, Inc.,
 12    * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
 13    * All rights reserved.
 14    *
 15    * This software is the confidential and proprietary information
 16    * of Sun Microsystems, Inc. ("Confidential Information"). You
 17    * shall not disclose such Confidential Information and shall use
 18    * it only in accordance with the terms of the license agreement
 19    * you entered into with Sun.
 20   
 21    This class is a modified version of ConcurrentReaderHashMap, which was written
 22    by Doug Lea (http://gee.cs.oswego.edu/dl/). The modifications where done
 23    by Pyxis Technologies. This is a base class for the OSCache module of the
 24    openSymphony project (www.opensymphony.com).
 25   
 26    History:
 27    Date Who What
 28    28oct1999 dl Created
 29    14dec1999 dl jmm snapshot
 30    19apr2000 dl use barrierLock
 31    12jan2001 dl public release
 32    Oct2001 abergevin@pyxis-tech.com
 33    Integrated persistence and outer algorithm support
 34    */
 35    package com.opensymphony.oscache.base.algorithm;
 36   
 37   
 38    /** OpenSymphony BEGIN */
 39    import com.opensymphony.oscache.base.CacheEntry;
 40    import com.opensymphony.oscache.base.persistence.CachePersistenceException;
 41    import com.opensymphony.oscache.base.persistence.PersistenceListener;
 42   
 43    import org.apache.commons.logging.Log;
 44    import org.apache.commons.logging.LogFactory;
 45   
 46    import java.io.IOException;
 47    import java.io.Serializable;
 48   
 49    import java.util.*;
 50   
 51    /**
 52    * A version of Hashtable that supports mostly-concurrent reading, but exclusive writing.
 53    * Because reads are not limited to periods
 54    * without writes, a concurrent reader policy is weaker than a classic
 55    * reader/writer policy, but is generally faster and allows more
 56    * concurrency. This class is a good choice especially for tables that
 57    * are mainly created by one thread during the start-up phase of a
 58    * program, and from then on, are mainly read (with perhaps occasional
 59    * additions or removals) in many threads. If you also need concurrency
 60    * among writes, consider instead using ConcurrentHashMap.
 61    * <p>
 62    *
 63    * Successful retrievals using get(key) and containsKey(key) usually
 64    * run without locking. Unsuccessful ones (i.e., when the key is not
 65    * present) do involve brief synchronization (locking). Also, the
 66    * size and isEmpty methods are always synchronized.
 67    *
 68    * <p> Because retrieval operations can ordinarily overlap with
 69    * writing operations (i.e., put, remove, and their derivatives),
 70    * retrievals can only be guaranteed to return the results of the most
 71    * recently <em>completed</em> operations holding upon their
 72    * onset. Retrieval operations may or may not return results
 73    * reflecting in-progress writing operations. However, the retrieval
 74    * operations do always return consistent results -- either those
 75    * holding before any single modification or after it, but never a
 76    * nonsense result. For aggregate operations such as putAll and
 77    * clear, concurrent reads may reflect insertion or removal of only
 78    * some entries. In those rare contexts in which you use a hash table
 79    * to synchronize operations across threads (for example, to prevent
 80    * reads until after clears), you should either encase operations
 81    * in synchronized blocks, or instead use java.util.Hashtable.
 82    *
 83    * <p>
 84    *
 85    * This class also supports optional guaranteed
 86    * exclusive reads, simply by surrounding a call within a synchronized
 87    * block, as in <br>
 88    * <code>AbstractConcurrentReadCache t; ... Object v; <br>
 89    * synchronized(t) { v = t.get(k); } </code> <br>
 90    *
 91    * But this is not usually necessary in practice. For
 92    * example, it is generally inefficient to write:
 93    *
 94    * <pre>
 95    * AbstractConcurrentReadCache t; ... // Inefficient version
 96    * Object key; ...
 97    * Object value; ...
 98    * synchronized(t) {
 99    * if (!t.containsKey(key))
 100    * t.put(key, value);
 101    * // other code if not previously present
 102    * }
 103    * else {
 104    * // other code if it was previously present
 105    * }
 106    * }
 107    *</pre>
 108    * Instead, just take advantage of the fact that put returns
 109    * null if the key was not previously present:
 110    * <pre>
 111    * AbstractConcurrentReadCache t; ... // Use this instead
 112    * Object key; ...
 113    * Object value; ...
 114    * Object oldValue = t.put(key, value);
 115    * if (oldValue == null) {
 116    * // other code if not previously present
 117    * }
 118    * else {
 119    * // other code if it was previously present
 120    * }
 121    *</pre>
 122    * <p>
 123    *
 124    * Iterators and Enumerations (i.e., those returned by
 125    * keySet().iterator(), entrySet().iterator(), values().iterator(),
 126    * keys(), and elements()) return elements reflecting the state of the
 127    * hash table at some point at or since the creation of the
 128    * iterator/enumeration. They will return at most one instance of
 129    * each element (via next()/nextElement()), but might or might not
 130    * reflect puts and removes that have been processed since they were
 131    * created. They do <em>not</em> throw ConcurrentModificationException.
 132    * However, these iterators are designed to be used by only one
 133    * thread at a time. Sharing an iterator across multiple threads may
 134    * lead to unpredictable results if the table is being concurrently
 135    * modified. Again, you can ensure interference-free iteration by
 136    * enclosing the iteration in a synchronized block. <p>
 137    *
 138    * This class may be used as a direct replacement for any use of
 139    * java.util.Hashtable that does not depend on readers being blocked
 140    * during updates. Like Hashtable but unlike java.util.HashMap,
 141    * this class does NOT allow <tt>null</tt> to be used as a key or
 142    * value. This class is also typically faster than ConcurrentHashMap
 143    * when there is usually only one thread updating the table, but
 144    * possibly many retrieving values from it.
 145    * <p>
 146    *
 147    * Implementation note: A slightly faster implementation of
 148    * this class will be possible once planned Java Memory Model
 149    * revisions are in place.
 150    *
 151    * <p>[<a href="http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html"> Introduction to this package. </a>]
 152    **/
 153    public abstract class AbstractConcurrentReadCache extends AbstractMap implements Map, Cloneable, Serializable {
 154    /**
 155    * The default initial number of table slots for this table (32).
 156    * Used when not otherwise specified in constructor.
 157    **/
 158    public static int DEFAULT_INITIAL_CAPACITY = 32;
 159   
 160    /**
 161    * The minimum capacity.
 162    * Used if a lower value is implicitly specified
 163    * by either of the constructors with arguments.
 164    * MUST be a power of two.
 165    */
 166    private static final int MINIMUM_CAPACITY = 4;
 167   
 168    /**
 169    * The maximum capacity.
 170    * Used if a higher value is implicitly specified
 171    * by either of the constructors with arguments.
 172    * MUST be a power of two <= 1<<30.
 173    */
 174    private static final int MAXIMUM_CAPACITY = 1 << 30;
 175   
 176    /**
 177    * The default load factor for this table.
 178    * Used when not otherwise specified in constructor, the default is 0.75f.
 179    **/
 180    public static final float DEFAULT_LOAD_FACTOR = 0.75f;
 181   
 182    //OpenSymphony BEGIN (pretty long!)
 183    protected static final String NULL = "_nul!~";
 184    protected static Log log = LogFactory.getLog(AbstractConcurrentReadCache.class);
 185   
 186    /*
 187    The basic strategy is an optimistic-style scheme based on
 188    the guarantee that the hash table and its lists are always
 189    kept in a consistent enough state to be read without locking:
 190   
 191    * Read operations first proceed without locking, by traversing the
 192    apparently correct list of the apparently correct bin. If an
 193    entry is found, but not invalidated (value field null), it is
 194    returned. If not found, operations must recheck (after a memory
 195    barrier) to make sure they are using both the right list and
 196    the right table (which can change under resizes). If
 197    invalidated, reads must acquire main update lock to wait out
 198    the update, and then re-traverse.
 199   
 200    * All list additions are at the front of each bin, making it easy
 201    to check changes, and also fast to traverse. Entry next
 202    pointers are never assigned. Remove() builds new nodes when
 203    necessary to preserve this.
 204   
 205    * Remove() (also clear()) invalidates removed nodes to alert read
 206    operations that they must wait out the full modifications.
 207   
 208    */
 209   
 210    /**
 211    * Lock used only for its memory effects. We use a Boolean
 212    * because it is serializable, and we create a new one because
 213    * we need a unique object for each cache instance.
 214    **/
 215    protected final Boolean barrierLock = new Boolean(true);
 216   
 217    /**
 218    * field written to only to guarantee lock ordering.
 219    **/
 220    protected transient Object lastWrite;
 221   
 222    /**
 223    * The hash table data.
 224    */
 225    protected transient Entry[] table;
 226   
 227    /**
 228    * The total number of mappings in the hash table.
 229    */
 230    protected transient int count;
 231   
 232    /**
 233    * Persistence listener.
 234    */
 235    protected PersistenceListener persistenceListener = null;
 236   
 237    /**
 238    * Use memory cache or not.
 239    */
 240    protected boolean memoryCaching = true;
 241   
 242    /**
 243    * Use unlimited disk caching.
 244    */
 245    protected boolean unlimitedDiskCache = false;
 246   
 247    /**
 248    * The load factor for the hash table.
 249    *
 250    * @serial
 251    */
 252    protected float loadFactor;
 253   
 254    /**
 255    * Default cache capacity (number of entries).
 256    */
 257    protected final int DEFAULT_MAX_ENTRIES = 100;
 258   
 259    /**
 260    * Max number of element in cache when considered unlimited.
 261    */
 262    protected final int UNLIMITED = 2147483646;
 263    protected transient Collection values = null;
 264   
 265    /**
 266    * A HashMap containing the group information.
 267    * Each entry uses the group name as the key, and holds a
 268    * <code>Set</code> of containing keys of all
 269    * the cache entries that belong to that particular group.
 270    */
 271    protected HashMap groups = new HashMap();
 272    protected transient Set entrySet = null;
 273   
 274    // Views
 275    protected transient Set keySet = null;
 276   
 277    /**
 278    * Cache capacity (number of entries).
 279    */
 280    protected int maxEntries = DEFAULT_MAX_ENTRIES;
 281   
 282    /**
 283    * The table is rehashed when its size exceeds this threshold.
 284    * (The value of this field is always (int)(capacity * loadFactor).)
 285    *
 286    * @serial
 287    */
 288    protected int threshold;
 289   
 290    /**
 291    * Use overflow persistence caching.
 292    */
 293    private boolean overflowPersistence = false;
 294   
 295    /**
 296    * Constructs a new, empty map with the specified initial capacity and the specified load factor.
 297    *
 298    * @param initialCapacity the initial capacity
 299    * The actual initial capacity is rounded to the nearest power of two.
 300    * @param loadFactor the load factor of the AbstractConcurrentReadCache
 301    * @throws IllegalArgumentException if the initial maximum number
 302    * of elements is less
 303    * than zero, or if the load factor is nonpositive.
 304    */
 305  132 public AbstractConcurrentReadCache(int initialCapacity, float loadFactor) {
 306  132 if (loadFactor <= 0) {
 307  0 throw new IllegalArgumentException("Illegal Load factor: " + loadFactor);
 308    }
 309   
 310  132 this.loadFactor = loadFactor;
 311   
 312  132 int cap = p2capacity(initialCapacity);
 313  132 table = new Entry[cap];
 314  132 threshold = (int) (cap * loadFactor);
 315    }
 316   
 317    /**
 318    * Constructs a new, empty map with the specified initial capacity and default load factor.
 319    *
 320    * @param initialCapacity the initial capacity of the
 321    * AbstractConcurrentReadCache.
 322    * @throws IllegalArgumentException if the initial maximum number
 323    * of elements is less
 324    * than zero.
 325    */
 326  0 public AbstractConcurrentReadCache(int initialCapacity) {
 327  0 this(initialCapacity, DEFAULT_LOAD_FACTOR);
 328    }
 329   
 330    /**
 331    * Constructs a new, empty map with a default initial capacity and load factor.
 332    */
 333  132 public AbstractConcurrentReadCache() {
 334  132 this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
 335    }
 336   
 337    /**
 338    * Constructs a new map with the same mappings as the given map.
 339    * The map is created with a capacity of twice the number of mappings in
 340    * the given map or 11 (whichever is greater), and a default load factor.
 341    */
 342  0 public AbstractConcurrentReadCache(Map t) {
 343  0 this(Math.max(2 * t.size(), 11), DEFAULT_LOAD_FACTOR);
 344  0 putAll(t);
 345    }
 346   
 347    /**
 348    * Returns <tt>true</tt> if this map contains no key-value mappings.
 349    *
 350    * @return <tt>true</tt> if this map contains no key-value mappings.
 351    */
 352  0 public synchronized boolean isEmpty() {
 353  0 return count == 0;
 354    }
 355   
 356    /**
 357    * Returns a set of the cache keys that reside in a particular group.
 358    *
 359    * @param groupName The name of the group to retrieve.
 360    * @return a set containing all of the keys of cache entries that belong
 361    * to this group, or <code>null</code> if the group was not found.
 362    * @exception NullPointerException if the groupName is <code>null</code>.
 363    */
 364  36 public Set getGroup(String groupName) {
 365  36 if (log.isDebugEnabled()) {
 366  0 log.debug("getGroup called (group=" + groupName + ")");
 367    }
 368   
 369  36 Set groupEntries = null;
 370   
 371  36 if (memoryCaching && (groups != null)) {
 372  27 groupEntries = (Set) getGroupForReading(groupName);
 373    }
 374   
 375  36 if (groupEntries == null) {
 376    // Not in the map, try the persistence layer
 377  12 groupEntries = persistRetrieveGroup(groupName);
 378    }
 379   
 380  36 return groupEntries;
 381    }
 382   
 383    /**
 384    * Set the cache capacity
 385    */
 386  80 public void setMaxEntries(int newLimit) {
 387  80 if (newLimit > 0) {
 388  64 maxEntries = newLimit;
 389   
 390  64 synchronized (this) { // because remove() isn't synchronized
 391   
 392  64 while (size() > maxEntries) {
 393  16 remove(removeItem(), false);
 394    }
 395    }
 396    } else {
 397    // Capacity must be at least 1
 398  16 throw new IllegalArgumentException("Cache maximum number of entries must be at least 1");
 399    }
 400    }
 401   
 402    /**
 403    * Retrieve the cache capacity (number of entries).
 404    */
 405  32 public int getMaxEntries() {
 406  32 return maxEntries;
 407    }
 408   
 409    /**
 410    * Sets the memory caching flag.
 411    */
 412  132 public void setMemoryCaching(boolean memoryCaching) {
 413  132 this.memoryCaching = memoryCaching;
 414    }
 415   
 416    /**
 417    * Check if memory caching is used.
 418    */
 419  24 public boolean isMemoryCaching() {
 420  24 return memoryCaching;
 421    }
 422   
 423    /**
 424    * Set the persistence listener to use.
 425    */
 426  102 public void setPersistenceListener(PersistenceListener listener) {
 427  102 this.persistenceListener = listener;
 428    }
 429   
 430    /**
 431    * Get the persistence listener.
 432    */
 433  64 public PersistenceListener getPersistenceListener() {
 434  64 return persistenceListener;
 435    }
 436   
 437    /**
 438    * Sets the unlimited disk caching flag.
 439    */
 440  108 public void setUnlimitedDiskCache(boolean unlimitedDiskCache) {
 441  108 this.unlimitedDiskCache = unlimitedDiskCache;
 442    }
 443   
 444    /**
 445    * Check if we use unlimited disk cache.
 446    */
 447  0 public boolean isUnlimitedDiskCache() {
 448  0 return unlimitedDiskCache;
 449    }
 450   
 451    /**
 452    * Check if we use overflowPersistence
 453    *
 454    * @return Returns the overflowPersistence.
 455    */
 456  16 public boolean isOverflowPersistence() {
 457  16 return this.overflowPersistence;
 458    }
 459   
 460    /**
 461    * Sets the overflowPersistence flag
 462    *
 463    * @param overflowPersistence The overflowPersistence to set.
 464    */
 465  132 public void setOverflowPersistence(boolean overflowPersistence) {
 466  132 this.overflowPersistence = overflowPersistence;
 467    }
 468   
 469    /**
 470    * Return the number of slots in this table.
 471    **/
 472  0 public synchronized int capacity() {
 473  0 return table.length;
 474    }
 475   
 476    /**
 477    * Removes all mappings from this map.
 478    */
 479  204 public synchronized void clear() {
 480  204 Entry[] tab = table;
 481   
 482  204 for (int i = 0; i < tab.length; ++i) {
 483    // must invalidate all to force concurrent get's to wait and then retry
 484  6528 for (Entry e = tab[i]; e != null; e = e.next) {
 485  204 e.value = null;
 486   
 487    /** OpenSymphony BEGIN */
 488  204 itemRemoved(e.key);
 489   
 490    /** OpenSymphony END */
 491    }
 492   
 493  6528 tab[i] = null;
 494    }
 495   
 496    // Clean out the entire disk cache
 497  204 persistClear();
 498   
 499  204 count = 0;
 500