Skip to content

Instantly share code, notes, and snippets.

@dfreeman
Created May 12, 2013 14:57
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dfreeman/5563829 to your computer and use it in GitHub Desktop.
Save dfreeman/5563829 to your computer and use it in GitHub Desktop.
A proof of concept of a utility to log and subsequently replay the results of method calls on an object. There are a number of obvious improvements (from both a cleanliness and feature perspective), but this at least gets the idea across.
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javassist.util.proxy.MethodHandler;
import javassist.util.proxy.ProxyFactory;
public class CallLoggingProxy {
private CallLoggingProxy() {}
public static class ReplayInfo extends HashMap<Method, Map<List<Object>, Object>> {}
public static interface CallLogger {
ReplayInfo getLoggedData();
}
public static ReplayInfo getReplayInfo(Object callLoggingProxy) {
return ((CallLogger)callLoggingProxy).getLoggedData();
}
/**
* Wraps the given object in a proxy that will keep track of return values for
* a given set of arguments, making that data available via {@link #getReplayInfo(Object)}
* for use in later replaying the original interaction.
*
* This implementation only keeps track of a single return value for any given
* set of arguments, but could be extended to track the sequence of varying returns
* over multiple calls.
*/
public static <T> T create(Class<T> clazz, final T instance) throws Exception {
ProxyFactory factory = new ProxyFactory();
if (clazz.isInterface()) {
factory.setInterfaces(new Class<?>[] { clazz, CallLogger.class });
} else {
factory.setSuperclass(clazz);
factory.setInterfaces(new Class<?>[] { CallLogger.class });
}
MethodHandler handler = new MethodHandler() {
private ReplayInfo data = new ReplayInfo();
private final Method getLoggedData = CallLogger.class.getMethod("getLoggedData", new Class<?>[0]);
private Map<List<Object>, Object> getMemo(Method m) {
Map<List<Object>, Object> memo = data.get(m);
if (memo == null) {
memo = new HashMap<List<Object>, Object>();
data.put(m, memo);
}
return memo;
}
@Override
public Object invoke(Object self, Method method, Method proceed, Object[] args)
throws Throwable {
if (method.equals(getLoggedData)) {
return data;
} else {
List<Object> arguments = Arrays.asList(args);
Object result = method.invoke(instance, args);
getMemo(method).put(arguments, result);
return result;
}
}
};
return clazz.cast(factory.create(new Class<?>[0], new Object[0], handler));
}
/**
* Creates an instance of the given class that will produce the same results (when
* given identical input) as the object that produced the given replay info.
*/
public static <T> T replay(Class<T> clazz, final ReplayInfo data) throws Exception {
ProxyFactory factory = new ProxyFactory();
if (clazz.isInterface()) {
factory.setInterfaces(new Class<?>[] { clazz });
} else {
factory.setSuperclass(clazz);
}
MethodHandler handler = new MethodHandler() {
@Override
public Object invoke(Object self, Method method, Method proceed, Object[] args)
throws Throwable {
Map<List<Object>, Object> memo = data.get(method);
List<Object> arguments = Arrays.asList(args);
if (memo == null || !memo.containsKey(arguments)) {
throw new RuntimeException("Input received for unrecorded scenario");
}
return memo.get(arguments);
}
};
return clazz.cast(factory.create(new Class<?>[0], new Object[0], handler));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment