package org.erlide.util;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
/**
* A dynamic proxy wrapper that caches method calls to the original object.
* <p>
* More specifically, each method on the interface to be memoized should meet
* all of the following criteria:
* <ul>
* <li>The return values of the method should not change from call to call.</li>
* <li>The method should not have side effects.</li>
* <li>The method should not take mutable arguments.</li>
* </ul>
*/
public class Memoizer implements InvocationHandler {
@SuppressWarnings("unchecked")
public static <T> T memoize(final T object) {
return (T) Proxy.newProxyInstance(object.getClass().getClassLoader(),
object.getClass().getInterfaces(), new Memoizer(object));
}
private final Object object;
private final Cache<Method, Map<List<?>, Object>> caches;
private Memoizer(final Object object) {
this.object = object;
caches = CacheBuilder.newBuilder().expireAfterAccess(1, TimeUnit.HOURS)
.maximumSize(250).build();
}
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args)
throws Throwable {
if (method.getReturnType().equals(Void.TYPE)) {
// Don't cache void methods
return invoke(method, args);
}
final Map<List<?>, Object> cache = getCache(method);
final List<?> key = Arrays.asList(args);
Object value = cache.get(key);
if (value == null && !cache.containsKey(key)) {
value = invoke(method, args);
cache.put(key, value);
}
return value;
}
private Object invoke(final Method method, final Object[] args) throws Throwable {
try {
return method.invoke(object, args);
} catch (final InvocationTargetException e) {
throw e.getTargetException();
}
}
private synchronized Map<List<?>, Object> getCache(final Method m) {
Map<List<?>, Object> cache = caches.getIfPresent(m);
if (cache == null) {
cache = Collections.synchronizedMap(new HashMap<List<?>, Object>());
caches.put(m, cache);
}
return cache;
}
}