Printing debug info on errors with java 8 lambda expressions
debugging, java, java-8, lambda, reflection
Solution
In case you expect method references as the only input, you can debug them to printable names with the following trick:
public static void main(String[] args) {
Person p = new Person();
Supplier<String> nameSupplier1 = () -> "MyName";
Supplier<String> nameSupplier2 = () -> { throw new RuntimeException(); };
set(p, Person::setName, nameSupplier1);
System.out.println(p.getName()); // prints MyName
set(p, Person::setName, nameSupplier2); // throws exception with message
System.out.println(p.getName()); // Does not execute
}
interface DebuggableBiConsumer<A, B> extends BiConsumer<A, B>, Serializable {}
private static <E, V> void set(
E o, DebuggableBiConsumer<E, V> setter, Supplier<V> valueSupplier) {
try {
setter.accept(o, valueSupplier.get());
} catch (RuntimeException e) {
throw new RuntimeException("Failed to set the value of "+name(setter), e);
}
}
private static String name(DebuggableBiConsumer<?, ?> setter) {
for (Class<?> cl = setter.getClass(); cl != null; cl = cl.getSuperclass()) {
try {
Method m = cl.getDeclaredMethod("writeReplace");
m.setAccessible(true);
Object replacement = m.invoke(setter);
if(!(replacement instanceof SerializedLambda))
break;// custom interface implementation
SerializedLambda l = (SerializedLambda) replacement;
return l.getImplClass() + "::" + l.getImplMethodName();
}
catch (NoSuchMethodException e) {}
catch (IllegalAccessException | InvocationTargetException e) {
break;
}
}
return "unknown property";
}
The limitations are that it will print not very useful method references for lambda expressions (references to the synthetic method containing the lambda code) and `"unknown property"` for custom implementations of the interface.
Problem
I want to use a static method as setter helper that catch exceptions and print debug info about the operation that failed. I don't want the exception details only. I want to show what property was being set so that detail help to debug the problem quickly. I am working with Java 8. How should I provide or detect the property being set? What I wish is to remove the "name" string in the example and get the same result. I know I can't use reflection over the supplied setter method supplied that is transformed to lambda expression and then to BiConsumer. I got this but the property name needs to be provided. ``` /** setter helper method **/ private static <E, V> void set(E o, BiConsumer<E, V> setter, Supplier<V> valueSupplier, String propertyName) { try { setter.accept(o, valueSupplier.get()); } catch (RuntimeException e) { throw new RuntimeException("Failed to set the value of " + propertyName, e); } } ``` Example: ``` Person p = new Person(); Supplier<String> nameSupplier1 = () -> "MyName"; Supplier<String> nameSupplier2 = () -> { throw new RuntimeException(); }; set(p, Person::setName, nameSupplier1, "name"); System.out.println(p.getName()); // prints MyName set(p, Person::setName, nameSupplier2, "name"); // throws exception with message Failed to set the value of name System.out.println(p.getName()); // Does not execute ``` EDIT: I know reflection does not help with the lambdas. I know AOP and I know this can be made with pure reflection too but I want to know if there a better way to get this done with Java 8 that didn't exist with Java 7. It seems it should to me. Now it is possible to do things like to pass a setter method to another one.