KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > jdon > util > UtilCache


1 /*
2  * $Id: UtilCache.java,v 1.2 2005/01/31 05:27:54 jdon Exp $
3  *
4  * Copyright (c) 2001, 2002 The Open For Business Project - www.ofbiz.org
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a
7  * copy of this software and associated documentation files (the "Software"),
8  * to deal in the Software without restriction, including without limitation
9  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10  * and/or sell copies of the Software, and to permit persons to whom the
11  * Software is furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included
14  * in all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
21  * OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
22  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23  */

24
25 package com.jdon.util;
26
27 import java.text.DecimalFormat JavaDoc;
28 import java.util.Iterator JavaDoc;
29 import java.util.LinkedList JavaDoc;
30 import java.util.Map JavaDoc;
31 import java.util.concurrent.ConcurrentHashMap JavaDoc;
32
33 /**
34  * <p>
35  * Generalized caching utility. Provides a number of caching features:
36  * <ul>
37  * <li>Limited or unlimited element capacity
38  * <li>If limited, removes elements with the LRU (Least Recently Used)
39  * algorithm
40  * <li>Keeps track of when each element was loaded into the cache
41  * <li>Using the expireTime can report whether a given element has expired
42  * <li>Counts misses and hits
43  * </ul>
44  *
45  * @author <a HREF="mailto:jonesde@ofbiz.org">David E. Jones </a>
46  * @version $Revision: 1.2 $
47  * @since 2.0
48  */

