001: /*
002: *
003: * JMoney - A Personal Finance Manager
004: * Copyright (c) 2007 Nigel Westbury <westbury@users.sourceforge.net>
005: *
006: *
007: * This program is free software; you can redistribute it and/or modify
008: * it under the terms of the GNU General Public License as published by
009: * the Free Software Foundation; either version 2 of the License, or
010: * (at your option) any later version.
011: *
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015: * GNU General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with this program; if not, write to the Free Software
019: * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
020: *
021: */
022:
023: package net.sf.jmoney.model2;
024:
025: import java.lang.ref.Reference;
026: import java.lang.ref.ReferenceQueue;
027: import java.lang.ref.WeakReference;
028: import java.util.HashMap;
029: import java.util.Map;
030:
031: public class WeakValuedMap<K, V> {
032:
033: /**
034: * We define our own subclass of WeakReference which contains not only the
035: * value but also the key to make it easier to find the entry in the HashMap
036: * after it's been garbage collected.
037: */
038: private static class WeakValueReference<K, V> extends
039: WeakReference<V> {
040: /**
041: * This key is always set when this weak reference is created and added
042: * to the map. However, there are two reasons why an entry may be
043: * removed from the map. An entry may be removed because there are no
044: * strong references to the value, but an entry may also be removed by
045: * an explicit call to the <code>remove</code> method. It is possible
046: * that an entry may be removed by a call to the <code>remove</code>
047: * method and then later the reference queue is processed and a second
048: * attempt is made to remove the entry. You may think this is not a
049: * problem because the attempt to remove the entry the second time will
050: * simply do nothing. However, it is possible that another entry has
051: * been put in the map by a call to the <code>put</code> method using
052: * the same key value. The queue processor would then be removing not a
053: * dead entry but an active entry.
054: *
055: * This can be solved as follows. If an entry is explicitly deleted from
056: * the map then the entry is removed from the map AND the key in this
057: * reference object is set to null.
058: */
059: private K key;
060:
061: private WeakValueReference(V value, K key,
062: ReferenceQueue<V> queue) {
063: super (value, queue);
064: this .key = key;
065: }
066: }
067:
068: private Map<K, WeakValueReference<K, V>> map = new HashMap<K, WeakValueReference<K, V>>();
069:
070: private ReferenceQueue<V> referenceQueue = new ReferenceQueue<V>();
071:
072: /**
073: *
074: * @param key the key which must not be null
075: * @param value the value to which a weak reference is maintained
076: */
077: public void put(K key, V value) {
078: removeDeadEntries();
079:
080: map.put(key, new WeakValueReference<K, V>(value, key,
081: referenceQueue));
082: }
083:
084: /**
085: *
086: * @param key
087: * @return the value if it is in the map, or null if
088: * either the key was not in the map or if the value
089: * had been garbage collected
090: */
091: public V get(K key) {
092: WeakReference<V> valueReference = map.get(key);
093: if (valueReference == null) {
094: return null;
095: } else {
096: return valueReference.get();
097: }
098: }
099:
100: public void remove(K key) {
101: /*
102: * This line is necessary to stop the reference queue processor from
103: * deleting an active entry if the same key was re-used for a new
104: * object.
105: */
106: map.get(key).key = null;
107:
108: map.remove(key);
109: }
110:
111: /**
112: * Check the queue for weak references to the value objects
113: * that no longer exist. It really does not matter where this is
114: * done as long as it is done on the thread for this class (this
115: * class is not thread-safe).
116: */
117: private void removeDeadEntries() {
118: Reference<? extends V> valueReference = referenceQueue.poll();
119: while (valueReference != null) {
120: Object key = ((WeakValueReference<?, ?>) valueReference).key;
121: if (key != null) {
122: map.remove(key);
123: }
124: valueReference = referenceQueue.poll();
125: }
126: }
127: }
|