0

I have a class that I want to dynamically generate a subclass from and add the proper generic on-the-fly. For example, here is a base class I'd want to extend.

public class Foo<A> {

     private A attribute;

     // constructor
     public Foo(A value) {
         this.attribute = value;
     }

     public A getAttribute() {
          return attribute;
     }
}

I want to dynamically generate a subclass like this, which fills in the "generic" A value with a specified type, lets say 'Dog' for this example.

public class SubClassOfFoo extends Foo<Dog> {

         public SubClassOfFoo(Dog dog) {
              super(dog);
         }
}

I have looked at CGLib but I don't see how to extend and add a "Generic" type to it. Am I missing something in CGLib or is there another library capable of this feature?

Jason
  • 2,006
  • 3
  • 21
  • 36
  • Do you want to generare source code, or classes at runtime? – NickL May 02 '17 at 20:59
  • I'd prefer to create classes on-the-fly at runtime. If I have to generate source code and then load the source at runtime, well, that is a second best option... – Jason May 02 '17 at 21:01
  • Well.. the 'generic A' type will be of type `Object` at runtime due to type erasure. – NickL May 02 '17 at 21:03

3 Answers3

3

cglib is a very old library and was created before generics were even discussed. Therefore, there is no support for adding generic signatures using the library unless you register an ASM visitor and add the signature manually. Doing so does however not add the appropriate bridge methods if you require those.

If you want to create generic classes, you can have a look at Byte Buddy (which I created, I also casually maintain cglib), which adopts Java's generic type system for all of its operations and transparently adds all bridges just as javac would do. You can create the example class with Byte Buddy as follows:

Class<?> subclass = new ByteBuddy()
  .subclass(TypeDescription.Generic.Builder.parameterizedType(Foo.class, Dog.class)
                                           .build())
  .make()
  .load(Foo.class.getClassLoader())
  .getLoaded();

You can now check the generic type of the generated subclass:

Type type = subclass.getGenericSuperclass();
assert type instanceof ParameterizedType;
assert ((ParameterizedType) type)).getRawType() == Foo.class;
assert ((ParameterizedType) type).getActualTypeArguments()[0] == Dog.class;
Rafael Winterhalter
  • 42,759
  • 13
  • 108
  • 192
-1

I want to dynamically generate a subclass like this which fills in the "generic" A value with a specified type

If for dynamically generate you mean at runtime. You can't do it.

Generics in Java do not exist at runtime. They are simply compile-time syntactic sugar.

