KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > opensymphony > oscache > base > algorithm > AbstractConcurrentReadCache


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 JavaDoc;
47 import java.io.Serializable JavaDoc;
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 JavaDoc, Serializable JavaDoc {
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 JavaDoc 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 JavaDoc barrierLock = new Boolean JavaDoc(true);
216
217     /**
218      * field written to only to guarantee lock ordering.
219      **/

220     protected transient Object JavaDoc 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 transient 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     public AbstractConcurrentReadCache(int initialCapacity, float loadFactor) {
306         if (loadFactor <= 0) {
307             throw new IllegalArgumentException JavaDoc("Illegal Load factor: " + loadFactor);
308         }
309
310         this.loadFactor = loadFactor;
311
312         int cap = p2capacity(initialCapacity);
313         table = new Entry[cap];
314         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     public AbstractConcurrentReadCache(int initialCapacity) {
327         this(initialCapacity, DEFAULT_LOAD_FACTOR);
328     }
329
330     /**
331      * Constructs a new, empty map with a default initial capacity and load factor.
332      */

333     public AbstractConcurrentReadCache() {
334         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     public AbstractConcurrentReadCache(Map t) {
343         this(Math.max(2 * t.size(), 11), DEFAULT_LOAD_FACTOR);
344         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     public synchronized boolean isEmpty() {
353         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     public Set getGroup(String JavaDoc groupName) {
365         if (log.isDebugEnabled()) {
366             log.debug("getGroup called (group=" + groupName + ")");
367         }
368
369         Set groupEntries = null;
370
371         if (memoryCaching && (groups != null)) {
372             groupEntries = (Set) getGroupForReading(groupName);
373         }
374
375         if (groupEntries == null) {
376             // Not in the map, try the persistence layer
377
groupEntries = persistRetrieveGroup(groupName);
378         }
379
380         return groupEntries;
381     }
382
383     /**
384      * Set the cache capacity
385      */

386     public void setMaxEntries(int newLimit) {
387         if (newLimit > 0) {
388             maxEntries = newLimit;
389
390             synchronized (this) { // because remove() isn't synchronized
391

392                 while (size() > maxEntries) {
393                     remove(removeItem(), false, false);
394                 }
395             }
396         } else {
397             // Capacity must be at least 1
398
throw new IllegalArgumentException JavaDoc("Cache maximum number of entries must be at least 1");
399         }
400     }
401
402     /**
403      * Retrieve the cache capacity (number of entries).
404      */

405     public int getMaxEntries() {
406         return maxEntries;
407     }
408
409     /**
410      * Sets the memory caching flag.
411      */

412     public void setMemoryCaching(boolean memoryCaching) {
413         this.memoryCaching = memoryCaching;
414     }
415
416     /**
417      * Check if memory caching is used.
418      */

419     public boolean isMemoryCaching() {
420         return memoryCaching;
421     }
422
423     /**
424      * Set the persistence listener to use.
425      */

426     public void setPersistenceListener(PersistenceListener listener) {
427         this.persistenceListener = listener;
428     }
429
430     /**
431      * Get the persistence listener.
432      */

433     public PersistenceListener getPersistenceListener() {
434         return persistenceListener;
435     }
436
437     /**
438      * Sets the unlimited disk caching flag.
439      */

440     public void setUnlimitedDiskCache(boolean unlimitedDiskCache) {
441         this.unlimitedDiskCache = unlimitedDiskCache;
442     }
443
444     /**
445      * Check if we use unlimited disk cache.
446      */

447     public boolean isUnlimitedDiskCache() {
448         return unlimitedDiskCache;
449     }
450
451     /**
452      * Check if we use overflowPersistence
453      *
454      * @return Returns the overflowPersistence.
455      */

456     public boolean isOverflowPersistence() {
457         return this.overflowPersistence;
458     }
459
460     /**
461      * Sets the overflowPersistence flag
462      *
463      * @param overflowPersistence The overflowPersistence to set.
464      */

465     public void setOverflowPersistence(boolean overflowPersistence) {
466         this.overflowPersistence = overflowPersistence;
467     }
468
469     /**
470      * Return the number of slots in this table.
471      **/

472     public synchronized int capacity() {
473         return table.length;
474     }
475
476     /**
477      * Removes all mappings from this map.
478      */

479     public synchronized void clear() {
480         Entry[] tab = table;
481
482         for (int i = 0; i < tab.length; ++i) {
483             // must invalidate all to force concurrent get's to wait and then retry
484
for (Entry e = tab[i]; e != null; e = e.next) {
485                 e.value = null;
486
487                 /** OpenSymphony BEGIN */
488                 itemRemoved(e.key);
489
490                 /** OpenSymphony END */
491             }
492
493             tab[i] = null;
494         }
495
496         // Clean out the entire disk cache
497
persistClear();
498
499         count = 0;
500         recordModification(tab);
501     }
502
503     /**
504      * Returns a shallow copy of this.
505      * <tt>AbstractConcurrentReadCache</tt> instance: the keys and
506      * values themselves are not cloned.
507      *
508      * @return a shallow copy of this map.
509      */

510     public synchronized Object JavaDoc clone() {
511         try {
512             AbstractConcurrentReadCache t = (AbstractConcurrentReadCache) super.clone();
513             t.keySet = null;
514             t.entrySet = null;
515             t.values = null;
516
517             Entry[] tab = table;
518             t.table = new Entry[tab.length];
519
520             Entry[] ttab = t.table;
521
522             for (int i = 0; i < tab.length; ++i) {
523                 Entry first = tab[i];
524
525                 if (first != null) {
526                     ttab[i] = (Entry) (first.clone());
527                 }
528             }
529
530             return t;
531         } catch (CloneNotSupportedException JavaDoc e) {
532             // this shouldn't happen, since we are Cloneable
533
throw new InternalError JavaDoc();
534         }
535     }
536
537     /**
538      * Tests if some key maps into the specified value in this table.
539      * This operation is more expensive than the <code>containsKey</code>
540      * method.<p>
541      *
542      * Note that this method is identical in functionality to containsValue,
543      * (which is part of the Map interface in the collections framework).
544      *
545      * @param value a value to search for.
546      * @return <code>true</code> if and only if some key maps to the
547      * <code>value</code> argument in this table as
548      * determined by the <tt>equals</tt> method;
549      * <code>false</code> otherwise.
550      * @exception NullPointerException if the value is <code>null</code>.
551      * @see #containsKey(Object)
552      * @see #containsValue(Object)
553      * @see Map
554      */

555     public boolean contains(Object JavaDoc value) {
556         return containsValue(value);
557     }
558
559     /**
560      * Tests if the specified object is a key in this table.
561      *
562      * @param key possible key.
563      * @return <code>true</code> if and only if the specified object
564      * is a key in this table, as determined by the
565      * <tt>equals</tt> method; <code>false</code> otherwise.
566      * @exception NullPointerException if the key is
567      * <code>null</code>.
568      * @see #contains(Object)
569      */

570     public boolean containsKey(Object JavaDoc key) {
571         return get(key) != null;
572
573         /** OpenSymphony BEGIN */
574
575         // TODO: Also check the persistence?
576

577         /** OpenSymphony END */
578     }
579
580     /**
581      * Returns <tt>true</tt> if this map maps one or more keys to the
582      * specified value. Note: This method requires a full internal
583      * traversal of the hash table, and so is much slower than
584      * method <tt>containsKey</tt>.
585      *
586      * @param value value whose presence in this map is to be tested.
587      * @return <tt>true</tt> if this map maps one or more keys to the
588      * specified value.
589      * @exception NullPointerException if the value is <code>null</code>.
590      */

591     public boolean containsValue(Object JavaDoc value) {
592         if (value == null) {
593             throw new NullPointerException JavaDoc();
594         }
595
596         Entry[] tab = getTableForReading();
597
598         for (int i = 0; i < tab.length; ++i) {
599             for (Entry e = tab[i]; e != null; e = e.next) {
600                 Object JavaDoc v = e.value;
601
602                 if ((v != null) && value.equals(v)) {
603                     return true;
604                 }
605             }
606         }
607
608         return false;
609     }
610
611     /**
612      * Returns an enumeration of the values in this table.
613      * Use the Enumeration methods on the returned object to fetch the elements
614      * sequentially.
615      *
616      * @return an enumeration of the values in this table.
617      * @see java.util.Enumeration
618      * @see #keys()
619      * @see #values()
620      * @see Map
621      */

622     public Enumeration elements() {
623         return new ValueIterator();
624     }
625
626     /**
627      * Returns a collection view of the mappings contained in this map.
628      * Each element in the returned collection is a <tt>Map.Entry</tt>. The
629      * collection is backed by the map, so changes to the map are reflected in
630      * the collection, and vice-versa. The collection supports element
631      * removal, which removes the corresponding mapping from the map, via the
632      * <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>,
633      * <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt> operations.
634      * It does not support the <tt>add</tt> or <tt>addAll</tt> operations.
635      *
636      * @return a collection view of the mappings contained in this map.
637      */

638     public Set entrySet() {
639         Set es = entrySet;
640
641         if (es != null) {
642             return es;
643         } else {
644             return entrySet = new AbstractSet() {
645                         public Iterator iterator() {
646                             return new HashIterator();
647                         }
648
649                         public boolean contains(Object JavaDoc o) {
650                             if (!(o instanceof Map.Entry)) {
651                                 return false;
652                             }
653
654                             Map.Entry entry = (Map.Entry) o;
655                             Object JavaDoc key = entry.getKey();
656                             Object JavaDoc v = AbstractConcurrentReadCache.this.get(key);
657
658                             return (v != null) && v.equals(entry.getValue());
659                         }
660
661                         public boolean remove(Object JavaDoc o) {
662                             if (!(o instanceof Map.Entry)) {
663                                 return false;
664                             }
665
666                             return AbstractConcurrentReadCache.this.findAndRemoveEntry((Map.Entry) o);
667                         }
668
669                         public int size() {
670                             return AbstractConcurrentReadCache.this.size();
671                         }
672
673                         public void clear() {
674                             AbstractConcurrentReadCache.this.clear();
675                         }
676                     };
677         }
678     }
679
680     /**
681      * Returns the value to which the specified key is mapped in this table.
682      *
683      * @param key a key in the table.
684      * @return the value to which the key is mapped in this table;
685      * <code>null</code> if the key is not mapped to any value in
686      * this table.
687      * @exception NullPointerException if the key is
688      * <code>null</code>.
689      * @see #put(Object, Object)
690      */

691     public Object JavaDoc get(Object JavaDoc key) {
692         if (log.isDebugEnabled()) {
693             log.debug("get called (key=" + key + ")");
694         }
695
696         // throw null pointer exception if key null
697
int hash = hash(key);
698
699         /*
700            Start off at the apparently correct bin. If entry is found, we
701            need to check after a barrier anyway. If not found, we need a
702            barrier to check if we are actually in right bin. So either
703            way, we encounter only one barrier unless we need to retry.
704            And we only need to fully synchronize if there have been
705            concurrent modifications.
706         */

707         Entry[] tab = table;
708         int index = hash & (tab.length - 1);
709         Entry first = tab[index];
710         Entry e = first;
711
712         for (;;) {
713             if (e == null) {
714                 // If key apparently not there, check to
715
// make sure this was a valid read
716
tab = getTableForReading();
717
718                 if (first == tab[index]) {
719                     /** OpenSymphony BEGIN */
720
721                     /* Previous code
722                     return null;*/

723
724                     // Not in the table, try persistence
725
Object JavaDoc value = persistRetrieve(key);
726
727                     if (value != null) {
728                         // Update the map, but don't persist the data
729
put(key, value, false);
730                     }
731
732                     return value;
733
734                     /** OpenSymphony END */
735                 } else {
736                     // Wrong list -- must restart traversal at new first
737
e = first = tab[index = hash & (tab.length - 1)];
738                 }
739             }
740             // checking for pointer equality first wins in most applications
741
else if ((key == e.key) || ((e.hash == hash) && key.equals(e.key))) {
742                 Object JavaDoc value = e.value;
743
744                 if (value != null) {
745                     /** OpenSymphony BEGIN */
746
747                     /* Previous code
748                     return value;*/

749                     if (NULL.equals(value)) {
750                         // Memory cache disable, use disk
751
value = persistRetrieve(e.key);
752
753                         if (value != null) {
754                             itemRetrieved(key);
755                         }
756
757                         return value; // fix [CACHE-13]
758
} else {
759                         itemRetrieved(key);
760
761                         return value;
762                     }
763
764                     /** OpenSymphony END */
765                 }
766
767                 // Entry was invalidated during deletion. But it could
768
// have been re-inserted, so we must retraverse.
769
// To avoid useless contention, get lock to wait out modifications
770
// before retraversing.
771
synchronized (this) {
772                     tab = table;
773                 }
774
775                 e = first = tab[index = hash & (tab.length - 1)];
776             } else {
777                 e = e.next;
778             }
779         }
780     }
781
782     /**
783      * Returns a set view of the keys contained in this map.
784      * The set is backed by the map, so changes to the map are reflected in the set, and
785      * vice-versa. The set supports element removal, which removes the
786      * corresponding mapping from this map, via the <tt>Iterator.remove</tt>,
787      * <tt>Set.remove</tt>, <tt>removeAll</tt>, <tt>retainAll</tt>, and
788      * <tt>clear</tt> operations. It does not support the <tt>add</tt> or
789      * <tt>addAll</tt> operations.
790      *
791      * @return a set view of the keys contained in this map.
792      */

793     public Set keySet() {
794         Set ks = keySet;
795
796         if (ks != null) {
797             return ks;
798         } else {
799             return keySet = new AbstractSet() {
800                         public Iterator iterator() {
801                             return new KeyIterator();
802                         }
803
804                         public int size() {
805                             return AbstractConcurrentReadCache.this.size();
806                         }
807
808                         public boolean contains(Object JavaDoc o) {
809                             return AbstractConcurrentReadCache.this.containsKey(o);
810                         }
811
812                         public boolean remove(Object JavaDoc o) {
813                             return AbstractConcurrentReadCache.this.remove(o) != null;
814                         }
815
816                         public void clear() {
817                             AbstractConcurrentReadCache.this.clear();
818                         }
819                     };
820         }
821     }
822
823     /**
824      * Returns an enumeration of the keys in this table.
825      *
826      * @return an enumeration of the keys in this table.
827      * @see Enumeration
828      * @see #elements()
829      * @see #keySet()
830      * @see Map
831      */

832     public Enumeration keys() {
833         return new KeyIterator();
834     }
835
836     /**
837      * Return the load factor
838      **/

839     public float loadFactor() {
840         return loadFactor;
841     }
842
843     /**
844      * Maps the specified <code>key</code> to the specified <code>value</code> in this table.
845      * Neither the key nor the
846      * value can be <code>null</code>. <p>
847      *
848      * The value can be retrieved by calling the <code>get</code> method
849      * with a key that is equal to the original key.
850      *
851      * @param key the table key.
852      * @param value the value.
853      * @return the previous value of the specified key in this table,
854      * or <code>null</code> if it did not have one.
855      * @exception NullPointerException if the key or value is
856      * <code>null</code>.
857      * @see Object#equals(Object)
858      * @see #get(Object)
859      */

860     /** OpenSymphony BEGIN */
861     public Object JavaDoc put(Object JavaDoc key, Object JavaDoc value) {
862         // Call the internal put using persistance
863
return put(key, value, true);
864     }
865
866     /**
867      * Copies all of the mappings from the specified map to this one.
868      *
869      * These mappings replace any mappings that this map had for any of the
870      * keys currently in the specified Map.
871      *
872      * @param t Mappings to be stored in this map.
873      */

874     public synchronized void putAll(Map t) {
875         for (Iterator it = t.entrySet().iterator(); it.hasNext();) {
876             Map.Entry entry = (Map.Entry) it.next();
877             Object JavaDoc key = entry.getKey();
878             Object JavaDoc value = entry.getValue();
879             put(key, value);
880         }
881     }
882
883     /**
884      * Removes the key (and its corresponding value) from this table.
885      * This method does nothing if the key is not in the table.
886      *
887      * @param key the key that needs to be removed.
888      * @return the value to which the key had been mapped in this table,
889      * or <code>null</code> if the key did not have a mapping.
890      * @exception NullPointerException if the key is
891      * <code>null</code>.
892      */

893     /** OpenSymphony BEGIN */
894     public Object JavaDoc remove(Object JavaDoc key) {
895         return remove(key, true, false);
896     }
897
898     /**
899      * Like <code>remove(Object)</code>, but ensures that the entry will be removed from the persistent store, too,
900      * even if overflowPersistence or unlimitedDiskcache are true.
901      *
902      * @param key
903      * @return
904      */

905     public Object JavaDoc removeForce(Object JavaDoc key) {
906       return remove(key, true, true);
907     }
908
909     /**
910      * Returns the total number of cache entries held in this map.
911      *
912      * @return the number of key-value mappings in this map.
913      */

914     public synchronized int size() {
915         return count;
916     }
917
918     /**
919      * Returns a collection view of the values contained in this map.
920      * The collection is backed by the map, so changes to the map are reflected in
921      * the collection, and vice-versa. The collection supports element
922      * removal, which removes the corresponding mapping from this map, via the
923      * <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>,
924      * <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt> operations.
925      * It does not support the <tt>add</tt> or <tt>addAll</tt> operations.
926      *
927      * @return a collection view of the values contained in this map.
928      */

929     public Collection values() {
930         Collection vs = values;
931
932         if (vs != null) {
933             return vs;
934         } else {
935             return values = new AbstractCollection() {
936                         public Iterator iterator() {
937                             return new ValueIterator();
938                         }
939
940                         public int size() {
941                             return AbstractConcurrentReadCache.this.size();
942                         }
943
944                         public boolean contains(Object