49 public class UtilCache {
50     public static String JavaDoc module = UtilCache.class.getName();
51     
52     // decimal formatter for cache values
53
static final DecimalFormat JavaDoc percentFormat = new DecimalFormat JavaDoc("#0.0");
54
55     private PropsUtil propsUtil;
56
57     /** A static Map to keep track of all of the UtilCache instances. */
58     public static Map JavaDoc utilCacheTable = new ConcurrentHashMap JavaDoc();
59
60     /**
61      * An index number appended to utilCacheTable names when there are
62      * conflicts.
63      */

64     protected static Map JavaDoc defaultIndices = new ConcurrentHashMap JavaDoc();
65
66     /**
67      * The name of the UtilCache instance, is also the key for the instance in
68      * utilCacheTable.
69      */

70     protected String JavaDoc name;
71
72     /** A list of the elements order by Least Recent Use */
73     public LinkedList JavaDoc keyLRUList = new LinkedList JavaDoc();
74
75     /**
76      * A hashtable containing a CacheLine object with a value and a loadTime for
77      * each element.
78      * for above jdk1.5
79      */

80     public Map JavaDoc cacheLineTable = new ConcurrentHashMap JavaDoc();
81
82   
83     /** A count of the number of cache hits */
84     protected long hitCount = 0;
85     
86
87     /** A count of the number of cache misses */
88     protected long missCount = 0;
89
90     /**
91      * The maximum number of elements in the cache. If set to 0, there will be
92      * no limit on the number of elements in the cache.
93      */

94     protected long maxSize = 0;
95
96     /**
97      * Specifies the amount of time since initial loading before an element will
98      * be reported as expired. If set to 0, elements will never expire.
99      */

100     protected long expireTime = 0;
101
102     /**
103      * Specifies whether or not to use soft references for this cache, defaults
104      * to false
105      */

106     protected boolean useSoftReference = false;
107
108     /**
109      * Constructor which specifies the cacheName as well as the maxSize,
110      * expireTime and useSoftReference. The passed maxSize, expireTime and
111      * useSoftReference will be overridden by values from cache.properties if
112      * found.
113      *
114      * @param maxSize
115      * The maxSize member is set to this value
116      * @param expireTime
117      * The expireTime member is set to this value
118      * @param cacheName
119      * The name of the cache.
120      * @param useSoftReference
121      * Specifies whether or not to use soft references for this
122      * cache.
123      */

124     public UtilCache(PropsUtil propsUtil, String JavaDoc cacheName, long maxSize, long expireTime, boolean useSoftReference) {
125         this.propsUtil = propsUtil;
126         this.useSoftReference = useSoftReference;
127         this.maxSize = maxSize;
128         this.expireTime = expireTime;
129         setPropertiesParams(cacheName);
130
131         name = cacheName + this.getNextDefaultIndex(cacheName);
132         utilCacheTable.put(name, this);
133     }
134
135     /**
136      * Constructor which specifies the cacheName as well as the maxSize and
137      * expireTime. The passed maxSize and expireTime will be overridden by
138      * values from cache.properties if found.
139      *
140      * @param maxSize
141      * The maxSize member is set to this value
142      * @param expireTime
143      * The expireTime member is set to this value
144      * @param cacheName
145      * The name of the cache.
146      */

147     public UtilCache(PropsUtil propsUtil, String JavaDoc cacheName, long maxSize, long expireTime) {
148         this(propsUtil, cacheName, maxSize, expireTime, false);
149     }
150
151     /**
152      * Constructor which specifies the maxSize and expireTime.
153      *
154      * @param maxSize
155      * The maxSize member is set to this value
156      * @param expireTime
157      * The expireTime member is set to this value
158      */

159     public UtilCache(PropsUtil propsUtil, long maxSize, long expireTime) {
160         this.propsUtil = propsUtil;
161         this.useSoftReference = false;
162         this.maxSize = maxSize;
163         this.expireTime = expireTime;
164         String JavaDoc name = "specified" + this.getNextDefaultIndex("specified");
165
166         setPropertiesParams(name);
167         utilCacheTable.put(name, this);
168     }
169
170     /**
171      * This constructor takes a name for the cache, puts itself in the
172      * utilCacheTable. It also uses the cacheName to lookup the initialization
173      * parameters from cache.properties.
174      *
175      * @param cacheName
176      * The name of the cache.
177      */

178     public UtilCache(PropsUtil propsUtil, String JavaDoc cacheName) {
179         this.propsUtil = propsUtil;
180         setPropertiesParams("default");
181         setPropertiesParams(cacheName);
182
183         name = cacheName + this.getNextDefaultIndex(cacheName);
184         utilCacheTable.put(name, this);
185     }
186
187     /**
188      * Default constructor, all members stay at default values as defined in
189      * cache.properties, or the defaults in this file if cache.properties is not
190      * found, or there are no 'default' entries in it.
191      */

192     public UtilCache(PropsUtil propsUtil) {
193         this.propsUtil = propsUtil;
194         setPropertiesParams("default");
195
196         name = "default" + this.getNextDefaultIndex("default");
197         utilCacheTable.put(name, this);
198     }
199
200     protected String JavaDoc getNextDefaultIndex(String JavaDoc cacheName) {
201         Integer JavaDoc curInd = (Integer JavaDoc) UtilCache.defaultIndices.get(cacheName);
202
203         if (curInd == null) {
204             UtilCache.defaultIndices.put(cacheName, new Integer JavaDoc(1));
205             return "";
206         } else {
207             UtilCache.defaultIndices.put(cacheName, new Integer JavaDoc(curInd.intValue() + 1));
208             return Integer.toString(curInd.intValue() + 1);
209         }
210     }
211
212     protected void setPropertiesParams(String JavaDoc cacheName) {
213         if (propsUtil == null) {
214             System.err.println(" UtilCache propsUtil not yet set!! ");
215             return;
216         }
217
218         try {
219             String JavaDoc value = propsUtil.getProperty("cache." + cacheName + ".maxSize");
220             if (UtilValidate.isNotEmpty(value)) {
221                 Debug.logVerbose("[JdonFramework]found cache configure: cache." + cacheName + ".maxSize = " + value, module);
222                 Long JavaDoc longValue = new Long JavaDoc(value);
223                 if (longValue != null) {
224                     maxSize = longValue.longValue();
225                 }
226             }
227         } catch (Exception JavaDoc e) {
228         }
229
230         try {
231             String JavaDoc value = propsUtil.getProperty("cache." + cacheName + ".expireTime");
232             if (UtilValidate.isNotEmpty(value)) {
233
234                 Long JavaDoc longValue = new Long JavaDoc(value);
235
236                 if (longValue != null) {
237                     expireTime = longValue.longValue();
238                 }
239             }
240         } catch (Exception JavaDoc e) {
241         }
242
243         try {
244             String JavaDoc value = propsUtil.getProperty("cache." + cacheName + ".useSoftReference");
245             if (UtilValidate.isNotEmpty(value)) {
246                 useSoftReference = "true".equals(value);
247             }
248         } catch (Exception JavaDoc e) {
249         }
250     }
251
252     /**
253      * Puts or loads the passed element into the cache
254      *
255      * @param key
256      * The key for the element, used to reference it in the hastables
257      * and LRU linked list
258      * @param value
259      * The value of the element
260      */

261     public synchronized void put(Object JavaDoc key, Object JavaDoc value) {
262         if (key == null)
263             return;
264
265         if (maxSize > 0) {
266             // when maxSize is changed, the setter will take care of filling the
267
// LRU list
268
if (cacheLineTable.containsKey(key)) {
269                 keyLRUList.remove(key);
270                 keyLRUList.addFirst(key);
271             } else {
272                 keyLRUList.addFirst(key);
273             }
274         }
275
276         if (expireTime > 0) {
277             cacheLineTable.put(key, new UtilCache.CacheLine(value, useSoftReference, System.currentTimeMillis()));
278         } else {
279             cacheLineTable.put(key, new UtilCache.CacheLine(value, useSoftReference));
280         }
281         if (maxSize > 0 && cacheLineTable.size() > maxSize) {
282             Object JavaDoc lastKey = keyLRUList.getLast();
283             remove(lastKey);
284         }
285         Debug.logVerbose("[JdonFramework]cache now size = " + keyLRUList.size() + " maxSize =" + maxSize +
286                 " this Cache id:" + this.hashCode(), module);
287
288     }
289
290     /**
291      * Gets an element from the cache according to the specified key. If the
292      * requested element hasExpired, it is removed before it is looked up which
293      * causes the function to return null.
294      *
295      * @param key
296      * The key for the element, used to reference it in the hastables
297      * and LRU linked list
298      * @return The value of the element specified by the key
299      */

300     public Object JavaDoc get(Object JavaDoc key) {
301         
302         if (key == null) {
303             missCount++;
304             return null;
305         }
306         UtilCache.CacheLine line = (UtilCache.CacheLine) cacheLineTable.get(key);
307
308         if (hasExpired(line)) {
309             // note that print.info in debug.properties cannot be checked
310
// through UtilProperties here, it would cause infinite recursion...
311
// if (Debug.infoOn()) Debug.logInfo("Element has expired with key "
312
// + key);
313
remove(key);
314             line = null;
315         }
316
317         if (line == null) {
318             // if (Debug.infoOn()) Debug.logInfo("Element not found with key " +
319
// key);
320
missCount++;
321             return null;
322         }
323         // if (Debug.infoOn()) Debug.logInfo("Element found with key " + key);
324
hitCount++;
325         //double hitPercent = 100*(double)hitCount/(hitCount + missCount);
326
//Debug.logVerbose("[JdonFramework]cache hit percent: " + percentFormat.format(hitPercent)+"%", module);
327

328         if (maxSize > 0) {
329             keyLRUList.remove(key);
330             keyLRUList.addFirst(key);
331         }
332         return line.getValue();
333     }
334
335     /**
336      * Removes an element from the cache according to the specified key
337      *
338      * @param key
339      * The key for the element, used to reference it in the hastables
340      * and LRU linked list
341      * @return The value of the removed element specified by the key
342      */

343     public synchronized Object JavaDoc remove(Object JavaDoc key) {
344         if (key == null) {
345             missCount++;
346             return null;
347         }
348
349         UtilCache.CacheLine line = (UtilCache.CacheLine) cacheLineTable.remove(key);
350         if (line != null) {
351             if (maxSize > 0)
352                 keyLRUList.remove(key);
353             return line.getValue();
354         } else {
355             missCount++;
356             return null;
357         }
358     }
359
360     /** Removes all elements from this cache */
361     public synchronized void clear() {
362         cacheLineTable.clear();
363         keyLRUList.clear();
364         clearCounters();
365     }
366
367     /** Removes all elements from this cache */
368     public static void clearAllCaches() {
369         Iterator JavaDoc entries = utilCacheTable.entrySet().iterator();
370         while (entries.hasNext()) {
371             Map.Entry JavaDoc entry = (Map.Entry JavaDoc) entries.next();
372             UtilCache utilCache = (UtilCache) entry.getValue();
373             utilCache.clear();
374         }
375     }
376
377     /**
378      * Getter for the name of the UtilCache instance.
379      *
380      * @return The name of the instance
381      */

382     public String JavaDoc getName() {
383         return name;
384     }
385
386     /**
387      * Returns the number of successful hits on the cache
388      *
389      * @return The number of successful cache hits
390      */

391     public long getHitCount() {
392         return hitCount;
393     }
394
395     /**
396      * Returns the number of cache misses
397      *
398      * @return The number of cache misses
399      */

400     public long getMissCount() {
401         return missCount;
402     }
403
404     /**
405      * Clears the hit and miss counters
406      */

407     public void clearCounters() {
408         hitCount = 0;
409         missCount = 0;
410     }
411
412     /**
413      * Sets the maximum number of elements in the cache. If 0, there is no
414      * maximum.
415      *
416      * @param maxSize
417      * The maximum number of elements in the cache
418      */

419     public void setMaxSize(long maxSize) {
420         // if the new maxSize is <= 0, clear keyLRUList
421
if (maxSize <= 0) {
422             keyLRUList.clear();
423         } else if (maxSize > 0 && this.maxSize <= 0) {
424             // if the new maxSize > 0 and the old is <= 0, fill in LRU list -
425
// order will be meaningless for now
426
Iterator JavaDoc keys = cacheLineTable.keySet().iterator();
427
428             while (keys.hasNext()) {
429                 keyLRUList.add(keys.next());
430             }
431         }
432
433         // if the new maxSize is less than the current cache size, shrink the
434
// cache.
435
if (maxSize > 0 && cacheLineTable.size() > maxSize) {
436             while (cacheLineTable.size() > maxSize) {
437                 Object JavaDoc lastKey = keyLRUList.getLast();
438
439                 remove(lastKey);
440             }
441         }
442
443         this.maxSize = maxSize;
444     }
445
446     /**
447      * Returns the current maximum number of elements in the cache
448      *
449      * @return The maximum number of elements in the cache
450      */

451     public long getMaxSize() {
452         return maxSize;
453     }
454
455     /**
456      * Sets the expire time for the cache elements. If 0, elements never expire.
457      *
458      * @param expireTime
459      * The expire time for the cache elements
460      */

461     public void setExpireTime(long expireTime) {
462         // if expire time was <= 0 and is now greater, fill expire table now
463
if (this.expireTime <= 0 && expireTime > 0) {
464             long currentTime = System.currentTimeMillis();
465             Iterator JavaDoc values = cacheLineTable.values().iterator();
466
467             while (values.hasNext()) {
468                 UtilCache.CacheLine line = (UtilCache.CacheLine) values.next();
469
470                 line.loadTime = currentTime;
471             }
472         } else if (this.expireTime <= 0 && expireTime > 0) {// if expire time
473
// was > 0 and is
474
// now <=, do
475
// nothing, just
476
// leave the load
477
// times in place,
478
// won't hurt
479
// anything...
480
}
481
482         this.expireTime = expireTime;
483     }
484
485     /**
486      * return the current expire time for the cache elements
487      *
488      * @return The expire time for the cache elements
489      */

490     public long getExpireTime() {
491         return expireTime;
492     }
493
494     /**
495      * Set whether or not the cache lines should use a soft reference to the
496      * data
497      */

498     public void setUseSoftReference(boolean useSoftReference) {
499         if (this.useSoftReference != useSoftReference) {
500             this.useSoftReference = useSoftReference;
501             Iterator JavaDoc values = cacheLineTable.values().iterator();
502
503             while (values.hasNext()) {
504                 UtilCache.CacheLine line = (UtilCache.CacheLine) values.next();
505
506                 line.setUseSoftReference(useSoftReference);
507             }
508         }
509     }
510
511     /**
512      * Return whether or not the cache lines should use a soft reference to the
513      * data
514      */

515     public boolean getUseSoftReference() {
516         return this.useSoftReference;
517     }
518
519     /**
520      * Returns the number of elements currently in the cache
521      *
522      * @return The number of elements currently in the cache
523      */

524     public long size() {
525         return cacheLineTable.size();
526     }
527
528     /**
529      * Returns a boolean specifying whether or not an element with the specified
530      * key is in the cache. If the requested element hasExpired, it is removed
531      * before it is looked up which causes the function to return false.
532      *
533      * @param key
534      * The key for the element, used to reference it in the hastables
535      * and LRU linked list
536      * @return True is the cache contains an element corresponding to the
537      * specified key, otherwise false
538      */

539     public boolean containsKey(Object JavaDoc key) {
540         UtilCache.CacheLine line = (UtilCache.CacheLine) cacheLineTable.get(key);
541
542         if (hasExpired(line)) {
543             remove(key);
544             line = null;
545         }
546         if (line != null) {
547             return true;
548         } else {
549             return false;
550         }
551     }
552
553     /**
554      * Returns a boolean specifying whether or not the element corresponding to
555      * the key has expired. Only returns true if element is in cache and has
556      * expired. Error conditions return false, if no expireTable entry, returns
557      * true. Always returns false if expireTime <= 0. Also, if SoftReference in
558      * the CacheLine object has been cleared by the gc return true.
559      *
560      * @param key
561      * The key for the element, used to reference it in the hastables
562      * and LRU linked list
563      * @return True is the element corresponding to the specified key has
564      * expired, otherwise false
565      */

566     public boolean hasExpired(Object JavaDoc key) {
567         if (key == null)
568             return false;
569
570         UtilCache.CacheLine line = (UtilCache.CacheLine) cacheLineTable.get(key);
571
572         return hasExpired(line);
573     }
574
575     protected boolean hasExpired(UtilCache.CacheLine line) {
576         if (line == null)
577             return false;
578         // check this BEFORE checking to see if expireTime <= 0, ie if time
579
// expiration is enabled
580
// check to see if we are using softReference first, slight performance
581
// increase
582
if (this.useSoftReference && line.getValue() == null)
583             return true;
584         if (expireTime <= 0)
585             return false;
586
587         if (line.loadTime <= 0)
588             return true;
589         if ((line.loadTime + expireTime) < System.currentTimeMillis()) {
590             return true;
591         } else {
592             return false;
593         }
594     }
595
596     /**
597      * Clears all expired cache entries; also clear any cache entries where the
598      * SoftReference in the CacheLine object has been cleared by the gc
599      */

600     public void clearExpired() {
601         Iterator JavaDoc keys = cacheLineTable.keySet().iterator();
602
603         while (keys.hasNext()) {
604             Object JavaDoc key = keys.next();
605
606             if (hasExpired(key)) {
607                 remove(key);
608             }
609         }
610     }
611
612     /** Clears all expired cache entries from all caches */
613     public static void clearExpiredFromAllCaches() {
614         Iterator JavaDoc entries = utilCacheTable.entrySet().iterator();
615
616         while (entries.hasNext()) {
617             Map.Entry JavaDoc entry = (Map.Entry JavaDoc) entries.next();
618             UtilCache utilCache = (UtilCache) entry.getValue();
619
620             utilCache.clearExpired();
621         }
622     }
623
624     /** Checks for a non-expired key in a specific cache */
625     public static boolean validKey(String JavaDoc cacheName, Object JavaDoc key) {
626         UtilCache cache = (UtilCache) utilCacheTable.get(cacheName);
627         if (cache != null) {
628             if (cache.containsKey(key))
629                 return true;
630         }
631         return false;
632     }
633
634     public static class CacheLine {
635         public Object JavaDoc valueRef = null;
636
637         public long loadTime = 0;
638
639         public boolean useSoftReference = false;
640
641         public CacheLine(Object JavaDoc value, boolean useSoftReference) {
642             if (useSoftReference) {
643                 this.valueRef = new java.lang.ref.SoftReference JavaDoc(value);
644             } else {
645                 this.valueRef = value;
646             }
647             this.useSoftReference = useSoftReference;
648         }
649
650         public CacheLine(Object JavaDoc value, boolean useSoftReference, long loadTime) {
651             this(value, useSoftReference);
652             this.loadTime = loadTime;
653         }
654
655         public Object JavaDoc getValue() {
656             if (valueRef == null)
657                 return null;
658             if (useSoftReference) {
659                 return ((java.lang.ref.SoftReference JavaDoc) valueRef).get();
660             } else {
661                 return valueRef;
662             }
663         }
664
665         public void setUseSoftReference(boolean useSoftReference) {
666             if (this.useSoftReference != useSoftReference) {
667                 synchronized (this) {
668                     this.useSoftReference = useSoftReference;
669                     if (useSoftReference) {
670                         this.valueRef = new java.lang.ref.SoftReference JavaDoc(this.valueRef);
671                     } else {
672                         this.valueRef = ((java.lang.ref.SoftReference JavaDoc) this.valueRef).get();
673                     }
674                 }
675             }
676         }
677     }
678
679     /**
680      * @return Returns the propsUtil.
681      */

682     public PropsUtil getPropsUtil() {
683         return propsUtil;
684     }
685
686     /**
687      * @param propsUtil
688      * The propsUtil to set.
689      */

690     public void setPropsUtil(PropsUtil propsUtil) {
691         this.propsUtil = propsUtil;
692     }
693 }
694
Popular Tags