14

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.

9
  • I don't like the question title. Suggestions or edits are welcome.
    – aalku
    Commented Feb 18, 2014 at 17:20
  • I'm not sure if this is helpfull, but maybe it will help with debug info: code.google.com/p/java-interceptor/wiki/Documentation
    – Zavior
    Commented Feb 18, 2014 at 17:28
  • Thank you @Zavior but I want something less intrusive, not intercepting every call but capturing the error and working only in case of error.
    – aalku
    Commented Feb 18, 2014 at 17:34
  • 1
    That’s a strange question. How does it help solving a problem to print a property name when it wasn’t the property but the Supplier that failed? If it is the property which fails, its setter method will be included in the stack trace. If it’s not, well, focus on the thing that failed rather than the uninvolved property.
    – Holger
    Commented Feb 19, 2014 at 10:10
  • 1
    @Holger It gives you a context that is very useful when is not the code that fails but the input data. If the data is wrong then the processing of it may fail and the wrong data is not enough context to debug, you need to know what is that data supossed to be, for example if it is the person name or the person phone number. The supplier may be a simple lambda like s -> s.substring(1).
    – aalku
    Commented Feb 19, 2014 at 10:32

2 Answers 2

24

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.

17
  • 2
    Well SerializedLambda is the form of keeping compatibility between different implementations. Only the writeReplace method could be replaced with a different mechanism theoretically. If you want to be completely safe you have to write the object for real, e.g. into a byte array and parse the output. The output must contain a SerializedLambda for conforming implementations. And yes, it’s slow in the exception case only.
    – Holger
    Commented Feb 19, 2014 at 12:09
  • 1
    Which compiler did you use? I discovered that the current eclipse beta does not compile it correctly. I used netbeans and jdk-8-fcs-bin-b128. But I tested it now with several versions. Works with netbeans and/or javac. For eclipse we have to wait for a newer version…
    – Holger
    Commented Feb 19, 2014 at 12:17
  • 1
    No, the problem is that the eclipse compiler does not generate a Serializable lambda where it should. The writeObject method will just throw a NotSerializableException.
    – Holger
    Commented Feb 19, 2014 at 12:21
  • 2
    I filed a report to the eclipse team; let’s see how long the fix will take. It might turn out to be an easy fix as Serialization for lambda expressions ((…) -> …) already works.
    – Holger
    Commented Feb 19, 2014 at 17:09
  • 3
    MethodHandle is very close to that. On the byte code level they can be used like constants (like Class or String literals) already. And with Java 8 you can decode a direct handle. By the way, this is used by the lambda expression/method reference implementation internally.
    – Holger
    Commented Feb 25, 2014 at 20:47
1

What about that?

Source code:

package test;

import java.io.Serializable;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.function.Function;

public class Person {
    public String getName() {
        return "Michael";
    }

    public static void main(String[] args) throws
            NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Serializable s = (Function<Person, String> & Serializable) Person::getName;

        Method method = s.getClass().getDeclaredMethod("writeReplace");
        method.setAccessible(true);
        SerializedLambda serializedLambda = (SerializedLambda) method.invoke(s);

        System.out.println(serializedLambda.getImplClass().replace("/", ".")
                + "::" + serializedLambda.getImplMethodName());
    }
}

Output:

test.Person::getName

You actually need to look up the method "writeReplace", call it and evaluate its return value which is of type SerializedLambda.

Not the answer you're looking for? Browse other questions tagged or ask your own question.