Javaのコードをインメモリでコンパイルして実行するサンプル

文字列のJavaコードをインメモリでコンパイルして実行するサンプル。
JavaCompiler によりJavaソースをコンパイルすることができる。以下はコンパイル後のクラスをメモリに保持してそのまま実行する。

package etc9;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.security.SecureClassLoader;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.ToolProvider;
import javax.tools.JavaFileObject.Kind;

public class Main {

    public void exec() throws Exception {
        
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        JavaFileManager manager = new ClassFileManager(compiler);
        
        compiler.getTask(null,
                manager,
                null,
                Arrays.asList("-verbose,-target,1.6".split(",")),
                null,
                createJavaFileObjects()
            ).call();
        
        ClassLoader cl = manager.getClassLoader(null);
        cl.loadClass("Hello").newInstance();
        manager.close();
    }
    
    protected List<? extends JavaFileObject> createJavaFileObjects() {
        String hello =
            "public class Hello {" +
            "  public Hello() {" +
            "    Message m = new Message();" +
            "    m.message = \"Hello, World!\";" +
            "    System.out.println(m.message);\n" +
            "  }" +
            "}";
        String message = 
            "public class Message { public String message; }";
            
        return Arrays.asList(
                new JavaCode("Hello", hello),
                new JavaCode("Message", message));
    }

    public static void main(String ... args) throws Exception {
        new Main().exec();
    }


    /** Javaソースコードを表すクラス */
    static class JavaCode extends SimpleJavaFileObject {
        final String code;
        public JavaCode(String name, String code) {
            super(getStringURI(name), Kind.SOURCE);
            this.code = code;
        }
        @Override
        public CharSequence getCharContent(boolean ignoreEncodingErrors) {
            return code;
        }
        private static URI getStringURI(final String name) {
            return URI.create("string:///" + name.replace('.','/') +
                    Kind.SOURCE.extension);
        }
    }
    
    /** Javaのクラスオブジェクトを表すクラス */
    static class JavaClassObject extends SimpleJavaFileObject {
        
        private Class<?> clazz;
        private final ByteArrayOutputStream bos = new ByteArrayOutputStream();

        protected JavaClassObject(String name, Kind kind) {
            super(URI.create("string:///" + name.replace('.', '/') + kind.extension), kind);
        }
        @Override
        public OutputStream openOutputStream() throws IOException {
            return bos;
        }
        public byte[] getBytes() {
            return bos.toByteArray();
        }
        public void setDefinedClass(Class<?> clazz) {
            this.clazz = clazz;
        }
        public Class<?> getDefinedClass() {
            return clazz;
        }
    }
    
    /** Javaのクラスを管理するマネージャ */
    static class ClassFileManager extends ForwardingJavaFileManager<JavaFileManager> {
        
        private final Map<String, JavaClassObject> classMap = 
            new HashMap<String, JavaClassObject>();

        private ClassLoader classLoader = new SecureClassLoader() {
            @Override
            protected Class<?> findClass(String name) throws ClassNotFoundException {
                if (!classMap.containsKey(name))
                    return super.findClass(name);
                
                JavaClassObject javaClass = classMap.get(name);
                Class<?> clazz = javaClass.getDefinedClass();
                
                if (clazz == null) {
                    byte[] b = javaClass.getBytes();
                    clazz = super.defineClass(name, b, 0, b.length);
                    javaClass.setDefinedClass(clazz);
                  }
                return clazz;
            }
        };

        public ClassFileManager(JavaCompiler compiler) {
            super(compiler.getStandardFileManager(null, null, null));
        }

        @Override
        public JavaFileObject getJavaFileForOutput(Location location,
                String className, Kind kind, FileObject sibling)
                throws IOException {
            classMap.put(className, new JavaClassObject(className, kind));
            return classMap.get(className);
        }

        @Override
        public ClassLoader getClassLoader(Location location) {
            return classLoader;
        }
    }
}


実行するとリテラルで書いたソースをコンパイルして実行し「Hello, World!」と表示。