Clover coverage report -
Coverage timestamp: do jan 22 2004 21:12:32 CET
file stats: LOC: 2.030   Methods: 86
NCLOC: 899   Classes: 5
 
 Source file Conditionals Statements Methods TOTAL
AbstractConcurrentReadCache.java 56,3% 60% 50% 57,9%
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 final transient 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.
 212   
      **/
 213   
     protected final transient Object barrierLock = new Object();
 214   
 
 215   
     /**
 216   
      * field written to only to guarantee lock ordering.
 217   
      **/
 218   
     protected transient Object lastWrite;
 219   
 
 220   
     /**
 221   
      * The hash table data.
 222   
      */
 223   
     protected transient Entry[] table;
 224   
 
 225   
     /**
 226   
      * The total number of mappings in the hash table.
 227   
      */
 228   
     protected transient int count;
 229   
 
 230   
     /**
 231   
      * Persistence listener.
 232   
      */
 233   
     protected PersistenceListener persistenceListener = null;
 234   
 
 235   
     /**
 236   
      * Use memory cache or not.
 237   
      */
 238   
     protected boolean memoryCaching = true;
 239   
 
 240   
     /**
 241   
      * Use unlimited disk caching.
 242   
      */
 243   
     protected boolean unlimitedDiskCache = false;
 244   
 
 245   
     /**
 246   
      * The load factor for the hash table.
 247   
      *
 248   
      * @serial
 249   
      */
 250   
     protected float loadFactor;
 251   
 
 252   
     /**
 253   
      * Default cache capacity (number of entries).
 254   
      */
 255   
     protected final int DEFAULT_MAX_ENTRIES = 100;
 256   
 
 257   
     /**
 258   
      * Max number of element in cache when considered unlimited.
 259   
      */
 260   
     protected final int UNLIMITED = 2147483646;
 261   
     protected transient Collection values = null;
 262   
 
 263   
     /**
 264   
      * A HashMap containing the group information.
 265   
      * Each entry uses the group name as the key, and holds a
 266   
      * <code>Set</code> of containing keys of all
 267   
      * the cache entries that belong to that particular group.
 268   
      */
 269   
     protected HashMap groups = null;
 270   
     protected transient Set entrySet = null;
 271   
 
 272   
     // Views
 273   
     protected transient Set keySet = null;
 274   
 
 275   
     /**
 276   
      * Cache capacity (number of entries).
 277   
      */
 278   
     protected int maxEntries = DEFAULT_MAX_ENTRIES;
 279   
 
 280   
     /**
 281   
      * The table is rehashed when its size exceeds this threshold.
 282   
      * (The value of this field is always (int)(capacity * loadFactor).)
 283   
      *
 284   
      * @serial
 285   
      */
 286   
     protected int threshold;
 287   
 
 288   
     /**
 289   
      * Constructs a new, empty map with the specified initial capacity and the specified load factor.
 290   
      *
 291   
      * @param initialCapacity the initial capacity
 292   
      *  The actual initial capacity is rounded to the nearest power of two.
 293   
      * @param loadFactor  the load factor of the AbstractConcurrentReadCache
 294   
      * @throws IllegalArgumentException  if the initial maximum number
 295   
      *               of elements is less
 296   
      *               than zero, or if the load factor is nonpositive.
 297   
      */
 298  81
     public AbstractConcurrentReadCache(int initialCapacity, float loadFactor) {
 299  81
         if (loadFactor <= 0) {
 300  0
             throw new IllegalArgumentException("Illegal Load factor: " + loadFactor);
 301   
         }
 302   
 
 303  81
         this.loadFactor = loadFactor;
 304   
 
 305  81
         int cap = p2capacity(initialCapacity);
 306  81
         table = new Entry[cap];
 307  81
         threshold = (int) (cap * loadFactor);
 308   
     }
 309   
 
 310   
     /**
 311   
      * Constructs a new, empty map with the specified initial capacity and default load factor.
 312   
      *
 313   
      * @param   initialCapacity   the initial capacity of the
 314   
      *                            AbstractConcurrentReadCache.
 315   
      * @throws    IllegalArgumentException if the initial maximum number
 316   
      *              of elements is less
 317   
      *              than zero.
 318   
      */
 319  0
     public AbstractConcurrentReadCache(int initialCapacity) {
 320  0
         this(initialCapacity, DEFAULT_LOAD_FACTOR);
 321   
     }
 322   
 
 323   
     /**
 324   
      * Constructs a new, empty map with a default initial capacity and load factor.
 325   
      */
 326  81
     public AbstractConcurrentReadCache() {
 327  81
         this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
 328   
     }
 329   
 
 330   
     /**
 331   
      * Constructs a new map with the same mappings as the given map.
 332   
      * The map is created with a capacity of twice the number of mappings in
 333   
      * the given map or 11 (whichever is greater), and a default load factor.
 334   
      */
 335  0
     public AbstractConcurrentReadCache(Map t) {
 336  0
         this(Math.max(2 * t.size(), 11), DEFAULT_LOAD_FACTOR);
 337  0
         putAll(t);
 338   
     }
 339   
 
 340   
     /**
 341   
      * Returns <tt>true</tt> if this map contains no key-value mappings.
 342   
      *
 343   
      * @return <tt>true</tt> if this map contains no key-value mappings.
 344   
      */
 345  0
     public synchronized boolean isEmpty() {
 346  0
         return count == 0;
 347   
     }
 348   
 
 349   
     /**
 350   
      * Returns a set of the cache keys that reside in a particular group.
 351   
      *
 352   
      * @param   groupName The name of the group to retrieve.
 353   
      * @return  a set containing all of the keys of cache entries that belong
 354   
      * to this group, or <code>null</code> if the group was not found.
 355   
      * @exception  NullPointerException if the groupName is <code>null</code>.
 356   
      */
 357  24
     public Set getGroup(String groupName) {
 358  24
         if (log.isDebugEnabled()) {
 359  0
             log.debug("getGroup called (group=" + groupName + ")");
 360   
         }
 361   
 
 362  24
         Set groupEntries = null;
 363   
 
 364  24
         if (memoryCaching && (groups != null)) {
 365  14
             groupEntries = (Set) getGroupsForReading().get(groupName);
 366   
         }
 367   
 
 368  24
         if (groupEntries == null) {
 369   
             // Not in the map, try the persistence layer
 370  10
             groupEntries = persistRetrieveGroup(groupName);
 371   
         } else {
 372   
             // We don't want to give them back the internal Set object
 373  14
             groupEntries = new HashSet(groupEntries);
 374   
         }
 375   
 
 376  24
         return groupEntries;
 377   
     }
 378   
 
 379   
     /**
 380   
      * Set the cache capacity
 381   
      */
 382  24
     public void setMaxEntries(int newLimit) {
 383  24
         if (newLimit > 0) {
 384  12
             maxEntries = newLimit;
 385   
 
 386  12
             synchronized (this) { // because remove() isn't synchronized
 387   
 
 388  12
                 while (size() > maxEntries) {
 389  12
                     remove(removeItem(), false);
 390   
                 }
 391   
             }
 392   
         } else {
 393   
             // Capacity must be at least 1
 394  12
             throw new IllegalArgumentException("Cache maximum number of entries must be at least 1");
 395   
         }
 396   
     }
 397   
 
 398   
     /**
 399   
      * Retrieve the cache capacity (number of entries).
 400   
      */
 401  24
     public int getMaxEntries() {
 402  24
         return maxEntries;
 403   
     }
 404   
 
 405   
     /**
 406   
      * Sets the memory caching flag.
 407   
      */
 408  81
     public void setMemoryCaching(boolean memoryCaching) {
 409  81
         this.memoryCaching = memoryCaching;
 410   
     }
 411   
 
 412   
     /**
 413   
      * Check if memory caching is used.
 414   
      */
 415  18
     public boolean isMemoryCaching() {
 416  18
         return memoryCaching;
 417   
     }
 418   
 
 419   
     /**
 420   
      * Set the persistence listener to use.
 421   
      */
 422  52
     public void setPersistenceListener(PersistenceListener listener) {
 423  52
         this.persistenceListener = listener;
 424   
     }
 425   
 
 426   
     /**
 427   
      * Get the persistence listener.
 428   
      */
 429  0
     public PersistenceListener getPersistenceListener() {
 430  0
         return persistenceListener;
 431   
     }
 432   
 
 433   
     /**
 434   
      * Sets the unlimited disk caching flag.
 435   
      */
 436  63
     public void setUnlimitedDiskCache(boolean unlimitedDiskCache) {
 437  63
         this.unlimitedDiskCache = unlimitedDiskCache;
 438   
     }
 439   
 
 440   
     /**
 441   
      * Check if we use unlimited disk cache.
 442   
      */
 443  0
     public boolean isUnlimitedDiskCache() {
 444  0
         return unlimitedDiskCache;
 445   
     }
 446   
 
 447   
     /**
 448   
      * Return the number of slots in this table.
 449   
      **/
 450  0
     public synchronized int capacity() {
 451  0
         return table.length;
 452   
     }
 453   
 
 454   
     /**
 455   
      * Removes all mappings from this map.
 456   
      */
 457  111
     public synchronized void clear() {
 458  111
         Entry[] tab = table;
 459   
 
 460  111
         for (int i = 0; i < tab.length; ++i) {
 461   
             // must invalidate all to force concurrent get's to wait and then retry
 462  3552
             for (Entry e = tab[i]; e != null; e = e.next) {
 463  117
                 e.value = null;
 464   
 
 465   
                 /** OpenSymphony BEGIN */
 466  117
                 itemRemoved(e.key);
 467   
 
 468   
                 /** OpenSymphony END */
 469   
             }
 470   
 
 471  3552
             tab[i] = null;
 472   
         }
 473   
 
 474   
         // Clean out the entire disk cache
 475  111
         persistClear();
 476   
 
 477  111
         count = 0;
 478  111
         recordModification(tab);
 479   
     }
 480   
 
 481   
     /**
 482   
      * Returns a shallow copy of this.
 483   
      * <tt>AbstractConcurrentReadCache</tt> instance: the keys and
 484   
      * values themselves are not cloned.
 485   
      *
 486   
      * @return a shallow copy of this map.
 487   
      */
 488  0
     public synchronized Object clone() {
 489  0
         try {
 490  0
             AbstractConcurrentReadCache t = (AbstractConcurrentReadCache) super.clone();
 491  0
             t.keySet = null;
 492  0
             t.entrySet = null;
 493  0
             t.values = null;
 494   
 
 495  0
             Entry[] tab = table;
 496  0
             t.table = new Entry[tab.length];
 497   
 
 498  0
             Entry[] ttab = t.table;
 499   
 
 500  0
             for (int i = 0; i < tab.length; ++i) {
 501  0
                 Entry first = tab[i];
 502   
 
 503  0
                 if (first != null) {
 504  0
                     ttab[i] = (Entry) (first.clone());
 505   
                 }
 506   
             }
 507   
 
 508  0
             return t;
 509   
         } catch (CloneNotSupportedException e) {
 510   
             // this shouldn't happen, since we are Cloneable
 511  0
             throw new InternalError();
 512   
         }
 513   
     }
 514   
 
 515   
     /**
 516   
      * Tests if some key maps into the specified value in this table.
 517   
      * This operation is more expensive than the <code>containsKey</code>
 518   
      * method.<p>
 519   
      *
 520   
      * Note that this method is identical in functionality to containsValue,
 521   
      * (which is part of the Map interface in the collections framework).
 522   
      *
 523   
      * @param      value   a value to search for.
 524   
      * @return     <code>true</code> if and only if some key maps to the
 525   
      *             <code>value</code> argument in this table as
 526   
      *             determined by the <tt>equals</tt> method;
 527   
      *             <code>false</code> otherwise.
 528   
      * @exception  NullPointerException  if the value is <code>null</code>.
 529   
      * @see        #containsKey(Object)
 530   
      * @see        #containsValue(Object)
 531   
      * @see           Map
 532   
      */
 533  0
     public boolean contains(Object value) {
 534  0
         return containsValue(value);
 535   
     }
 536   
 
 537   
     /**
 538   
      * Tests if the specified object is a key in this table.
 539   
      *
 540   
      * @param   key   possible key.
 541   
      * @return  <code>true</code> if and only if the specified object
 542   
      *          is a key in this table, as determined by the
 543   
      *          <tt>equals</tt> method; <code>false</code> otherwise.
 544   
      * @exception  NullPointerException  if the key is
 545   
      *               <code>null</code>.
 546   
      * @see     #contains(Object)
 547   
      */
 548  18
     public boolean containsKey(Object key) {
 549  18
         return get(key) != null;
 550   
 
 551   
         /** OpenSymphony BEGIN */
 552   
 
 553   
         // TODO: Also check the persistence?
 554   
 
 555   
         /** OpenSymphony END */
 556   
     }
 557   
 
 558   
     /**
 559   
      * Returns <tt>true</tt> if this map maps one or more keys to the
 560   
      * specified value. Note: This method requires a full internal
 561   
      * traversal of the hash table, and so is much slower than
 562   
      * method <tt>containsKey</tt>.
 563   
      *
 564   
      * @param value value whose presence in this map is to be tested.
 565   
      * @return <tt>true</tt> if this map maps one or more keys to the
 566   
      * specified value.
 567   
      * @exception  NullPointerException  if the value is <code>null</code>.
 568   
      */
 569  0
     public boolean containsValue(Object value) {
 570  0
         if (value == null) {
 571  0
             throw new NullPointerException();
 572   
         }
 573   
 
 574  0
         Entry[] tab = getTableForReading();
 575   
 
 576  0
         for (int i = 0; i < tab.length; ++i) {
 577  0
             for (Entry e = tab[i]; e != null; e = e.next) {
 578  0
                 Object v = e.value;
 579   
 
 580  0
                 if ((v != null) && value.equals(v)) {
 581  0
                     return true;
 582   
                 }
 583   
             }
 584   
         }
 585   
 
 586  0
         return false;
 587   
     }
 588   
 
 589   
     /**
 590   
      * Returns an enumeration of the values in this table.
 591   
      * Use the Enumeration methods on the returned object to fetch the elements
 592   
      * sequentially.
 593   
      *
 594   
      * @return  an enumeration of the values in this table.
 595   
      * @see     java.util.Enumeration
 596   
      * @see     #keys()
 597   
      * @see        #values()
 598   
      * @see        Map
 599   
      */
 600  0
     public Enumeration elements() {
 601  0
         return new ValueIterator();
 602   
     }
 603   
 
 604   
     /**
 605   
      * Returns a collection view of the mappings contained in this map.
 606   
      * Each element in the returned collection is a <tt>Map.Entry</tt>.  The
 607   
      * collection is backed by the map, so changes to the map are reflected in
 608   
      * the collection, and vice-versa.  The collection supports element
 609   
      * removal, which removes the corresponding mapping from the map, via the
 610   
      * <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>,
 611   
      * <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt> operations.
 612   
      * It does not support the <tt>add</tt> or <tt>addAll</tt> operations.
 613   
      *
 614   
      * @return a collection view of the mappings contained in this map.
 615   
      */
 616  18
     public Set entrySet() {
 617  18
         Set es = entrySet;
 618   
 
 619  18
         if (es != null) {
 620  0
             return es;
 621   
         } else {
 622  18
             return entrySet = new AbstractSet() {
 623  18
                         public Iterator iterator() {
 624  18
                             return new HashIterator();
 625   
                         }
 626   
 
 627  0
                         public boolean contains(Object o) {
 628  0
                             if (!(o instanceof Map.Entry)) {
 629  0
                                 return false;
 630   
                             }
 631   
 
 632  0
                             Map.Entry entry = (Map.Entry) o;
 633  0
                             Object key = entry.getKey();
 634  0
                             Object v = AbstractConcurrentReadCache.this.get(key);
 635   
 
 636  0
                             return (v != null) && v.equals(entry.getValue());
 637   
                         }
 638   
 
 639  0
                         public boolean remove(Object o) {
 640  0
                             if (!(o instanceof Map.Entry)) {
 641  0
                                 return false;
 642   
                             }
 643   
 
 644  0
                             return AbstractConcurrentReadCache.this.findAndRemoveEntry((Map.Entry) o);
 645   
                         }
 646   
 
 647  0
                         public int size() {
 648  0
                             return AbstractConcurrentReadCache.this.size();
 649   
                         }
 650   
 
 651  0
                         public void clear() {
 652  0
                             AbstractConcurrentReadCache.this.clear();
 653   
                         }
 654   
                     };
 655   
         }
 656   
     }
 657   
 
 658   
     /**
 659   
      * Returns the value to which the specified key is mapped in this table.
 660   
      *
 661   
      * @param   key   a key in the table.
 662   
      * @return  the value to which the key is mapped in this table;
 663   
      *          <code>null</code> if the key is not mapped to any value in
 664   
      *          this table.
 665   
      * @exception  NullPointerException  if the key is
 666   
      *               <code>null</code>.
 667   
      * @see     #put(Object, Object)
 668   
      */
 669  644
     public Object get(Object key) {
 670  644
         if (log.isDebugEnabled()) {
 671  0
             log.debug("get called (key=" + key + ")");
 672   
         }
 673   
 
 674   
         // throw null pointer exception if key null
 675  644
         int hash = hash(key);
 676   
 
 677   
         /*
 678   
            Start off at the apparently correct bin.  If entry is found, we
 679   
            need to check after a barrier anyway.  If not found, we need a
 680   
            barrier to check if we are actually in right bin. So either
 681   
            way, we encounter only one barrier unless we need to retry.
 682   
            And we only need to fully synchronize if there have been
 683   
            concurrent modifications.
 684   
         */
 685  626
         Entry[] tab = table;
 686  626
         int index = hash & (tab.length - 1);
 687  626
         Entry first = tab[index];
 688  626
         Entry e = first;
 689   
 
 690  626
         for (;;) {
 691  675
             if (e == null) {
 692   
                 // If key apparently not there, check to
 693   
                 // make sure this was a valid read
 694  218
                 tab = getTableForReading();
 695   
 
 696  218
                 if (first == tab[index]) {
 697   
                     /** OpenSymphony BEGIN */
 698   
 
 699   
                     /* Previous code
 700   
                     return null;*/
 701   
 
 702   
                     // Not in the table, try persistence
 703  218
                     Object value = persistRetrieve(key);
 704   
 
 705  218
                     if (value != null) {
 706   
                         // Update the map, but don't persist the data
 707  27
                         put(key, value, false);
 708   
                     }
 709   
 
 710  218
                     return value;
 711   
 
 712   
                     /** OpenSymphony END */
 713   
                 } else {
 714   
                     // Wrong list -- must restart traversal at new first
 715  0
                     e = first = tab[index = hash & (tab.length - 1)];
 716   
                 }
 717   
             }
 718   
             // checking for pointer equality first wins in most applications
 719  457
             else if ((key == e.key) || ((e.hash == hash) && key.equals(e.key))) {
 720  408
                 Object value = e.value;
 721   
 
 722  408
                 if (value != null) {
 723   
                     /** OpenSymphony BEGIN */
 724   
 
 725   
                     /* Previous code
 726   
                     return value;*/
 727  408
                     if (NULL.equals(value)) {
 728   
                         // Memory cache disable, use disk
 729  122
                         value = persistRetrieve(e.key);
 730   
 
 731  122
                         if (value != null) {
 732  122
                             itemRetrieved(key);
 733   
                         }
 734   
 
 735  122
                         return value; // fix [CACHE-13]
 736   
                     } else {
 737  286
                         itemRetrieved(key);
 738   
 
 739  286
                         return value;
 740   
                     }
 741   
 
 742   
                     /** OpenSymphony END */
 743   
                 }
 744   
 
 745   
                 // Entry was invalidated during deletion. But it could
 746   
                 // have been re-inserted, so we must retraverse.
 747   
                 // To avoid useless contention, get lock to wait out modifications
 748   
                 // before retraversing.
 749  0
                 synchronized (this) {
 750  0
                     tab = table;
 751   
                 }
 752   
 
 753  0
                 e = first = tab[index = hash & (tab.length - 1)];
 754   
             } else {
 755  49
                 e = e.next;
 756   
             }
 757   
         }
 758   
     }
 759   
 
 760   
     /**
 761   
      * Returns a set view of the keys contained in this map.
 762   
      * The set is backed by the map, so changes to the map are reflected in the set, and
 763   
      * vice-versa.  The set supports element removal, which removes the
 764   
      * corresponding mapping from this map, via the <tt>Iterator.remove</tt>,
 765   
      * <tt>Set.remove</tt>, <tt>removeAll</tt>, <tt>retainAll</tt>, and
 766   
      * <tt>clear</tt> operations.  It does not support the <tt>add</tt> or
 767   
      * <tt>addAll</tt> operations.
 768   
      *
 769   
      * @return a set view of the keys contained in this map.
 770   
      */
 771  18
     public Set keySet() {
 772  18
         Set ks = keySet;
 773   
 
 774  18
         if (ks != null) {
 775  6
             return ks;
 776   
         } else {
 777  12
             return keySet = new AbstractSet() {
 778  18
                         public Iterator iterator() {
 779  18
                             return new KeyIterator();
 780   
                         }
 781   
 
 782  0
                         public int size() {
 783  0
                             return AbstractConcurrentReadCache.this.size();
 784   
                         }
 785   
 
 786  0
                         public boolean contains(Object o) {
 787  0
                             return AbstractConcurrentReadCache.this.containsKey(o);
 788   
                         }
 789   
 
 790  0
                         public boolean remove(Object o) {
 791  0
                             return AbstractConcurrentReadCache.this.remove(o) != null;
 792   
                         }
 793   
 
 794  0
                         public void clear() {
 795  0
                             AbstractConcurrentReadCache.this.clear();
 796   
                         }
 797   
                     };
 798   
         }
 799   
     }
 800   
 
 801   
     /**
 802   
      * Returns an enumeration of the keys in this table.
 803   
      *
 804   
      * @return  an enumeration of the keys in this table.
 805   
      * @see     Enumeration
 806   
      * @see     #elements()
 807   
      * @see        #keySet()
 808   
      * @see        Map
 809   
      */
 810  0
     public Enumeration keys() {
 811  0
         return new KeyIterator();
 812   
     }
 813   
 
 814   
     /**
 815   
      * Return the load factor
 816   
      **/
 817  0
     public float loadFactor() {
 818  0
         return loadFactor;
 819   
     }
 820   
 
 821   
     /**
 822   
      * Maps the specified <code>key</code> to the specified <code>value</code> in this table.
 823   
      * Neither the key nor the
 824   
      * value can be <code>null</code>. <p>
 825   
      *
 826   
      * The value can be retrieved by calling the <code>get</code> method
 827   
      * with a key that is equal to the original key.
 828   
      *
 829   
      * @param      key     the table key.
 830   
      * @param      value   the value.
 831   
      * @return     the previous value of the specified key in this table,
 832   
      *             or <code>null</code> if it did not have one.
 833   
      * @exception  NullPointerException  if the key or value is
 834   
      *               <code>null</code>.
 835   
      * @see     Object#equals(Object)
 836   
      * @see     #get(Object)
 837   
      */
 838   
     /** OpenSymphony BEGIN */
 839  408
     public Object put(Object key, Object value) {
 840   
         // Call the internal put using persistance
 841  408
         return put(key, value, true);
 842   
     }
 843   
 
 844   
     /**
 845   
      * Copies all of the mappings from the specified map to this one.
 846   
      *
 847   
      * These mappings replace any mappings that this map had for any of the
 848   
      * keys currently in the specified Map.
 849   
      *
 850   
      * @param t Mappings to be stored in this map.
 851   
      */
 852  0
     public synchronized void putAll(Map t) {
 853  0
         for (Iterator it = t.entrySet().iterator(); it.hasNext();) {
 854  0
             Map.Entry entry = (Map.Entry) it.next();
 855  0
             Object key = entry.getKey();
 856  0
             Object value = entry.getValue();
 857  0
             put(key, value);
 858   
         }
 859   
     }
 860   
 
 861   
     /**
 862   
      * Removes the key (and its corresponding value) from this table.
 863   
      * This method does nothing if the key is not in the table.
 864   
      *
 865   
      * @param   key   the key that needs to be removed.
 866   
      * @return  the value to which the key had been mapped in this table,
 867   
      *          or <code>null</code> if the key did not have a mapping.
 868   
      * @exception  NullPointerException  if the key is
 869   
      *               <code>null</code>.
 870   
      */
 871   
     /** OpenSymphony BEGIN */
 872  18
     public Object remove(Object key) {
 873  18
         return remove(key, true);
 874   
     }
 875   
 
 876   
     /**
 877   
      * Returns the total number of cache entries held in this map.
 878   
      *
 879   
      * @return the number of key-value mappings in this map.
 880   
      */
 881  419
     public synchronized int size() {
 882  419
         return count;
 883   
     }
 884   
 
 885   
     /**
 886   
      * Returns a collection view of the values contained in this map.
 887   
      * The collection is backed by the map, so changes to the map are reflected in
 888   
      * the collection, and vice-versa.  The collection supports element
 889   
      * removal, which removes the corresponding mapping from this map, via the
 890   
      * <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>,
 891   
      * <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt> operations.
 892   
      * It does not support the <tt>add</tt> or <tt>addAll</tt> operations.
 893   
      *
 894   
      * @return a collection view of the values contained in this map.
 895   
      */
 896  0
     public Collection values() {
 897  0
         Collection vs = values;
 898   
 
 899  0
         if (vs != null) {
 900  0
             return vs;
 901   
         } else {
 902  0
             return values = new AbstractCollection() {
 903  0
                         public Iterator iterator() {
 904  0
                             return new ValueIterator();
 905   
                         }
 906   
 
 907  0
                         public int size() {
 908  0
                             return AbstractConcurrentReadCache.this.size();
 909   
                         }
 910   
 
 911  0
                         public boolean contains(Object o) {
 912  0
                             return AbstractConcurrentReadCache.this.containsValue(o);
 913   
                         }
 914   
 
 915  0
                         public void clear() {
 916  0
                             AbstractConcurrentReadCache.this.clear();
 917   
                         }
 918   
                     };
 919   
         }
 920   
     }
 921   
 
 922   
     /**
 923   
      * Get ref to groups.
 924   
      * The reference and the cells it
 925   
      * accesses will be at least as fresh as from last
 926   
      * use of barrierLock
 927   
      **/
 928  14
     protected final Map getGroupsForReading() {
 929  14
         synchronized (barrierLock) {
 930  14
             return groups;
 931   
         }
 932   
     }
 933   
 
 934   
     /**
 935   
      * Get ref to table; the reference and the cells it
 936   
      * accesses will be at least as fresh as from last
 937   
      * use of barrierLock
 938   
      **/
 939  254
     protected final Entry[] getTableForReading() {
 940  254
         synchronized (barrierLock) {
 941  254
             return table;
 942   
         }
 943   
     }
 944   
 
 945   
     /**
 946   
      * Force a memory synchronization that will cause
 947   
      * all readers to see table. Call only when already
 948   
      * holding main synch lock.
 949   
      **/
 950  482
     protected final void recordModification(Object x) {
 951  482
         synchronized (barrierLock) {
 952  482
             lastWrite = x;
 953   
         }
 954   
     }
 955   
 
 956   
     /**
 957   
      * Helper method for entrySet remove.
 958   
      **/
 959  0
     protected synchronized boolean findAndRemoveEntry(Map.Entry entry) {
 960  0
         Object key = entry.getKey();
 961  0
         Object v = get(key);
 962   
 
 963  0
         if ((v != null) && v.equals(entry.getValue())) {
 964  0
             remove(key);
 965   
 
 966  0
             return true;
 967   
         } else {
 968  0
             return false;
 969   
         }
 970   
     }
 971   
 
 972   
     /**
 973   
      * Remove an object from the persistence.
 974   
      * @param key The key of the object to remove
 975   
      */
 976  36
     protected void persistRemove(Object key) {
 977  36
         if (log.isDebugEnabled()) {
 978  0
             log.debug("PersistRemove called (key=" + key + ")");
 979   
         }
 980   
 
 981  36
         if (persistenceListener != null) {
 982  0
             try {
 983  0
                 persistenceListener.remove((String) key);
 984   
             } catch (CachePersistenceException e) {
 985  0
                 log.error("[oscache] Exception removing cache entry with key '" + key + "' from persistence", e);
 986   
             }
 987   
         }
 988   
     }
 989   
 
 990   
     /**
 991   
      * Removes a cache group using the persistence listener.
 992   
      * @param groupName The name of the group to remove
 993   
      */
 994  0
     protected void persistRemoveGroup(String groupName) {
 995  0
         if (log.isDebugEnabled()) {
 996  0
             log.debug("persistRemoveGroup called (groupName=" + groupName + ")");
 997   
         }
 998   
 
 999  0
         if (persistenceListener != null) {
 1000  0
             try {
 1001  0
                 persistenceListener.removeGroup(groupName);
 1002   
             } catch (CachePersistenceException e) {
 1003  0
                 log.error("[oscache] Exception removing group " + groupName, e);
 1004   
             }
 1005   
         }
 1006   
     }
 1007   
 
 1008   
     /**
 1009   
      * Retrieve an object from the persistence listener.
 1010   
      * @param key The key of the object to retrieve
 1011   
      */
 1012  340
     protected Object persistRetrieve(Object key) {
 1013  340
         if (log.isDebugEnabled()) {
 1014  0
             log.debug("persistRetrieve called (key=" + key + ")");
 1015   
         }
 1016   
 
 1017  340
         Object entry = null;
 1018   
 
 1019  340
         if (persistenceListener != null) {
 1020  269
             try {
 1021  269
                 entry = persistenceListener.retrieve((String) key);
 1022   
             } catch (CachePersistenceException e) {
 1023   
                 /**
 1024   
                  * It is normal that we get an exception occasionally.
 1025   
                  * It happens when the item is invalidated (written or removed)
 1026   
                  * during read. The logic is constructed so that read is retried.
 1027   
                  */
 1028   
             }
 1029   
         }
 1030   
 
 1031  340
         return entry;
 1032   
     }
 1033   
 
 1034   
     /**
 1035   
      * Retrieves a cache group using the persistence listener.
 1036   
      * @param groupName The name of the group to retrieve
 1037   
      */
 1038  107
     protected Set persistRetrieveGroup(String groupName) {
 1039  107
         if (log.isDebugEnabled()) {
 1040  0
             log.debug("persistRetrieveGroup called (groupName=" + groupName + ")");
 1041   
         }
 1042   
 
 1043  107
         if (persistenceListener != null) {
 1044  77
             try {
 1045  77
                 return persistenceListener.retrieveGroup(groupName);
 1046   
             } catch (CachePersistenceException e) {
 1047  0
                 log.error("[oscache] Exception retrieving group " + groupName, e);
 1048   
             }
 1049   
         }
 1050   
 
 1051  30
         return null;
 1052   
     }
 1053   
 
 1054   
     /**
 1055   
      * Store an object in the cache using the persistence listener.
 1056   
      * @param key The object key
 1057   
      * @param obj The object to store
 1058   
      */
 1059  390
     protected void persistStore(Object key, Object obj) {
 1060  390
         if (log.isDebugEnabled()) {
 1061  0
             log.debug("persistStore called (key=" + key + ")");
 1062   
         }
 1063   
 
 1064  390
         if (persistenceListener != null) {
 1065  161
             try {
 1066  161
                 persistenceListener.store((String) key, obj);
 1067   
             } catch (CachePersistenceException e) {
 1068  0
                 log.error("[oscache] Exception persisting " + key, e);
 1069   
             }
 1070   
         }
 1071   
     }
 1072   
 
 1073   
     /**
 1074   
      * Creates or Updates a cache group using the persistence listener.
 1075   
      * @param groupName The name of the group to update
 1076   
      * @param group The entries for the group
 1077   
      */
 1078  97
     protected void persistStoreGroup(String groupName, Set group) {
 1079  97
         if (log.isDebugEnabled()) {
 1080  0
             log.debug("persistStoreGroup called (groupName=" + groupName + ")");
 1081   
         }
 1082   
 
 1083  97
         if (persistenceListener != null) {
 1084  68
             try {
 1085  68
                 if ((group == null) || group.isEmpty()) {
 1086  0
                     persistenceListener.removeGroup(groupName);
 1087   
                 } else {
 1088  68
                     persistenceListener.storeGroup(groupName, group);
 1089   
                 }
 1090   
             } catch (CachePersistenceException e) {
 1091  0
                 log.error("[oscache] Exception persisting group " + groupName, e);
 1092   
             }
 1093   
         }
 1094   
     }
 1095   
 
 1096   
     /**
 1097   
      * Removes the entire cache from persistent storage.
 1098   
      */
 1099  111
     protected void persistClear() {
 1100  111
         if (log.isDebugEnabled()) {
 1101  0
             log.debug("persistClear called");
 1102   
             ;
 1103   
         }
 1104   
 
 1105  111
         if (persistenceListener != null) {
 1106  6
             try {
 1107  6
                 persistenceListener.clear();
 1108   
             } catch (CachePersistenceException e) {
 1109  0
                 log.error("[oscache] Exception clearing persistent cache", e);
 1110   
             }
 1111   
         }
 1112   
     }
 1113   
 
 1114   
     /**
 1115   
      * Notify the underlying implementation that an item was put in the cache.
 1116   
      *
 1117   
      * @param key The cache key of the item that was put.
 1118   
      */
 1119   
     protected abstract void itemPut(Object key);
 1120   
 
 1121   
     /**
 1122   
      * Notify any underlying algorithm that an item has been retrieved from the cache.
 1123   
      *
 1124   
      * @param key The cache key of the item that was retrieved.
 1125   
      */
 1126   
     protected abstract void itemRetrieved(Object key);
 1127   
 
 1128   
     /**
 1129   
      * Notify the underlying implementation that an item was removed from the cache.
 1130   
      *
 1131   
      * @param key The cache key of the item that was removed.
 1132   
      */
 1133   
     protected abstract void itemRemoved(Object key);
 1134   
 
 1135   
     /**
 1136   
      * The cache has reached its cacpacity and an item needs to be removed.
 1137   
      * (typically according to an algorithm such as LRU or FIFO).
 1138   
      *
 1139   
      * @return The key of whichever item was removed.
 1140   
      */
 1141   
     protected abstract Object removeItem();
 1142   
 
 1143   
     /**
 1144   
      * Reconstitute the <tt>AbstractConcurrentReadCache</tt>.
 1145   
      * instance from a stream (i.e.,
 1146   
      * deserialize it).
 1147   
      */
 1148  0
     protected synchronized void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
 1149   
         // Read in the threshold, loadfactor, and any hidden stuff
 1150  0
         s.defaultReadObject();
 1151   
 
 1152   
         // Read in number of buckets and allocate the bucket array;
 1153  0
         int numBuckets = s.readInt();
 1154  0
         table = new Entry[numBuckets];
 1155   
 
 1156   
         // Read in size (number of Mappings)
 1157  0
         int size = s.readInt();
 1158   
 
 1159   
         // Read the keys and values, and put the mappings in the table
 1160  0
         for (int i = 0; i < size; i++) {
 1161  0
             Object key = s.readObject();
 1162  0
             Object value = s.readObject();
 1163  0
             put(key, value);
 1164   
         }
 1165   
     }
 1166   
 
 1167   
     /**
 1168   
      * Rehashes the contents of this map into a new table with a larger capacity.
 1169   
      * This method is called automatically when the
 1170   
      * number of keys in this map exceeds its capacity and load factor.
 1171   
      */
 1172  3
     protected void rehash() {
 1173  3
         Entry[] oldMap = table;
 1174  3
         int oldCapacity = oldMap.length;
 1175   
 
 1176  3
         if (oldCapacity >= MAXIMUM_CAPACITY) {
 1177  0
             return;
 1178   
         }
 1179   
 
 1180  3
         int newCapacity = oldCapacity << 1;
 1181  3
         Entry[] newMap = new Entry[newCapacity];
 1182  3
         threshold = (int) (newCapacity * loadFactor);
 1183   
 
 1184   
         /*
 1185   
           We need to guarantee that any existing reads of oldMap can
 1186   
           proceed. So we cannot yet null out each oldMap bin.
 1187   
 
 1188   
           Because we are using power-of-two expansion, the elements
 1189   
           from each bin must either stay at same index, or move
 1190   
           to oldCapacity+index. We also minimize new node creation by
 1191   
           catching cases where old nodes can be reused because their
 1192   
           .next fields won't change. (This is checked only for sequences
 1193   
           of one and two. It is not worth checking longer ones.)
 1194   
         */
 1195  3
         for (int i = 0; i < oldCapacity; ++i) {
 1196  96
             Entry l = null;
 1197  96
             Entry h = null;
 1198  96
             Entry e = oldMap[i];
 1199   
 
 1200  96
             while (e != null) {
 1201  67
                 int hash = e.hash;
 1202  67
                 Entry next = e.next;
 1203   
 
 1204  67
                 if ((hash & oldCapacity) == 0) {
 1205   
                     // stays at newMap[i]
 1206  23
                     if (l == null) {
 1207   
                         // try to reuse node
 1208  22
                         if ((next == null) || ((next.next == null) && ((next.hash & oldCapacity) == 0))) {
 1209  14
                             l = e;
 1210   
 
 1211  14
                             break;
 1212   
                         }
 1213   
                     }
 1214   
 
 1215  9
                     l = new Entry(hash, e.key, e.value, l);
 1216   
                 } else {
 1217   
                     // moves to newMap[oldCapacity+i]
 1218  44
                     if (h == null) {
 1219  41
                         if ((next == null) || ((next.next == null) && ((next.hash & oldCapacity) != 0))) {
 1220  36
                             h = e;
 1221   
 
 1222  36
                             break;
 1223   
                         }
 1224   
                     }
 1225   
 
 1226  8
                     h = new Entry(hash, e.key, e.value, h);
 1227   
                 }
 1228   
 
 1229  17
                 e = next;
 1230   
             }
 1231   
 
 1232  96
             newMap[i] = l;
 1233  96
             newMap[oldCapacity + i] = h;
 1234   
         }
 1235   
 
 1236  3
         table = newMap;
 1237  3
         recordModification(newMap);
 1238   
     }
 1239   
 
 1240   
     /**
 1241   
      * Continuation of put(), called only when synch lock is
 1242   
      * held and interference has been detected.
 1243   
      **/
 1244   
     /** OpenSymphony BEGIN */
 1245   
 
 1246   
     /* Previous code
 1247   
     protected Object sput(Object key, Object value, int hash) {*/
 1248  5
     protected Object sput(Object key, Object value, int hash, boolean persist) {
 1249   
         /** OpenSymphony END */
 1250  5
         Entry[] tab = table;
 1251  5
         int index = hash & (tab.length - 1);
 1252  5
         Entry first = tab[index];
 1253  5
         Entry e = first;
 1254   
 
 1255  5
         for (;;) {
 1256  10
             if (e == null) {
 1257   
                 /** OpenSymphony BEGIN */
 1258   
 
 1259   
                 // Previous code
 1260   
                 //          Entry newEntry = new Entry(hash, key, value, first);
 1261  5
                 Entry newEntry;
 1262   
 
 1263  5
                 if (memoryCaching) {
 1264  3
                     newEntry = new Entry(hash, key, value, first);
 1265   
                 } else {
 1266  2
                     newEntry = new Entry(hash, key, NULL, first);
 1267   
                 }
 1268   
 
 1269  5
                 itemPut(key);
 1270   
 
 1271   
                 // Persist if required
 1272  5
                 if (persist) {
 1273  5
                     persistStore(key, value);
 1274   
                 }
 1275   
 
 1276   
                 // If we have a CacheEntry, update the group lookups
 1277  5
                 if (value instanceof CacheEntry) {
 1278  5
                     updateGroups(null, (CacheEntry) value, persist);
 1279   
                 }
 1280   
 
 1281   
                 /**        OpenSymphony END */
 1282  5
                 tab[index] = newEntry;
 1283   
 
 1284  5
                 if (++count >= threshold) {
 1285  1
                     rehash();
 1286   
                 } else {
 1287  4
                     recordModification(newEntry);
 1288   
                 }
 1289   
 
 1290  5
                 return null;
 1291  5
             } else if ((key == e.key) || ((e.hash == hash) && key.equals(e.key))) {
 1292  0
                 Object oldValue = e.value;
 1293   
 
 1294   
                 /** OpenSymphony BEGIN */
 1295   
 
 1296   
                 /* Previous code
 1297   
                 e.value = value; */
 1298  0
                 if (memoryCaching) {
 1299  0
                     e.value = value;
 1300   
                 }
 1301   
 
 1302  0
                 if (persist) {
 1303  0
                     persistStore(key, value);
 1304   
                 }
 1305   
 
 1306  0
                 updateGroups(oldValue, value, persist);
 1307   
 
 1308  0
                 itemPut(key);
 1309   
 
 1310   
                 /** OpenSymphony END */
 1311  0
                 return oldValue;
 1312   
             } else {
 1313  5
                 e = e.next;
 1314   
             }
 1315   
         }
 1316   
     }
 1317   
 
 1318   
     /**
 1319   
      * Continuation of remove(), called only when synch lock is
 1320   
      * held and interference has been detected.
 1321   
      **/
 1322   
     /** OpenSymphony BEGIN */
 1323   
 
 1324   
     /* Previous code
 1325   
     protected Object sremove(Object key, int hash) { */
 1326  0
     protected Object sremove(Object key, int hash, boolean invokeAlgorithm) {
 1327   
         /** OpenSymphony END */
 1328  0
         Entry[] tab = table;
 1329  0
         int index = hash & (tab.length - 1);
 1330  0
         Entry first = tab[index];
 1331  0
         Entry e = first;
 1332   
 
 1333  0
         for (;;) {
 1334  0
             if (e == null) {
 1335  0
                 return null;
 1336  0
             } else if ((key == e.key) || ((e.hash == hash) && key.equals(e.key))) {
 1337  0
                 Object oldValue = e.value;
 1338  0
                 e.value = null;
 1339  0
                 count--;
 1340   
 
 1341   
                 /** OpenSymphony BEGIN */
 1342  0
                 if (!unlimitedDiskCache) {
 1343  0
                     persistRemove(e.key);
 1344   
                 }
 1345   
 
 1346  0
                 if (invokeAlgorithm) {
 1347  0
                     itemRemoved(key);
 1348   
                 }
 1349   
 
 1350   
                 /** OpenSymphony END */
 1351  0
                 Entry head = e.next;
 1352   
 
 1353  0
                 for (Entry p = first; p != e; p = p.next) {
 1354  0
                     head = new Entry(p.hash, p.key, p.value, head);
 1355   
                 }
 1356   
 
 1357  0
                 tab[index] = head;
 1358  0
                 recordModification(head);
 1359   
 
 1360  0
                 return oldValue;
 1361   
             } else {
 1362  0
                 e = e.next;
 1363   
             }
 1364   
         }
 1365   
     }
 1366   
 
 1367   
     /**
 1368   
      * Save the state of the <tt>AbstractConcurrentReadCache</tt> instance to a stream.
 1369   
      * (i.e., serialize it).
 1370   
      *
 1371   
      * @serialData The <i>capacity</i> of the
 1372   
      * AbstractConcurrentReadCache (the length of the
 1373   
      * bucket array) is emitted (int), followed  by the
 1374   
      * <i>size</i> of the AbstractConcurrentReadCache (the number of key-value
 1375   
      * mappings), followed by the key (Object) and value (Object)
 1376   
      * for each key-value mapping represented by the AbstractConcurrentReadCache
 1377   
      * The key-value mappings are emitted in no particular order.
 1378   
      */
 1379  0
     protected synchronized void writeObject(java.io.ObjectOutputStream s) throws IOException {
 1380   
         // Write out the threshold, loadfactor, and any hidden stuff
 1381  0
         s.defaultWriteObject();
 1382   
 
 1383   
         // Write out number of buckets
 1384  0
         s.writeInt(table.length);
 1385   
 
 1386   
         // Write out size (number of Mappings)
 1387  0
         s.writeInt(count);
 1388   
 
 1389   
         // Write out keys and values (alternating)
 1390  0
         for (int index = table.length - 1; index >= 0; index--) {
 1391  0
             Entry entry = table[index];
 1392   
 
 1393  0
             while (entry != null) {
 1394  0
                 s.writeObject(entry.key);
 1395  0
                 s.writeObject(entry.value);
 1396  0
                 entry = entry.next;
 1397   
             }
 1398   
         }
 1399   
     }
 1400   
 
 1401   
     /**
 1402   
      * Return hash code for Object x.
 1403   
      * Since we are using power-of-two
 1404   
      * tables, it is worth the effort to improve hashcode via
 1405   
      * the same multiplicative scheme as used in IdentityHashMap.
 1406   
      */
 1407  1097
     private static int hash(Object x) {
 1408  1097
         int h = x.hashCode();
 1409   
 
 1410   
         // Multiply by 127 (quickly, via shifts), and mix in some high
 1411   
         // bits to help guard against bunching of codes that are
 1412   
         // consecutive or equally spaced.
 1413  1079
         return ((h << 7) - h + (h >>> 9) + (h >>> 17));
 1414   
     }
 1415   
 
 1416   
     /**
 1417   
      * Add this cache key to the groups specified groups.
 1418   
      * We have to treat the
 1419   
      * memory and disk group mappings seperately so they remain valid for their
 1420   
      * corresponding memory/disk caches. (eg if mem is limited to 100 entries
 1421   
      * and disk is unlimited, the group mappings will be different).
 1422   
      *
 1423   
      * @param key The cache key that we are ading to the groups.
 1424   
      * @param newGroups the set of groups we want to add this cache entry to.
 1425   
      * @param persist A flag to indicate whether the keys should be added to
 1426   
      * the persistent cache layer.
 1427   
      */
 1428  104
     private void addGroupMappings(String key, Set newGroups, boolean persist) {
 1429   
         // Add this CacheEntry to the groups that it is now a member of
 1430  104
         for (Iterator it = newGroups.iterator(); it.hasNext();) {
 1431  127
             String groupName = (String) it.next();
 1432   
 
 1433   
             // Update the in-memory groups
 1434  127
             if (memoryCaching) {
 1435  61
                 if (groups == null) {
 1436  6
                     groups = new HashMap();
 1437   
                 }
 1438   
 
 1439  61
                 Set memoryGroup = (Set) groups.get(groupName);
 1440   
 
 1441  61
                 if (memoryGroup == null) {
 1442  18
                     memoryGroup = new HashSet();
 1443  18
                     groups.put(groupName, memoryGroup);
 1444   
                 }
 1445   
 
 1446  61
                 memoryGroup.add(key);
 1447   
             }
 1448   
 
 1449   
             // Update the persistent group maps
 1450  127
             if (persist) {
 1451  97
                 Set persistentGroup = persistRetrieveGroup(groupName);
 1452   
 
 1453  97
                 if (persistentGroup == null) {
 1454  35
                     persistentGroup = new HashSet();
 1455   
                 }
 1456   
 
 1457  97
                 persistentGroup.add(key);
 1458  97
                 persistStoreGroup(groupName, persistentGroup);
 1459   
             }
 1460   
         }
 1461   
     }
 1462   
 
 1463   
     /** OpenSymphony END (pretty long!) */
 1464   
     /**
 1465   
      * Returns the appropriate capacity (power of two) for the specified
 1466   
      * initial capacity argument.
 1467   
      */
 1468  81
     private int p2capacity(int initialCapacity) {
 1469  81
         int cap = initialCapacity;
 1470   
 
 1471   
         // Compute the appropriate capacity
 1472  81
         int result;
 1473   
 
 1474  81
         if ((cap > MAXIMUM_CAPACITY) || (cap < 0)) {
 1475  0
             result = MAXIMUM_CAPACITY;
 1476   
         } else {
 1477  81
             result = MINIMUM_CAPACITY;
 1478   
 
 1479  81
             while (result < cap) {
 1480  243
                 result <<= 1;
 1481   
             }
 1482   
         }
 1483   
 
 1484  81
         return result;
 1485   
     }
 1486   
 
 1487   
     /* Previous code
 1488   
     public Object put(Object key, Object value)*/
 1489  435
     private Object put(Object key, Object value, boolean persist) {
 1490   
         /** OpenSymphony END */
 1491  435
         if (value == null) {
 1492  18
             throw new NullPointerException();
 1493   
         }
 1494   
 
 1495  417
         int hash = hash(key);
 1496  417
         Entry[] tab = table;
 1497  417
         int index = hash & (tab.length - 1);
 1498  417
         Entry first = tab[index];
 1499  417
         Entry e = first;
 1500   
 
 1501  417
         for (;;) {
 1502  455
             if (e == null) {
 1503  335
                 synchronized (this) {
 1504  335
                     tab = table;
 1505   
 
 1506   
                     /** OpenSymphony BEGIN */
 1507   
 
 1508   
                     // Previous code
 1509   
 
 1510   
                     /*                                        if (first == tab[index]) {
 1511   
                                                                     //  Add to front of list
 1512   
                                                                     Entry newEntry = new Entry(hash, key, value, first);
 1513   
                                                                     tab[index] = newEntry;
 1514   
                                                                     if (++count >= threshold) rehash();
 1515   
                                                                     else recordModification(newEntry);
 1516   
                                                                     return null; */
 1517   
 
 1518   
                     // Remove an item if the cache is full
 1519  335
                     if (size() >= maxEntries) {
 1520  6
                         remove(removeItem(), false);
 1521   
                     }
 1522   
 
 1523  335
                     if (first == tab[index]) {
 1524   
                         //  Add to front of list
 1525  330
                         Entry newEntry = null;
 1526   
 
 1527  330
                         if (memoryCaching) {
 1528  266
                             newEntry = new Entry(hash, key, value, first);
 1529   
                         } else {
 1530  64
                             newEntry = new Entry(hash, key, NULL, first);
 1531   
                         }
 1532   
 
 1533  330
                         tab[index] = newEntry;
 1534  330
                         itemPut(key);
 1535   
 
 1536   
                         // Persist if required
 1537  330
                         if (persist) {
 1538  303
                             persistStore(key, value);
 1539   
                         }
 1540   
 
 1541   
                         // If we have a CacheEntry, update the group lookups
 1542  330
                         if (value instanceof CacheEntry) {
 1543  186
                             updateGroups(null, (CacheEntry) value, persist);
 1544   
                         }
 1545   
 
 1546  330
                         if (++count >= threshold) {
 1547  2
                             rehash();
 1548   
                         } else {
 1549  328
                             recordModification(newEntry);
 1550   
                         }
 1551   
 
 1552  330
                         return newEntry;
 1553   
 
 1554   
                         /** OpenSymphony END  */
 1555   
                     } else {
 1556   
                         // wrong list -- retry
 1557   
 
 1558   
                         /** OpenSymphony BEGIN */
 1559   
 
 1560   
                         /* Previous code
 1561   
                         return sput(key, value, hash);*/
 1562  5
                         return sput(key, value, hash, persist);
 1563   
 
 1564   
                         /** OpenSymphony END */
 1565   
                     }
 1566   
                 }
 1567  120
             } else if ((key == e.key) || ((e.hash == hash) && key.equals(e.key))) {
 1568   
                 // synch to avoid race with remove and to
 1569   
                 // ensure proper serialization of multiple replaces
 1570  82
                 synchronized (this) {
 1571  82
                     tab = table;
 1572   
 
 1573  82
                     Object oldValue = e.value;
 1574   
 
 1575  82
                     if ((first == tab[index]) && (oldValue != null)) {
 1576   
                         /** OpenSymphony BEGIN */
 1577   
 
 1578   
                         /* Previous code
 1579   
                         e.value = value;
 1580   
                         return oldValue; */
 1581  82
                         if (memoryCaching) {
 1582  51
                             e.value = value;
 1583   
                         }
 1584   
 
 1585  82
                         if (persist) {
 1586  82
                             persistStore(key, value);
 1587   
                         }
 1588   
 
 1589  82
                         updateGroups(oldValue, value, persist);
 1590  82
                         itemPut(key);
 1591   
 
 1592  82
                         return oldValue;
 1593   
 
 1594   
                         /**        OpenSymphony END */
 1595   
                     } else {
 1596   
                         // retry if wrong list or lost race against concurrent remove
 1597   
 
 1598   
                         /** OpenSymphony BEGIN */
 1599   
 
 1600   
                         /* Previous code
 1601   
                         return sput(key, value, hash);*/
 1602  0
                         return sput(key, value, hash, persist);
 1603   
 
 1604   
                         /** OpenSymphony END */
 1605   
                     }
 1606   
                 }
 1607   
             } else {
 1608  38
                 e = e.next;
 1609   
             }
 1610   
         }
 1611   
     }
 1612   
 
 1613  36
     private Object remove(Object key, boolean invokeAlgorithm)
 1614   
     /* Previous code
 1615   
     public Object remove(Object key) */
 1616   
 
 1617   
     /** OpenSymphony END */  {
 1618   
         /*
 1619   
           Strategy:
 1620   
 
 1621   
           Find the entry, then
 1622   
             1. Set value field to null, to force get() to retry
 1623   
             2. Rebuild the list without this entry.
 1624   
                All entries following removed node can stay in list, but
 1625   
                all preceeding ones need to be cloned.  Traversals rely
 1626   
                on this strategy to ensure that elements will not be
 1627   
               repeated during iteration.
 1628   
         */
 1629   
 
 1630   
         /** OpenSymphony BEGIN */
 1631  36
         if (key == null) {
 1632  0
             return null;
 1633   
         }
 1634   
 
 1635   
         /** OpenSymphony END */
 1636  36
         int hash = hash(key);
 1637  36
         Entry[] tab = table;
 1638  36
         int index = hash & (tab.length - 1);
 1639  36
         Entry first = tab[index];
 1640  36
         Entry e = first;
 1641   
 
 1642  36
         for (;;) {
 1643  36
             if (e == null) {
 1644  0
                 tab = getTableForReading();
 1645   
 
 1646  0
                 if (first == tab[index]) {
 1647  0
                     return null;
 1648   
                 } else {
 1649   
                     // Wrong list -- must restart traversal at new first
 1650   
 
 1651   
                     /** OpenSymphony BEGIN */
 1652   
 
 1653   
                     /* Previous Code
 1654   
                     return sremove(key, hash); */
 1655  0
                     return sremove(key, hash, invokeAlgorithm);
 1656   
 
 1657   
                     /** OpenSymphony END */
 1658   
                 }
 1659  36
             } else if ((key == e.key) || ((e.hash == hash) && key.equals(e.key))) {
 1660  36
                 synchronized (this) {
 1661  36
                     tab = table;
 1662   
 
 1663  36
                     Object oldValue = e.value;
 1664   
 
 1665   
                     // re-find under synch if wrong list
 1666  36
                     if ((first != tab[index]) || (oldValue == null)) {
 1667   
                         /** OpenSymphony BEGIN */
 1668   
 
 1669   
                         /* Previous Code
 1670   
                         return sremove(key, hash); */
 1671  0
                         return sremove(key, hash, invokeAlgorithm);
 1672   
                     }
 1673   
 
 1674   
                     /** OpenSymphony END */
 1675  36
                     e.value = null;
 1676  36
                     count--;
 1677   
 
 1678   
                     /** OpenSymphony BEGIN */
 1679  36
                     if (!unlimitedDiskCache) {
 1680  36
                         persistRemove(e.key);
 1681   
                     }
 1682   
 
 1683  36
                     if (invokeAlgorithm) {
 1684  18
                         itemRemoved(key);
 1685   
                     }
 1686   
 
 1687   
                     /** OpenSymphony END */
 1688  36
                     Entry head = e.next;
 1689   
 
 1690  36
                     for (Entry p = first; p != e; p = p.next) {
 1691  0
                         head = new Entry(p.hash, p.key, p.value, head);
 1692   
                     }
 1693   
 
 1694  36
                     tab[index] = head;
 1695  36
                     recordModification(head);
 1696   
 
 1697  36
                     return oldValue;
 1698   
                 }
 1699   
             } else {
 1700  0
                 e = e.next;
 1701   
             }
 1702   
         }
 1703   
     }
 1704   
 
 1705   
     /**
 1706   
      * Remove this CacheEntry from the groups it no longer belongs to.
 1707   
      *  We have to treat the memory and disk group mappings seperately so they remain
 1708   
      * valid for their corresponding memory/disk caches. (eg if mem is limited
 1709   
      * to 100 entries and disk is unlimited, the group mappings will be
 1710   
      * different).
 1711   
      *
 1712   
      * @param key The cache key that we are removing from the groups.
 1713   
      * @param oldGroups the set of groups we want to remove the cache entry
 1714   
      * from.
 1715   
      * @param persist A flag to indicate whether the keys should be removed
 1716   
      * from the persistent cache layer.
 1717   
      */
 1718  27
     private void removeGroupMappings(String key, Set oldGroups, boolean persist) {
 1719  27
         for (Iterator it = oldGroups.iterator(); it.hasNext();) {
 1720  0
             String groupName = (String) it.next();
 1721   
 
 1722   
             // Update the in-memory groups
 1723  0
             if (memoryCaching && (this.groups != null)) {
 1724  0
                 Set memoryGroup = (Set) groups.get(groupName);
 1725   
 
 1726  0
                 if (memoryGroup != null) {
 1727  0
                     memoryGroup.remove(key);
 1728   
 
 1729  0
                     if (memoryGroup.isEmpty()) {
 1730  0
                         groups.remove(groupName);
 1731   
                     }
 1732   
                 }
 1733   
             }
 1734   
 
 1735   
             // Update the persistent group maps
 1736  0
             if (persist) {
 1737  0
                 Set persistentGroup = persistRetrieveGroup(groupName);
 1738   
 
 1739  0
                 if (persistentGroup != null) {
 1740  0
                     persistentGroup.remove(key);
 1741   
 
 1742  0
                     if (persistentGroup.isEmpty()) {
 1743  0
                         persistRemoveGroup(groupName);
 1744   
                     } else {
 1745  0
                         persistStoreGroup(groupName, persistentGroup);
 1746   
                     }
 1747   
                 }
 1748   
             }
 1749   
         }
 1750   
     }
 1751   
 
 1752   
     /**
 1753   
      * Updates the groups to reflect the differences between the old and new
 1754   
      * cache entries. Either of the old or new values can be <code>null</code>
 1755   
      * or contain a <code>null</code> group list, in which case the entry's
 1756   
      * groups will all be added or removed respectively.
 1757   
      *
 1758   
      * @param oldValue The old CacheEntry that is being replaced.
 1759   
      * @param newValue The new CacheEntry that is being inserted.
 1760   
      */
 1761  82
     private void updateGroups(Object oldValue, Object newValue, boolean persist) {
 1762   
         // If we have/had a CacheEntry, update the group lookups
 1763  82
         boolean oldIsCE = oldValue instanceof CacheEntry;
 1764  82
         boolean newIsCE = newValue instanceof CacheEntry;
 1765   
 
 1766  82
         if (newIsCE && oldIsCE) {
 1767  51
             updateGroups((CacheEntry) oldValue, (CacheEntry) newValue, persist);
 1768  31
         } else if (newIsCE) {
 1769  31
             updateGroups(null, (CacheEntry) newValue, persist);
 1770  0
         } else if (oldIsCE) {
 1771  0
             updateGroups((CacheEntry) oldValue, null, persist);
 1772   
         }
 1773   
     }
 1774   
 
 1775   
     /**
 1776   
      * Updates the groups to reflect the differences between the old and new cache entries.
 1777   
      * Either of the old or new values can be <code>null</code>
 1778   
      * or contain a <code>null</code> group list, in which case the entry's
 1779   
      * groups will all be added or removed respectively.
 1780   
      *
 1781   
      * @param oldValue The old CacheEntry that is being replaced.
 1782   
      * @param newValue The new CacheEntry that is being inserted.
 1783   
      */
 1784  273
     private void updateGroups(CacheEntry oldValue, CacheEntry newValue, boolean persist) {
 1785  273
         Set oldGroups = null;
 1786  273
         Set newGroups = null;
 1787   
 
 1788  273
         if (oldValue != null) {
 1789  51
             oldGroups = oldValue.getGroups();
 1790   
         }
 1791   
 
 1792  273
         if (newValue != null) {
 1793  273
             newGroups = newValue.getGroups();
 1794   
         }
 1795   
 
 1796   
         // Get the names of the groups to remove
 1797  273
         if (oldGroups != null) {
 1798  27
             Set removeFromGroups = new HashSet();
 1799   
 
 1800  27
             for (Iterator it = oldGroups.iterator(); it.hasNext();) {
 1801  49
                 String groupName = (String) it.next();
 1802   
 
 1803  49
                 if ((newGroups == null) || !newGroups.contains(groupName)) {
 1804   
                     // We need to remove this group
 1805  0
                     removeFromGroups.add(groupName);
 1806   
                 }
 1807   
             }
 1808   
 
 1809  27
             removeGroupMappings(oldValue.getKey(), removeFromGroups, persist);
 1810   
         }
 1811   
 
 1812   
         // Get the names of the groups to add
 1813  273
         if (newGroups != null) {
 1814  104
             Set addToGroups = new HashSet();
 1815   
 
 1816  104
             for (Iterator it = newGroups.iterator(); it.hasNext();) {
 1817  176
                 String groupName = (String) it.next();
 1818   
 
 1819  176
                 if ((oldGroups == null) || !oldGroups.contains(groupName)) {
 1820   
                     // We need to add this group
 1821  127
                     addToGroups.add(groupName);
 1822   
                 }
 1823   
             }
 1824   
 
 1825  104
             addGroupMappings(newValue.getKey(), addToGroups, persist);
 1826   
         }
 1827   
     }
 1828   
 
 1829   
     /**
 1830   
      * AbstractConcurrentReadCache collision list entry.
 1831   
      */
 1832   
     protected static class Entry implements Map.Entry {
 1833   
         protected final Entry next;
 1834   
         protected final Object key;
 1835   
 
 1836   
         /*
 1837   
            The use of volatile for value field ensures that
 1838   
            we can detect status changes without synchronization.
 1839   
            The other fields are never changed, and are
 1840   
            marked as final.
 1841   
         */
 1842   
         protected final int hash;
 1843   
         protected volatile Object value;
 1844   
 
 1845  352
         Entry(int hash, Object key, Object value, Entry next) {
 1846  352
             this.hash = hash;
 1847  352
             this.key = key;
 1848  352
             this.next = next;
 1849  352
             this.value = value;
 1850   
         }
 1851   
 
 1852   
         // Map.Entry Ops
 1853  0
         public Object getKey() {
 1854  0
             return key;
 1855   
         }
 1856   
 
 1857   
         /**
 1858   
          * Set the value of this entry.
 1859   
          * Note: In an entrySet or
 1860   
          * entrySet.iterator), unless the set or iterator is used under
 1861   
          * synchronization of the table as a whole (or you can otherwise
 1862   
          * guarantee lack of concurrent modification), <tt>setValue</tt>
 1863   
          * is not strictly guaranteed to actually replace the value field
 1864   
          * obtained via the <tt>get</tt> operation of the underlying hash
 1865   
          * table in multithreaded applications.  If iterator-wide
 1866   
          * synchronization is not used, and any other concurrent
 1867   
          * <tt>put</tt> or <tt>remove</tt> operations occur, sometimes
 1868   
          * even to <em>other</em> entries, then this change is not
 1869   
          * guaranteed to be reflected in the hash table. (It might, or it
 1870   
          * might not. There are no assurances either way.)
 1871   
          *
 1872   
          * @param      value   the new value.
 1873   
          * @return     the previous value, or null if entry has been detectably
 1874   
          * removed.
 1875   
          * @exception  NullPointerException  if the value is <code>null</code>.
 1876   
          *
 1877   
          **/
 1878  0
         public Object setValue(Object value) {
 1879  0
             if (value == null) {
 1880  0
                 throw new NullPointerException();
 1881   
             }
 1882   
 
 1883  0
             Object oldValue = this.value;
 1884  0
             this.value = value;
 1885   
 
 1886  0
             return oldValue;
 1887   
         }
 1888   
 
 1889   
         /**
 1890   
          * Get the value.
 1891   
          * Note: In an entrySet or entrySet.iterator,
 1892   
          * unless the set or iterator is used under synchronization of the
 1893   
          * table as a whole (or you can otherwise guarantee lack of
 1894   
          * concurrent modification), <tt>getValue</tt> <em>might</em>
 1895   
          * return null, reflecting the fact that the entry has been
 1896   
          * concurrently removed. However, there are no assurances that
 1897   
          * concurrent removals will be reflected using this method.
 1898   
          *
 1899   
          * @return     the current value, or null if the entry has been
 1900   
          * detectably removed.
 1901   
          **/
 1902  0
         public Object getValue() {
 1903  0
             return value;
 1904   
         }
 1905   
 
 1906  0
         public boolean equals(Object o) {
 1907  0
             if (!(o instanceof Map.Entry)) {
 1908  0
                 return false;
 1909   
             }
 1910   
 
 1911  0
             Map.Entry e = (Map.Entry) o;
 1912   
 
 1913  0
             if (!key.equals(e.getKey())) {
 1914  0
                 return false;
 1915   
             }
 1916   
 
 1917  0
             Object v = value;
 1918   
 
 1919  0
             return (v == null) ? (e.getValue() == null) : v.equals(e.getValue());
 1920   
         }
 1921   
 
 1922  0
         public int hashCode() {
 1923  0
             Object v = value;
 1924   
 
 1925  0
             return hash ^ ((v == null) ? 0 : v.hashCode());
 1926   
         }
 1927   
 
 1928  0
         public String toString() {
 1929  0
             return key + "=" + value;
 1930   
         }
 1931   
 
 1932  0
         protected Object clone() {
 1933  0
             return new Entry(hash, key, value, ((next == null) ? null : (Entry) next.clone()));
 1934   
         }
 1935   
     }
 1936   
 
 1937   
     protected class HashIterator implements Iterator, Enumeration {
 1938   
         protected final Entry[] tab; // snapshot of table
 1939   
         protected Entry entry = null; // current node of slot
 1940   
         protected Entry lastReturned = null; // last node returned by next
 1941   
         protected Object currentKey; // key for current node
 1942   
         protected Object currentValue; // value for current node
 1943   
         protected int index; // current slot
 1944   
 
 1945  36
         protected HashIterator() {
 1946  36
             tab = AbstractConcurrentReadCache.this.getTableForReading();
 1947  36
             index = tab.length - 1;
 1948   
         }
 1949   
 
 1950  0
         public boolean hasMoreElements() {
 1951  0
             return hasNext();
 1952   
         }
 1953   
 
 1954  90
         public boolean hasNext() {
 1955   
             /*
 1956   
               currentkey and currentValue are set here to ensure that next()
 1957   
               returns normally if hasNext() returns true. This avoids
 1958   
               surprises especially when final element is removed during
 1959   
               traversal -- instead, we just ignore the removal during
 1960   
               current traversal.
 1961   
             */
 1962  90
             for (;;) {
 1963  144
                 if (entry != null) {
 1964  54
                     Object v = entry.value;
 1965   
 
 1966  54
                     if (v != null) {
 1967  54
                         currentKey = entry.key;
 1968  54
                         currentValue = v;
 1969   
 
 1970  54
                         return true;
 1971   
                     } else {
 1972  0
                         entry = entry.next;
 1973   
                     }
 1974   
                 }
 1975   
 
 1976  90
                 while ((entry == null) && (index >= 0)) {
 1977  1152
                     entry = tab[index--];
 1978   
                 }
 1979   
 
 1980  90
                 if (entry == null) {
 1981  36
                     currentKey = currentValue = null;
 1982   
 
 1983  36
                     return false;
 1984   
                 }
 1985   
             }
 1986   
         }
 1987   
 
 1988  54
         public Object next() {
 1989  54
             if ((currentKey == null) && !hasNext()) {
 1990  0
                 throw new NoSuchElementException();
 1991   
             }
 1992   
 
 1993  54
             Object result = returnValueOfNext();
 1994  54
             lastReturned = entry;
 1995  54
             currentKey = currentValue = null;
 1996  54
             entry = entry.next;
 1997   
 
 1998  54
             return result;
 1999   
         }
 2000   
 
 2001  0
         public Object nextElement() {
 2002  0
             return next();
 2003   
         }
 2004   
 
 2005  0
         public void remove() {
 2006  0
             if (lastReturned == null) {
 2007  0
                 throw new IllegalStateException();
 2008   
             }
 2009   
 
 2010  0
             AbstractConcurrentReadCache.this.remove(lastReturned.key);
 2011   
         }
 2012   
 
 2013  0
         protected Object returnValueOfNext() {
 2014  0
             return entry;
 2015   
         }
 2016   
     }
 2017   
 
 2018   
     protected class KeyIterator extends HashIterator {
 2019  54
         protected Object returnValueOfNext() {
 2020  54
             return currentKey;
 2021   
         }
 2022   
     }
 2023   
 
 2024   
     protected class ValueIterator extends HashIterator {
 2025  0
         protected Object returnValueOfNext() {
 2026  0
             return currentValue;
 2027   
         }
 2028   
     }
 2029   
 }
 2030