View Javadoc

1   /* Copyright (2007-2008) Schibsted Søk AS
2    * This file is part of Sesat Commons.
3    *
4    *   Sesat Commons is free software: you can redistribute it and/or modify
5    *   it under the terms of the GNU Lesser General Public License as published by
6    *   the Free Software Foundation, either version 3 of the License, or
7    *   (at your option) any later version.
8    *
9    *   Sesat Commons is distributed in the hope that it will be useful,
10   *   but WITHOUT ANY WARRANTY; without even the implied warranty of
11   *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   *   GNU Lesser General Public License for more details.
13   *
14   *   You should have received a copy of the GNU Lesser General Public License
15   *   along with Sesat Commons.  If not, see <http://www.gnu.org/licenses/>.
16   *
17   */
18  package no.sesat.commons.ref;
19  
20  import java.lang.ref.*;
21  import java.util.Map;
22  
23  import org.apache.log4j.Logger;
24  
25  /** Provides a Map where values are referenced either weakly or softly.
26   * This can be used to cache immutable objects without preventing them from bing garbaged collected.    <br/><br/>
27   *
28   * Concurrency and synchronisation of this class fallbacks to the Map implementation passed in as the cache parameter
29   *  in the constructor. For example: use a Hashtable for synchronised access, a ConcurrentHashMap for concurrent access,
30   *  and a HashMap for single threaded access (make sure to specify the argument indicating single thread use).
31   *
32   * <br/><br/>
33   *
34   * This implementation improves over org.apache.commons.collections.map.ReferenceMap in that the synchronisation and
35   *  concurrency is determined through delegation to the map supplied in the constructor as described above.   <br/><br/>
36   *
37   * @param <K> key type
38   * @param <V> value type
39   * @version $Id: ReferenceMap.java 1127 2009-01-21 16:16:08Z ssmiweve $
40   */
41  public final class ReferenceMap<K,V extends Object> {
42  
43      public enum Type {
44          WEAK,
45          SOFT;
46  
47          <K,V> Reference<V> createReference(
48                  final ReferenceMap<K,V> cache,
49                  final K key,
50                  final V value){
51  
52              switch(this){
53                  case WEAK:
54                      return cache.new WeakReference(key, value);
55                  case SOFT:
56                      return cache.new SoftReference(key, value);
57              }
58              throw new IllegalStateException("Please implement createReference(..) for " + toString());
59          }
60      }
61  
62      // Constants -----------------------------------------------------
63  
64      private static final Logger LOG = Logger.getLogger(ReferenceMap.class);
65  
66      // Attributes ----------------------------------------------------
67  
68      private final Type type;
69      private final Map<K,Reference<V>> cache;
70      private final boolean singleThreaded;
71  
72      // Static --------------------------------------------------------
73  
74      private static final ReferenceQueue<Object> queue = new ReferenceQueue<Object>();
75      private static final ReferenceCleaner cleaner = new ReferenceCleaner(queue);
76  
77      static {
78          cleaner.start();
79      }
80  
81      // Constructors --------------------------------------------------
82  
83      /**
84       *
85       * @param type
86       * @param cache
87       */
88      public ReferenceMap(final Type type, final Map<K,Reference<V>> cache) {
89          this(type, cache, false);
90      }
91  
92      /**
93       *
94       * @param type
95       * @param cache
96       * @param singleThreaded
97       */
98      public ReferenceMap(final Type type, final Map<K,Reference<V>> cache, final boolean singleThreaded) {
99          this.type = type;
100         this.cache = cache;
101         this.singleThreaded = singleThreaded;
102     }
103 
104     // Public --------------------------------------------------------
105 
106     /**
107      *
108      * @param key
109      * @param value
110      * @return
111      */
112     public V put(final K key, final V value) {
113         // log cache size every 100 increments
114         if(LOG.isDebugEnabled() && cache.size() % 100 == 0){
115             LOG.debug(value.getClass().getSimpleName() + " cache size is "  + cache.size());
116         }
117 
118         // clean if in single threaded mode
119         if(singleThreaded){
120             Reference<?> reference = (Reference<?>)queue.poll();
121             while(null != reference){
122                 reference.clear();
123                 reference = (Reference<?>)queue.poll();
124             }
125         }
126 
127         final Reference<V> change = cache.put(key, type.createReference(this, key, value));
128         return null != change ? change.get() : null;
129     }
130 
131     /**
132      *
133      * @param key
134      * @return
135      */
136     public V get(final K key){
137 
138         final Reference<V> reference = cache.get(key);
139         return null != reference ? reference.get() : null;
140     }
141 
142     @Override
143     public String toString() {
144         return super.toString() + " Type:" + type + " Size:" + cache.size();
145     }
146 
147     // Package protected ---------------------------------------------
148 
149     // Protected -----------------------------------------------------
150 
151     // Private -------------------------------------------------------
152 
153     // Inner classes -------------------------------------------------
154 
155     private final class WeakReference extends java.lang.ref.WeakReference<V>{
156 
157         private K key;
158 
159         WeakReference(final K key, final V value){
160 
161             super(value, queue);
162             this.key = key;
163         }
164 
165         @Override
166         public void clear() {
167 
168             // clear the hashmap entry too.
169             cache.remove(key);
170             key = null;
171 
172             // clear the referent
173             super.clear();
174         }
175     }
176 
177     private final class SoftReference extends java.lang.ref.SoftReference<V>{
178 
179         private K key;
180 
181         SoftReference(final K key, final V value){
182 
183             super(value, queue);
184             this.key = key;
185         }
186 
187         @Override
188         public void clear() {
189 
190             // clear the hashmap entry too.
191             cache.remove(key);
192             key = null;
193 
194             // clear the referent
195             super.clear();
196         }
197     }
198 
199     private final static class ReferenceCleaner extends Thread{
200 
201         private final ReferenceQueue<?> queue;
202 
203         ReferenceCleaner(ReferenceQueue<?> queue){
204             super("ReferenceMap.ReferenceCleaner");
205             this.queue = queue;
206             setPriority(Thread.MAX_PRIORITY);
207             setDaemon(true);
208         }
209 
210         @Override
211         public void run() {
212             while(true){
213                 try {
214                     queue.remove().clear();
215                 }catch (InterruptedException ex) {
216                     LOG.error(ex.getMessage(), ex);
217                 }
218             }
219         }
220     }
221 }