FileDocCategorySizeDatePackage
BaseCache.javaAPI DocGlassfish v2 API31640Fri May 04 22:35:28 BST 2007com.sun.appserv.util.cache

BaseCache

public class BaseCache extends Object implements Cache
BaseCache Generic in-memory, abstract cache

Fields Summary
protected static ResourceBundle
_rb
The resource bundle containing the localized message strings.
static final int
MAX_ENTRIES
static final float
DEFAULT_LOAD_FACTOR
int
maxEntries
protected int
entryCount
private Object
entryCountLk
protected int
threshold
threshold for the cache; once the threshold is reached entries are removed to accomodate newer inserts
private int
hitCount
private Object
hitCountLk
private int
missCount
private Object
missCountLk
private int
removalCount
private Object
removalCountLk
private int
refreshCount
private Object
refreshCountLk
private int
addCount
private Object
addCountLk
private int
overflowCount
private Object
overflowCountLk
protected int
maxBuckets
protected CacheItem[]
buckets
protected Object[]
bucketLocks
protected boolean[]
refreshFlags
protected ArrayList
listeners
Constructors Summary
public BaseCache()
default constructor for the basic cache


               
       
Methods Summary
protected java.lang.Object_put(int hashCode, java.lang.Object key, java.lang.Object value, int size, boolean addValue)
cache the given value at the specified key and return previous value

param
hashCode previously computed hashCode for the key
param
key lookup key
param
object item value to be stored
param
size in bytes of the value being cached
param
addValue treate this operation to add (default is to replace)
returns
the previous item stored at the key; null if not found. Note: This can be used just to refresh the cached item as well, altho it may call trimCache() if the cache reached its threshold -- which is is probably not very intuitive.

		int index = getIndex(hashCode);

		CacheItem item, newItem = null, oldItem = null, overflow = null;
        Object oldValue;
        int oldSize = 0;

        // lookup the item
		synchronized (bucketLocks[index]) {
			for (item = buckets[index]; item != null; item = item.next) {
				if ((hashCode == item.hashCode) && eq(key, item.key)) {

                    oldItem = item;
					break;
				}
			}

            // if there was no item in the cache, insert the given item
			if (addValue || oldItem == null) {
                newItem = createItem(hashCode, key, value, size); 
        
                // add the item at the head of the bucket list
			    newItem.next = buckets[index];
			    buckets[index] = newItem;

                oldValue = null;
                overflow = itemAdded(newItem);
			}
            else {
                oldSize = oldItem.getSize();
                oldValue = oldItem.refreshValue(value, size);
                itemRefreshed(oldItem, oldSize);
            }
		}

        if (newItem != null) {
            incrementEntryCount();
            incrementAddCount();

            // make sure we are are not crossing the threshold
            if (overflow != null)
                trimItem(overflow);
        }
        else
            incrementRefreshCount();

        return oldValue;
    
protected com.sun.appserv.util.cache.BaseCache$CacheItem_remove(int hashCode, java.lang.Object key, java.lang.Object value)
remove the item stored at the key.

param
hashCode a precomputed hashCode
param
key lookup key
param
value of the item to be matched
returns
the item stored at the key; null if not found.

		int index = getIndex(hashCode);

		CacheItem prev = null, item = null;

		synchronized (bucketLocks[index]) {
			for (item = buckets[index]; item != null; item = item.next) {
			    if (hashCode == item.hashCode && key.equals(item.key)) {

				    if (value == null || value == item.value) {

                        if (prev == null) {
                            buckets[index] = item.next;
                        } else  {
                            prev.next = item.next;
                        }
                        item.next = null;

                        itemRemoved(item);
                        break;
                    }
			    }
			    prev = item;
			}
        }
        
        if (item != null) {
            decrementEntryCount();
            incrementRemovalCount();

            incrementHitCount();
        } else
            incrementMissCount();

        return item;
    
protected com.sun.appserv.util.cache.BaseCache$CacheItem_removeItem(com.sun.appserv.util.cache.BaseCache$CacheItem ritem)
remove the item stored at the key.

param
item CacheItem to be removed
return
the item stored at the key; null if not found.


		int index = getIndex(ritem.hashCode);

		CacheItem prev = null, item = null;

		synchronized (bucketLocks[index]) {
			for (item = buckets[index]; item != null; item = item.next) {
			    if (item == ritem) {
                    if (prev == null) {
                        buckets[index] = item.next;
                    } else  {
                        prev.next = item.next;
                    }
                    item.next = null;
                    break;
			    }
			    prev = item;
			}
        }
        
        if (item != null) {
            decrementEntryCount();
        }

        return item;
    
