Laird Nelson
Laird Nelson

Reputation: 16248

How do I invoke a MethodCall with another MethodCall on the first MethodCall's first argument?

I am attempting to implement this:

@Override // java.util.Function
public Object apply(final ThingWithParameters thing) {
  return bozo(thing.getParameters());
}
private Object bozo(final Object[] parameters) {
  // Use magic ByteBuddy stuff to "spread" the parameters out
  return this.object.baz(parameter0, parameter1); // ...where these are the first two parameters in the array
}

I've tried:

builder = builder
  .defineMethod("bozo" ...)
  .implement(MethodCall.invoke(bazDescription)
             .onField(thisObjectFieldDescription)
             .withArgumentArrayElements(0, 2))

  .implement(parameterizedTypeFunctionThingWithParametersObject) // Function<ThingWithParameters, Object>
  .intercept(MethodCall.invoke(bozoDescription) // implement apply(ThingWithParameters thing): call bozo()...
             .withMethodCall(MethodCall.invoke(getParametersMethodDescription) // ...with the result of invoking getParameters()...
                             .onArgument(0))); // ...on thing (except see below)

…but this does not work. The error message is deeply weird and leads me to believe that argument 0 is not referring to thing, but to something else entirely (maybe the receiver type somehow?). Here is a snippet from the stack:

java.lang.IllegalStateException: Cannot invoke public java.lang.Object[] com.foo.ThingWithParameters.getParameters() on java.util.function.Function<? super V, ? extends com.foo.ThingWithParameters>
    at net.bytebuddy.implementation.MethodCall$TargetHandler$ForMethodParameter$Resolved.toStackManipulation(MethodCall.java:2530)
    at net.bytebuddy.implementation.MethodCall$Appender.toStackManipulation(MethodCall.java:3548)
    at net.bytebuddy.implementation.MethodCall$ArgumentLoader$ForMethodCall.toStackManipulation(MethodCall.java:1687)
    at net.bytebuddy.implementation.MethodCall$Appender.toStackManipulation(MethodCall.java:3545)
    at net.bytebuddy.implementation.MethodCall$Appender.apply(MethodCall.java:3509)
    at net.bytebuddy.dynamic.scaffold.TypeWriter$MethodPool$Record$ForDefinedMethod$WithBody.applyCode(TypeWriter.java:708)
    at net.bytebuddy.dynamic.scaffold.TypeWriter$MethodPool$Record$ForDefinedMethod$WithBody.applyBody(TypeWriter.java:693)
    at net.bytebuddy.dynamic.scaffold.TypeWriter$MethodPool$Record$ForDefinedMethod.apply(TypeWriter.java:600)
    at net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForCreation.create(TypeWriter.java:5660)
    at net.bytebuddy.dynamic.scaffold.TypeWriter$Default.make(TypeWriter.java:2166)
    at net.bytebuddy.dynamic.scaffold.subclass.SubclassDynamicTypeBuilder.make(SubclassDynamicTypeBuilder.java:232)

Note that while my proxy class does in fact implement Function, it actually implements Function<ThingWithParameters, Object>. And, I never define V. I'm assuming ByteBuddy does this internally somewhere.

What is the recipe I should be using?

Upvotes: 2

Views: 251

Answers (1)

Rafael Winterhalter
Rafael Winterhalter

Reputation: 44077

The Function class defines multiple methods, some of them as default methods. Byte Buddy will attempt to implement all those methods by your Implementation where it works for the apply method but fails for others. Therefore, rather define an explicit matcher to indicate the method you'd like to implement.

Upvotes: 1

Related Questions