001: /*
002: * hgcommons 7
003: * Hammurapi Group Common Library
004: * Copyright (C) 2003 Hammurapi Group
005: *
006: * This program is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2 of the License, or (at your option) any later version.
010: *
011: * This program is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this library; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: *
020: * URL: http://www.hammurapi.biz/hammurapi-biz/ef/xmenu/hammurapi-group/products/products/hgcommons/index.html
021: * e-Mail: support@hammurapi.biz
022: */
023: package biz.hammurapi.cache;
024:
025: import java.lang.ref.SoftReference;
026: import java.util.HashMap;
027: import java.util.HashSet;
028: import java.util.Iterator;
029: import java.util.Map;
030: import java.util.Set;
031:
032: import biz.hammurapi.config.ConfigurationException;
033: import biz.hammurapi.util.Acceptor;
034:
035: /**
036: * Memory sensitive cache. Uses soft references to cache objects.
037: * Doesn't create helper threads - housekeepeng happens on every 'cleanupInterval' get() invocation.
038: * @author Pavel Vlasov
039: * @version $Revision: 1.3 $
040: */
041: public class PassiveMemoryCache extends AbstractProducer implements
042: Cache {
043: private Map cache = new HashMap();
044: private Producer producer;
045: private Cache fallBack;
046: private int cleanupInterval;
047: private int cleanupCounter;
048:
049: private class CacheReference extends SoftReference {
050: private long expires;
051: private long time;
052:
053: /**
054: * @param referent
055: * @param q
056: * @param expires
057: */
058: public CacheReference(Object value, long time, long expires) {
059: super (value);
060: this .expires = expires;
061: this .time = time;
062: }
063: }
064:
065: /**
066: * Used as entry if fallBack cache is used.
067: * @author Pavel Vlasov
068: * @version $Revision: 1.3 $
069: */
070: private class FallBackEntry {
071: Object value;
072: private long expires;
073: private long time;
074: private Object key;
075:
076: FallBackEntry(Object key, Object value, long time, long expires) {
077: this .key = key;
078: this .value = value;
079: this .expires = expires;
080: this .time = time;
081: }
082:
083: protected void finalize() throws Throwable {
084: if (fallBack.isActive()
085: && (expires <= 0 || System.currentTimeMillis() < expires)) {
086: fallBack.put(key, value, time, expires);
087: }
088:
089: super .finalize();
090: }
091: }
092:
093: private void cleanup() {
094: long now = System.currentTimeMillis();
095: synchronized (cache) {
096: Iterator it = cache.entrySet().iterator();
097: while (it.hasNext()) {
098: Map.Entry entry = (Map.Entry) it.next();
099: CacheReference cacheReference = (CacheReference) entry
100: .getValue();
101: if (cacheReference.get() == null
102: || (cacheReference.expires > 0 && cacheReference.expires < now)) {
103: it.remove();
104: }
105: }
106: }
107: cleanupCounter = 0;
108: }
109:
110: private void checkShutdown() {
111: if (shutDown) {
112: throw new IllegalStateException("Shut down");
113: }
114: }
115:
116: /**
117: *
118: */
119: public PassiveMemoryCache(Producer producer, Cache fallBack,
120: int cleanupInterval) {
121: super ();
122:
123: this .producer = producer;
124: if (producer != null) {
125: producer.addCache(this );
126: }
127:
128: this .fallBack = fallBack;
129: if (fallBack != null) {
130: addCache(fallBack);
131: }
132:
133: this .cleanupInterval = cleanupInterval;
134: }
135:
136: public void put(Object key, Object value, long time,
137: long expirationTime) {
138: checkShutdown();
139: synchronized (cache) {
140: cache.put(key, new CacheReference(toValue(key, value, time,
141: expirationTime), time, expirationTime));
142: }
143: }
144:
145: /**
146: * @param key
147: * @param value
148: * @param expirationTime
149: * @return
150: */
151: private Object toValue(Object key, Object value, long time,
152: long expirationTime) {
153: return fallBack == null ? value : new FallBackEntry(key, value,
154: time, expirationTime);
155: }
156:
157: private Object fromValue(Object object) {
158: if (object instanceof FallBackEntry) {
159: return ((FallBackEntry) object).value;
160: }
161: return object;
162: }
163:
164: public Entry get(Object key) {
165: checkShutdown();
166: if (cleanupInterval > 0 && cleanupCounter++ > cleanupInterval) {
167: cleanup();
168: }
169:
170: synchronized (cache) {
171: CacheReference cr = (CacheReference) cache.get(key);
172: long now = System.currentTimeMillis();
173: boolean doFallBack = true;
174: if (cr != null) {
175: if ((cr.expires > 0 && cr.expires < now)
176: || cr.get() == null) {
177: cache.remove(key);
178: doFallBack = false;
179: } else {
180: final Object o = fromValue(cr.get());
181: final long et = cr.expires;
182: final long time = cr.time;
183: return new Entry() {
184:
185: public long getExpirationTime() {
186: return et;
187: }
188:
189: public long getTime() {
190: return time;
191: }
192:
193: public Object get() {
194: return o;
195: }
196:
197: };
198: }
199: }
200:
201: if (doFallBack && fallBack != null) {
202: Entry entry = fallBack.get(key);
203: if (entry != null) {
204: cache.put(key, new CacheReference(toValue(key,
205: entry.get(), entry.getTime(), entry
206: .getExpirationTime()), entry
207: .getTime(), entry.getExpirationTime()));
208: return entry;
209: }
210: }
211:
212: if (producer != null) {
213: Entry entry = producer.get(key);
214: cache.put(key, new CacheReference(toValue(key, entry
215: .get(), entry.getTime(), entry
216: .getExpirationTime()), entry.getTime(), entry
217: .getExpirationTime()));
218: return entry;
219: }
220:
221: return null;
222: }
223: }
224:
225: public void clear() {
226: synchronized (cache) {
227: cache.clear();
228:
229: if (fallBack != null) {
230: fallBack.clear();
231: }
232: }
233: }
234:
235: public void remove(Object key) {
236: synchronized (cache) {
237: cache.remove(key);
238: }
239: onRemove(key);
240: }
241:
242: public void remove(Acceptor acceptor) {
243: synchronized (cache) {
244: Iterator it = cache.keySet().iterator();
245: while (it.hasNext()) {
246: Object key = it.next();
247: if (acceptor.accept(key)) {
248: it.remove();
249: onRemove(key);
250: }
251: }
252: }
253: }
254:
255: private boolean shutDown = false;
256:
257: public void stop() {
258: if (!shutDown) {
259: cache.clear();
260: shutDown = true;
261: }
262: }
263:
264: protected void finalize() throws Throwable {
265: if (isActive()) {
266: stop();
267: }
268: super .finalize();
269: }
270:
271: public Set keySet() {
272: HashSet ret = new HashSet();
273: synchronized (cache) {
274: ret.addAll(cache.keySet());
275: }
276:
277: if (producer != null) {
278: Set pkeys = producer.keySet();
279: if (pkeys != null) {
280: ret.addAll(pkeys);
281: }
282: }
283:
284: if (fallBack != null) {
285: Set fkeys = fallBack.keySet();
286: if (fkeys != null) {
287: ret.addAll(fkeys);
288: }
289: }
290:
291: return ret;
292: }
293:
294: /* (non-Javadoc)
295: * @see biz.hammurapi.cache.Cache#isActive()
296: */
297: public boolean isActive() {
298: return !shutDown;
299: }
300:
301: public void start() throws ConfigurationException {
302: // TODO Nothing
303:
304: }
305:
306: public void setOwner(Object owner) {
307: // TODO Nothing
308:
309: }
310: }
|