Search code examples
javacode-generationintrospectionbyte-buddy

Invoke method on a argument with a cast using Byte Buddy


I'm very new to Byte Buddy, and I'm trying to use it to create implementations of an interface that execute getter methods on objects. My interface looks like this:

public interface Executor {
    Object execute(final Object target);
}

And the idea is that if I have a class such as:

public class User {
   ...
   public String getName() { return this.name; }
   public String getSurname() { return this.surname; }
}

I need to be able to create one implementation of the Executor interface which execute(obj) method assumes obj is a User and calls its getName(), then another implementation which does the same for getSurname(), etc. Equivalent java code would therefore be:

public class MyHypotheticalByteBuddyExecutorImpl implements Executor {
    @Override
    Object execute(final Object target) {
        return ((User) target).getName();
    }
}

So the idea is to be able to create classes like the above for any combination of class + getter, like in this case User + getName().

I (think I) know how to make Byte Buddy create a class that almost does that:

final Method nameMethod = User.class.getMethod("getName", null);

final Class<?> myHypotheticalByteBuddyExecutorImpl =
    new ByteBuddy()
        .subclass(Object.class)
        .implement(Executor.class)
        .method(ElementMatchers.named("execute"))
            .intercept(MethodCall.invoke(nameMethod).onArgument(0))
        .make()
        .load(ByteBuddyTest.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
        .getLoaded();

...but then Byte Buddy rightly throws an Exception saying that I cannot execute method getName() on an Object. I'm assuming therefore that I'm lacking the ((User) target) cast:

Exception in thread "main" java.lang.IllegalStateException: Cannot invoke public java.lang.String com.example.User.getName() on class java.lang.Object
    at net.bytebuddy.implementation.MethodCall$TargetHandler$ForMethodParameter$Resolved.toStackManipulation(MethodCall.java:2527)
    at net.bytebuddy.implementation.MethodCall$Appender.toStackManipulation(MethodCall.java:3541)
    at net.bytebuddy.implementation.MethodCall$Appender.apply(MethodCall.java:3502)
    ...

I believe this could be defined as a StackManipulation (I might be totally wrong), something like:

final StackManipulation typeCasting =
    TypeCasting.to(TypeDescription.ForLoadedType.of(User.class));

But I cannot find anywhere in the Byte Buddy API how I can apply this cast (or any other code I might need for casting) to the argument of the execute(Object) method before executing the getter.

How can I implement this?


Solution

  • This should work by using dynamic typing which you can configure by:

    MethodCall.invoke(nameMethod)
      .onArgument(0)
      .withAssigner(Assigner.DEFAULT, Assigner.Typing.DYNAMIC);
    

    The stack manipulation is used for creating custom byte code, I do not think that this is what you want to do here.