public voidadd(java.lang.Object key, java.lang.Object value)
add the given value to the cache at the specified key

param
key lookup key
param
object item value to be stored

        int hashCode = hash(key);

        _put(hashCode, key, value, -1, true);
    
public voidadd(java.lang.Object key, java.lang.Object value, int size)
add the given value with specified size to the cache at specified key

param
key lookup key
param
object item value to be stored
param
size in bytes of the value being added This function is suitable for multi-valued keys.

        int hashCode = hash(key);

        _put(hashCode, key, value, size, true);
    
public voidaddCacheListener(CacheListener listener)
add the cache module listener

param
listener CacheListener implementation

        listeners.add(listener);
    
public intclear()
clear all the entries from the cache.

returns
the number of entries cleared from the cache

        
		CacheItem item=null, next=null;
        int count = 0;

        for (int index = 0; index < maxBuckets; index++) {
		    synchronized (bucketLocks[index]) {
			    for (item = buckets[index]; item != null; 
                                            item = item.next) {
                    next = item.next;
                    item.next = null;

                    count++;
                    decrementEntryCount();
                    itemRemoved(item);

                    if (entryCount == 0)
                        break;
			    }
                buckets[index] = null;
            }
        }


        return count;
    
public voidclearStats()
clear the stats

        hitCount = 0;
        missCount = 0;
        removalCount = 0;
        refreshCount = 0;
        overflowCount = 0;
        addCount = 0;
    
public booleancontains(java.lang.Object key)
check if the cache contains the item at the key

param
key lookup key
returns
true if there is an item stored at the key; false if not.

    	return (get(key) != null);
    
protected com.sun.appserv.util.cache.BaseCache$CacheItemcreateItem(int hashCode, java.lang.Object key, java.lang.Object value, int size)
create new item

param
hashCode for the entry
param
key Object key
param
value Object value
param
size size in bytes of the item subclasses may override to provide their own CacheItem extensions e.g. one that permits persistence.

        return new CacheItem(hashCode, key, value, size);
    
protected final voiddecrementEntryCount()

        synchronized(entryCountLk) {
            entryCount--;
        }
    
public voiddestroy()
Sets all references to null. This method should be called at the end of this object's life cycle.

        if ((listeners != null) && (buckets != null) && (bucketLocks != null)) {
            clear();
            listeners.clear();
        }

        entryCountLk     = null;
        hitCountLk       = null;
        missCountLk      = null;
        removalCountLk   = null;
        refreshCountLk   = null;
        addCountLk       = null;
        overflowCountLk  = null;
        buckets          = null;
        bucketLocks      = null;
        refreshFlags     = null;
        listeners        = null;
    
public java.util.Enumerationelements()
get an Enumeration for the keys stored in the cache

returns
an Enumeration XXX: should use Iterator which is based on Collections

        Vector keyList = new Vector();

        for (int index=0; index < maxBuckets; index++) {
            synchronized (bucketLocks[index]) {
                for (CacheItem item = buckets[index]; item != null; 
                                item = item.next) {
                    keyList.addElement(item.key);
                }
            }
        }

        return keyList.elements();
    
protected booleaneq(java.lang.Object x, java.lang.Object y)
Check for equality of non-null reference x and possibly-null y.

        return x == y || x.equals(y);
    
public java.lang.Objectget(java.lang.Object key)
get the item stored at the key.

param
key lookup key
returns
the item stored at the key; null if not found.

        int hashCode = hash(key);

        return get(hashCode, key);
    
public java.lang.Objectget(int hashCode, java.lang.Object key)
get the item stored at the given pre-computed hash code and the key.

param
key lookup key
returns
the item stored at the key; null if not found.


		int index = getIndex(hashCode);
        Object value;
        CacheItem item = null;

		synchronized (bucketLocks[index]) {
		    item = buckets[index];

			for (; item != null; item = item.next) {
				if ( (hashCode == item.hashCode) && eq(key, item.key) ) {
					break;
				}
			}

            // update the stats in line
            if (item != null) {
                value = item.getValue();
                itemAccessed(item);
            }
            else
                value = loadValue(key, hashCode);
		}

        if (item != null)
            incrementHitCount();
        else
            incrementMissCount();

        return value;
    
public java.util.IteratorgetAll(java.lang.Object key)
get all the items stored at the key.

