001: package org.shiftone.cache.util;
002:
003: import org.shiftone.cache.util.reaper.ReapableCache;
004:
005: /**
006: * Class AbstractPolicyCache
007: *
008: *
009: * @author <a href="mailto:jeff@shiftone.org">Jeff Drost</a>
010: * @version $Revision: 1.5 $
011: */
012: public abstract class AbstractPolicyCache implements ReapableCache {
013:
014: private static final double MIN_PCT_FREE = 1.0;
015: private static final int SHRINK_MULT = 2;
016: public static final int SHRINK_DIV = 3;
017: private final String name;
018: private final int maxSize;
019: private final long timeoutMilliSeconds;
020:
021: protected AbstractPolicyCache(String name,
022: long timeoutMilliSeconds, int maxSize) {
023:
024: this .name = name;
025: this .timeoutMilliSeconds = timeoutMilliSeconds;
026: this .maxSize = maxSize;
027: }
028:
029: public final void addObject(Object userKey, Object cacheObject) {
030:
031: CacheNode node;
032:
033: node = findNodeByKey(userKey);
034:
035: if (node != null) {
036:
037: // if the node exists, then set it's value, and revalue it.
038: // this is better than deleting it, because it doesn't require
039: // more memory to be allocated
040: node.setValue(cacheObject);
041: revalueNode(node);
042: } else {
043: shrinkToSize(getMaxSize() - 1);
044: createNode(userKey, cacheObject);
045: }
046:
047: removeExpiredElements();
048:
049: //checkFreeMemory();
050: }
051:
052: public final Object getObject(Object key) {
053:
054: Object value = null;
055: CacheNode node;
056:
057: removeExpiredElements();
058:
059: node = findNodeByKey(key);
060:
061: if (node == null) {
062: ; // cache miss
063: } else if (node.isExpired()) {
064: delete(node);
065: } else if (node != null) {
066: revalueNode(node);
067:
068: value = node.getValue();
069: }
070:
071: return value;
072: }
073:
074: public final void remove(Object key) {
075:
076: CacheNode node = findNodeByKey(key);
077:
078: if (node != null) {
079: delete(node);
080: }
081:
082: removeExpiredElements();
083: }
084:
085: protected String getName() {
086: return name;
087: }
088:
089: protected final int getMaxSize() {
090: return this .maxSize;
091: }
092:
093: protected final long getTimeoutMilliSeconds() {
094: return this .timeoutMilliSeconds;
095: }
096:
097: /**
098: * Method removes the least valuable nodes, one by one until the cache's
099: * size is less than or equal to the desired size.
100: */
101: private final void shrinkToSize(int desiredSize) {
102:
103: while (size() > desiredSize) {
104:
105: //Log.info(getClass(), "desiredSize = " + desiredSize + " size=" + size());
106: removeLeastValuableNode();
107: }
108: }
109:
110: /**
111: * This method calls shrinkToSize(0) which will loop through
112: * each element, removing them one by one (in order of lease valued
113: * to most valued). Derived classes may wish to implement this in a
114: * more efficient way (by just reinitalizing itself).
115: */
116: public void clear() {
117: shrinkToSize(0);
118: }
119:
120: /**
121: * Method checkFreeMemory
122: */
123: private final void checkFreeMemory() {
124:
125: /*
126: double percentFree = MemUtil.freeMemoryPct();
127: long start = System.currentTimeMillis();
128: DecimalFormat format = null;
129:
130: if (percentFree < MIN_PCT_FREE)
131: {
132:
133: //log.warn("xxx", new Exception("XXX"));
134: format = new DecimalFormat("##.##");
135:
136: shrinkToSize((size() * SHRINK_MULT) / SHRINK_DIV);
137: Runtime.getRuntime().gc();
138: Runtime.getRuntime().runFinalization();
139: Thread.yield();
140: log.warn(format.format(percentFree) + "% memory free - cleanup took "
141: + (System.currentTimeMillis() - start) + "ms - size is now " + size());
142: }
143: */
144: }
145:
146: /**
147: * Method findNodeByKey
148: */
149: abstract protected CacheNode findNodeByKey(Object key);
150:
151: /**
152: * Update the node's value. This is done immediately after
153: * the node is retrieved.
154: */
155: abstract protected void revalueNode(CacheNode node);
156:
157: /**
158: * Remove a node from the cache.
159: */
160: abstract protected void delete(CacheNode node);
161:
162: /**
163: * This method will execute the cache's eviction strategy.
164: * If this method is called, there will be at least one
165: * element in the cache to remove. The method itself does
166: * not need to check for the existance of a element.
167: * <p>
168: * This method is only called by shrinkToSize();
169: */
170: abstract protected void removeLeastValuableNode();
171:
172: /**
173: * Purge the cache of expired elements.
174: */
175: abstract public void removeExpiredElements();
176:
177: /**
178: * Create a new node.
179: */
180: abstract protected CacheNode createNode(Object userKey,
181: Object cacheObject);
182:
183: private static String shortName(Class klass) {
184:
185: String className = klass.getName();
186: int lastDot = className.lastIndexOf('.');
187:
188: if (lastDot != -1) {
189: className = className.substring(lastDot + 1);
190: }
191:
192: return className;
193: }
194:
195: public String toString() {
196: return shortName(getClass()) + "(" + getName() + ","
197: + getTimeoutMilliSeconds() + "," + getMaxSize() + ")";
198: }
199: }
|