granmirupa
  • 2,780
  • 16
  • 27
  • I think there are byte-code writing libraries that will do this. I think some JEE containers do this for implementing certain functions, such as entities for JPA. But I don't think you can do it with pure Java (you'll need some JNI code) and it sure as heck ain't easy. – markspace May 02 '17 at 21:35
  • Type erasure only removes type information from "normal" instances not from references or class objects. – k5_ May 02 '17 at 22:19
  • @markspace I know it can be done using some bytecode manipulation framework as ASM. But if he is generating classes in this way (let's say ASM). Why does he need to use generic then? What I'm pointing out is that generic is only compile time. – granmirupa May 02 '17 at 23:09
  • @k5_ I don't understand this downvote. What the OP is asking is IMHO conceptually wrong. – granmirupa May 02 '17 at 23:10
  • @granmirupa first some generic parameters are available at runtime, e.g the `extends Foo` (you get it with `instance.getClass().getGenericSuperclass()`). Second: it is possible, see my answer. Your answer does not critise the question or its concepts, it makes claims that are just false. – k5_ May 02 '17 at 23:17
  • @k5_ The OP is stating: _I have a class that I want to dynamically generate a subclass from and add the proper generic on-the-fly_ this CANNOT be done. Because generics are ONLY at COMPILE TIME. Read [here](https://docs.oracle.com/javase/tutorial/java/generics/erasure.html), [here](https://docs.oracle.com/javase/tutorial/java/generics/genTypes.html) and [here](http://stackoverflow.com/questions/14670839/how-to-set-the-generic-type-of-an-arraylist-at-runtime-in-java). Then, about _.getGenericSuperclass()_ getting the information at runtime doesn't mean you can use generics at runtime. – granmirupa May 03 '17 at 01:08
  • @granmirupa A lot of libraries (such as Spring or Hibernate) rely on the generic information which is why it can make sense to add the information in generated classes. You are right that the JVM's runtime engine does not use generics but the information is preserved and available via reflection. Generating generic types as runtime is something that can be done (and increasingly is), see my answer. – Rafael Winterhalter May 03 '17 at 06:33
-2

All things possible with Java code are possible with bytecode generation.

I'm not that familiar with cglib, but here is some asm code that will do what you asked for.

The only special part related to generics it the genericSig() method that defines "a second signature" for the superclass besides the rawform Type.getInternalName(Foo.class).

This is not an general solution, it creates the bytecode for the example in the question. For the general solution many other things need to be considered, especially bridge methods.

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.signature.SignatureVisitor;
import org.objectweb.asm.signature.SignatureWriter;



public class GenExtendFoo {
    public static void main(String[] args) throws Exception {
        ClassWriter cw = new ClassWriter(0);
        /** Extend Foo with normal raw superclass "com/Foo"
            And generic superclass genericSig() Lcom/Foo<Ljava/lang/Integer;>;
        **/
        cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "SubClass", genericSig(Foo.class, Integer.class),
            Type.getInternalName(Foo.class), new String[] {});
        createConstructor(cw);
        cw.visitEnd();

        byte[] b = cw.toByteArray();
        Class<?> cls = (Class<?>) new MyClassLoader().defineClass("SubClass", b);

        Foo<Integer> instance = (Foo<Integer>) cls.getConstructor(Integer.class).newInstance(1);
        System.out.println(instance.getValue());

        /* The generic type is available with GenericSuperclass just like in the plain java version */
        ParameterizedType para = (ParameterizedType) instance.getClass().getGenericSuperclass();
        System.out.println(para.getActualTypeArguments()[0]);
    }

    private static void createConstructor(ClassWriter cw) {
        // Create constructor with one parameter that calls superclass
        // constructor with one parameter
        MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>",
            "(L" + Type.getInternalName(Integer.class) + ";)V", null, null);
        mv.visitMaxs(2, 2);
        mv.visitVarInsn(Opcodes.ALOAD, 0);
        mv.visitVarInsn(Opcodes.ALOAD, 1);
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(Foo.class), "<init>",
            "(L" + Type.getInternalName(Object.class) + ";)V", false); // call
        mv.visitInsn(Opcodes.RETURN);

        mv.visitEnd();
    }

    public static String genericSig(Class<?> mainType, Class<?> typeParameter) {
        SignatureVisitor sv = new SignatureWriter();
        SignatureVisitor psv = sv.visitSuperclass();
        psv.visitClassType(Type.getInternalName(mainType));
        SignatureVisitor ppsv = psv.visitTypeArgument('=');
        ppsv.visitClassType(Type.getInternalName(typeParameter));
        ppsv.visitEnd();
        psv.visitEnd();
        return sv.toString();
    }
}
static class MyClassLoader extends ClassLoader {
    public Class<?> defineClass(String name, byte[] b) {
        return defineClass(name, b, 0, b.length);
    }
}
k5_
  • 5,450
  • 2
  • 19
  • 27
  • Why is this voted down (-2)? If someone could comment why it is voted down, I will not waste my time pursuing this example as an answer. Thanks. – Jason May 03 '17 at 16:54
  • @Jason one can only guess. But one thing for certain, I should have mentioned that this "solution" is written for your stated problem and is far from a general solution. Depending on complexity of the extended class you might need to introduce bridge methods. – k5_ May 03 '17 at 19:04