param
key lookup key
returns
an Iterator over the items with the given key.

        int hashCode = hash(key);
		int index = getIndex(hashCode);

        ArrayList valueList = new ArrayList(entryCount);
		synchronized (bucketLocks[index]) {
		    CacheItem item = buckets[index];

			for (; item != null; item = item.next) {
				if ( (hashCode == item.hashCode) && eq(key, item.key) ) {
                    incrementHitCount();
                    valueList.add(item.getValue());
				}
			}

		}

        return valueList.iterator();
    
public intgetEntryCount()
get the number of entries in the cache

return
the number of entries the cache currently holds

        return entryCount;
    
protected final intgetIndex(int hashCode)
get the index of the item in the cache

param
hashCode of the entry
return
the index to be used in the cache

        return (hashCode & (maxBuckets - 1));
    
public final intgetIndex(java.lang.Object key)
get the index of the item given a key

param
key of the entry
return
the index to be used in the cache

        return getIndex(hash(key));
    
public java.lang.ObjectgetStatByName(java.lang.String key)
get the desired statistic counter

param
key to corresponding stat
return
an Object corresponding to the stat See also: Constant.java for the key

        Object stat = null;

        if (key == null)
            return null;

        if (key.equals(Constants.STAT_BASECACHE_MAX_ENTRIES))
            stat = Integer.valueOf(maxEntries);
        else if (key.equals(Constants.STAT_BASECACHE_THRESHOLD))
            stat = Integer.valueOf(threshold);
        else if (key.equals(Constants.STAT_BASECACHE_TABLE_SIZE))
            stat = Integer.valueOf(maxBuckets);
        else if (key.equals(Constants.STAT_BASECACHE_ENTRY_COUNT))
            stat = Integer.valueOf(entryCount);
        else if (key.equals(Constants.STAT_BASECACHE_HIT_COUNT))
            stat = Integer.valueOf(hitCount);
        else if (key.equals(Constants.STAT_BASECACHE_MISS_COUNT))
            stat = Integer.valueOf(missCount);
        else if (key.equals(Constants.STAT_BASECACHE_REMOVAL_COUNT))
            stat = Integer.valueOf(removalCount);
        else if (key.equals(Constants.STAT_BASECACHE_REFRESH_COUNT))
            stat = Integer.valueOf(refreshCount);
        else if (key.equals(Constants.STAT_BASECACHE_OVERFLOW_COUNT))
            stat = Integer.valueOf(overflowCount);
        else if (key.equals(Constants.STAT_BASECACHE_ADD_COUNT))
            stat = Integer.valueOf(addCount);

        return stat;
    
public java.util.MapgetStats()
get the stats snapshot

return
a Map of stats See also: Constant.java for the keys

        HashMap stats = new HashMap();

        stats.put(Constants.STAT_BASECACHE_MAX_ENTRIES,
                  Integer.valueOf(maxEntries));
        stats.put(Constants.STAT_BASECACHE_THRESHOLD,
                  Integer.valueOf(threshold));
        stats.put(Constants.STAT_BASECACHE_TABLE_SIZE,
                  Integer.valueOf(maxBuckets));
        stats.put(Constants.STAT_BASECACHE_ENTRY_COUNT,
                  Integer.valueOf(entryCount));
        stats.put(Constants.STAT_BASECACHE_HIT_COUNT,
                  Integer.valueOf(hitCount));
        stats.put(Constants.STAT_BASECACHE_MISS_COUNT,
                  Integer.valueOf(missCount));
        stats.put(Constants.STAT_BASECACHE_REMOVAL_COUNT,
                  Integer.valueOf(removalCount));
        stats.put(Constants.STAT_BASECACHE_REFRESH_COUNT,
                  Integer.valueOf(refreshCount));
        stats.put(Constants.STAT_BASECACHE_OVERFLOW_COUNT,
                  Integer.valueOf(overflowCount));
        stats.put(Constants.STAT_BASECACHE_ADD_COUNT,
                  Integer.valueOf(addCount));

        return stats;
    
protected voidhandleOverflow()
increase the threshold

        // just double the threshold; this may degenerate the cache.
        threshold = (threshold * 2);
        incrementOverflowCount();
    
protected inthash(java.lang.Object x)
Returns a hash code for non-null Object x.

See
also HashMap

        int h = x.hashCode();
        return h - (h << 7);  // i.e., -127 * h
    
protected final voidincrementAddCount()

        synchronized (addCountLk) {
            addCount++;
        }
    
protected final voidincrementEntryCount()
synchronized counter updates

        synchronized(entryCountLk) {
            entryCount++;
        }
    
protected final voidincrementHitCount()

        synchronized (hitCountLk) {
            hitCount++;
        }
    
