Path: blob/master/src/java.desktop/share/classes/sun/awt/SoftCache.java
41152 views
/*1* Copyright (c) 1998, 2014, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.awt;2627import java.lang.ref.SoftReference;28import java.lang.ref.ReferenceQueue;2930import java.util.Iterator;31import java.util.Map;32import java.util.AbstractMap;33import java.util.HashMap;34import java.util.Set;35import java.util.AbstractSet;36import java.util.NoSuchElementException;373839/**40* A memory-sensitive implementation of the <code>Map</code> interface.41*42* <p> A <code>SoftCache</code> object uses {@link java.lang.ref.SoftReference43* soft references} to implement a memory-sensitive hash map. If the garbage44* collector determines at a certain point in time that a value object in a45* <code>SoftCache</code> entry is no longer strongly reachable, then it may46* remove that entry in order to release the memory occupied by the value47* object. All <code>SoftCache</code> objects are guaranteed to be completely48* cleared before the virtual machine will throw an49* <code>OutOfMemoryError</code>. Because of this automatic clearing feature,50* the behavior of this class is somewhat different from that of other51* <code>Map</code> implementations.52*53* <p> Both null values and the null key are supported. This class has the54* same performance characteristics as the <code>HashMap</code> class, and has55* the same efficiency parameters of <em>initial capacity</em> and <em>load56* factor</em>.57*58* <p> Like most collection classes, this class is not synchronized. A59* synchronized <code>SoftCache</code> may be constructed using the60* <code>Collections.synchronizedMap</code> method.61*62* <p> In typical usage this class will be subclassed and the <code>fill</code>63* method will be overridden. When the <code>get</code> method is invoked on a64* key for which there is no mapping in the cache, it will in turn invoke the65* <code>fill</code> method on that key in an attempt to construct a66* corresponding value. If the <code>fill</code> method returns such a value67* then the cache will be updated and the new value will be returned. Thus,68* for example, a simple URL-content cache can be constructed as follows:69*70* <pre>71* public class URLCache extends SoftCache {72* protected Object fill(Object key) {73* return ((URL)key).getContent();74* }75* }76* </pre>77*78* <p> The behavior of the <code>SoftCache</code> class depends in part upon79* the actions of the garbage collector, so several familiar (though not80* required) <code>Map</code> invariants do not hold for this class. <p>81* Because entries are removed from a <code>SoftCache</code> in response to82* dynamic advice from the garbage collector, a <code>SoftCache</code> may83* behave as though an unknown thread is silently removing entries. In84* particular, even if you synchronize on a <code>SoftCache</code> instance and85* invoke none of its mutator methods, it is possible for the <code>size</code>86* method to return smaller values over time, for the <code>isEmpty</code>87* method to return <code>false</code> and then <code>true</code>, for the88* <code>containsKey</code> method to return <code>true</code> and later89* <code>false</code> for a given key, for the <code>get</code> method to90* return a value for a given key but later return <code>null</code>, for the91* <code>put</code> method to return <code>null</code> and the92* <code>remove</code> method to return <code>false</code> for a key that93* previously appeared to be in the map, and for successive examinations of the94* key set, the value set, and the entry set to yield successively smaller95* numbers of elements.96*97* @author Mark Reinhold98* @since 1.299* @see java.util.HashMap100* @see java.lang.ref.SoftReference101* @deprecated No direct replacement; {@link java.util.WeakHashMap}102* addresses a related by different use-case.103*/104105@Deprecated106public class SoftCache extends AbstractMap<Object, Object> implements Map<Object, Object> {107108/* The basic idea of this implementation is to maintain an internal HashMap109that maps keys to soft references whose referents are the keys' values;110the various accessor methods dereference these soft references before111returning values. Because we don't have access to the innards of the112HashMap, each soft reference must contain the key that maps to it so113that the processQueue method can remove keys whose values have been114discarded. Thus the HashMap actually maps keys to instances of the115ValueCell class, which is a simple extension of the SoftReference class.116*/117118119private static class ValueCell extends SoftReference<Object> {120private static Object INVALID_KEY = new Object();121private static int dropped = 0;122private Object key;123124private ValueCell(Object key, Object value, ReferenceQueue<Object> queue) {125super(value, queue);126this.key = key;127}128129private static ValueCell create(Object key, Object value,130ReferenceQueue<Object> queue)131{132if (value == null) return null;133return new ValueCell(key, value, queue);134}135136private static Object strip(Object val, boolean drop) {137if (val == null) return null;138ValueCell vc = (ValueCell)val;139Object o = vc.get();140if (drop) vc.drop();141return o;142}143144private boolean isValid() {145return (key != INVALID_KEY);146}147148private void drop() {149super.clear();150key = INVALID_KEY;151dropped++;152}153154}155156157/* Hash table mapping keys to ValueCells */158private Map<Object, Object> hash;159160/* Reference queue for cleared ValueCells */161private ReferenceQueue<Object> queue = new ReferenceQueue<>();162163164/* Process any ValueCells that have been cleared and enqueued by the165garbage collector. This method should be invoked once by each public166mutator in this class. We don't invoke this method in public accessors167because that can lead to surprising ConcurrentModificationExceptions.168*/169private void processQueue() {170ValueCell vc;171while ((vc = (ValueCell)queue.poll()) != null) {172if (vc.isValid()) hash.remove(vc.key);173else ValueCell.dropped--;174}175}176177178/* -- Constructors -- */179180/**181* Construct a new, empty <code>SoftCache</code> with the given182* initial capacity and the given load factor.183*184* @param initialCapacity The initial capacity of the cache185*186* @param loadFactor A number between 0.0 and 1.0187*188* @throws IllegalArgumentException If the initial capacity is less than189* or equal to zero, or if the load190* factor is less than zero191*/192public SoftCache(int initialCapacity, float loadFactor) {193hash = new HashMap<>(initialCapacity, loadFactor);194}195196/**197* Construct a new, empty <code>SoftCache</code> with the given198* initial capacity and the default load factor.199*200* @param initialCapacity The initial capacity of the cache201*202* @throws IllegalArgumentException If the initial capacity is less than203* or equal to zero204*/205public SoftCache(int initialCapacity) {206hash = new HashMap<>(initialCapacity);207}208209/**210* Construct a new, empty <code>SoftCache</code> with the default211* capacity and the default load factor.212*/213public SoftCache() {214hash = new HashMap<>();215}216217218/* -- Simple queries -- */219220/**221* Return the number of key-value mappings in this cache. The time222* required by this operation is linear in the size of the map.223*/224public int size() {225return entrySet().size();226}227228/**229* Return <code>true</code> if this cache contains no key-value mappings.230*/231public boolean isEmpty() {232return entrySet().isEmpty();233}234235/**236* Return <code>true</code> if this cache contains a mapping for the237* specified key. If there is no mapping for the key, this method will not238* attempt to construct one by invoking the <code>fill</code> method.239*240* @param key The key whose presence in the cache is to be tested241*/242public boolean containsKey(Object key) {243return ValueCell.strip(hash.get(key), false) != null;244}245246247/* -- Lookup and modification operations -- */248249/**250* Create a value object for the given <code>key</code>. This method is251* invoked by the <code>get</code> method when there is no entry for252* <code>key</code>. If this method returns a non-<code>null</code> value,253* then the cache will be updated to map <code>key</code> to that value,254* and that value will be returned by the <code>get</code> method.255*256* <p> The default implementation of this method simply returns257* <code>null</code> for every <code>key</code> value. A subclass may258* override this method to provide more useful behavior.259*260* @param key The key for which a value is to be computed261*262* @return A value for <code>key</code>, or <code>null</code> if one263* could not be computed264* @see #get265*/266protected Object fill(Object key) {267return null;268}269270/**271* Return the value to which this cache maps the specified272* <code>key</code>. If the cache does not presently contain a value for273* this key, then invoke the <code>fill</code> method in an attempt to274* compute such a value. If that method returns a non-<code>null</code>275* value, then update the cache and return the new value. Otherwise,276* return <code>null</code>.277*278* <p> Note that because this method may update the cache, it is considered279* a mutator and may cause <code>ConcurrentModificationException</code>s to280* be thrown if invoked while an iterator is in use.281*282* @param key The key whose associated value, if any, is to be returned283*284* @see #fill285*/286public Object get(Object key) {287processQueue();288Object v = hash.get(key);289if (v == null) {290v = fill(key);291if (v != null) {292hash.put(key, ValueCell.create(key, v, queue));293return v;294}295}296return ValueCell.strip(v, false);297}298299/**300* Update this cache so that the given <code>key</code> maps to the given301* <code>value</code>. If the cache previously contained a mapping for302* <code>key</code> then that mapping is replaced and the old value is303* returned.304*305* @param key The key that is to be mapped to the given306* <code>value</code>307* @param value The value to which the given <code>key</code> is to be308* mapped309*310* @return The previous value to which this key was mapped, or311* <code>null</code> if there was no mapping for the key312*/313public Object put(Object key, Object value) {314processQueue();315ValueCell vc = ValueCell.create(key, value, queue);316return ValueCell.strip(hash.put(key, vc), true);317}318319/**320* Remove the mapping for the given <code>key</code> from this cache, if321* present.322*323* @param key The key whose mapping is to be removed324*325* @return The value to which this key was mapped, or <code>null</code> if326* there was no mapping for the key327*/328public Object remove(Object key) {329processQueue();330return ValueCell.strip(hash.remove(key), true);331}332333/**334* Remove all mappings from this cache.335*/336public void clear() {337processQueue();338hash.clear();339}340341342/* -- Views -- */343344private static boolean valEquals(Object o1, Object o2) {345return (o1 == null) ? (o2 == null) : o1.equals(o2);346}347348349/* Internal class for entries.350Because it uses SoftCache.this.queue, this class cannot be static.351*/352private class Entry implements Map.Entry<Object, Object> {353private Map.Entry<Object, Object> ent;354private Object value; /* Strong reference to value, to prevent the GC355from flushing the value while this Entry356exists */357358Entry(Map.Entry<Object, Object> ent, Object value) {359this.ent = ent;360this.value = value;361}362363public Object getKey() {364return ent.getKey();365}366367public Object getValue() {368return value;369}370371public Object setValue(Object value) {372return ent.setValue(ValueCell.create(ent.getKey(), value, queue));373}374375@SuppressWarnings("unchecked")376public boolean equals(Object o) {377if (! (o instanceof Map.Entry)) return false;378Map.Entry<Object, Object> e = (Map.Entry<Object, Object>)o;379return (valEquals(ent.getKey(), e.getKey())380&& valEquals(value, e.getValue()));381}382383public int hashCode() {384Object k;385return ((((k = getKey()) == null) ? 0 : k.hashCode())386^ ((value == null) ? 0 : value.hashCode()));387}388389}390391392/* Internal class for entry sets */393private class EntrySet extends AbstractSet<Map.Entry<Object, Object>> {394Set<Map.Entry<Object, Object>> hashEntries = hash.entrySet();395396public Iterator<Map.Entry<Object, Object>> iterator() {397398return new Iterator<Map.Entry<Object, Object>>() {399Iterator<Map.Entry<Object, Object>> hashIterator = hashEntries.iterator();400Entry next = null;401402public boolean hasNext() {403while (hashIterator.hasNext()) {404Map.Entry<Object, Object> ent = hashIterator.next();405ValueCell vc = (ValueCell)ent.getValue();406Object v = null;407if ((vc != null) && ((v = vc.get()) == null)) {408/* Value has been flushed by GC */409continue;410}411next = new Entry(ent, v);412return true;413}414return false;415}416417public Map.Entry<Object, Object> next() {418if ((next == null) && !hasNext())419throw new NoSuchElementException();420Entry e = next;421next = null;422return e;423}424425public void remove() {426hashIterator.remove();427}428429};430}431432public boolean isEmpty() {433return !(iterator().hasNext());434}435436public int size() {437int j = 0;438for (Iterator<Map.Entry<Object, Object>> i = iterator(); i.hasNext(); i.next()) j++;439return j;440}441442public boolean remove(Object o) {443processQueue();444if (o instanceof Entry) return hashEntries.remove(((Entry)o).ent);445else return false;446}447448}449450451private Set<Map.Entry<Object, Object>> entrySet = null;452453/**454* Return a <code>Set</code> view of the mappings in this cache.455*/456public Set<Map.Entry<Object, Object>> entrySet() {457if (entrySet == null) entrySet = new EntrySet();458return entrySet;459}460461}462463464