Fork of Poseidon providing Bukkit #1060 to older Beta versions (b1.0-b1.7.3)
1package org.bukkit.craftbukkit.util;
2
3import com.google.common.collect.MapMaker;
4
5import java.lang.ref.ReferenceQueue;
6import java.lang.ref.SoftReference;
7import java.util.*;
8import java.util.concurrent.ConcurrentHashMap;
9
10/**
11 * Creates a map that uses soft reference. This indicates to the garbage collector
12 * that they can be removed if necessary
13 * <p>
14 * A minimum number of strong references can be set. These most recent N objects added
15 * to the map will not be removed by the garbage collector.
16 * <p>
17 * Objects will never be removed if they are referenced strongly from somewhere else
18 * <p>
19 * Note: While data corruption won't happen, the garbage collector is potentially async
20 * This could lead to the return values from containsKey() and similar methods being
21 * out of date by the time they are used. The class could return null when the object
22 * is retrieved by a .get() call directly after a .containsKey() call returned true
23 *
24 * @author raphfrk
25 * @deprecated Use {@link MapMaker} to create a concurrent soft-reference map, this class is inefficient and will be removed
26 */
27
28@Deprecated
29public class ConcurrentSoftMap<K, V> {
30
31 private final ConcurrentHashMap<K, SoftMapReference<K, V>> map = new ConcurrentHashMap<K, SoftMapReference<K, V>>();
32 private final ReferenceQueue<SoftMapReference> queue = new ReferenceQueue<SoftMapReference>();
33 private final LinkedList<V> strongReferenceQueue = new LinkedList<V>();
34 private final int strongReferenceSize;
35
36 public ConcurrentSoftMap() {
37 this(20);
38 }
39
40 public ConcurrentSoftMap(int size) {
41 strongReferenceSize = size;
42 }
43
44 // When a soft reference is deleted by the garbage collector, it is set to reference null
45 // and added to the queue
46 //
47 // However, these null references still exist in the ConcurrentHashMap as keys. This method removes these keys.
48 //
49 // It is called whenever there is a method call of the map.
50
51 private void emptyQueue() {
52 SoftMapReference ref;
53
54 while ((ref = (SoftMapReference) queue.poll()) != null) {
55 map.remove(ref.key);
56 }
57 }
58
59 public void clear() {
60 synchronized (strongReferenceQueue) {
61 strongReferenceQueue.clear();
62 }
63 map.clear();
64 emptyQueue();
65 }
66
67 // Shouldn't support this, since the garbage collection is async
68
69 public boolean containsKey(K key) {
70 emptyQueue();
71 return map.containsKey(key);
72 }
73
74 // Shouldn't support this, since the garbage collection is async
75
76 public boolean containsValue(V value) {
77 emptyQueue();
78 return map.containsValue(value);
79 }
80
81 // Shouldn't support this since it would create strong references to all the entries
82
83 public Set entrySet() {
84 emptyQueue();
85 throw new UnsupportedOperationException("SoftMap does not support this operation, since it creates potentially stong references");
86 }
87
88 // Doesn't support these either
89
90 public boolean equals(Object o) {
91 emptyQueue();
92 throw new UnsupportedOperationException("SoftMap doesn't support equals checks");
93 }
94
95 // This operation returns null if the entry is not in the map
96
97 public V get(K key) {
98 emptyQueue();
99 return fastGet(key);
100 }
101
102 private V fastGet(K key) {
103 SoftMapReference<K, V> ref = map.get(key);
104
105 if (ref == null) {
106 return null;
107 }
108 V value = ref.get();
109
110 if (value != null) {
111 synchronized (strongReferenceQueue) {
112 strongReferenceQueue.addFirst(value);
113 if (strongReferenceQueue.size() > strongReferenceSize) {
114 strongReferenceQueue.removeLast();
115 }
116 }
117 }
118 return value;
119 }
120
121 // Doesn't support this either
122
123 public int hashCode() {
124 emptyQueue();
125 throw new UnsupportedOperationException("SoftMap doesn't support hashCode");
126 }
127
128 // This is another risky method, since again, garbage collection is async
129
130 public boolean isEmpty() {
131 emptyQueue();
132 return map.isEmpty();
133 }
134
135 // Return all the keys, again could go out of date
136
137 public Set keySet() {
138 emptyQueue();
139 return map.keySet();
140 }
141
142 // Adds the mapping to the map
143
144 public V put(K key, V value) {
145 emptyQueue();
146 V old = fastGet(key);
147 fastPut(key, value);
148 return old;
149 }
150
151 private void fastPut(K key, V value) {
152 map.put(key, new SoftMapReference<K, V>(key, value, queue));
153 synchronized (strongReferenceQueue) {
154 strongReferenceQueue.addFirst(value);
155 if (strongReferenceQueue.size() > strongReferenceSize) {
156 strongReferenceQueue.removeLast();
157 }
158 }
159 }
160
161 public V putIfAbsent(K key, V value) {
162 emptyQueue();
163 return fastPutIfAbsent(key, value);
164 }
165
166 private V fastPutIfAbsent(K key, V value) {
167 V ret = null;
168
169 if (map.containsKey(key)) {
170 SoftMapReference<K, V> current = map.get(key);
171
172 if (current != null) {
173 ret = current.get();
174 }
175 }
176
177 if (ret == null) {
178 SoftMapReference<K, V> newValue = new SoftMapReference<K, V>(key, value, queue);
179 boolean success = false;
180
181 while (!success) {
182 SoftMapReference<K, V> oldValue = map.putIfAbsent(key, newValue);
183
184 if (oldValue == null) { // put was successful (key didn't exist)
185 ret = null;
186 success = true;
187 } else {
188 ret = oldValue.get();
189 if (ret == null) { // key existed, but referenced null
190 success = map.replace(key, oldValue, newValue); // try to swap old for new
191 } else { // key existed, and referenced a valid object
192 success = true;
193 }
194 }
195 }
196 }
197
198 if (ret == null) {
199 synchronized (strongReferenceQueue) {
200 strongReferenceQueue.addFirst(value);
201 if (strongReferenceQueue.size() > strongReferenceSize) {
202 strongReferenceQueue.removeLast();
203 }
204 }
205 }
206
207 return ret;
208 }
209
210 // Adds the mappings to the map
211
212 public void putAll(Map other) {
213 emptyQueue();
214 Iterator<K> itr = other.keySet().iterator();
215 while (itr.hasNext()) {
216 K key = itr.next();
217 fastPut(key, (V) other.get(key));
218 }
219 }
220
221 // Remove object
222
223 public V remove(K key) {
224 emptyQueue();
225 SoftMapReference<K, V> ref = map.remove(key);
226
227 if (ref != null) {
228 return ref.get();
229 }
230 return null;
231 }
232
233 // Returns size, could go out of date
234
235 public int size() {
236 emptyQueue();
237 return map.size();
238 }
239
240 // Shouldn't support this since it would create strong references to all the entries
241
242 public Collection values() {
243 emptyQueue();
244 throw new UnsupportedOperationException("SoftMap does not support this operation, since it creates potentially stong references");
245 }
246
247 private static class SoftMapReference<K, V> extends SoftReference<V> {
248 K key;
249
250 SoftMapReference(K key, V value, ReferenceQueue queue) {
251 super(value, queue);
252 this.key = key;
253 }
254
255 @Override
256 public boolean equals(Object o) {
257 if (o == null) {
258 return false;
259 }
260 if (!(o instanceof SoftMapReference)) {
261 return false;
262 }
263 SoftMapReference other = (SoftMapReference) o;
264 return other.get() == get();
265 }
266 }
267}