Search code examples
javabytecodebyte-buddy

How do I format getBirthday() with an annotation @MyFormat("%td.%<tm.%<tY")


How do I generate a class which takes a Person instance and returns the birthday as a String instead of a Date formatted with the value of the @MyFormat annotation, without writing that subclass by hand?

The purpose is the use the generated instances for HTML page generation

class Person {

    @MyFormat("%td.%<tm.%<tY")
    public Date getBirthday() { return birthday; }
}

// Usage somewhere in the code

...
List<Person> people = people.parallelStream()
    .map(p -> MyFormatInterceptor.wrap(p))
    .collect(toCollection(ArrayList::new));


System.out.println(people.iterator().next().getBirtday()) // 31.Mai.2015

I have this (see below).

It doesn't matter that the return type changes from Date to String because the call is made by reflection from evaluating the expression "person.birthday".

new ByteBuddy()
   .subclass(person.getClass())
   .method(isAnnotatedWith(MyFormat.class))
   .intercept(MethodDelegation.to(MyFormatInterceptor.class))
   .make()
   .load(person.getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
   .getLoaded();

class MyFormatInterceptor {

    @RuntimeType 
    public static Object format(@Origin Method m, @SuperCall Callable<?> zuper) {

        MyFormat formatAnnotation = m.getAnnotation(MyFormat.class);

        return String.format(formatAnnotation.value(), zuper.call());
    }
}

So the new class would have the same methode name "String getBirthday()" but with String as return value.


Solution

  • I am not entirely sure if I understand what you try to accomplish. The following code is creating a subclass of Person which you do not use:

    new ByteBuddy()
      .subclass(person.getClass())
      .method(isAnnotatedWith(MyFormat.class))
      .intercept(MethodDelegation.to(MyFormatInterceptor.class))
      .make()
      .load(person.getClass().getClassLoader(),                  
            ClassLoadingStrategy.Default.WRAPPER)
      .getLoaded();
    

    When you use this generated subclass at runtime, calling the getBirthday method causes a ClassCastException as the String value cannot be cast to a Date. Byte Buddy does not change the return type, even when invoking a method via reflection.