raphw / byte-buddy

Runtime code generation for the Java virtual machine.
https://bytebuddy.net
Apache License 2.0
6.23k stars 804 forks source link

Cannot invoke public constructor #1638

Closed jimshowalter closed 4 months ago

jimshowalter commented 4 months ago

This fails with "Exception in thread "main" java.lang.IllegalStateException: Cannot invoke public java.util.HashSet() on private final java.util.Set org.foo.bytebuddyctor.Entity2$Setter$Tracking$ByteBuddy$Subclass._modified$Fields$Tracker".

What am I doing wrong?

package org.foo.bytebuddyctor;

import static org.foo.bytebuddyctor.Constants.MODIFIED_FIELDS_TRACKER_FIELD_NAME;
import static org.foo.bytebuddyctor.Constants.MODIFIED_FIELDS_TRACKER_SUBCLASS_NAMING_STRATEGY;
import static org.foo.bytebuddyctor.Utils.getModifiedFieldsTracker;

import java.util.HashSet;
import java.util.Set;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.modifier.FieldManifestation;
import net.bytebuddy.description.modifier.SynchronizationState;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.Transformer;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
import net.bytebuddy.implementation.MethodCall;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.SuperMethodCall;
import net.bytebuddy.matcher.ElementMatchers;

// This is copied from the answer given to the question in
// https://github.com/raphw/byte-buddy/issues/1634, but it blows up with:
// Exception in thread "main" java.lang.IllegalStateException: Cannot invoke public
// java.util.HashSet() on private final java.util.Set
// org.foo.bytebuddyctor.Entity2$Setter$Tracking$ByteBuddy$Subclass._modified$Fields$Tracker
public class AddConstructorSecondAttempt {

  public static void main(String[] args)
      throws InstantiationException, IllegalAccessException, NoSuchMethodException {
    Class<?> setterTrackingEntityClass =
        new ByteBuddy()
            .with(MODIFIED_FIELDS_TRACKER_SUBCLASS_NAMING_STRATEGY)
            .subclass(Entity2.class, ConstructorStrategy.Default.NO_CONSTRUCTORS)
            .defineField(
                MODIFIED_FIELDS_TRACKER_FIELD_NAME,
                TypeDescription.Generic.Builder.parameterizedType(Set.class, String.class).build(),
                Visibility.PRIVATE,
                FieldManifestation.FINAL)
            .defineConstructor()
            .intercept(
                MethodCall.invoke(Entity2.class.getConstructor())
                    .onSuper()
                    .andThen(
                        MethodCall.invoke(HashSet.class.getConstructor())
                            .onField(MODIFIED_FIELDS_TRACKER_FIELD_NAME)))
            .method(ElementMatchers.isSetter())
            .intercept(
                MethodDelegation.to(SetterInterceptor.class).andThen(SuperMethodCall.INSTANCE))
            .transform(Transformer.ForMethod.withModifiers(SynchronizationState.SYNCHRONIZED))
            .make()
            .load(Entity2.class.getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
            .getLoaded();
    Entity2 entity = (Entity2) setterTrackingEntityClass.newInstance();
    entity.setFoo("foo");
    Set<String> modifiedFieldsTracker = getModifiedFieldsTracker(entity);
    System.out.println(modifiedFieldsTracker.iterator().next());
  }
}
jimshowalter commented 4 months ago

byte-buddy-ctor.zip

raphw commented 4 months ago

You want to use setsField, not onField. Also, you likely want to make the constructor public in defineConstructor.

jimshowalter commented 4 months ago

How do I specify the field that it sets in setsField, when that field is being dynamically added in the same builder chain?

raphw commented 4 months ago

Create a latent field description and supply it.

jimshowalter commented 4 months ago

This works:

.defineConstructor(Visibility.PUBLIC)
.intercept(
    MethodCall.invoke(getConstructor(entityClass))
        .onSuper()
        .andThen(
            FieldAccessor.ofField(MODIFIED_FIELDS_TRACKER_FIELD_NAME)
                .setsValue(new HashSet<String>())))