001    /*--------------------------------------------------------------------------+
002    $Id: SoftRefCacheBase.java 26268 2010-02-18 10:44:30Z juergens $
003    |                                                                          |
004    | Copyright 2005-2010 Technische Universitaet Muenchen                     |
005    |                                                                          |
006    | Licensed under the Apache License, Version 2.0 (the "License");          |
007    | you may not use this file except in compliance with the License.         |
008    | You may obtain a copy of the License at                                  |
009    |                                                                          |
010    |    http://www.apache.org/licenses/LICENSE-2.0                            |
011    |                                                                          |
012    | Unless required by applicable law or agreed to in writing, software      |
013    | distributed under the License is distributed on an "AS IS" BASIS,        |
014    | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
015    | See the License for the specific language governing permissions and      |
016    | limitations under the License.                                           |
017    +--------------------------------------------------------------------------*/
018    package edu.tum.cs.commons.cache;
019    
020    import java.lang.ref.SoftReference;
021    import java.util.HashMap;
022    
023    import edu.tum.cs.commons.error.NeverThrownRuntimeException;
024    
025    /**
026     * A base class for dynamic caches based on {@link SoftReference}s. If
027     * identifiers itself are suitable hash keys, use class
028     * {@link edu.tum.cs.commons.cache.SoftRefStraightCacheBase}.
029     * <p>
030     * The implementation is memory-sensitive, i.e. it dynamically removes entries
031     * from the cache if the virtual machine is short on memory. However, this
032     * dynamic is completely transparent to the user.
033     * <p>
034     * <b>Note:</b> To make this cache efficient the virtual machine must work in
035     * server mode.
036     * 
037     * @author Florian Deissenboeck
038     * @author Tilman Seifert
039     * @author $Author: juergens $
040     * 
041     * @version $Rev: 26268 $
042     * @levd.rating GREEN Hash: A74130243CAE0E00D2C6A51DDFEA68A0
043     * 
044     * @param <I>
045     *            the index type of the cache
046     * @param <H>
047     *            the hash map key type
048     * @param <E>
049     *            the type stored in the cache
050     * @param <X>
051     *            the type of exception thrown by the {@link #obtainItem(Object)}
052     *            method. Use the {@link NeverThrownRuntimeException} if no
053     *            exception will be thrown.
054     */
055    public abstract class SoftRefCacheBase<I, H, E, X extends Exception> extends
056                    CacheBase<I, H, E, X> {
057    
058            /** Number of times the cache was accessed (for debugging purposes only) */
059            private static int accessCounter = 0;
060    
061            /** Number of cache hits (for debugging purposes only) */
062            private static int hitCounter = 0;
063    
064            /**
065             * Number of cache misses that were caused by accesses to items that were
066             * never cached (for debugging purposes only)
067             */
068            private static int missBecauseNotInCacheCounter = 0;
069    
070            /**
071             * Number of cache misses that were caused by accesses to items that were
072             * removed from cache by garbage collection (for debugging purposes only)
073             */
074            private static int missBecauseRemovedFromCacheCounter = 0;
075    
076            /** The actual cache. */
077            protected final HashMap<H, SoftReference<E>> cache = new HashMap<H, SoftReference<E>>();
078    
079            /** {@inheritDoc} */
080            @Override
081            public E getItem(I identifier) throws X {
082                    accessCounter++;
083    
084                    // check if item is cached
085                    SoftReference<E> ref = cache.get(getHashKey(identifier));
086                    if (ref == null) {
087                            return obtainUncachedItem(identifier);
088                    }
089    
090                    // item was cached
091                    E item = ref.get();
092    
093                    // check if item was garbage collected
094                    if (item == null) {
095                            return obtainGarbageCollectedItem(identifier);
096                    }
097    
098                    // return item
099                    hitCounter++;
100                    return item;
101            }
102    
103            /** Obtain (and cache) and item which was killed by the GC. */
104            private E obtainGarbageCollectedItem(I identifier) throws X {
105                    missBecauseRemovedFromCacheCounter++;
106                    E item = obtainItem(identifier);
107    
108                    // if null remove from cache, otherwise re-cache
109                    if (item == null) {
110                            cache.remove(getHashKey(identifier));
111                    } else {
112                            cache.put(getHashKey(identifier), new SoftReference<E>(item));
113                    }
114                    return item;
115            }
116    
117            /** Obtain (and cache) an uncached item. */
118            private E obtainUncachedItem(I identifier) throws X {
119                    E item = obtainItem(identifier);
120    
121                    // only cache if not null
122                    if (item != null) {
123                            cache.put(getHashKey(identifier), new SoftReference<E>(item));
124                            missBecauseNotInCacheCounter++;
125                    }
126                    return item;
127            }
128    
129            /**
130             * Returns a statistics string with information about cache hits and misses
131             * for debugging purposes. As this is implemented in a static way results
132             * are only valid if only a single instance of the specific cache class
133             * exists.
134             */
135            public static String getStatistics() {
136                    StringBuilder stats = new StringBuilder();
137                    stats.append("#access: " + accessCounter);
138                    stats.append(" #hits: " + hitCounter);
139                    stats.append(" #miss (not cached): " + missBecauseNotInCacheCounter);
140                    stats.append(" #miss (removed): " + missBecauseRemovedFromCacheCounter);
141                    return stats.toString();
142            }
143    }