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.

gondor89
  • 53
  • 5
  • Show your attempt? – Salem Dec 19 '17 at 12:56
  • I added the attempts, but with bytebuddy I only searched documentations and didn't write actual code. – gondor89 Dec 19 '17 at 13:10
  • 1
    Possible duplicate of [Static initializers in bcel](https://stackoverflow.com/questions/264126/static-initializers-in-bcel) – JimmyB Dec 19 '17 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? – gondor89 Dec 19 '17 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. – Holger Dec 19 '17 at 18:47
  • I can initialize any static final field with any primitive or string value, why now the problem of initializing it with an object from an already existed class is a chicken-and-egg problem?!! – gondor89 Dec 20 '17 at 14:13
  • “Primitive types and strings” should ring a bell, as these types are the only types supported for *compile-time constants*. You can embed constants of these types, because there is a defined bytecode representation for them. A runtime instance of `MyClass` or any other class other than `String` can’t be a compile-time constant. You can still declare `static final` fields of these types, but need to specify an initializer, consisting of *code*, for them. – Holger Dec 28 '17 at 12:29

1 Answers1

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"));
kaos
  • 1,598
  • 11
  • 15