0x56794E
0x56794E

Reputation: 21281

Compile and execute java source file in memory

Say I have a String containing the content of a .java file. Are any APIs out there that would allow me to compile this source file into a virtual .class file (i.e. generate and store the content in memory, NOT creating an actual physical .class file on disk)? And this "virtual" .class would then be loaded and executed in the JVM?

Edit 1: The only reason I want to do this is because sometimes, my application might not have the write permission.

Upvotes: 2

Views: 1509

Answers (3)

javax.tools has everything you need, but needs a bit of coaxing to stop storing class files on the file system. Fortunately, it can be done with a single class of about 100 lines of code:

package simple.tools;

import java.io.*;
import java.net.URI;
import java.util.*;

import javax.tools.*;
import javax.tools.JavaCompiler.CompilationTask;

public class SimpleCompiler {

    public static JavaFileObject sourceFile(String name, String source) {
        return inputFile(name, JavaFileObject.Kind.SOURCE, source);
    }

    private static URI uri(String name, JavaFileObject.Kind kind) {
        return URI.create(name.replace('.', '/') + kind.extension);
    }

    private final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

    private final JavaFileManager manager =
    new ForwardingJavaFileManager<JavaFileManager>(compiler.getStandardFileManager(null, null, null)) {
        @Override
        public JavaFileObject getJavaFileForOutput(Location l, String name, JavaFileObject.Kind kind, FileObject f) {
            return outputFile(name, kind);
        }
    };

    private static JavaFileObject inputFile(String name, JavaFileObject.Kind kind, String content) {
        return new SimpleJavaFileObject(uri(name, kind), kind) {
            @Override
            public CharSequence getCharContent(boolean b) {
                return content;
            }
        };
    }

    private JavaFileObject outputFile(String name, JavaFileObject.Kind kind) {
        return new SimpleJavaFileObject(uri(name, kind), kind) {
            @Override
            public OutputStream openOutputStream() {
                return outputStream(name);
            }
        };
    }

    private final Map<String, byte[]> classes = new HashMap<>();

    private OutputStream outputStream(String name) {
        return new ByteArrayOutputStream() {
            @Override
            public void close() {
                classes.put(name, toByteArray());
            }
        };
    }

    private final ClassLoader loader = new ClassLoader() {
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            byte[] bytes = classes.get(name);
            if (bytes == null) throw new ClassNotFoundException(name);
            return super.defineClass(name, bytes, 0, bytes.length);
        }
    };

    public Class<?> compile(String name, String source) {
        compile(sourceFile(name, source));
        try { return loadClass(name); }
        catch (ClassNotFoundException e) { throw new IllegalStateException(e.toString(), e); }
    }

    public void compile(JavaFileObject... files) {
        compile(Arrays.asList(files));
    }

    public void compile(List<JavaFileObject> files) {
        if (files.isEmpty()) throw new RuntimeException("No input files");
        DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<>();
        CompilationTask task = compiler.getTask(null, manager, collector, null, null, files);
        boolean success = task.call();
        check(success, collector);
    }

    private void check(boolean success, DiagnosticCollector<?> collector) {
        for (Diagnostic<?> diagnostic : collector.getDiagnostics()) {
            if (diagnostic.getKind() == Diagnostic.Kind.ERROR) {
                String message = diagnostic.getMessage(Locale.US);
                throw new RuntimeException(message.split("[\r\n]")[0]);
            }
        }
        if (! success) throw new RuntimeException("Unknown error");
    }

    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loader.loadClass(name);
    }
}

See https://gitlab.com/jcsahnwaldt/simple-java-tools.

Upvotes: 0

Deepak Bala
Deepak Bala

Reputation: 11185

Java does have a compilation API to compile files dynamically, but I'm not aware of an option that would not persist the class files to disk. You can always use a ClassLoader and load those classes dynamically and then use them. You might be able to load the classes in memory by overriding the getFileForOutput method.

Optionally, this file manager might consider the sibling as a hint for where to place the output. The exact semantics of this hint is unspecified. The JDK compiler, javac, for example, will place class files in the same directories as originating source files unless a class file output directory is provided. To facilitate this behavior, javac might provide the originating source file as sibling when calling this method.

Another option is to use an Interpreter like BeanShell that will run the java code for you. It executes script like code and can work in repl mode.

Upvotes: 1

Andrew Thompson
Andrew Thompson

Reputation: 168815

Use the JavaCompiler for this. I think the trick will be to define a custom JavaFileManager.

Upvotes: 1

Related Questions