1 /*
2 * RCache - A collection of simple reference-based cache implementations.
3 * Copyright (C) 2007 Rodrigo Ruiz
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 *
19 * Alternatively, the contents of this file may be used under the terms
20 * of the Apache 2.0 license (the "Apache License"), in which case its
21 * provisions are applicable instead of those above. If you wish to allow use
22 * of your version of this file only* under the terms of the Apache License
23 * and not to allow others to use your version of this file under the LGPL,
24 * indicate your decision by* deleting the provisions above and replace them
25 * with the notice and other provisions required by the Apache License. If
26 * you do not delete the provisions above, a recipient may use your version of
27 * this file under either the LGPL or the Apache License.
28 */
29 package net.sourceforge.rcache;
30
31 import java.lang.ref.ReferenceQueue;
32 import java.util.AbstractMap;
33 import java.util.Collection;
34 import java.util.HashMap;
35 import java.util.Hashtable;
36 import java.util.Map;
37 import java.util.Set;
38 import java.util.concurrent.ConcurrentHashMap;
39 import java.util.concurrent.ConcurrentMap;
40
41 /**
42 * <p>Memory-Sensitive Cache base implementation.</p>
43 *
44 * <p>Thread-safety of this class will depend on the selected implementation
45 * for the internal map. If the convenience constructors are used, resulting
46 * instances will be thread-safe, unless a concurrency level of -1 (or less)
47 * is specified.</p>
48 *
49 * @author Rodrigo Ruiz
50 * @param <K> the type of keys maintained by this cache
51 * @param <V> the type of cached values
52 */
53 public abstract class BaseCache<K, V> extends AbstractMap<K, V>
54 implements Cache<K, V> {
55
56 /**
57 * The map containing the cached values.
58 */
59 private final Map<K, KeyedReference<K, V>> cache;
60
61 /**
62 * Queue used to collect unused keys.
63 */
64 private final ReferenceQueue<V> queue;
65
66 /**
67 * <p>Creates an instance with custom capacity and concurrency level.</p>
68 *
69 * <p>The concurrency level is used as a hint to select the internal Map
70 * implementation:</p>
71 *
72 * <ul>
73 * <li>< 0 : Do not care about concurrency.
74 * A {@link java.util.HashMap} will be used.</li>
75 * <li>= 0 : No concurrent access allowed.
76 * A {@link java.util.Hashtable} will be selected.</li>
77 * <li>> 0 : A {@link java.util.concurrent.ConcurrentHashMap} will be
78 * created with the specified concurrency level.</li>
79 * </ul>
80 *
81 * @param initCapacity The map initial capacity
82 * @param loadFactor The map load factor
83 * @param concurrencyLevel The concurrency level.
84 */
85 public BaseCache(int initCapacity, float loadFactor, int concurrencyLevel) {
86 if (concurrencyLevel < 0) {
87 this.cache
88 = new Hashtable<K, KeyedReference<K, V>>(initCapacity, loadFactor);
89 } else if (concurrencyLevel == 0) {
90 this.cache
91 = new HashMap<K, KeyedReference<K, V>>(initCapacity, loadFactor);
92 } else {
93 this.cache = new ConcurrentHashMap<K, KeyedReference<K, V>>(
94 initCapacity, loadFactor, concurrencyLevel);
95 }
96
97 this.queue = new ReferenceQueue<V>();
98 }
99
100 /**
101 * <p>Advanced constructor.</p>
102 *
103 * With this constructor, the programmer can specify the Map to be used
104 * to hold the values. Useful for those cases in which the default
105 * implementations do not have the correct performance profile.
106 *
107 * @param map The map to use internally
108 */
109 public BaseCache(Map<K, KeyedReference<K, V>> map) {
110 this.cache = map;
111 this.queue = new ReferenceQueue<V>();
112 }
113
114 /**
115 * {@inheritDoc}
116 */
117 @Override public final V get(Object key) {
118 KeyedReference<K, V> ref = (KeyedReference<K, V>)cache.get(key);
119 return (ref == null) ? null : ref.get();
120 }
121
122 /**
123 * {@inheritDoc}
124 */
125 @Override public final V put(K key, V value) {
126 purge();
127
128 KeyedReference<K, V> ref;
129 if (this.cache instanceof ConcurrentMap) {
130 ConcurrentMap<K, KeyedReference<K, V>> cmap
131 = (ConcurrentMap<K, KeyedReference<K, V>>)this.cache;
132 ref = cmap.putIfAbsent(key, createRef(key, value, queue));
133 } else {
134 ref = cache.get(key);
135 if (ref == null) {
136 ref = cache.put(key, createRef(key, value, queue));
137 }
138 }
139 return (ref == null) ? null : ref.get();
140 }
141
142 /**
143 * {@inheritDoc}
144 */
145 @Override public final V remove(Object key) {
146 purge();
147 KeyedReference<K, V> ref = cache.remove(key);
148 return (ref == null) ? null : ref.get();
149 }
150
151 /**
152 * {@inheritDoc}
153 */
154 @Override public final int size() {
155 purge();
156 return cache.size();
157 }
158
159 /**
160 * {@inheritDoc}
161 */
162 @Override public final void clear() {
163 purge();
164 cache.clear();
165 }
166
167 /**
168 * {@inheritDoc}
169 */
170 @Override public final Set<Entry<K, V>> entrySet() {
171 throw new UnsupportedOperationException();
172 }
173
174 /**
175 * {@inheritDoc}
176 */
177 @Override public final Collection<V> values() {
178 throw new UnsupportedOperationException();
179 }
180
181 /**
182 * {@inheritDoc}
183 *
184 * <p>The superclass implementation of this method relies on the
185 * {@link BaseCache#entrySet} method, which always throws an exception. This
186 * re-implementation removes the issue.</p>
187 */
188 @Override public final int hashCode() {
189 return System.identityHashCode(this);
190 }
191
192 /**
193 * {@inheritDoc}
194 *
195 * <p>Cache instances are always different by nature, even if they contain
196 * exactly the same entries.</p>
197 */
198 @Override public final boolean equals(Object obj) {
199 return obj == this;
200 }
201
202 /**
203 * Factory method for creating a new reference instance.
204 *
205 * @param key The key
206 * @param value The value
207 * @param rqueue A reference queue
208 * @return A reference instance containing the key and value
209 */
210 protected abstract KeyedReference<K, V> createRef(K key, V value,
211 ReferenceQueue<V> rqueue);
212
213 /**
214 * <p>Removes keys for References that have been enqueued.</p>
215 *
216 * <p>This method is not public because applications should never need to
217 * manually purge the cache. However, it is left protected to allow invocation
218 * from tests.</p>
219 *
220 * <p>This method is synchronised because {@link java.lang.ref.ReferenceQueue}
221 * is not thread-safe.</p>
222 */
223 @SuppressWarnings("unchecked")
224 protected final synchronized void purge() {
225 KeyedReference<K, V> ref = (KeyedReference<K, V>)queue.poll();
226
227 while (ref != null) {
228 K key = ref.getKey();
229 cache.remove(key);
230 ref = (KeyedReference<K, V>)queue.poll();
231 }
232 }
233 }