Search code examples
javacode-generationbytecodebyte-buddy

How to add field and manipulate value to a class with ByteBuddy?


I'd like to add the following code to existing classes using ByteBuddy. Given an existing class SomeSample I want to turn this into the follwoing:

class SomeSample {

  private @Transient boolean isNew = true;

  public boolean isNew() {
    return isNew;
  }

  @PrePersist
  @PostLoad
  void markNotNew() {
    this.isNew = false;
  }
}

Original attempt

I can get the field and methods added properly. What I cannot really get to work is the assignment of the value. I've learned that I need to augment all existing constructors of the class I want to augment as technically an assignment declared like this is compiled into the constructors.

I've created the following helper:

public class Helper {

  @OnMethodExit
  public static void initField(@FieldValue(value = "isNew", readOnly = false) boolean value) {
    value = !value;
  }
}

and tried to assign this as follows:

builder.constructor(ElementMatchers.any())
  .intercept(Advice.to(Helper.class));

I would've expected the advice to be added at the end of the original declarations but during the build I receive the following error:

Failed to transform class files in …: Cannot call super (or default) method for public ….SomeSample()

Alternative approach

Instead of flipping the value of the field I thought I could also stay with the default (false) and negate the value returned from the generated isNew() method. If I change my helper to this:

public class Helper {

  public static boolean isNew(@FieldValue(value = "isNew") boolean value) {
    return !value;
  }
}

When I change my method generating code for isNew() to the following:

builder = builder.defineMethod("isNew", boolean.class, Visibility.PUBLIC)
  .intercept(MethodDelegation.to(Helper.class));

I get:

None of [public static boolean ….Helper.isNew(boolean)] allows for delegation from public boolean SomeSample.isNew()

Any idea?


Solution

  • That was maybe an unfortunate API choice but you can use Advice both as decorator and interceptor. What you likely would want to would be to set:

    builder = builder.visit(Advice.to(Helper.class).on(isConstructor()))
    

    This adds the code around the existing code. With the suggested approach, you replace the method around a call to the original implementation. If you define a new method, such an implementation does not exist and the error you are seeing is yielded.