protected final voidincrementMissCount()

        synchronized (missCountLk) {
            missCount++;
        }
    
protected final voidincrementOverflowCount()

        synchronized (overflowCountLk) {
            overflowCount++;
        }
    
protected final voidincrementRefreshCount()

        synchronized (refreshCountLk) {
            refreshCount++;
        }
    
protected final voidincrementRemovalCount()

        synchronized (removalCountLk) {
            removalCount++;
        }
    
public voidinit(int maxEntries, java.util.Properties props)
initialize the cache

param
maxEntries maximum number of entries expected in the cache
param
props opaque list of properties for a given cache implementation
throws
a generic Exception if the initialization failed

        init(maxEntries, DEFAULT_LOAD_FACTOR, props);
    
public voidinit(int maxEntries, float loadFactor, java.util.Properties props)
initialize the cache

param
maxEntries maximum number of entries expected in the cache
param
loadFactor the load factor
param
props opaque list of properties for a given cache implementation
throws
a generic Exception if the initialization failed


        // web container logger
        _rb = LogDomains.getLogger(LogDomains.CMN_LOGGER).getResourceBundle();

        if (maxEntries <= 0) {
            String msg = _rb.getString("cache.BaseCache.illegalMaxEntries");

            Integer obj = Integer.valueOf(maxEntries);
            Object[] params = { obj };
            msg = MessageFormat.format(msg, params);

            throw new IllegalArgumentException(msg);
        }

        if (maxEntries > MAX_ENTRIES)
             maxEntries = MAX_ENTRIES;

        this.maxEntries = maxEntries;

        // find a power of 2 >= maxEntries
        maxBuckets = 1;
        while (maxBuckets < maxEntries)
            maxBuckets <<= 1;

        //Cannot have the loadfactor as a negative value
        if( loadFactor < 0 )
            loadFactor = 0;

        /** initialize the threshold; a zero value for maxEntries
         *  implies no caching.
         */
        if (maxEntries != 0) {
            threshold = (int)(maxEntries * loadFactor) + 1;
        }

        // create the cache and the bucket locks
        entryCount = 0;
       	buckets = new CacheItem[maxBuckets];
       	bucketLocks = new Object[maxBuckets];
        refreshFlags = new boolean[maxBuckets];

        for (int i=0; i<maxBuckets; i++) {
       		buckets[i] = null;
			bucketLocks[i] = new Object();
            refreshFlags[i] = false;
        }
    
public booleanisEmpty()
is this cache empty?

returns
true if the cache is empty; false otherwise.

        return (entryCount == 0);
    
protected booleanisThresholdReached()
has cache reached its threshold

return
true when the cache reached its threshold

        return (entryCount > threshold);
    
protected voiditemAccessed(com.sun.appserv.util.cache.BaseCache$CacheItem item)
this item is accessed

param
item CacheItem accessed Cache bucket is already synchronized by the caller

 
protected com.sun.appserv.util.cache.BaseCache$CacheItemitemAdded(com.sun.appserv.util.cache.BaseCache$CacheItem item)
this item is just added to the cache

param
item CacheItem that was created
return
a overflow item; may be null Cache bucket is already synchronized by the caller Here, if cache is overflowing (i.e. reached threshold); this class simply makes the cache unbounded by raising the threshold. Subclasses are expected to provide a robust cache replacement algorithm. Subclasses should enhance this implemntation.

        if (isThresholdReached()) {
            handleOverflow();
        }
        return null;
    
protected voiditemRefreshed(com.sun.appserv.util.cache.BaseCache$CacheItem item, int oldSize)
item value has been refreshed

param
item CacheItem that was refreshed
param
oldSize size of the previous value that was refreshed Cache bucket is already synchronized by the caller

 
protected voiditemRemoved(com.sun.appserv.util.cache.BaseCache$CacheItem item)
item value has been removed from the cache

param
item CacheItem that was just removed Cache bucket is already synchronized by the caller

 
public java.util.Iteratorkeys()
get an Iterator for the keys stored in the cache

returns
an Iterator

        ArrayList keyList = new ArrayList(entryCount);

        for (int index=0; index < maxBuckets; index++) {
            synchronized (bucketLocks[index]) {
                for (CacheItem item = buckets[index]; item != null; 
                                item = item.next) {
                    keyList.add(item.key);
                }
            }
        }

        return keyList.iterator();
    
protected java.lang.ObjectloadValue(java.lang.Object key, int hashCode)
Cannot find an item with the given key and hashCode

param
key Object that is not found
param
hashCode int its hashCode
returns
the Object value associated with the item Cache bucket is already synchronized by the caller

 
        return null;
    
