1 |
/*
|
1 |
/*
|
2 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
|
2 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
|
3 |
*
|
3 |
*
|
4 |
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
|
4 |
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
|
5 |
*
|
5 |
*
|
6 |
* The contents of this file are subject to the terms of the GNU General Public License Version 3
|
6 |
* The contents of this file are subject to the terms of the GNU General Public License Version 3
|
7 |
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
|
7 |
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
|
8 |
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
|
8 |
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
|
9 |
* language governing permissions and limitations under the License.
|
9 |
* language governing permissions and limitations under the License.
|
10 |
*
|
10 |
*
|
11 |
* When distributing the software, include this License Header Notice in each file.
|
11 |
* When distributing the software, include this License Header Notice in each file.
|
12 |
*/
|
12 |
*/
|
13 |
|
13 |
|
14 |
package org.openconcerto.utils.cache;
|
14 |
package org.openconcerto.utils.cache;
|
15 |
|
15 |
|
16 |
import org.openconcerto.utils.Log;
|
16 |
import org.openconcerto.utils.Log;
|
17 |
import org.openconcerto.utils.cache.CacheItem.RemovalType;
|
17 |
import org.openconcerto.utils.cache.CacheItem.RemovalType;
|
18 |
import org.openconcerto.utils.cache.CacheResult.State;
|
18 |
import org.openconcerto.utils.cache.CacheResult.State;
|
19 |
import org.openconcerto.utils.cc.IClosure;
|
19 |
import org.openconcerto.utils.cc.IClosure;
|
20 |
|
20 |
|
21 |
import java.beans.PropertyChangeEvent;
|
21 |
import java.beans.PropertyChangeEvent;
|
22 |
import java.beans.PropertyChangeListener;
|
22 |
import java.beans.PropertyChangeListener;
|
23 |
import java.beans.PropertyChangeSupport;
|
23 |
import java.beans.PropertyChangeSupport;
|
24 |
import java.util.ArrayList;
|
24 |
import java.util.ArrayList;
|
25 |
import java.util.Collections;
|
25 |
import java.util.Collections;
|
26 |
import java.util.HashMap;
|
26 |
import java.util.HashMap;
|
27 |
import java.util.HashSet;
|
27 |
import java.util.HashSet;
|
28 |
import java.util.LinkedHashMap;
|
28 |
import java.util.LinkedHashMap;
|
29 |
import java.util.List;
|
29 |
import java.util.List;
|
30 |
import java.util.Map;
|
30 |
import java.util.Map;
|
31 |
import java.util.Set;
|
31 |
import java.util.Set;
|
32 |
import java.util.concurrent.TimeUnit;
|
32 |
import java.util.concurrent.TimeUnit;
|
33 |
import java.util.logging.Level;
|
33 |
import java.util.logging.Level;
|
34 |
|
34 |
|
- |
|
35 |
import net.jcip.annotations.GuardedBy;
|
- |
|
36 |
|
35 |
/**
|
37 |
/**
|
36 |
* To keep results computed from some data. The results will be automatically invalidated after some
|
38 |
* To keep results computed from some data. The results will be automatically invalidated after some
|
37 |
* period of time or when the data is modified.
|
39 |
* period of time or when the data is modified.
|
38 |
*
|
40 |
*
|
39 |
* @author Sylvain CUAZ
|
41 |
* @author Sylvain CUAZ
|
40 |
* @param <K> key type, eg String.
|
42 |
* @param <K> key type, eg String.
|
41 |
* @param <V> value type, eg List of SQLRow.
|
43 |
* @param <V> value type, eg List of SQLRow.
|
42 |
* @param <D> source data type, eg SQLTable.
|
44 |
* @param <D> source data type, eg SQLTable.
|
43 |
*/
|
45 |
*/
|
44 |
public class ICache<K, V, D> {
|
46 |
public class ICache<K, V, D> {
|
45 |
|
47 |
|
46 |
private static final Level LEVEL = Level.FINEST;
|
48 |
private static final Level LEVEL = Level.FINEST;
|
47 |
|
49 |
|
48 |
public static final String ITEMS_CHANGED = "itemsChanged";
|
50 |
public static final String ITEMS_CHANGED = "itemsChanged";
|
49 |
public static final String ITEM_ADDED = "itemAdded";
|
51 |
public static final String ITEM_ADDED = "itemAdded";
|
50 |
public static final String ITEM_REMOVED = "itemRemoved";
|
52 |
public static final String ITEM_REMOVED = "itemRemoved";
|
51 |
|
53 |
|
52 |
private final ICacheSupport<D> supp;
|
54 |
private final ICacheSupport<D> supp;
|
53 |
// linked to fifo, ATTN the values in this map can be invalid since clear() is called without
|
55 |
// linked to fifo, ATTN the values in this map can be invalid since clear() is called without
|
54 |
// the lock on CacheValue
|
56 |
// the lock on CacheValue
|
- |
|
57 |
@GuardedBy("this")
|
55 |
private final LinkedHashMap<K, CacheItem<K, V, D>> cache;
|
58 |
private final LinkedHashMap<K, CacheItem<K, V, D>> cache;
|
- |
|
59 |
@GuardedBy("this")
|
56 |
private final Map<K, CacheItem<K, V, D>> running;
|
60 |
private final Map<K, CacheItem<K, V, D>> running;
|
57 |
private final int delay;
|
61 |
private final int delay;
|
58 |
private final int size;
|
62 |
private final int size;
|
59 |
private final String name;
|
63 |
private final String name;
|
60 |
|
64 |
|
61 |
private ICache<K, V, D> parent;
|
65 |
private ICache<K, V, D> parent;
|
62 |
|
66 |
|
63 |
private final PropertyChangeSupport propSupp = new PropertyChangeSupport(this);
|
67 |
private final PropertyChangeSupport propSupp = new PropertyChangeSupport(this);
|
64 |
|
68 |
|
65 |
public ICache() {
|
69 |
public ICache() {
|
66 |
this(60);
|
70 |
this(60);
|
67 |
}
|
71 |
}
|
68 |
|
72 |
|
69 |
public ICache(int delay) {
|
73 |
public ICache(int delay) {
|
70 |
this(delay, -1);
|
74 |
this(delay, -1);
|
71 |
}
|
75 |
}
|
72 |
|
76 |
|
73 |
public ICache(int delay, int size) {
|
77 |
public ICache(int delay, int size) {
|
74 |
this(delay, size, null);
|
78 |
this(delay, size, null);
|
75 |
}
|
79 |
}
|
76 |
|
80 |
|
77 |
/**
|
81 |
/**
|
78 |
* Creates a cache with the given parameters.
|
82 |
* Creates a cache with the given parameters.
|
79 |
*
|
83 |
*
|
80 |
* @param delay the delay in seconds before a key is cleared.
|
84 |
* @param delay the delay in seconds before a key is cleared.
|
81 |
* @param size the maximum size of the cache, negative means no limit.
|
85 |
* @param size the maximum size of the cache, negative means no limit.
|
82 |
* @param name name of this cache and associated thread.
|
86 |
* @param name name of this cache and associated thread.
|
83 |
* @throws IllegalArgumentException if size is 0.
|
87 |
* @throws IllegalArgumentException if size is 0.
|
84 |
*/
|
88 |
*/
|
85 |
public ICache(int delay, int size, String name) {
|
89 |
public ICache(int delay, int size, String name) {
|
86 |
this(null, delay, size, name);
|
90 |
this(null, delay, size, name);
|
87 |
}
|
91 |
}
|
88 |
|
92 |
|
89 |
public ICache(final ICacheSupport<D> supp, int delay, int size, String name) {
|
93 |
public ICache(final ICacheSupport<D> supp, int delay, int size, String name) {
|
90 |
this.supp = supp == null ? createSupp(getCacheSuppName(name)) : supp;
|
94 |
this.supp = supp == null ? createSupp(getCacheSuppName(name)) : supp;
|
91 |
this.running = new HashMap<K, CacheItem<K, V, D>>();
|
95 |
this.running = new HashMap<K, CacheItem<K, V, D>>();
|
92 |
this.delay = delay;
|
96 |
this.delay = delay;
|
93 |
if (size == 0)
|
97 |
if (size == 0)
|
94 |
throw new IllegalArgumentException("0 size");
|
98 |
throw new IllegalArgumentException("0 size");
|
95 |
this.size = size;
|
99 |
this.size = size;
|
96 |
this.cache = new LinkedHashMap<K, CacheItem<K, V, D>>(size < 0 ? 64 : size);
|
100 |
this.cache = new LinkedHashMap<K, CacheItem<K, V, D>>(size < 0 ? 64 : size);
|
97 |
this.name = name;
|
101 |
this.name = name;
|
98 |
|
102 |
|
99 |
this.parent = null;
|
103 |
this.parent = null;
|
100 |
}
|
104 |
}
|
101 |
|
105 |
|
102 |
protected ICacheSupport<D> createSupp(final String name) {
|
106 |
protected ICacheSupport<D> createSupp(final String name) {
|
103 |
return new ICacheSupport<D>(name);
|
107 |
return new ICacheSupport<D>(name);
|
104 |
}
|
108 |
}
|
105 |
|
109 |
|
106 |
protected String getCacheSuppName(final String cacheName) {
|
110 |
protected String getCacheSuppName(final String cacheName) {
|
107 |
return cacheName;
|
111 |
return cacheName;
|
108 |
}
|
112 |
}
|
109 |
|
113 |
|
110 |
public final ICacheSupport<D> getSupp() {
|
114 |
public final ICacheSupport<D> getSupp() {
|
111 |
return this.supp;
|
115 |
return this.supp;
|
112 |
}
|
116 |
}
|
113 |
|
117 |
|
114 |
public final int getMaximumSize() {
|
118 |
public final int getMaximumSize() {
|
115 |
return this.size;
|
119 |
return this.size;
|
116 |
}
|
120 |
}
|
117 |
|
121 |
|
118 |
public final String getName() {
|
122 |
public final String getName() {
|
119 |
return this.name;
|
123 |
return this.name;
|
120 |
}
|
124 |
}
|
121 |
|
125 |
|
122 |
/**
|
126 |
/**
|
123 |
* Allow to continue the search for a key in another instance.
|
127 |
* Allow to continue the search for a key in another instance.
|
124 |
*
|
128 |
*
|
125 |
* @param parent the cache to search when a key isn't found in this.
|
129 |
* @param parent the cache to search when a key isn't found in this.
|
126 |
*/
|
130 |
*/
|
127 |
public final synchronized void setParent(final ICache<K, V, D> parent) {
|
131 |
public final synchronized void setParent(final ICache<K, V, D> parent) {
|
128 |
ICache<K, V, D> current = parent;
|
132 |
ICache<K, V, D> current = parent;
|
129 |
while (current != null) {
|
133 |
while (current != null) {
|
130 |
if (current == this)
|
134 |
if (current == this)
|
131 |
throw new IllegalArgumentException("Cycle detected, cannot set parent to " + parent);
|
135 |
throw new IllegalArgumentException("Cycle detected, cannot set parent to " + parent);
|
132 |
current = current.getParent();
|
136 |
current = current.getParent();
|
133 |
}
|
137 |
}
|
134 |
this.parent = parent;
|
138 |
this.parent = parent;
|
135 |
}
|
139 |
}
|
136 |
|
140 |
|
137 |
public final synchronized ICache<K, V, D> getParent() {
|
141 |
public final synchronized ICache<K, V, D> getParent() {
|
138 |
return this.parent;
|
142 |
return this.parent;
|
139 |
}
|
143 |
}
|
140 |
|
144 |
|
141 |
/**
|
145 |
/**
|
142 |
* If <code>sel</code> is in cache returns its value, else if key is running block until the key
|
146 |
* If <code>sel</code> is in cache returns its value, else if key is running block until the key
|
143 |
* is put (or the current thread is interrupted). Then if a {@link #setParent(ICache) parent}
|
147 |
* is put (or the current thread is interrupted). Then if a {@link #setParent(ICache) parent}
|
144 |
* has been set, use it. Otherwise the key is not in cache so return a CacheResult of state
|
148 |
* has been set, use it. Otherwise the key is not in cache so return a CacheResult of state
|
145 |
* {@link State#NOT_IN_CACHE}.
|
149 |
* {@link State#NOT_IN_CACHE}.
|
146 |
*
|
150 |
*
|
147 |
* @param sel the key we're getting the value for.
|
151 |
* @param sel the key we're getting the value for.
|
148 |
* @return a CacheResult with the appropriate state.
|
152 |
* @return a CacheResult with the appropriate state.
|
149 |
*/
|
153 |
*/
|
150 |
public final CacheResult<V> get(K sel) {
|
154 |
public final CacheResult<V> get(K sel) {
|
151 |
return this.get(sel, true);
|
155 |
return this.get(sel, true);
|
152 |
}
|
156 |
}
|
153 |
|
157 |
|
154 |
private final CacheResult<V> get(K sel, final boolean checkRunning) {
|
158 |
private final CacheResult<V> get(K sel, final boolean checkRunning) {
|
155 |
ICache<K, V, D> parent = null;
|
159 |
ICache<K, V, D> parent = null;
|
156 |
synchronized (this) {
|
160 |
synchronized (this) {
|
157 |
final CacheResult<V> localRes = this.cache.containsKey(sel) ? this.cache.get(sel).getResult() : CacheResult.<V> getNotInCache();
|
161 |
final CacheResult<V> localRes = this.cache.containsKey(sel) ? this.cache.get(sel).getResult() : CacheResult.<V> getNotInCache();
|
158 |
if (localRes.getState() == State.VALID) {
|
162 |
if (localRes.getState() == State.VALID) {
|
159 |
log("IN cache", sel);
|
163 |
log("IN cache", sel);
|
160 |
return localRes;
|
164 |
return localRes;
|
161 |
} else if (checkRunning && isRunning(sel)) {
|
165 |
} else if (checkRunning && isRunning(sel)) {
|
162 |
log("RUNNING", sel);
|
166 |
log("RUNNING", sel);
|
163 |
try {
|
167 |
try {
|
164 |
this.wait();
|
168 |
this.wait();
|
165 |
} catch (InterruptedException e) {
|
169 |
} catch (InterruptedException e) {
|
166 |
// return sinon thread ne peut sortir que lorsque sel sera fini
|
170 |
// return sinon thread ne peut sortir que lorsque sel sera fini
|
167 |
return CacheResult.getInterrupted();
|
171 |
return CacheResult.getInterrupted();
|
168 |
}
|
172 |
}
|
169 |
return this.get(sel);
|
173 |
return this.get(sel);
|
170 |
} else if (this.parent != null) {
|
174 |
} else if (this.parent != null) {
|
171 |
log("CALLING parent", sel);
|
175 |
log("CALLING parent", sel);
|
172 |
parent = this.parent;
|
176 |
parent = this.parent;
|
173 |
} else {
|
177 |
} else {
|
174 |
log("NOT in cache", sel);
|
178 |
log("NOT in cache", sel);
|
175 |
return CacheResult.getNotInCache();
|
179 |
return CacheResult.getNotInCache();
|
176 |
}
|
180 |
}
|
177 |
}
|
181 |
}
|
178 |
// don't call our parent with our lock
|
182 |
// don't call our parent with our lock
|
179 |
return parent.get(sel, false);
|
183 |
return parent.get(sel, false);
|
180 |
}
|
184 |
}
|
181 |
|
185 |
|
182 |
/**
|
186 |
/**
|
183 |
* Tell this cache that we're in process of getting the value for key, so if someone else ask
|
187 |
* Tell this cache that we're in process of getting the value for key, so if someone else ask
|
184 |
* have them wait. ATTN after calling this method you MUST call put() or removeRunning(),
|
188 |
* have them wait. ATTN after calling this method you MUST call put() or removeRunning(),
|
185 |
* otherwise get() will always block for <code>key</code>.
|
189 |
* otherwise get() will always block for <code>key</code>.
|
186 |
*
|
190 |
*
|
187 |
* @param val the value that will receive the result.
|
191 |
* @param val the value that will receive the result.
|
188 |
* @return <code>true</code> if the value was added, <code>false</code> if the key was already
|
192 |
* @return <code>true</code> if the value was added, <code>false</code> if the key was already
|
189 |
* running.
|
193 |
* running.
|
190 |
* @see #put(Object, Object, Set)
|
194 |
* @see #put(Object, Object, Set)
|
191 |
* @see #removeRunning(Object)
|
195 |
* @see #removeRunning(Object)
|
192 |
*/
|
196 |
*/
|
193 |
private final synchronized boolean addRunning(final CacheItem<K, V, D> val) {
|
197 |
private final synchronized boolean addRunning(final CacheItem<K, V, D> val) {
|
194 |
if (!this.isRunning(val.getKey())) {
|
198 |
if (!this.isRunning(val.getKey())) {
|
195 |
// ATTN this can invalidate val
|
199 |
// ATTN this can invalidate val
|
196 |
val.addToWatchers();
|
200 |
val.addToWatchers();
|
197 |
if (val.getRemovalType() == null) {
|
201 |
if (val.getRemovalType() == null) {
|
198 |
this.running.put(val.getKey(), val);
|
202 |
this.running.put(val.getKey(), val);
|
199 |
return true;
|
203 |
return true;
|
200 |
}
|
204 |
}
|
201 |
}
|
205 |
}
|
202 |
return false;
|
206 |
return false;
|
203 |
}
|
207 |
}
|
204 |
|
208 |
|
205 |
// return null if the item wasn't added to this
|
209 |
// return null if the item wasn't added to this
|
206 |
final CacheItem<K, V, D> getRunningValFromRes(final CacheResult<V> cacheRes) {
|
210 |
final CacheItem<K, V, D> getRunningValFromRes(final CacheResult<V> cacheRes) {
|
207 |
if (cacheRes.getState() != CacheResult.State.NOT_IN_CACHE)
|
211 |
if (cacheRes.getState() != CacheResult.State.NOT_IN_CACHE)
|
208 |
throw new IllegalArgumentException("Wrong state : " + cacheRes.getState());
|
212 |
throw new IllegalArgumentException("Wrong state : " + cacheRes.getState());
|
209 |
if (cacheRes.getVal() == null) {
|
213 |
if (cacheRes.getVal() == null) {
|
210 |
// happens when check() is called and ICacheSupport is dead, i.e. this.running was not
|
214 |
// happens when check() is called and ICacheSupport is dead, i.e. this.running was not
|
211 |
// modified and CacheResult.getNotInCache() was returned
|
215 |
// modified and CacheResult.getNotInCache() was returned
|
212 |
assert cacheRes == CacheResult.getNotInCache();
|
216 |
assert cacheRes == CacheResult.getNotInCache();
|
213 |
} else {
|
217 |
} else {
|
214 |
if (cacheRes.getVal().getCache() != this)
|
218 |
if (cacheRes.getVal().getCache() != this)
|
215 |
throw new IllegalArgumentException("Not running in this cache");
|
219 |
throw new IllegalArgumentException("Not running in this cache");
|
216 |
assert cacheRes.getVal().getState() == CacheItem.State.RUNNING || cacheRes.getVal().getState() == CacheItem.State.INVALID;
|
220 |
assert cacheRes.getVal().getState() == CacheItem.State.RUNNING || cacheRes.getVal().getState() == CacheItem.State.INVALID;
|
217 |
}
|
221 |
}
|
218 |
@SuppressWarnings("unchecked")
|
222 |
@SuppressWarnings("unchecked")
|
219 |
final CacheItem<K, V, D> res = (CacheItem<K, V, D>) cacheRes.getVal();
|
223 |
final CacheItem<K, V, D> res = (CacheItem<K, V, D>) cacheRes.getVal();
|
220 |
return res;
|
224 |
return res;
|
221 |
}
|
225 |
}
|
222 |
|
226 |
|
223 |
public final synchronized void removeRunning(final CacheResult<V> res) {
|
227 |
public final synchronized void removeRunning(final CacheResult<V> res) {
|
224 |
removeRunning(getRunningValFromRes(res));
|
228 |
removeRunning(getRunningValFromRes(res));
|
225 |
}
|
229 |
}
|
226 |
|
230 |
|
227 |
private final synchronized void removeRunning(final CacheItem<K, V, D> val) {
|
231 |
private final synchronized boolean removeRunning(final CacheItem<K, V, D> val) {
|
228 |
if (val == null)
|
232 |
if (val == null)
|
229 |
return;
|
233 |
return false;
|
230 |
final K key = val.getKey();
|
234 |
final K key = val.getKey();
|
- |
|
235 |
final boolean removed;
|
231 |
if (this.running.get(key) == val)
|
236 |
if (this.running.get(key) == val) {
|
232 |
this.removeRunning(key);
|
237 |
this.removeRunning(key);
|
- |
|
238 |
removed = true;
|
233 |
else
|
239 |
} else {
|
234 |
// either val wasn't created in this cache or another value was already put in this
|
240 |
// either val wasn't created in this cache or another value was already put in this
|
235 |
// cache
|
241 |
// cache
|
236 |
val.setRemovalType(RemovalType.EXPLICIT);
|
242 |
removed = val.setRemovalType(RemovalType.EXPLICIT);
|
- |
|
243 |
}
|
237 |
assert val.getRemovalType() != null;
|
244 |
assert val.getRemovalType() != null;
|
- |
|
245 |
return removed;
|
238 |
}
|
246 |
}
|
239 |
|
247 |
|
240 |
private final synchronized void removeRunning(K key) {
|
248 |
private final synchronized void removeRunning(K key) {
|
241 |
final CacheItem<K, V, D> removed = this.running.remove(key);
|
249 |
final CacheItem<K, V, D> removed = this.running.remove(key);
|
242 |
if (removed != null) {
|
250 |
if (removed != null) {
|
243 |
// if the removed value isn't in us (this happens if put() is called without passing the
|
251 |
// if the removed value isn't in us (this happens if put() is called without passing the
|
244 |
// value returned by check()), kill it so that it stops listening to its data
|
252 |
// value returned by check()), kill it so that it stops listening to its data
|
245 |
if (this.cache.get(key) != removed)
|
253 |
if (this.cache.get(key) != removed)
|
246 |
removed.setRemovalType(RemovalType.EXPLICIT);
|
254 |
removed.setRemovalType(RemovalType.EXPLICIT);
|
247 |
this.notifyAll();
|
255 |
this.notifyAll();
|
248 |
}
|
256 |
}
|
249 |
}
|
257 |
}
|
250 |
|
258 |
|
251 |
public final synchronized boolean isRunning(K sel) {
|
259 |
public final synchronized boolean isRunning(K sel) {
|
252 |
return this.running.containsKey(sel);
|
260 |
return this.running.containsKey(sel);
|
253 |
}
|
261 |
}
|
254 |
|
262 |
|
255 |
public final synchronized Set<K> getRunning() {
|
263 |
public final synchronized Set<K> getRunning() {
|
256 |
return Collections.unmodifiableSet(new HashSet<K>(this.running.keySet()));
|
264 |
return Collections.unmodifiableSet(new HashSet<K>(this.running.keySet()));
|
257 |
}
|
265 |
}
|
258 |
|
266 |
|
259 |
/**
|
267 |
/**
|
260 |
* Check if key is in cache, in that case returns the value otherwise adds key to running and
|
268 |
* Check if key is in cache, in that case returns the value otherwise adds key to running and
|
261 |
* returns <code>NOT_IN_CACHE</code>.
|
269 |
* returns <code>NOT_IN_CACHE</code>.
|
262 |
*
|
270 |
*
|
263 |
* @param key the key to be checked.
|
271 |
* @param key the key to be checked.
|
264 |
* @return the associated value, never <code>null</code>.
|
272 |
* @return the associated value, never <code>null</code>.
|
265 |
* @see #addRunning(Object)
|
273 |
* @see #addRunning(Object)
|
266 |
* @see #removeRunning(CacheResult)
|
274 |
* @see #removeRunning(CacheResult)
|
267 |
* @see #put(CacheResult, Object, long)
|
275 |
* @see #put(CacheResult, Object, long)
|
268 |
*/
|
276 |
*/
|
269 |
public final CacheResult<V> check(K key) {
|
277 |
public final CacheResult<V> check(K key) {
|
270 |
return this.check(key, Collections.<D> emptySet());
|
278 |
return this.check(key, Collections.<D> emptySet());
|
271 |
}
|
279 |
}
|
272 |
|
280 |
|
273 |
public final CacheResult<V> check(K key, final Set<? extends D> data) {
|
281 |
public final CacheResult<V> check(K key, final Set<? extends D> data) {
|
274 |
return this.check(key, true, true, data);
|
282 |
return this.check(key, true, true, data);
|
275 |
}
|
283 |
}
|
276 |
|
284 |
|
277 |
public final CacheResult<V> check(K key, final boolean readCache, final boolean willWriteToCache, final Set<? extends D> data) {
|
285 |
public final CacheResult<V> check(K key, final boolean readCache, final boolean willWriteToCache, final Set<? extends D> data) {
|
278 |
return this.check(key, readCache, willWriteToCache, data, this.delay * 1000);
|
286 |
return this.check(key, readCache, willWriteToCache, data, this.delay * 1000);
|
279 |
}
|
287 |
}
|
280 |
|
288 |
|
281 |
public final synchronized CacheResult<V> check(K key, final boolean readCache, final boolean willWriteToCache, final Set<? extends D> data, final long timeout) {
|
289 |
public final synchronized CacheResult<V> check(K key, final boolean readCache, final boolean willWriteToCache, final Set<? extends D> data, final long timeout) {
|
282 |
final CacheResult<V> l = readCache ? this.get(key) : CacheResult.<V> getNotInCache();
|
290 |
final CacheResult<V> l = readCache ? this.get(key) : CacheResult.<V> getNotInCache();
|
283 |
if (willWriteToCache && l.getState() == State.NOT_IN_CACHE) {
|
291 |
if (willWriteToCache && l.getState() == State.NOT_IN_CACHE) {
|
284 |
final CacheItem<K, V, D> val = new CacheItem<K, V, D>(this, key, data);
|
292 |
final CacheItem<K, V, D> val = new CacheItem<K, V, D>(this, key, data);
|
285 |
if (this.addRunning(val)) {
|
293 |
if (this.addRunning(val)) {
|
286 |
val.addTimeout(timeout, TimeUnit.MILLISECONDS);
|
294 |
val.addTimeout(timeout, TimeUnit.MILLISECONDS);
|
287 |
return new CacheResult<V>(val);
|
295 |
return new CacheResult<V>(val);
|
288 |
} else {
|
296 |
} else {
|
289 |
// val was never referenced so it will be garbage collected
|
297 |
// val was never referenced so it will be garbage collected
|
290 |
assert !val.getState().isActive() : "active value : " + val;
|
298 |
assert !val.getState().isActive() : "active value : " + val;
|
291 |
}
|
299 |
}
|
292 |
}
|
300 |
}
|
293 |
return l;
|
301 |
return l;
|
294 |
}
|
302 |
}
|
295 |
|
303 |
|
296 |
/**
|
304 |
/**
|
297 |
* Put a result which doesn't depend on variable data in this cache.
|
305 |
* Put a result which doesn't depend on variable data in this cache.
|
298 |
*
|
306 |
*
|
299 |
* @param sel the key.
|
307 |
* @param sel the key.
|
300 |
* @param res the result associated with <code>sel</code>.
|
308 |
* @param res the result associated with <code>sel</code>.
|
301 |
* @return the item that was created.
|
309 |
* @return the item that was created.
|
302 |
*/
|
310 |
*/
|
303 |
public final CacheItem<K, V, D> put(K sel, V res) {
|
311 |
public final CacheItem<K, V, D> put(K sel, V res) {
|
304 |
return this.put(sel, res, Collections.<D> emptySet());
|
312 |
return this.put(sel, res, Collections.<D> emptySet());
|
305 |
}
|
313 |
}
|
306 |
|
314 |
|
307 |
/**
|
315 |
/**
|
308 |
* Put a result in this cache.
|
316 |
* Put a result in this cache.
|
309 |
*
|
317 |
*
|
310 |
* @param sel the key.
|
318 |
* @param sel the key.
|
311 |
* @param res the result associated with <code>sel</code>.
|
319 |
* @param res the result associated with <code>sel</code>.
|
312 |
* @param data the data from which <code>res</code> is computed.
|
320 |
* @param data the data from which <code>res</code> is computed.
|
313 |
* @return the item that was created.
|
321 |
* @return the item that was created.
|
314 |
*/
|
322 |
*/
|
315 |
public final CacheItem<K, V, D> put(K sel, V res, Set<? extends D> data) {
|
323 |
public final CacheItem<K, V, D> put(K sel, V res, Set<? extends D> data) {
|
316 |
return this.put(sel, res, data, this.delay * 1000);
|
324 |
return this.put(sel, res, data, this.delay * 1000);
|
317 |
}
|
325 |
}
|
318 |
|
326 |
|
319 |
public final CacheItem<K, V, D> put(K sel, V res, Set<? extends D> data, final long timeoutDelay) {
|
327 |
public final CacheItem<K, V, D> put(K sel, V res, Set<? extends D> data, final long timeoutDelay) {
|
320 |
return this.put(sel, true, res, data, timeoutDelay);
|
328 |
return this.put(sel, true, res, data, timeoutDelay);
|
321 |
}
|
329 |
}
|
322 |
|
330 |
|
323 |
private final CacheItem<K, V, D> put(K key, final boolean allowReplace, V res, Set<? extends D> data, final long timeoutDelay) {
|
331 |
private final CacheItem<K, V, D> put(K key, final boolean allowReplace, V res, Set<? extends D> data, final long timeoutDelay) {
|
324 |
final CacheItem<K, V, D> item = new CacheItem<K, V, D>(this, key, res, data);
|
332 |
final CacheItem<K, V, D> item = new CacheItem<K, V, D>(this, key, res, data);
|
325 |
item.addTimeout(timeoutDelay, TimeUnit.MILLISECONDS);
|
333 |
item.addTimeout(timeoutDelay, TimeUnit.MILLISECONDS);
|
326 |
item.addToWatchers();
|
334 |
item.addToWatchers();
|
327 |
return put(item, allowReplace);
|
335 |
return put(item, allowReplace);
|
328 |
}
|
336 |
}
|
329 |
|
337 |
|
330 |
/**
|
338 |
/**
|
331 |
* Assign a value to a {@link CacheItem.State#RUNNING} item.
|
339 |
* Assign a value to a {@link CacheItem.State#RUNNING} item.
|
332 |
*
|
340 |
*
|
333 |
* @param cacheRes an instance obtained from <code>check()</code>.
|
341 |
* @param cacheRes an instance obtained from <code>check()</code>.
|
334 |
* @param val the value to store.
|
342 |
* @param val the value to store.
|
335 |
* @return the item that was added, <code>null</code> if none was added.
|
343 |
* @return the item that was added, <code>null</code> if none was added.
|
336 |
* @see #check(Object, boolean, boolean, Set)
|
344 |
* @see #check(Object, boolean, boolean, Set)
|
337 |
*/
|
345 |
*/
|
338 |
public final CacheItem<K, V, D> put(CacheResult<V> cacheRes, V val) {
|
346 |
public final CacheItem<K, V, D> put(CacheResult<V> cacheRes, V val) {
|
339 |
final CacheItem<K, V, D> item = getRunningValFromRes(cacheRes);
|
347 |
final CacheItem<K, V, D> item = getRunningValFromRes(cacheRes);
|
340 |
if (item == null)
|
348 |
if (item == null)
|
341 |
return null;
|
349 |
return null;
|
342 |
item.setValue(val);
|
350 |
item.setValue(val);
|
343 |
return put(item, true);
|
351 |
return put(item, true);
|
344 |
}
|
352 |
}
|
345 |
|
353 |
|
346 |
private final CacheItem<K, V, D> put(final CacheItem<K, V, D> val, final boolean allowReplace) {
|
354 |
private final CacheItem<K, V, D> put(final CacheItem<K, V, D> val, final boolean allowReplace) {
|
347 |
final K sel = val.getKey();
|
355 |
final K sel = val.getKey();
|
348 |
synchronized (this) {
|
356 |
synchronized (this) {
|
349 |
final CacheItem.State valState = val.getState();
|
357 |
final CacheItem.State valState = val.getState();
|
350 |
if (!valState.isActive())
|
358 |
if (!valState.isActive())
|
351 |
return null;
|
359 |
return null;
|
352 |
else if (valState != CacheItem.State.VALID)
|
360 |
else if (valState != CacheItem.State.VALID)
|
353 |
throw new IllegalStateException("Non valid : " + val);
|
361 |
throw new IllegalStateException("Non valid : " + val);
|
354 |
final boolean replacing = this.cache.containsKey(sel) && this.cache.get(sel).getRemovalType() == null;
|
362 |
final boolean replacing = this.cache.containsKey(sel) && this.cache.get(sel).getRemovalType() == null;
|
355 |
if (!allowReplace && replacing)
|
363 |
if (!allowReplace && replacing)
|
356 |
return null;
|
364 |
return null;
|
357 |
|
365 |
|
358 |
if (!replacing && this.size > 0 && this.cache.size() == this.size)
|
366 |
if (!replacing && this.size > 0 && this.cache.size() == this.size)
|
359 |
this.cache.values().iterator().next().setRemovalType(RemovalType.SIZE_LIMIT);
|
367 |
this.cache.values().iterator().next().setRemovalType(RemovalType.SIZE_LIMIT);
|
360 |
final CacheItem<K, V, D> prev = this.cache.put(sel, val);
|
368 |
final CacheItem<K, V, D> prev = this.cache.put(sel, val);
|
361 |
if (replacing)
|
369 |
if (replacing)
|
362 |
prev.setRemovalType(RemovalType.DATA_CHANGE);
|
370 |
prev.setRemovalType(RemovalType.DATA_CHANGE);
|
363 |
assert this.size <= 0 || this.cache.size() <= this.size;
|
371 |
assert this.size <= 0 || this.cache.size() <= this.size;
|
364 |
this.removeRunning(sel);
|
372 |
this.removeRunning(sel);
|
365 |
}
|
373 |
}
|
366 |
this.propSupp.firePropertyChange(new Event<K, V, D>(this, ITEMS_CHANGED, null, null));
|
374 |
this.propSupp.firePropertyChange(new Event<K, V, D>(this, ITEMS_CHANGED, null, null));
|
367 |
this.propSupp.firePropertyChange(this.createItemEvent(ITEM_ADDED, null, val));
|
375 |
this.propSupp.firePropertyChange(this.createItemEvent(ITEM_ADDED, null, val));
|
368 |
return val;
|
376 |
return val;
|
369 |
}
|
377 |
}
|
370 |
|
378 |
|
371 |
/**
|
379 |
/**
|
372 |
* Get the remaining time before the passed key will be removed.
|
380 |
* Get the remaining time before the passed key will be removed.
|
373 |
*
|
381 |
*
|
374 |
* @param key the key.
|
382 |
* @param key the key.
|
375 |
* @return the remaining milliseconds before the removal, negative if the passed key isn't in
|
383 |
* @return the remaining milliseconds before the removal, negative if the passed key isn't in
|
376 |
* this.
|
384 |
* this.
|
377 |
* @see #getRemovalTime(Object)
|
385 |
* @see #getRemovalTime(Object)
|
378 |
*/
|
386 |
*/
|
379 |
public final long getRemainingTime(K key) {
|
387 |
public final long getRemainingTime(K key) {
|
380 |
final CacheItem<K, V, D> val;
|
388 |
final CacheItem<K, V, D> val;
|
381 |
synchronized (this) {
|
389 |
synchronized (this) {
|
382 |
val = this.cache.get(key);
|
390 |
val = this.cache.get(key);
|
383 |
}
|
391 |
}
|
384 |
if (val == null)
|
392 |
if (val == null)
|
385 |
return -1;
|
393 |
return -1;
|
386 |
return val.getRemainingTimeoutDelay();
|
394 |
return val.getRemainingTimeoutDelay();
|
387 |
}
|
395 |
}
|
388 |
|
396 |
|
389 |
public final void putAll(final ICache<K, V, D> otherCache, final boolean allowReplace) {
|
397 |
public final void putAll(final ICache<K, V, D> otherCache, final boolean allowReplace) {
|
390 |
if (otherCache == this)
|
398 |
if (otherCache == this)
|
391 |
return;
|
399 |
return;
|
392 |
if (otherCache.getSupp() != this.getSupp())
|
400 |
if (otherCache.getSupp() != this.getSupp())
|
393 |
Log.get().warning("Since both caches don't share watchers, some early events might not be notified to this cache");
|
401 |
Log.get().warning("Since both caches don't share watchers, some early events might not be notified to this cache");
|
394 |
final List<CacheItem<K, V, D>> oItems = new ArrayList<CacheItem<K, V, D>>();
|
402 |
final List<CacheItem<K, V, D>> oItems = new ArrayList<CacheItem<K, V, D>>();
|
395 |
synchronized (otherCache) {
|
403 |
synchronized (otherCache) {
|
396 |
oItems.addAll(otherCache.cache.values());
|
404 |
oItems.addAll(otherCache.cache.values());
|
397 |
}
|
405 |
}
|
398 |
for (final CacheItem<K, V, D> oItem : oItems) {
|
406 |
for (final CacheItem<K, V, D> oItem : oItems) {
|
399 |
final CacheItem<K, V, D> newItem = this.put(oItem.getKey(), allowReplace, oItem.getValue(), oItem.getData(), oItem.getRemainingTimeoutDelay());
|
407 |
final CacheItem<K, V, D> newItem = this.put(oItem.getKey(), allowReplace, oItem.getValue(), oItem.getData(), oItem.getRemainingTimeoutDelay());
|
400 |
// if oItem was changed before newItem was created or see CacheWatcher.dataChanged() :
|
408 |
// if oItem was changed before newItem was created or see CacheWatcher.dataChanged() :
|
401 |
// 1. if newItem was added to a watcher before the first synchronized block, it will be
|
409 |
// 1. if newItem was added to a watcher before the first synchronized block, it will be
|
402 |
// notified
|
410 |
// notified
|
403 |
// 2. if newItem was added between the synchronized blocks (during the first iteration)
|
411 |
// 2. if newItem was added between the synchronized blocks (during the first iteration)
|
404 |
// it will be notified by the second iteration
|
412 |
// it will be notified by the second iteration
|
405 |
// 3. if newItem was added after the second synchronized block, oItem will already be
|
413 |
// 3. if newItem was added after the second synchronized block, oItem will already be
|
406 |
// notified
|
414 |
// notified
|
407 |
if (newItem != null && oItem.getRemovalType() == RemovalType.DATA_CHANGE) {
|
415 |
if (newItem != null && oItem.getRemovalType() == RemovalType.DATA_CHANGE) {
|
408 |
newItem.setRemovalType(oItem.getRemovalType());
|
416 |
newItem.setRemovalType(oItem.getRemovalType());
|
409 |
}
|
417 |
}
|
410 |
}
|
418 |
}
|
411 |
}
|
419 |
}
|
412 |
|
420 |
|
413 |
public final ICache<K, V, D> copy(final String name, final boolean copyItems) {
|
421 |
public final ICache<K, V, D> copy(final String name, final boolean copyItems) {
|
414 |
final ICache<K, V, D> res = new ICache<K, V, D>(this.getSupp(), this.delay, this.getMaximumSize(), name);
|
422 |
final ICache<K, V, D> res = new ICache<K, V, D>(this.getSupp(), this.delay, this.getMaximumSize(), name);
|
415 |
if (copyItems)
|
423 |
if (copyItems)
|
416 |
res.putAll(this, false);
|
424 |
res.putAll(this, false);
|
417 |
return res;
|
425 |
return res;
|
418 |
}
|
426 |
}
|
419 |
|
427 |
|
420 |
public final synchronized void clear(K select) {
|
428 |
public final synchronized void clear(K select) {
|
421 |
log("clear", select);
|
429 |
log("clear", select);
|
422 |
if (this.cache.containsKey(select)) {
|
430 |
if (this.cache.containsKey(select)) {
|
423 |
this.cache.get(select).setRemovalType(RemovalType.EXPLICIT);
|
431 |
this.cache.get(select).setRemovalType(RemovalType.EXPLICIT);
|
424 |
}
|
432 |
}
|
425 |
}
|
433 |
}
|
426 |
|
434 |
|
427 |
final boolean clear(final CacheItem<K, V, D> val) {
|
435 |
final boolean clear(final CacheItem<K, V, D> val) {
|
428 |
if (val.getRemovalType() == null)
|
436 |
if (val.getRemovalType() == null)
|
429 |
throw new IllegalStateException("Not yet removed : " + val);
|
437 |
throw new IllegalStateException("Not yet removed : " + val);
|
430 |
final boolean toBeRemoved;
|
438 |
final boolean removedFromRunning, toBeRemoved;
|
431 |
synchronized (this) {
|
439 |
synchronized (this) {
|
432 |
log("clear", val);
|
440 |
log("clear", val);
|
433 |
this.removeRunning(val);
|
441 |
removedFromRunning = this.removeRunning(val);
|
434 |
toBeRemoved = this.cache.get(val.getKey()) == val;
|
442 |
toBeRemoved = this.cache.get(val.getKey()) == val;
|
435 |
if (toBeRemoved) {
|
443 |
if (toBeRemoved) {
|
436 |
this.cache.remove(val.getKey());
|
444 |
this.cache.remove(val.getKey());
|
437 |
}
|
445 |
}
|
438 |
}
|
446 |
}
|
439 |
// NOTE these events are often fired with our monitor since this method is called with it
|
447 |
// NOTE these events are often fired with our monitor since this method is called with it
|
- |
|
448 |
if (removedFromRunning || toBeRemoved) {
|
440 |
this.propSupp.firePropertyChange(new Event<K, V, D>(this, ITEMS_CHANGED, null, null));
|
449 |
this.propSupp.firePropertyChange(new Event<K, V, D>(this, ITEMS_CHANGED, null, null));
|
441 |
this.propSupp.firePropertyChange(this.createItemEvent(ITEM_REMOVED, val, null));
|
450 |
this.propSupp.firePropertyChange(this.createItemEvent(ITEM_REMOVED, val, null));
|
- |
|
451 |
}
|
442 |
return toBeRemoved;
|
452 |
return toBeRemoved;
|
443 |
}
|
453 |
}
|
444 |
|
454 |
|
445 |
public final synchronized void clear() {
|
455 |
public final synchronized void clear() {
|
446 |
for (final CacheItem<K, V, D> val : new ArrayList<CacheItem<K, V, D>>(this.cache.values()))
|
456 |
for (final CacheItem<K, V, D> val : new ArrayList<CacheItem<K, V, D>>(this.cache.values())) {
|
- |
|
457 |
// We have our monitor so if val is still in us but setRemovalType() was already called,
|
- |
|
458 |
// then it means another thread is waiting on our monitor in clear(CacheItem). In that
|
- |
|
459 |
// case, just call it now so that this is empty at the end of this method.
|
447 |
val.setRemovalType(RemovalType.EXPLICIT);
|
460 |
if (!val.setRemovalType(RemovalType.EXPLICIT)) {
|
- |
|
461 |
final boolean removed = this.clear(val);
|
448 |
assert this.size() == 0;
|
462 |
assert removed;
|
- |
|
463 |
}
|
- |
|
464 |
}
|
- |
|
465 |
assert this.size() == 0 : this + " expected to be empty but contains : " + this.cache.keySet();
|
- |
|
466 |
|
449 |
for (final CacheItem<K, V, D> val : new ArrayList<CacheItem<K, V, D>>(this.running.values()))
|
467 |
for (final CacheItem<K, V, D> val : new ArrayList<CacheItem<K, V, D>>(this.running.values()))
|
450 |
val.setRemovalType(RemovalType.EXPLICIT);
|
468 |
val.setRemovalType(RemovalType.EXPLICIT);
|
451 |
assert this.running.size() == 0;
|
469 |
assert this.running.size() == 0 : this + " expected to have no running but contains : " + this.running.keySet();
|
452 |
}
|
470 |
}
|
453 |
|
471 |
|
454 |
private final void log(String msg, Object subject) {
|
472 |
private final void log(String msg, Object subject) {
|
455 |
// do the toString() on subject only if necessary
|
473 |
// do the toString() on subject only if necessary
|
456 |
if (Log.get().isLoggable(LEVEL))
|
474 |
if (Log.get().isLoggable(LEVEL))
|
457 |
Log.get().log(LEVEL, msg + ": " + subject);
|
475 |
Log.get().log(LEVEL, msg + ": " + subject);
|
458 |
}
|
476 |
}
|
459 |
|
477 |
|
460 |
public final synchronized int size() {
|
478 |
public final synchronized int size() {
|
461 |
return this.cache.size();
|
479 |
return this.cache.size();
|
462 |
}
|
480 |
}
|
463 |
|
481 |
|
464 |
static public class Event<K, V, D> extends PropertyChangeEvent {
|
482 |
static public class Event<K, V, D> extends PropertyChangeEvent {
|
465 |
|
483 |
|
466 |
public Event(ICache<K, V, D> source, String propertyName, Object oldValue, Object newValue) {
|
484 |
public Event(ICache<K, V, D> source, String propertyName, Object oldValue, Object newValue) {
|
467 |
super(source, propertyName, oldValue, newValue);
|
485 |
super(source, propertyName, oldValue, newValue);
|
468 |
}
|
486 |
}
|
469 |
|
487 |
|
470 |
@SuppressWarnings("unchecked")
|
488 |
@SuppressWarnings("unchecked")
|
471 |
@Override
|
489 |
@Override
|
472 |
public ICache<K, V, D> getSource() {
|
490 |
public ICache<K, V, D> getSource() {
|
473 |
return (ICache<K, V, D>) super.getSource();
|
491 |
return (ICache<K, V, D>) super.getSource();
|
474 |
}
|
492 |
}
|
475 |
}
|
493 |
}
|
476 |
|
494 |
|
477 |
private final Event<K, V, D> castEvent(final PropertyChangeEvent evt) {
|
495 |
private final Event<K, V, D> castEvent(final PropertyChangeEvent evt) {
|
478 |
final Event<?, ?, ?> casted = (Event<?, ?, ?>) evt;
|
496 |
final Event<?, ?, ?> casted = (Event<?, ?, ?>) evt;
|
479 |
// Needed sine this method can be called from outside this class
|
497 |
// Needed sine this method can be called from outside this class
|
480 |
// Only other option is to not use PropertyChangeSupport, and create our own generics-aware
|
498 |
// Only other option is to not use PropertyChangeSupport, and create our own generics-aware
|
481 |
// version
|
499 |
// version
|
482 |
if (casted.getSource() != ICache.this)
|
500 |
if (casted.getSource() != ICache.this)
|
483 |
throw new IllegalArgumentException("Cannot uphold type safety");
|
501 |
throw new IllegalArgumentException("Cannot uphold type safety");
|
484 |
@SuppressWarnings("unchecked")
|
502 |
@SuppressWarnings("unchecked")
|
485 |
final Event<K, V, D> res = (Event<K, V, D>) casted;
|
503 |
final Event<K, V, D> res = (Event<K, V, D>) casted;
|
486 |
return res;
|
504 |
return res;
|
487 |
}
|
505 |
}
|
488 |
|
506 |
|
489 |
private ItemEvent<K, V, D> createItemEvent(String propertyName, CacheItem<K, V, D> oldValue, CacheItem<K, V, D> newValue) {
|
507 |
private ItemEvent<K, V, D> createItemEvent(String propertyName, CacheItem<K, V, D> oldValue, CacheItem<K, V, D> newValue) {
|
490 |
if (oldValue == null && newValue == null)
|
508 |
if (oldValue == null && newValue == null)
|
491 |
throw new IllegalArgumentException("No values");
|
509 |
throw new IllegalArgumentException("No values");
|
492 |
assert (oldValue == null || oldValue.getCache() == this);
|
510 |
assert (oldValue == null || oldValue.getCache() == this);
|
493 |
assert (newValue == null || newValue.getCache() == this);
|
511 |
assert (newValue == null || newValue.getCache() == this);
|
494 |
return new ItemEvent<K, V, D>(this, propertyName, oldValue, newValue);
|
512 |
return new ItemEvent<K, V, D>(this, propertyName, oldValue, newValue);
|
495 |
}
|
513 |
}
|
496 |
|
514 |
|
497 |
static public class ItemEvent<K, V, D> extends Event<K, V, D> {
|
515 |
static public class ItemEvent<K, V, D> extends Event<K, V, D> {
|
498 |
|
516 |
|
499 |
// ATTN doesn't check parameters
|
517 |
// ATTN doesn't check parameters
|
500 |
private ItemEvent(ICache<K, V, D> source, String propertyName, CacheItem<K, V, D> oldValue, CacheItem<K, V, D> newValue) {
|
518 |
private ItemEvent(ICache<K, V, D> source, String propertyName, CacheItem<K, V, D> oldValue, CacheItem<K, V, D> newValue) {
|
501 |
super(source, propertyName, oldValue, newValue);
|
519 |
super(source, propertyName, oldValue, newValue);
|
502 |
}
|
520 |
}
|
503 |
|
521 |
|
504 |
@SuppressWarnings("unchecked")
|
522 |
@SuppressWarnings("unchecked")
|
505 |
@Override
|
523 |
@Override
|
506 |
public CacheItem<K, V, D> getOldValue() {
|
524 |
public CacheItem<K, V, D> getOldValue() {
|
507 |
return (CacheItem<K, V, D>) super.getOldValue();
|
525 |
return (CacheItem<K, V, D>) super.getOldValue();
|
508 |
}
|
526 |
}
|
509 |
|
527 |
|
510 |
@SuppressWarnings("unchecked")
|
528 |
@SuppressWarnings("unchecked")
|
511 |
@Override
|
529 |
@Override
|
512 |
public CacheItem<K, V, D> getNewValue() {
|
530 |
public CacheItem<K, V, D> getNewValue() {
|
513 |
return (CacheItem<K, V, D>) super.getNewValue();
|
531 |
return (CacheItem<K, V, D>) super.getNewValue();
|
514 |
}
|
532 |
}
|
515 |
}
|
533 |
}
|
516 |
|
534 |
|
517 |
public final PropertyChangeListener addItemListener(final IClosure<? super ItemEvent<K, V, D>> listener) {
|
535 |
public final PropertyChangeListener addItemListener(final IClosure<? super ItemEvent<K, V, D>> listener) {
|
518 |
return addListener(new PropertyChangeListener() {
|
536 |
return addListener(new PropertyChangeListener() {
|
519 |
@Override
|
537 |
@Override
|
520 |
public void propertyChange(PropertyChangeEvent evt) {
|
538 |
public void propertyChange(PropertyChangeEvent evt) {
|
521 |
if (evt instanceof ItemEvent)
|
539 |
if (evt instanceof ItemEvent)
|
522 |
listener.executeChecked((ItemEvent<K, V, D>) castEvent(evt));
|
540 |
listener.executeChecked((ItemEvent<K, V, D>) castEvent(evt));
|
523 |
}
|
541 |
}
|
524 |
});
|
542 |
});
|
525 |
}
|
543 |
}
|
526 |
|
544 |
|
527 |
public final PropertyChangeListener addListener(final IClosure<? super Event<K, V, D>> listener) {
|
545 |
public final PropertyChangeListener addListener(final IClosure<? super Event<K, V, D>> listener) {
|
528 |
return addListener(new PropertyChangeListener() {
|
546 |
return addListener(new PropertyChangeListener() {
|
529 |
@Override
|
547 |
@Override
|
530 |
public void propertyChange(PropertyChangeEvent evt) {
|
548 |
public void propertyChange(PropertyChangeEvent evt) {
|
531 |
listener.executeChecked(castEvent(evt));
|
549 |
listener.executeChecked(castEvent(evt));
|
532 |
}
|
550 |
}
|
533 |
});
|
551 |
});
|
534 |
}
|
552 |
}
|
535 |
|
553 |
|
536 |
public final PropertyChangeListener addListener(final PropertyChangeListener listener) {
|
554 |
public final PropertyChangeListener addListener(final PropertyChangeListener listener) {
|
537 |
this.propSupp.addPropertyChangeListener(listener);
|
555 |
this.propSupp.addPropertyChangeListener(listener);
|
538 |
return listener;
|
556 |
return listener;
|
539 |
}
|
557 |
}
|
540 |
|
558 |
|
541 |
public final void removeListener(final PropertyChangeListener listener) {
|
559 |
public final void removeListener(final PropertyChangeListener listener) {
|
542 |
this.propSupp.removePropertyChangeListener(listener);
|
560 |
this.propSupp.removePropertyChangeListener(listener);
|
543 |
}
|
561 |
}
|
544 |
|
562 |
|
545 |
@Override
|
563 |
@Override
|
546 |
public final String toString() {
|
564 |
public final String toString() {
|
547 |
return this.toString(false);
|
565 |
return this.toString(false);
|
548 |
}
|
566 |
}
|
549 |
|
567 |
|
550 |
public final String toString(final boolean withKeys) {
|
568 |
public final String toString(final boolean withKeys) {
|
551 |
final String keys;
|
569 |
final String keys;
|
552 |
if (withKeys) {
|
570 |
if (withKeys) {
|
553 |
synchronized (this) {
|
571 |
synchronized (this) {
|
554 |
keys = ", keys cached: " + this.cache.keySet().toString();
|
572 |
keys = ", keys cached: " + this.cache.keySet().toString();
|
555 |
}
|
573 |
}
|
556 |
} else {
|
574 |
} else {
|
557 |
keys = "";
|
575 |
keys = "";
|
558 |
}
|
576 |
}
|
559 |
return this.getClass().getName() + " '" + this.getName() + "'" + keys;
|
577 |
return this.getClass().getName() + " '" + this.getName() + "'" + keys;
|
560 |
}
|
578 |
}
|
561 |
}
|
579 |
}
|