org.jopendocument.util.cache.ICache Maven / Gradle / Ivy
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2008 jOpenDocument, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU
* General Public License Version 3 only ("GPL").
* You may not use this file except in compliance with the License.
* You can obtain a copy of the License at http://www.gnu.org/licenses/gpl-3.0.html
* See the License for the specific language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*
*/
package org.jopendocument.util.cache;
import org.jopendocument.util.CollectionMap;
import org.jopendocument.util.ExceptionUtils;
import org.jopendocument.util.Log;
import org.jopendocument.util.cache.CacheResult.State;
import org.jopendocument.util.cc.Transformer;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.logging.Level;
import org.apache.commons.collections.map.LazyMap;
/**
* To keep results computed from some data. The results will be automatically invalidated after some
* period of time or when the data is modified.
*
* @author Sylvain CUAZ
* @param key type, eg String.
* @param value type, eg List of SQLRow.
* @param source data type, eg SQLTable.
*/
public class ICache {
private static final Level LEVEL = Level.FINEST;
// linked to fifo
private final LinkedHashMap cache;
private final Set running;
private final int delay;
private final int size;
// lazy initialization to avoid creating unnecessary threads
private Timer timer;
private final String name;
private final Map> timeoutTasks;
private Map> watchers;
private final CollectionMap> watchersByKey;
public ICache() {
this(60);
}
public ICache(int delay) {
this(delay, -1);
}
public ICache(int delay, int size) {
this(delay, size, null);
}
/**
* Creates a cache with the given parameters.
*
* @param delay the delay in seconds before a key is cleared.
* @param size the maximum size of the cache, negative means no limit.
* @param name name of this cache and associated thread.
* @throws IllegalArgumentException if size is 0.
*/
public ICache(int delay, int size, String name) {
this.running = new HashSet();
this.delay = delay;
if (size == 0)
throw new IllegalArgumentException("0 size");
this.size = size;
this.cache = new LinkedHashMap(size < 0 ? 64 : size);
this.timer = null;
this.name = name;
this.timeoutTasks = new HashMap>();
this.watchers = null;
this.watchersByKey = new CollectionMap>(HashSet.class);
}
private final Timer getTimer() {
if (this.timer == null)
this.timer = this.name == null ? new Timer(true) : new Timer("cache for " + this.name, true);
return this.timer;
}
@SuppressWarnings("unchecked")
public final void setWatcherFactory(final CacheWatcherFactory f) {
this.watchers = LazyMap.decorate(new HashMap(), new Transformer>() {
@Override
public CacheWatcher transformChecked(D input) {
try {
return f.createWatcher(ICache.this, input);
} catch (Exception e) {
throw ExceptionUtils.createExn(IllegalStateException.class, "could not create watcher for " + input, e);
}
}
});
}
/**
* If sel is in cache returns its value, else if key is running block until the key
* is put (or the current thread is interrupted). Otherwise the key is not in cache so return a
* CacheResult of state {@link State#NOT_IN_CACHE}.
*
* @param sel the key we're getting the value for.
* @return a CacheResult with the appropriate state.
*/
public final CacheResult get(K sel) {
synchronized (this) {
if (this.cache.containsKey(sel)) {
log("IN cache", sel);
return new CacheResult(this.cache.get(sel));
} else if (isRunning(sel)) {
log("RUNNING", sel);
try {
this.wait();
} catch (InterruptedException e) {
// return sinon thread ne peut sortir que lorsque sel sera fini
return CacheResult.getInterrupted();
}
return this.get(sel);
} else {
log("NOT in cache", sel);
return CacheResult.getNotInCache();
}
}
}
/**
* Tell this cache that we're in process of getting the value for key, so if someone else ask
* have them wait. ATTN after calling this method you MUST call put(), otherwise get() will
* always block for key.
*
* @param key the key we're getting the value for.
* @see #put(Object, Object, Set)
*/
public final synchronized void addRunning(K key) {
this.running.add(key);
}
public final synchronized void removeRunning(K key) {
this.running.remove(key);
this.notifyAll();
}
public final synchronized boolean isRunning(K sel) {
return this.running.contains(sel);
}
/**
* Check if key is in cache, in that case returns the value otherwise adds key to running and
* returns NOT_IN_CACHE.
*
* @param key the key to be checked.
* @return the associated value, or null.
* @see #addRunning(Object)
*/
public final synchronized CacheResult check(K key) {
final CacheResult l = this.get(key);
if (l.getState() == State.NOT_IN_CACHE)
this.addRunning(key);
return l;
}
/**
* Put a result which doesn't depend on variable data in this cache.
*
* @param sel the key.
* @param res the result associated with sel.
*/
public final synchronized void put(K sel, V res) {
this.put(sel, res, Collections. emptySet());
}
/**
* Put a result in this cache.
*
* @param sel the key.
* @param res the result associated with sel.
* @param data the data from which res is computed.
* @return the watchers monitoring the passed key.
*/
public final synchronized Set extends CacheWatcher> put(K sel, V res, Set extends D> data) {
if (this.size > 0 && this.cache.size() == this.size)
this.clear(this.cache.keySet().iterator().next());
this.cache.put(sel, res);
this.removeRunning(sel);
for (final D datum : data) {
if (this.watchers != null) {
final CacheWatcher watcher = this.watchers.get(datum);
watcher.add(sel);
this.watchersByKey.put(sel, watcher);
}
}
final CacheTimeOut timeout = new CacheTimeOut(this, sel);
this.timeoutTasks.put(sel, timeout);
this.getTimer().schedule(timeout, this.delay * 1000);
return (Set>) this.watchersByKey.getNonNull(sel);
}
public final synchronized void clear(K select) {
log("clear", select);
if (this.cache.containsKey(select)) {
this.cache.remove(select);
this.timeoutTasks.remove(select).cancel();
final Set> keyWatchers = (Set>) this.watchersByKey.remove(select);
// a key can specify no watchers at all
if (keyWatchers != null) {
for (final CacheWatcher w : keyWatchers) {
w.remove(select);
if (w.isEmpty()) {
w.die();
this.watchers.remove(w.getData());
}
}
}
}
}
public final synchronized void clear() {
if (this.size() > 0) {
this.cache.clear();
if (this.timer != null) {
this.timer.cancel();
this.timer = null;
this.timeoutTasks.clear();
}
if (this.watchers != null) {
this.watchersByKey.clear();
for (final CacheWatcher w : this.watchers.values()) {
// die() will call clear() but since this.cache is now empty it won't do
// anything
w.die();
}
this.watchers.clear();
}
}
}
final synchronized boolean dependsOn(D data) {
return this.watchers.containsKey(data);
}
private final void log(String msg, Object subject) {
// do the toString() on subject only if necessary
if (Log.get().isLoggable(LEVEL))
Log.get().log(LEVEL, msg + ": " + subject);
}
public final synchronized int size() {
return this.cache.size();
}
public final String toString() {
return this.getClass().getName() + ", keys cached: " + this.timeoutTasks.keySet();
}
} © 2015 - 2025 Weber Informatics LLC | Privacy Policy