public voidnotifyRefresh(int index)
notify threads waiting for a refresh on the object associated with the key

param
key lookup key

        // notify other threads waiting for refresh
		synchronized (bucketLocks[index]) {
            refreshFlags[index] = false;
            bucketLocks[index].notifyAll();
        }
    
public java.lang.Objectput(java.lang.Object key, java.lang.Object value)
/** cache the given value at the specified key and return previous value

param
key lookup key
param
object item value to be stored
returns
the previous item stored at the key; null if not found.

        int hashCode = hash(key);

        return _put(hashCode, key, value, -1, false);
    
public java.lang.Objectput(java.lang.Object key, java.lang.Object value, int size)
cache the given value at the specified key and return previous value

param
key lookup key
param
object item value to be stored
param
size in bytes of the value being cached
returns
the previous item stored at the key; null if not found.

        int hashCode = hash(key);

        return _put(hashCode, key, value, size, false);
    
public java.lang.Objectremove(java.lang.Object key)
remove the item stored at the key.

param
key lookup key
returns
the item stored at the key; null if not found.

        int hashCode = hash(key);

        Object retVal  = null;
        CacheItem removed = _remove( hashCode, key, null);
        
        if (removed != null)
            retVal = removed.getValue();
        return retVal;
    
public java.lang.Objectremove(int hashCode, java.lang.Object key)
remove the item stored at the key.

param
hashCode a precomputed hashCode
param
key lookup key
returns
the item stored at the key; null if not found.

        Object retVal  = null;
        CacheItem removed = _remove( hashCode, key, null);
        
        if (removed != null)
            retVal = removed.getValue();
        return retVal;
    
public java.lang.Objectremove(java.lang.Object key, java.lang.Object value)
remove the given value stored at the key; value-specific removals.

param
key lookup key
param
value to match (for a multi-valued keys)
returns
the item stored at the key; null if not found.

        int hashCode = hash(key);

        Object retVal  = null;
        CacheItem removed = _remove( hashCode, key, value);
        
        if (removed != null)
            retVal = removed.getValue();
        return retVal;
    
public voidremoveAll(java.lang.Object key)
remove all the item with the given key.

param
key lookup key

        int hashCode = hash(key);
		int index = getIndex(hashCode);

		CacheItem prev = null, item = null;
        ArrayList items = new ArrayList(entryCount);

		synchronized (bucketLocks[index]) {
			for (item = buckets[index]; item != null;
                                    item = item.next) {
			    if (hashCode == item.hashCode && key.equals(item.key)) {
                        if (prev == null) {
                            buckets[index] = item.next;
                        } else  {
                            prev.next = item.next;
                        }
                        item.next = null;
        
                        decrementEntryCount();
                        incrementRemovalCount();

                        items.add(item);
			    }
			    prev = item;
			}
        }

        // notify subclasses
        for (int i = 0; i < items.size(); i++) {
            itemRemoved((CacheItem)items.get(i));
        }
    
public voidtrimExpiredEntries(int maxCount)
trim the expired entries from the cache.

param
maxCount maximum number of invalid entries to trim specify Integer.MAX_VALUE to trim all timedout entries This call is to be scheduled by a thread managed by the container.

protected voidtrimItem(com.sun.appserv.util.cache.BaseCache$CacheItem item)
trim the item from the cache and notify listeners

param
item to be trimmed

        CacheItem removed = _removeItem(item);

        if (removed != null) {
            for (int i = 0; i < listeners.size(); i++) {
                CacheListener listener = (CacheListener) listeners.get(i);
                listener.trimEvent(removed.key, removed.value);
            }
        }
    
public java.util.Iteratorvalues()
get an Iterator for the values stored in the cache

returns
an Iterator

        ArrayList valueList = new ArrayList(entryCount);

        for (int index=0; index < maxBuckets; index++) {
            synchronized (bucketLocks[index]) {
                for (CacheItem item = buckets[index]; item != null; 
                                item = item.next) {
                    valueList.add(item.value);
                }
            }
        }

        return valueList.iterator();
    
public booleanwaitRefresh(int index)
wait for a refresh on the object associated with the key

param
key lookup key
returns
true on successful notification, or false if there is no thread refreshing this entry.

		synchronized (bucketLocks[index]) {
            if (refreshFlags[index] == false) {
                refreshFlags[index] = true;
                return false;
            }

            // wait till refresh is finished
            try {
                bucketLocks[index].wait();
            } catch (InterruptedException ie) {}
        }
        return true;