View Javadoc

1   /**
2    * This class was taken from<br/>
3    * <a href="http://www.gruntz.ch/papers/references/SoftHashMap.java">SoftHashMap (Java Code)</a><br/>
4    * <a href="http://www.gruntz.ch/papers/references/">References API (PDF Artikel)</a><br/>
5    * by Dominik Gruntz, Fachhochschule Aargau/Nordwestschweiz<br/>
6    * @author <a href="mailto:d.gruntz@fh-aargau.ch">Dominik Gruntz</a>
7    */
8   package junitx.ddtunit.util;
9   
10  import java.lang.ref.Reference;
11  import java.lang.ref.ReferenceQueue;
12  import java.lang.ref.SoftReference;
13  import java.util.AbstractMap;
14  import java.util.AbstractSet;
15  import java.util.HashMap;
16  import java.util.Iterator;
17  import java.util.LinkedList;
18  import java.util.Map;
19  import java.util.NoSuchElementException;
20  import java.util.Set;
21  
22  public class SoftHashMap<K, V> extends AbstractMap<K, V> {
23      private Map<K, Reference> innerMap = null;
24  
25      private ReferenceQueue refQueue = new ReferenceQueue();
26  
27      /** The number of "hard" references to hold internally. */
28      private final int HARD_SIZE;
29  
30      /** The FIFO list of hard references, order of last access. */
31      private final LinkedList hardCache = new LinkedList();
32  
33      public SoftHashMap() {
34          innerMap = new HashMap<K, Reference>();
35          this.HARD_SIZE = 100;
36      }
37  
38      public SoftHashMap(int initialCapacity) {
39          innerMap = new HashMap<K, Reference>(initialCapacity);
40          this.HARD_SIZE = initialCapacity;
41      }
42  
43      public SoftHashMap(int initialCapacity, float loadFactor) {
44          innerMap = new HashMap<K, Reference>(initialCapacity, loadFactor);
45          this.HARD_SIZE = initialCapacity;
46      }
47  
48      public Object get(String key) {
49          Reference res = innerMap.get(key);
50          Object result = null;
51          // We get the SoftReference represented by that key
52          if (res != null) {
53              // From the SoftReference we get the value, which can be
54              // null if it was not in the map, or it was removed in
55              // the processQueue() method defined below
56              result = ((Reference) res).get();
57              if (result == null) {
58                  // If the value has been garbage collected, remove the
59                  // entry from the HashMap.
60                  innerMap.remove(key);
61              } else {
62                  // We now add this object to the beginning of the hard
63                  // reference queue. One reference can occur more than
64                  // once, because lookups of the FIFO queue are slow, so
65                  // we don't want to search through it each time to remove
66                  // duplicates.
67                  hardCache.addFirst(result);
68                  if (hardCache.size() > HARD_SIZE) {
69                      // Remove the last entry if list longer than HARD_SIZE
70                      hardCache.removeLast();
71                  }
72              }
73          }
74          return result;
75      }
76  
77      public V put(K key, V value) {
78          processQueue();
79          Reference ref = (Reference) new SoftEntry<K>(key, value, refQueue);
80          Reference res = innerMap.put(key, ref);
81          return res == null ? null : (V) res.get();
82      }
83  
84      private Set entrySet = null;
85  
86      public Set entrySet() {
87          if (entrySet == null)
88              entrySet = new EntrySet();
89          return entrySet;
90      }
91  
92      private void processQueue() {
93          Reference ref;
94          while ((ref = refQueue.poll()) != null) {
95              SoftEntry sEntry = (SoftEntry) ref;
96              innerMap.remove(sEntry.key);
97          }
98      }
99  
100     public int size() {
101         processQueue();
102         // return entrySet().size();
103         return innerMap.size();
104     }
105 
106     public V remove(Object key) {
107         processQueue();
108         Reference res = innerMap.remove(key);
109         return res == null ? null : (V) res.get();
110     }
111 
112     public void clear() {
113         hardCache.clear();
114         processQueue();
115         innerMap.clear();
116     }
117 
118     private static class SoftEntry<K> extends SoftReference {
119         private K key; // neccessary so that freed objects can be removed
120 
121         private SoftEntry(K key, Object value, ReferenceQueue queue) {
122             super(value, queue);
123             this.key = key;
124         }
125     }
126 
127     private class EntrySet extends AbstractSet {
128         private Set entrySet = innerMap.entrySet();
129 
130         public int size() {
131             int s = 0;
132             for (Iterator iter = iterator(); iter.hasNext(); iter.next())
133                 s++;
134             return s;
135         }
136 
137         public boolean isEmpty() {
138             // default implementation computes size which is inefficient
139             return !(iterator().hasNext());
140         }
141 
142         public boolean remove(Object o) {
143             processQueue();
144             return super.remove(o);
145         }
146 
147         public Iterator iterator() {
148 
149             return new Iterator() {
150                 Iterator it = entrySet.iterator();
151 
152                 Entry next = null;
153 
154                 Object value = null;
155 
156                 /*
157                  * Strong reference to key, so that the GC will leave it alone
158                  * as long as this Entry exists
159                  */
160 
161                 public boolean hasNext() {
162                     while (it.hasNext()) {
163                         final Entry entry = (Entry) it.next();
164                         SoftEntry se = (SoftEntry) entry.getValue();
165                         value = null;
166                         if ((se != null) && ((value = se.get()) == null)) {
167                             /* Weak key has been cleared by GC */
168                             continue;
169                         }
170                         next = new Map.Entry() {
171                             public Object getKey() {
172                                 return entry.getKey();
173                             }
174 
175                             public Object getValue() {
176                                 return value;
177                             }
178 
179                             public Object setValue(Object v) {
180                                 Object res = value;
181                                 value = v;
182                                 entry.setValue(new SoftEntry((String) entry
183                                     .getKey(), value, refQueue));
184                                 return res;
185                             }
186 
187                             public boolean equals(Object x) {
188                                 if (!(x instanceof Map.Entry))
189                                     return false;
190                                 Map.Entry e = (Map.Entry) x;
191                                 Object key = getKey();
192                                 return key == null ? e.getKey() == null : key
193                                     .equals(e.getKey())
194                                         && value == null ? e.getValue() == null
195                                         : value.equals(e.getValue());
196                             }
197 
198                             public int hashCode() {
199                                 Object key = getKey();
200                                 return (((key == null) ? 0 : key.hashCode()) ^ ((value == null) ? 0
201                                         : value.hashCode()));
202                             }
203 
204                         };
205                         return true;
206                     }
207                     return false;
208                 }
209 
210                 public Object next() {
211                     if ((next == null) && !hasNext())
212                         throw new NoSuchElementException();
213                     Entry e = next;
214                     next = null;
215                     return e;
216                 }
217 
218                 public void remove() {
219                     it.remove();
220                 }
221 
222             };
223         }
224     }
225 }