OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Rev 144 | Go to most recent revision | Only display areas with differences | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 144 Rev 182
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
}