stepan2271
stepan2271

Reputation: 205

How to replace generic with exact implementation using byte-buddy?

I am trying to use byte-buddy to replace generic with the exact implementation. I have the following class:

public class ForwardingConsumer<D extends DoubleConsumer> implements DoubleConsumer {
    private final D next;

    public ForwardingConsumer(D next) {
        this.next = next;
    }

    public void accept(double value) {
        System.out.println("Some internal logic here");
        this.next.accept(value);
    }
}

and exact implementation of DoubleConsumer:

public class Logger implements DoubleConsumer {
    @Override
    public void accept(double value) {
        System.out.println(value);
    }
}

and I want to generate a new class:

public class ForwardingConsumerExact<D extends Logger> implements DoubleConsumer {
    private final Logger next;

    public ForwardingConsumerExact(D next) {
        this.next = next;
    }

    public void accept(double value) {
        System.out.println("Some internal logic here");
        this.next.accept(value);
    }
}

The main goal is to have INVOKEVIRTUAL org/example/templating/Logger.accept (D)V bytecode instead of INVOKEINTERFACE java/util/function/DoubleConsumer.accept. So, I tried to redefine a field type, and also type variable with the following code:

var builder = new ByteBuddy().redefine(baseClass).name(baseClass.getName() + "Exact");
Generic genericType = Generic.Builder.rawType(exactClass).build();

builder = builder.field((ElementMatcher<FieldDescription>) fieldDescription -> true)
                .transform(new ForField((td, token) -> new Token(token.getName(), token.getModifiers(), genericType)));

builder = builder.transform(named("D"),
                                    (typeDescription, typeVariableToken) -> new TypeVariableToken("D", List.of(genericType)));

Method acceptExact = exactClass.getMethod("accept", double.class);
builder = builder.visit(MemberSubstitution.relaxed().invokable(
                    methodDescription -> methodDescription.isInvokableOn(genericType.asErasure()))
                                            .replaceWith(acceptExact)
                                            .on(ElementMatchers.any()));

But the final method transformation fails with the following error:

Caused by: java.lang.IllegalStateException: Cannot invoke public void org.example.templating.Logger.accept(double) on parameter 0 of type interface java.util.function.DoubleConsumer
    at net.bytebuddy.asm.MemberSubstitution$Substitution$ForMethodInvocation.resolve(MemberSubstitution.java:1136)
    at net.bytebuddy.asm.MemberSubstitution$Replacement$Binding$Resolved.make(MemberSubstitution.java:1741)
    at net.bytebuddy.asm.MemberSubstitution$SubstitutingMethodVisitor.visitMethodInsn(MemberSubstitution.java:2357)

I looked into the generated bytecode (without last method transformation), and see some strange things there. Firstly, there are two fields instead of one now:

  final Ljava/util/function/DoubleConsumer; next

  // access flags 0x10
  final Lorg/example/templating/Logger; next

Secondly, when I read this field it still has DoubleConsumer type:

GETFIELD org/example/templating/ForwardingConsumerExact.next : Ljava/util/function/DoubleConsumer;

Is my goal (change INVOKEINTERFACE java/util/function/DoubleConsumer.accept -> INVOKEVIRTUAL org/example/templating/Logger.accept) achievable with byte-buddy? What should be done differently then?

Details: java 17, byte-buddy 1.12.20

Upvotes: 0

Views: 111

Answers (1)

Rafael Winterhalter
Rafael Winterhalter

Reputation: 44032

The JVM does actually allow two fields with the same name but with different types. So by defining an additional field, this is what you get.

You would need to use a transformation for this, but you will run into an additional issue where this transformation is applied late and not visible to most other transformations. Byte Buddy was designed as an additive transformer, and changes like this are not well-supported.

I would therefore recommend you to rather define a new class with the desired shape, if this is possible. Other than that, ASM is the right tool for work like this. Byte Buddy exposes the ASM API in its visit API.

Upvotes: 1

Related Questions