1

I'm trying to create a simple class that contains static final object fields, using any byte code library. I have tried BCEL and Byte Buddy but had no success. The class I want to construct looks like this. Thanks.

public class ConstructedClass{

   public static final MyClass a = new MyClass();

   public static final MyClass b = new MyClass(); 
}

My attempt with BCEL:

ClassGen classGen=new ClassGen("org.test.lib.core", "java.lang.Object","core.java", Const.ACC_PUBLIC, null);
classGen.addEmptyConstructor(Const.ACC_PUBLIC); 

ConstantPoolGen constantPoolGen=classGen.getConstantPool();
int access_flags = Const.ACC_PUBLIC | Const.ACC_STATIC | Const.ACC_FINAL; 
final FieldGen FieldGen=new FieldGen( access_flags,Type.getType(Property.class), "test", constantPoolGen);
//FieldGen.setInitValue(new MyClass());

My second attempt also with BCEL:

private static final Type[] arg =  {Type.getType(MyClass.class)};
InstructionList init = new InstructionList();
InstructionFactory factory=new InstructionFactory(classGen);
//init.append(new PUSH(constantPoolGen, new MyClass())); 
init.append(factory.createInvoke(MyClass.class.getName(), "valueOf", 
             Type.getType(MyClass.class), arg, Const.INVOKESTATIC)); 
init.append(factory.createPutStatic("org.test.lib.core", "test", Type.getType(Property.class))); 

The commented lines is where pushing my object didn't work.

7
  • Show your attempt? Commented Dec 19, 2017 at 12:56
  • I added the attempts, but with bytebuddy I only searched documentations and didn't write actual code. Commented Dec 19, 2017 at 13:10
  • 1
    Possible duplicate of Static initializers in bcel Commented Dec 19, 2017 at 13:20
  • I think the main problem is how to cast MyClass to ObjectType. Because The commented methods take only ObjectType, so where to insert MyClass object? Commented Dec 19, 2017 at 13:23
  • You can’t push MyClass objects. How should that work? Storing a MyClass instance inside a class file? You can generate code that will create a new MyClass instance and assign it to the field. That may be the equivalent of new MyClass() or an invocation of a factory method like MyClass.valueOf(), but when your factory method requires an existing instance as argument, you have a chicken-and-egg problem. Commented Dec 19, 2017 at 18:47

1 Answer 1

2

With ByteBuddy You can generate a static initialization block using ByteCodeAppender. This will result in a little different class then You wanted but i think close enough:

public class ConstructedClass {

   public static final MyClass a;

   static {
        a = new MyClass();
   } 
}

Generation code:

public class ByteBuddyTest {

    public static void main(String[] args) throws Exception {
        DynamicType.Loaded loaded =
            new ByteBuddy()
            .subclass(Object.class)
                .initializer( new ByteCodeAppender() {
                    @Override public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext,
                                                MethodDescription instrumentedMethod) {
                        StackManipulation.Size size = new StackManipulation.Compound(
                            TypeCreation.of(new TypeDescription.ForLoadedType(MyClass.class)),
                            Duplication.SINGLE,
                            MethodInvocation.invoke(new TypeDescription.ForLoadedType(MyClass.class).getDeclaredMethods().filter(ElementMatchers.isDefaultConstructor()).getOnly()),
                            FieldAccess.forField(implementationContext.getInstrumentedType().getDeclaredFields().filter(ElementMatchers.named("a")).getOnly()).write()
                        ).apply(methodVisitor, implementationContext);

                        return new Size(size.getMaximalSize(), instrumentedMethod.getStackSize());
                    }
                })
            .name("org.test.lib.core.ConstructedClass")
            .modifiers(Opcodes.ACC_PUBLIC)
            .defineField("a", MyClass.class, Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL)
            .make()
            .load(ByteBuddyTest.class.getClassLoader(), ClassLoadingStrategy.Default.INJECTION);

        Object obj = loaded.getLoaded().getConstructor().newInstance();
        System.out.println(obj.getClass().getField("a").get(obj));
    }

    public static class MyClass {
        public MyClass(String arg) {}

        public static MyClass createMyClass(String arg) {
            return new MyClass(arg);
        }
    }
}

Update for comment

To call a static factory method instead of constructor You just need to replace constructor call:

StackManipulation.Size size = new StackManipulation.Compound(
    new TextConstant("test"),
    MethodInvocation.invoke(new TypeDescription.ForLoadedType(MyClass.class).getDeclaredMethods().filter(ElementMatchers.named("createMyClass")).getOnly()),
         FieldAccess.forField(implementationContext.getInstrumentedType().getDeclaredFields().filter(ElementMatchers.named("a")).getOnly()).write()
).apply(methodVisitor, implementationContext);

Multiple fields

    DynamicType.Builder builder = new ByteBuddy().subclass(Object.class);
    List<String> fields = Lists.newArrayList("a", "b", "c");

    for (String str : fields) {
        builder = builder.defineField(str, MyClass.class, Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL);
    }

    DynamicType.Loaded loaded = builder.make().load(ByteBuddyTest.class.getClassLoader(), ClassLoadingStrategy.Default.INJECTION);

    Object obj = loaded.getLoaded().getConstructor().newInstance();
    System.out.println(obj.getClass().getField("a"));
    System.out.println(obj.getClass().getField("c"));
Sign up to request clarification or add additional context in comments.

6 Comments

Thanks a lot. Can I initialize a with sth like this: a=B.createBClass("arg");
Yes You can, is quite simple if the argument can be a compile time constant. I updated the answer.
Many thanks you made my day, I've been searching for a proper docs for the past two days.
I found ByteBuddy unit tests a good place to search for examples not covered by docs.
I updated the answer, basically You don't have to chain all builder methods in one go.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.