javabytecodebyte-buddybcel

How to create class with final static fields in any byte code library?


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.


Solution

  • 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"));