Question

Let's say I have an abstract class:

abstract class Foo extends Bar {

    public abstract int foo();

}

that I want to extend at runtime to create a Class object. The hope would be that I could have a dynamically generated class:

class FooImpl extends Foo {

    @Override
    public int foo() {
        return 5;
    }

}

that would be represented by a Class object and that I could then use reflection to create new instances of. The key is that I would like to decide the return value of the method foo() at runtime. My thought is to use ASM to create the bytecode for the class and then use reflection on a ClassLoader object to define the Class.

Is using ASM and then reflection of the method ClassLoader#defineClass on the generated bytes the best way to implement abstract methods at runtime with non-hardcoded values?

If yes, how would I go about doing that. My gut is to utilize the ASMifierClassVisitor, but I'm not quite sure on the exact method of doing that. I know that if all else fails I can manually go through the JVM instructions required to define a specific class but I feel there must be an easier way.

If no, what is the best way and how would I go about using the best way?

EDIT: I checked out all of the answers and I decided that none of them were exactly what I was looking for. I ended up creating a small implementation of what I was talking about with ASM. I figured I should post it here:

import org.objectweb.asm.*;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;

/**
 * Created by IntelliJ IDEA.
 * User: Matt
 * Date: 9/17/11
 * Time: 12:42 PM
 */
public class OverrideClassAdapter extends ClassAdapter {

    private final HashMap<String, Object> code;
    private final String className;

    private final ClassWriter writer;

    private String superName;

    public OverrideClassAdapter(ClassWriter writer, String className, Queue<int[]> constructorCode, HashMap<String, Object> code) {
        super(writer);
        this.writer = writer;
        this.className = className;
        this.code = code;
    }

    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        this.superName = name;
        if((access & Opcodes.ACC_ABSTRACT) != 0)
            access &= ~Opcodes.ACC_ABSTRACT;
        if((access & Opcodes.ACC_INTERFACE) != 0)
            access &= ~Opcodes.ACC_INTERFACE;
        cv.visit(version, access, className, signature, name, null);
    }

    @Override
    public void visitSource(String source, String debug) {
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        boolean isAbstract = (access & Opcodes.ACC_ABSTRACT) != 0;
        if(isAbstract)
            access &= ~Opcodes.ACC_ABSTRACT;
        MethodWriter mw = (MethodWriter) cv.visitMethod(access, name, desc, signature, exceptions);
        Object value = code.get(name);
        if(isAbstract || value != null) {
            if(value instanceof BytecodeValue) {
                BytecodeValue returnableValue = (BytecodeValue) value;
                int[] byteCode = new int[returnableValue.getValueCode().length + 1];
                System.arraycopy(returnableValue.getValueCode(), 0, byteCode, 0, returnableValue.getValueCode().length);
                if(returnableValue.getValueCode().length > 1 && returnableValue.getValueCode()[1] == 0) {
                    byteCode[1] = writer.newConst(returnableValue.getValue());
                }
                byteCode[byteCode.length - 1] = returnableValue.getReturnCode();
                value = byteCode;
            }
            return new OverrideMethodAdapter(mw, (int[]) value);
        }
        return mw;
    }

    private class OverrideMethodAdapter extends MethodAdapter {

        private final int[] code;

        private final MethodWriter writer;

        public OverrideMethodAdapter(MethodWriter writer, int[] code) {
            super(writer);
            this.writer = writer;
            this.code = code;
        }

        @Override
        public void visitEnd() {
            try {
                Field code = MethodWriter.class.getDeclaredField("code");
                code.setAccessible(true);
                ByteVector bytes = new ByteVector();
                for(int b : this.code)
                    bytes.putByte(b);
                code.set(writer, bytes);
            } catch (Exception e) {
              e.printStackTrace();
            }
        }
    }

    public static byte[] extendClassBytes(Class clazz, String className, HashMap<String, Object> methodImpls) throws IOException {
        ClassReader cr = new ClassReader(clazz.getName());
        ClassWriter cw = new ClassWriter(0);
        cr.accept(new OverrideClassAdapter(cw, className, methodImpls), ClassReader.SKIP_DEBUG);
        cr = new ClassReader(cw.toByteArray());
        cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
        cr.accept(cw, ClassReader.SKIP_DEBUG);
        //CheckClassAdapter.verify(new org.objectweb.asm.ClassReader(cw.toByteArray()), true, new PrintWriter(System.out));
        /*File file = new File(className + ".class");
        new FileOutputStream(file).write(cw.toByteArray());*/
        return cw.toByteArray();
    }


    public static Class extendClass(Class clazz, String className, HashMap<String, Object> methodImpls) throws IOException {
        return defineClass(extendClassBytes(clazz, className, methodImpls), className);
    }

    public static Class defineClass(byte[] code, String name) {
        try {
            Method method = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
            method.setAccessible(true);
            return (Class) method.invoke(Thread.currentThread().getContextClassLoader(), name, code, 0, code.length);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

No correct solution

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top