001: /* Copyright 2002 The JA-SIG Collaborative. All rights reserved.
002: * See license distributed with this file and
003: * available online at http://www.uportal.org/license.html
004: */
005:
006: package org.jasig.portal.concurrency.caching;
007:
008: import java.util.HashMap;
009:
010: /**
011: * A rewrite of SmartCache that uses a moderate LRU algorithm: entries
012: * are purged from the cache via periodic sweeps rather than in response to
013: * specific cache additions. Note that sweeps have to be kicked off
014: * externally, e.g.,
015: * <p>
016: * <code>
017: * int MAX_CACHE_SIZE = 1000;<br>
018: * int MAX_UNUSED_TIME_MILLIS = 30*60*1000;<br>
019: * LRUCache cache = new LRUCache(MAX_CACHE_SIZE, MAX_UNUSED_TIME_MILLIS);<br>
020: * // ... put stuff in ...<br>
021: * cache.sweepCache()<br>
022: * // ... put more stuff in ...<br>
023: * </code>
024: * <p>
025: * At the end of the sweep, the cache will have no more (and possibly less)
026: * than <code>maxSize</code> entries, though the sweep may have to reduce
027: * <code>maxUnusedTimeMillis</code> in order to get there.
028: * <p>
029: * @author Ken Weiner
030: * @author Dan Ellentuck
031: * @version $Revision: 34996 $
032: * @see org.jasig.portal.utils.SmartCache
033: */
034: public class LRUCache extends HashMap {
035: // Maximum size of cache, after sweep. Defaults to 1000.
036: protected static int DEFAULT_MAX_SIZE = 1000;
037: protected int maxSize;
038:
039: // Maximum unused time for cache entries, used only when size
040: // exceeds maxSize. Defaults to 30 minutes.
041: protected static int DEFAULT_MAX_UNUSED_TIME_MILLIS = 30 * 60 * 1000;
042: protected int maxUnusedTimeMillis;
043:
044: // Wrapper adds last used timestamp.
045: private class ValueWrapper {
046: private long lastReferenceTime = System.currentTimeMillis();
047: private Object oValue;
048:
049: protected ValueWrapper(Object oValue) {
050: this .oValue = oValue;
051: }
052:
053: protected Object getValue() {
054: return oValue;
055: }
056:
057: protected void setValue(Object oValue) {
058: this .oValue = oValue;
059: }
060:
061: protected long getLastReferenceTime() {
062: return lastReferenceTime;
063: }
064:
065: protected void resetLastReferenceTime() {
066: this .lastReferenceTime = System.currentTimeMillis();
067: }
068: }
069:
070: /**
071: */
072: public LRUCache() {
073: this (DEFAULT_MAX_SIZE, DEFAULT_MAX_UNUSED_TIME_MILLIS);
074: }
075:
076: /**
077: */
078: public LRUCache(int size) {
079: this (size, DEFAULT_MAX_UNUSED_TIME_MILLIS);
080: }
081:
082: /**
083: * @param size int
084: * @param maxUnusedAge int
085: */
086: public LRUCache(int size, int maxUnusedAge) {
087: super ();
088: maxSize = size;
089: maxUnusedTimeMillis = maxUnusedAge;
090: }
091:
092: /**
093: * Synchronizes removal of ALL entries from the cache.
094: */
095: public synchronized void clear() {
096: super .clear();
097: }
098:
099: /**
100: * Get the object from the cache and reset the timestamp.
101: * @param key the key, typically a String
102: * @return the value to which the key is mapped in this cache;
103: * null if the key is not mapped to any value in this cache.
104: */
105: public synchronized Object get(Object key) {
106: ValueWrapper valueWrapper = (ValueWrapper) super .get(key);
107: if (valueWrapper != null) {
108: // Update timestamp
109: valueWrapper.resetLastReferenceTime();
110: return valueWrapper.getValue();
111: } else
112: return null;
113: }
114:
115: /**
116: * Add a new value to the cache.
117: * @param key the key, typically a String
118: * @param value the value
119: * @return the previous value of the specified key in this hashtable, or null if it did not have one.
120: */
121: public synchronized Object put(Object key, Object value) {
122: ValueWrapper valueWrapper = new ValueWrapper(value);
123: return super .put(key, valueWrapper);
124: }
125:
126: /**
127: * Synchronizes removal of an entry from the cache.
128: * @param key the key, typically a String
129: * @return the previous value of the specified key in this hashtable,
130: * or null if it did not have one.
131: */
132: public synchronized Object remove(Object key) {
133: return super .remove(key);
134: }
135:
136: /**
137: * Sweep the cache until it gets back under <code>maxSize</code>.
138: */
139: public void sweepCache() {
140: long maxAge = maxUnusedTimeMillis;
141: while (size() > maxSize) {
142: long cutOff = System.currentTimeMillis() - maxAge;
143: Object[] keys = getKeySetArray();
144: for (int i = 0; i < keys.length; i++) {
145: ValueWrapper valueWrapper = (ValueWrapper) super
146: .get(keys[i]);
147: if (valueWrapper != null) {
148: if (valueWrapper.getLastReferenceTime() < cutOff) {
149: remove(keys[i]);
150: }
151: }
152: }
153: maxAge = maxAge * 3 / 4;
154: }
155: }
156:
157: private synchronized Object[] getKeySetArray() {
158: return keySet().toArray(new Object[size()]);
159: }
160: }
|