Reputation: 18177
I want to capture calls to a mock object
public interface Service {
public String stringify(Object o);
}
service = mockery.mock(Service.class);
mockery.allowing(service::stringify).with(42).will(() -> "42");
So inside allowing
I have a Function<Object, String>
Is there any reflecto-magic that will let me find the service from the function created from the method reference?
public WithClause allowing(Function<T,R> f) {
Object myServiceBackAgain = findTargetOf(function);
....
}
I know that the Function will always come from these method references, so I'm happy to down-cast as much as is necessary.
This is not the same question as the related Is it possible to convert method reference to MethodHandle? because, well for a start it isn't the same question, just in a related area. And even if I can get a MethodHandle, I can't get the target from it.
Upvotes: 6
Views: 1636
Reputation: 44808
Using the trick from this SO post you can find the target. The important method below is findTarget
. As it turns out, lambdas do indeed capture their targets, and you can access them from the SerializedLambda
.
However, this is a pretty nasty reflection hack and it's likely to break in future versions. I do not condone its usage.
import java.io.Serializable;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Optional;
import java.util.function.Function;
public class FindMethodReferenceTarget {
public static void main(String[] args) {
String s = "123";
Optional<Object> target = findTarget(s::charAt);
System.out.println(target.get().equals(s));
Object o = new FindMethodReferenceTarget();
target = findTarget(o::equals);
System.out.println(target.get().equals(o));
}
private static <T, R> Optional<Object> findTarget(
DebuggableFunction<T, R> methodReference) {
return getLambda(methodReference).map(l -> l.getCapturedArg(0));
}
private static Optional<SerializedLambda> getLambda(Serializable lambda) {
for (Class<?> cl = lambda.getClass(); cl != null; cl = cl.getSuperclass()) {
try {
Method m = cl.getDeclaredMethod("writeReplace");
m.setAccessible(true);
Object replacement = m.invoke(lambda);
if (!(replacement instanceof SerializedLambda)) {
break; // custom interface implementation
}
SerializedLambda l = (SerializedLambda) replacement;
return Optional.of(l);
} catch (NoSuchMethodException e) {
// do nothing
} catch (IllegalAccessException | InvocationTargetException e) {
break;
}
}
return Optional.empty();
}
@FunctionalInterface
private static interface DebuggableFunction<T, R> extends
Serializable,
Function<T, R> {}
}
Upvotes: 9
Reputation: 328
There's no direct way to find the target, because method references just get translated to lambdas (which are, by definition, anonymous) under the covers. So you'll need to use a workaround.
Presumably you're familiar with Java 7's proxies, since you've managed to implement your mock
factory method.
The workaround then is that when someone invokes your allowing
method, you set some sort of global flag to alert all your mocks that you want to record the next call, and then you invoke the lambda you were given. By seeing which mock recorded the call, you've now found the target of the method reference, and you can unset the global flag and proceed on with the rest of your mocking framework.
It's ugly, I know.
Upvotes: 1