• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 1998, 2006, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package sun.misc;
27 
28 import java.lang.ref.SoftReference;
29 import java.lang.ref.ReferenceQueue;
30 
31 import java.util.Iterator;
32 import java.util.Map;
33 import java.util.AbstractMap;
34 import java.util.HashMap;
35 import java.util.Set;
36 import java.util.AbstractSet;
37 import java.util.NoSuchElementException;
38 
39 
40 /**
41  * A memory-sensitive implementation of the <code>Map</code> interface.
42  *
43  * <p> A <code>SoftCache</code> object uses {@link java.lang.ref.SoftReference
44  * soft references} to implement a memory-sensitive hash map.  If the garbage
45  * collector determines at a certain point in time that a value object in a
46  * <code>SoftCache</code> entry is no longer strongly reachable, then it may
47  * remove that entry in order to release the memory occupied by the value
48  * object.  All <code>SoftCache</code> objects are guaranteed to be completely
49  * cleared before the virtual machine will throw an
50  * <code>OutOfMemoryError</code>.  Because of this automatic clearing feature,
51  * the behavior of this class is somewhat different from that of other
52  * <code>Map</code> implementations.
53  *
54  * <p> Both null values and the null key are supported.  This class has the
55  * same performance characteristics as the <code>HashMap</code> class, and has
56  * the same efficiency parameters of <em>initial capacity</em> and <em>load
57  * factor</em>.
58  *
59  * <p> Like most collection classes, this class is not synchronized.  A
60  * synchronized <code>SoftCache</code> may be constructed using the
61  * <code>Collections.synchronizedMap</code> method.
62  *
63  * <p> In typical usage this class will be subclassed and the <code>fill</code>
64  * method will be overridden.  When the <code>get</code> method is invoked on a
65  * key for which there is no mapping in the cache, it will in turn invoke the
66  * <code>fill</code> method on that key in an attempt to construct a
67  * corresponding value.  If the <code>fill</code> method returns such a value
68  * then the cache will be updated and the new value will be returned.  Thus,
69  * for example, a simple URL-content cache can be constructed as follows:
70  *
71  * <pre>
72  *     public class URLCache extends SoftCache {
73  *         protected Object fill(Object key) {
74  *             return ((URL)key).getContent();
75  *         }
76  *     }
77  * </pre>
78  *
79  * <p> The behavior of the <code>SoftCache</code> class depends in part upon
80  * the actions of the garbage collector, so several familiar (though not
81  * required) <code>Map</code> invariants do not hold for this class.  <p>
82  * Because entries are removed from a <code>SoftCache</code> in response to
83  * dynamic advice from the garbage collector, a <code>SoftCache</code> may
84  * behave as though an unknown thread is silently removing entries.  In
85  * particular, even if you synchronize on a <code>SoftCache</code> instance and
86  * invoke none of its mutator methods, it is possible for the <code>size</code>
87  * method to return smaller values over time, for the <code>isEmpty</code>
88  * method to return <code>false</code> and then <code>true</code>, for the
89  * <code>containsKey</code> method to return <code>true</code> and later
90  * <code>false</code> for a given key, for the <code>get</code> method to
91  * return a value for a given key but later return <code>null</code>, for the
92  * <code>put</code> method to return <code>null</code> and the
93  * <code>remove</code> method to return <code>false</code> for a key that
94  * previously appeared to be in the map, and for successive examinations of the
95  * key set, the value set, and the entry set to yield successively smaller
96  * numbers of elements.
97  *
98  * @author      Mark Reinhold
99  * @since       1.2
100  * @see         java.util.HashMap
101  * @see         java.lang.ref.SoftReference
102  */
103 
104 
105 public class SoftCache extends AbstractMap implements Map {
106 
107     /* The basic idea of this implementation is to maintain an internal HashMap
108        that maps keys to soft references whose referents are the keys' values;
109        the various accessor methods dereference these soft references before
110        returning values.  Because we don't have access to the innards of the
111        HashMap, each soft reference must contain the key that maps to it so
112        that the processQueue method can remove keys whose values have been
113        discarded.  Thus the HashMap actually maps keys to instances of the
114        ValueCell class, which is a simple extension of the SoftReference class.
115      */
116 
117 
118     static private class ValueCell extends SoftReference {
119         static private Object INVALID_KEY = new Object();
120         static private int dropped = 0;
121         private Object key;
122 
ValueCell(Object key, Object value, ReferenceQueue queue)123         private ValueCell(Object key, Object value, ReferenceQueue queue) {
124             super(value, queue);
125             this.key = key;
126         }
127 
create(Object key, Object value, ReferenceQueue queue)128         private static ValueCell create(Object key, Object value,
129                                         ReferenceQueue queue)
130         {
131             if (value == null) return null;
132             return new ValueCell(key, value, queue);
133         }
134 
strip(Object val, boolean drop)135         private static Object strip(Object val, boolean drop) {
136             if (val == null) return null;
137             ValueCell vc = (ValueCell)val;
138             Object o = vc.get();
139             if (drop) vc.drop();
140             return o;
141         }
142 
isValid()143         private boolean isValid() {
144             return (key != INVALID_KEY);
145         }
146 
drop()147         private void drop() {
148             super.clear();
149             key = INVALID_KEY;
150             dropped++;
151         }
152 
153     }
154 
155 
156     /* Hash table mapping keys to ValueCells */
157     private Map hash;
158 
159     /* Reference queue for cleared ValueCells */
160     private ReferenceQueue queue = new ReferenceQueue();
161 
162 
163     /* Process any ValueCells that have been cleared and enqueued by the
164        garbage collector.  This method should be invoked once by each public
165        mutator in this class.  We don't invoke this method in public accessors
166        because that can lead to surprising ConcurrentModificationExceptions.
167      */
processQueue()168     private void processQueue() {
169         ValueCell vc;
170         while ((vc = (ValueCell)queue.poll()) != null) {
171             if (vc.isValid()) hash.remove(vc.key);
172             else ValueCell.dropped--;
173         }
174     }
175 
176 
177     /* -- Constructors -- */
178 
179     /**
180      * Construct a new, empty <code>SoftCache</code> with the given
181      * initial capacity and the given load factor.
182      *
183      * @param  initialCapacity  The initial capacity of the cache
184      *
185      * @param  loadFactor       A number between 0.0 and 1.0
186      *
187      * @throws IllegalArgumentException  If the initial capacity is less than
188      *                                   or equal to zero, or if the load
189      *                                   factor is less than zero
190      */
SoftCache(int initialCapacity, float loadFactor)191     public SoftCache(int initialCapacity, float loadFactor) {
192         hash = new HashMap(initialCapacity, loadFactor);
193     }
194 
195     /**
196      * Construct a new, empty <code>SoftCache</code> with the given
197      * initial capacity and the default load factor.
198      *
199      * @param  initialCapacity  The initial capacity of the cache
200      *
201      * @throws IllegalArgumentException  If the initial capacity is less than
202      *                                   or equal to zero
203      */
SoftCache(int initialCapacity)204     public SoftCache(int initialCapacity) {
205         hash = new HashMap(initialCapacity);
206     }
207 
208     /**
209      * Construct a new, empty <code>SoftCache</code> with the default
210      * capacity and the default load factor.
211      */
SoftCache()212     public SoftCache() {
213         hash = new HashMap();
214     }
215 
216 
217     /* -- Simple queries -- */
218 
219     /**
220      * Return the number of key-value mappings in this cache.  The time
221      * required by this operation is linear in the size of the map.
222      */
size()223     public int size() {
224         return entrySet().size();
225     }
226 
227     /**
228      * Return <code>true</code> if this cache contains no key-value mappings.
229      */
isEmpty()230     public boolean isEmpty() {
231         return entrySet().isEmpty();
232     }
233 
234     /**
235      * Return <code>true</code> if this cache contains a mapping for the
236      * specified key.  If there is no mapping for the key, this method will not
237      * attempt to construct one by invoking the <code>fill</code> method.
238      *
239      * @param   key   The key whose presence in the cache is to be tested
240      */
containsKey(Object key)241     public boolean containsKey(Object key) {
242         return ValueCell.strip(hash.get(key), false) != null;
243     }
244 
245 
246     /* -- Lookup and modification operations -- */
247 
248     /**
249      * Create a value object for the given <code>key</code>.  This method is
250      * invoked by the <code>get</code> method when there is no entry for
251      * <code>key</code>.  If this method returns a non-<code>null</code> value,
252      * then the cache will be updated to map <code>key</code> to that value,
253      * and that value will be returned by the <code>get</code> method.
254      *
255      * <p> The default implementation of this method simply returns
256      * <code>null</code> for every <code>key</code> value.  A subclass may
257      * override this method to provide more useful behavior.
258      *
259      * @param  key  The key for which a value is to be computed
260      *
261      * @return      A value for <code>key</code>, or <code>null</code> if one
262      *              could not be computed
263      * @see #get
264      */
fill(Object key)265     protected Object fill(Object key) {
266         return null;
267     }
268 
269     /**
270      * Return the value to which this cache maps the specified
271      * <code>key</code>.  If the cache does not presently contain a value for
272      * this key, then invoke the <code>fill</code> method in an attempt to
273      * compute such a value.  If that method returns a non-<code>null</code>
274      * value, then update the cache and return the new value.  Otherwise,
275      * return <code>null</code>.
276      *
277      * <p> Note that because this method may update the cache, it is considered
278      * a mutator and may cause <code>ConcurrentModificationException</code>s to
279      * be thrown if invoked while an iterator is in use.
280      *
281      * @param  key  The key whose associated value, if any, is to be returned
282      *
283      * @see #fill
284      */
get(Object key)285     public Object get(Object key) {
286         processQueue();
287         Object v = hash.get(key);
288         if (v == null) {
289             v = fill(key);
290             if (v != null) {
291                 hash.put(key, ValueCell.create(key, v, queue));
292                 return v;
293             }
294         }
295         return ValueCell.strip(v, false);
296     }
297 
298     /**
299      * Update this cache so that the given <code>key</code> maps to the given
300      * <code>value</code>.  If the cache previously contained a mapping for
301      * <code>key</code> then that mapping is replaced and the old value is
302      * returned.
303      *
304      * @param  key    The key that is to be mapped to the given
305      *                <code>value</code>
306      * @param  value  The value to which the given <code>key</code> is to be
307      *                mapped
308      *
309      * @return  The previous value to which this key was mapped, or
310      *          <code>null</code> if if there was no mapping for the key
311      */
put(Object key, Object value)312     public Object put(Object key, Object value) {
313         processQueue();
314         ValueCell vc = ValueCell.create(key, value, queue);
315         return ValueCell.strip(hash.put(key, vc), true);
316     }
317 
318     /**
319      * Remove the mapping for the given <code>key</code> from this cache, if
320      * present.
321      *
322      * @param  key  The key whose mapping is to be removed
323      *
324      * @return  The value to which this key was mapped, or <code>null</code> if
325      *          there was no mapping for the key
326      */
remove(Object key)327     public Object remove(Object key) {
328         processQueue();
329         return ValueCell.strip(hash.remove(key), true);
330     }
331 
332     /**
333      * Remove all mappings from this cache.
334      */
clear()335     public void clear() {
336         processQueue();
337         hash.clear();
338     }
339 
340 
341     /* -- Views -- */
342 
valEquals(Object o1, Object o2)343     private static boolean valEquals(Object o1, Object o2) {
344         return (o1 == null) ? (o2 == null) : o1.equals(o2);
345     }
346 
347 
348     /* Internal class for entries.
349        Because it uses SoftCache.this.queue, this class cannot be static.
350      */
351     private class Entry implements Map.Entry {
352         private Map.Entry ent;
353         private Object value;   /* Strong reference to value, to prevent the GC
354                                    from flushing the value while this Entry
355                                    exists */
356 
Entry(Map.Entry ent, Object value)357         Entry(Map.Entry ent, Object value) {
358             this.ent = ent;
359             this.value = value;
360         }
361 
getKey()362         public Object getKey() {
363             return ent.getKey();
364         }
365 
getValue()366         public Object getValue() {
367             return value;
368         }
369 
setValue(Object value)370         public Object setValue(Object value) {
371             return ent.setValue(ValueCell.create(ent.getKey(), value, queue));
372         }
373 
equals(Object o)374         public boolean equals(Object o) {
375             if (! (o instanceof Map.Entry)) return false;
376             Map.Entry e = (Map.Entry)o;
377             return (valEquals(ent.getKey(), e.getKey())
378                     && valEquals(value, e.getValue()));
379         }
380 
hashCode()381         public int hashCode() {
382             Object k;
383             return ((((k = getKey()) == null) ? 0 : k.hashCode())
384                     ^ ((value == null) ? 0 : value.hashCode()));
385         }
386 
387     }
388 
389 
390     /* Internal class for entry sets */
391     private class EntrySet extends AbstractSet {
392         Set hashEntries = hash.entrySet();
393 
iterator()394         public Iterator iterator() {
395 
396             return new Iterator() {
397                 Iterator hashIterator = hashEntries.iterator();
398                 Entry next = null;
399 
400                 public boolean hasNext() {
401                     while (hashIterator.hasNext()) {
402                         Map.Entry ent = (Map.Entry)hashIterator.next();
403                         ValueCell vc = (ValueCell)ent.getValue();
404                         Object v = null;
405                         if ((vc != null) && ((v = vc.get()) == null)) {
406                             /* Value has been flushed by GC */
407                             continue;
408                         }
409                         next = new Entry(ent, v);
410                         return true;
411                     }
412                     return false;
413                 }
414 
415                 public Object next() {
416                     if ((next == null) && !hasNext())
417                         throw new NoSuchElementException();
418                     Entry e = next;
419                     next = null;
420                     return e;
421                 }
422 
423                 public void remove() {
424                     hashIterator.remove();
425                 }
426 
427             };
428         }
429 
isEmpty()430         public boolean isEmpty() {
431             return !(iterator().hasNext());
432         }
433 
size()434         public int size() {
435             int j = 0;
436             for (Iterator i = iterator(); i.hasNext(); i.next()) j++;
437             return j;
438         }
439 
remove(Object o)440         public boolean remove(Object o) {
441             processQueue();
442             if (o instanceof Entry) return hashEntries.remove(((Entry)o).ent);
443             else return false;
444         }
445 
446     }
447 
448 
449     private Set entrySet = null;
450 
451     /**
452      * Return a <code>Set</code> view of the mappings in this cache.
453      */
entrySet()454     public Set entrySet() {
455         if (entrySet == null) entrySet = new EntrySet();
456         return entrySet;
457     }
458 
459 }
460