Created
May 12, 2013 14:57
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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