Creating valid files.class from Java code

There is an interest in creating a compiled class file directly from the code.

For example, in the main() method, we use the following code:

File file = new File("C:/.../out/production/projectName/SomeClass.class");   
file.createNewFile();

We got an empty file SomeClass.class.

Now you need to write something in it.
OutputStream os = new FileOutputStream(file); os.write("public".getBytes());

This approach does not work. The output is a file that can only be read with notepad. From the development environment file SomeClass.class empty.

How to write bytecode to .class files, which will the JVM understand?

Author: Donatello, 2018-12-04

2 answers

First, you need to know the format of the class files. Secondly, you need to know the bytecode and the rules for writing it. It is desirable to know the principles of the virutal machine. After mastering all this, you can take a bytecode manipulation tool, like ASM, and write your own "compiler"

import java.io.FileOutputStream;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;

import static org.objectweb.asm.Opcodes.*;

public class HelloClass {

    public static void main(String[] args) throws Exception {
        ClassWriter cw=new ClassWriter(0);

        cw.visit(V1_6, ACC_PUBLIC+ACC_SUPER, "sample/HelloGen", null, "java/lang/Object", null);

        // Конструктор по умолчанию
        {
            MethodVisitor mv=cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
            mv.visitCode();
            mv.visitVarInsn(ALOAD, 0);
            mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
            mv.visitInsn(RETURN);
            mv.visitMaxs(1, 1);
            mv.visitEnd();
        }

        // Метод main
        {
            MethodVisitor mv=cw.visitMethod(ACC_PUBLIC+ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
            mv.visitCode();
            mv.visitFieldInsn(GETSTATIC,"java/lang/System", "out", "Ljava/io/PrintStream;");
            mv.visitLdcInsn("Hello");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
            mv.visitInsn(RETURN);
            mv.visitMaxs(2, 1);
            mv.visitEnd();
        }
        cw.visitEnd();

        // Сохраняем байткод на диск
        FileOutputStream out=new FileOutputStream("/tmp/sample/HelloGen.class");
        out.write(cw.toByteArray());
        out.close();
    }
}

Or don't dive too deep into bike building and use the Compiler API:

import java.net.URI;
import java.util.ArrayList;

import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.ToolProvider;

import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.file.JavacFileManager;
import com.sun.tools.javac.model.JavacElements;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.Name;


/**
 * Класс эмулирующий для компилятора файлы исходного кода
 * и позволяющий компилировать код прямо из памяти
 */
class JavaSourceFromString extends SimpleJavaFileObject {
    private final String code;

    public JavaSourceFromString(String name, String code) {
        super(URI.create("string:///" + name.replace('.','/') + Kind.SOURCE.extension), Kind.SOURCE);
        this.code = code;
    }

    @Override
    public CharSequence getCharContent(boolean ignoreEncodingErrors) {
        return code;
    }
}

public class CompilerDemo {
    private static final ClassLoader classLoader = ToolProvider.getSystemToolClassLoader();
    private static final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

    /**
     * Метод формирующий абстрактное синтаксическое дерево
     * и преобразующий его в исходный код
     */
    private static String generateSource() {
        Context ctx = new Context();
        JavacFileManager.preRegister(ctx);
        TreeMaker treeMaker = TreeMaker.instance(ctx);
        JavacElements elements = JavacElements.instance(ctx);

        JCTree.JCModifiers modifiers = treeMaker.Modifiers(Flags.PUBLIC);

        // Тело генерируемого метода
        JCTree.JCBlock methodBody = treeMaker.Block(0, List.of(
            treeMaker.Exec(
                treeMaker.Apply(
                    List.<JCTree.JCExpression>nil(),
                        treeMaker.Select(
                            treeMaker.Select(
                                treeMaker.Ident(
                                    elements.getName("System")
                                ),
                                elements.getName("out")
                            ),
                            elements.getName("println")
                        ),
                        List.<JCTree.JCExpression>of(
                            treeMaker.Literal("Hello, World!")
                        )
                    )
                )
            )
        );

        // Определение генерируемого метода
        JCTree.JCMethodDecl method = treeMaker.MethodDef(
            modifiers,
            elements.getName("sayHi"),
            treeMaker.Type(new Type.JCVoidType()),
            List.<JCTree.JCTypeParameter>nil(),
            List.<JCTree.JCVariableDecl>nil(),
            List.<JCTree.JCExpression>nil(),
            methodBody,
            null
        );

        // Определение генерируемого класса
        JCTree.JCClassDecl tree = treeMaker.ClassDef(
            modifiers,
            elements.getName("Example"),
            List.<JCTree.JCTypeParameter>nil(),
            null,
            List.<JCTree.JCExpression>nil(),
            List.of(method)
        );

        return tree.toString();
    }

    /**
     * Метод компилирующий исходный код
     */
    public static void compile(Iterable<? extends JavaFileObject> compilationUnits) {
        CompilationTask task = compiler.getTask(null, null, null, null, null, compilationUnits);
        task.call();    
    }

    public static void main(String[] args) {
        java.util.List<JavaFileObject> sources = new ArrayList<>();

        // Генерируем исходный код класса
        sources.add(new JavaSourceFromString("Example", generateSource()));

        // Компилируем сгенерированный код
        compile(sources);
    }
}
 1
Author: Sergey Gornostaev, 2018-12-05 14:50:32

First, you need to do flush() on OutputStream to save the data. Secondly, to get a valid .class file, you need to compile valid Java code. And it's not just writing a set of bytes to a file. Then the resulting Class can be saved to a file, or you can immediately download and execute it. Yes, this can be done from Java code. Most recently, there was an article on Habr on how to do this in the latest versions of Java. Well, Googling the topic of compiling java code from java code further, you will find thousands of examples.

 3
Author: zolt, 2018-12-05 02:46:05