Oct 17, 2005

Java Compiler API にチャレンジ

Java Compiler API とは

クラスライブラリからソースコードをコンパイルしてバイトコード化できる API。 仕様は JCP で JSR199 として策定中。

本当は Tiger で JDK に導入されるはずだったのだが、いつの間にやら Mustang に延期されていた。 現在の Mustang の snapshot には動作するコードが入っているので、Mustang でのお披露目は大丈夫そう。 Code Generator のランタイム実行、Rule Engine の高速化などなど色々な用途がありそうなので正式リリースが待ち遠しい。

JSR199 Java Compiler API
http://jcp.org/en/jsr/detail?id=199

Java Compiler API の実行環境構築

  1. Mustang をインストールすれば万事 OK

サンプルコード

↓は以下の処理を行うサンプルコード。

  1. ハードコーディングされているソースコードをコンパイル
  2. コンパイルで生成されたバイトコードをオンメモリのまま ClassLoader でローディング
  3. ClassLoader から Class を取得して main メソッドを実行
たったこれだけなのに、結構なコーディングが必要だった。 まだ Mustang が開発中だから仕方がないか。 今後に期待!!

Codelet.java

package jp.in_vitro.codelets.mustang.compilerapi;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import javax.tools.DiagnosticMessage;
import javax.tools.JavaCompilerTool;
import javax.tools.JavaFileObject;
import javax.tools.ToolProvider;
import javax.tools.JavaCompilerTool.CompilationTask;

public class Codelet {
    public Codelet() {
        super();
    }

    public static void main(final String[] args) {
        Codelet me = new Codelet();
        me.execute();
    }

    protected void execute() {
        this.execute01();
        this.execute02();
        this.execute03();
    }

    protected void execute01() {
        // HelloWorld
        JavaCompilerTool compiler = this.prepareJavaCompilerTool();
        JavaFileManagerImpl fileManager = this
                .prepareJavaFileManagerImpl(compiler);

        String sourceCode = "public class HelloWorld {" + 
          "public static void main(String[] args){" + 
          "System.out.println(\"Hello, World!\");}}";
        String className = "HelloWorld";
        JavaSourceFileObject sourceObject = new JavaSourceFileObject(className,
                sourceCode);
        this.compile(compiler, fileManager, sourceObject);
        this.executeMain(fileManager, "HelloWorld", new String[0]);

    }

    protected void execute02() {
        // 複数クラスのコンパイル
        JavaCompilerTool compiler = this.prepareJavaCompilerTool();
        JavaFileManagerImpl fileManager = this
                .prepareJavaFileManagerImpl(compiler);

        // Main
        String mainSourceCode = "public class Main {" + 
          "public static void main(String[] args){" + 
          "Sub sub = new Sub(); sub.execute();}}";
        String mainClassName = "Main";
        JavaSourceFileObject mainSourceObject = new JavaSourceFileObject(
                mainClassName, mainSourceCode);

        // Sub
        String subSourceCode = "public class Sub {" + 
          "public void execute(){" + 
          "System.out.println(\"Sub was executed!!\");}}";
        String subClassName = "Sub";
        JavaSourceFileObject subSourceObject = new JavaSourceFileObject(
                subClassName, subSourceCode);

        this.compile(compiler, fileManager, mainSourceObject, subSourceObject);
        this.executeMain(fileManager, "Main", new String[0]);
    }

    protected void execute03() {
        // コンパイルエラー
        JavaCompilerTool compiler = this.prepareJavaCompilerTool();
        JavaFileManagerImpl fileManager = this
                .prepareJavaFileManagerImpl(compiler);

        String sourceCode = "public class Uncompilable {" + 
          "public static void main(String[] args){" + 
          "executeAbEsseMethod();}}";
        String className = "Uncompilable";
        JavaSourceFileObject sourceObject = new JavaSourceFileObject(className,
                sourceCode);
        this.compile(compiler, fileManager, sourceObject);
        this.executeMain(fileManager, "Uncompilable", new String[0]);
    }

    protected void compile(final JavaCompilerTool compiler,
            final JavaFileManagerImpl fileManager,
            final JavaSourceFileObject... sourceObjects) {

        CompilationTask task = compiler.run(null,
                (JavaFileObject[]) sourceObjects);
        if (task.getResult()) {
            System.out.println();
            System.out
                    .println("************************************************");
            System.out.println("*  Compilation souce(s) completed!!");
            System.out
                    .println("************************************************");
            for (JavaSourceFileObject sourceObject : sourceObjects) {
                System.out.println("" + sourceObject.getName());
            }
        } else {
            System.out.println();
            System.out
                    .println("************************************************");
            System.out.println("*  Compilation souce(s) failed!!");
            System.out
                    .println("************************************************");
            for (DiagnosticMessage message : task.getDiagnostics()) {
                System.out.println("" + message);
            }
            throw new IllegalStateException();
        }
    }

    protected void executeMain(final JavaFileManagerImpl fileManager,
            final String className, final String[] args) {
        System.out.println();
        System.out.println("************************************************");
        System.out.println("*  Execute " + className);
        System.out.println("************************************************");

        ClassLoader loader = this.prepareClassLoader(fileManager);
        try {
            Class testClass = loader.loadClass(className);
            Method mainMethod = testClass.getMethod("main",
                    new Class[] { String[].class });
            mainMethod.invoke(null, new Object[] { args });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    protected JavaCompilerTool prepareJavaCompilerTool() {
        JavaCompilerTool compiler = ToolProvider.defaultJavaCompiler();
        compiler.setExtendedOption("-Xlint:all");
        return compiler;
    }

    protected JavaFileManagerImpl prepareJavaFileManagerImpl(
            final JavaCompilerTool compiler) {
        JavaFileManagerImpl fileManager = new JavaFileManagerImpl(compiler
                .getStandardFileManager());
        compiler.setFileManager(fileManager);
        return fileManager;
    }

    protected ClassLoader prepareClassLoader(
            final JavaFileManagerImpl fileManager) {
        ClassLoaderImpl loader = new ClassLoaderImpl();
        for (String className : fileManager.getClassNames()) {
            loader.addClass(className, fileManager.getClass(className)
                    .getCode());
        }
        return loader;
    }
}

AbstractJavaFileObject.java

package jp.in_vitro.codelets.mustang.compilerapi;

import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.nio.CharBuffer;

import javax.tools.JavaFileObject;

public abstract class AbstractJavaFileObject implements JavaFileObject {
    private Kind kind;
    private String name;

    public AbstractJavaFileObject(final String name, final Kind kind) {
        super();
        this.name = name;
        this.kind = kind;
    }

    public Kind getKind() {
        return this.kind;
    }

    public boolean delete() {
        return false;
    }

    public String getNameWithoutExtension() {
        return this.name;
    }

    public String getName() {
        return this.name;
    }

    public String getPath() {
        return this.name;
    }

    public long lastModified() {
        return 0L;
    }

    public long lengthInBytes() {
        return -1L;
    }

    public boolean matches(final String simpleName, final Kind kind) {
        return this.kind.equals(kind) && name.equals(simpleName);
    }

    public CharBuffer getCharContent(boolean ignoreEncodingErrors) {
        throw new UnsupportedOperationException();
    }

    public InputStream openInputStream() {
        throw new UnsupportedOperationException();
    }

    public OutputStream openOutputStream() {
        throw new UnsupportedOperationException();
    }

    public Reader openReader() {
        throw new UnsupportedOperationException();
    }

    public Writer openWriter() {
        throw new UnsupportedOperationException();
    }
}

JavaSourceFileObject.java

package jp.in_vitro.codelets.mustang.compilerapi;

import java.io.Reader;
import java.io.StringReader;
import java.nio.CharBuffer;

public class JavaSourceFileObject extends AbstractJavaFileObject {
    private String code;

    public JavaSourceFileObject(final String name, final String code) {
        super(name, Kind.SOURCE);
        this.code = code;
    }

    @Override
    public CharBuffer getCharContent(boolean ignoreEncodingErrors) {
        return CharBuffer.wrap(code);
    }

    @Override
    public Reader openReader() {
        return new StringReader(code);
    }

    @Override
    public String getName() {
        return getNameWithoutExtension() + ".java";
    }
}

JavaClassFileObject.java

package jp.in_vitro.codelets.mustang.compilerapi;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class JavaClassFileObject extends AbstractJavaFileObject {
    private byte[] code;

    public JavaClassFileObject(final String name) {
        super(name, Kind.CLASS);
    }

    @Override
    public OutputStream openOutputStream() {
        return new ClassOutputStream();
    }

    public class ClassOutputStream extends ByteArrayOutputStream {
        public void close() throws IOException {
            super.close();
            JavaClassFileObject.this.code = super.toByteArray();
        }
    }

    public byte[] getCode() {
        return code;
    }
}

JavaFileManagerImpl.java

package jp.in_vitro.codelets.mustang.compilerapi;

import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;

public class JavaFileManagerImpl implements JavaFileManager {
    private Map classes = new HashMap();
    protected final JavaFileManager parentFileManager;

    public JavaFileManagerImpl(final JavaFileManager parentFileManager) {
        super();
        this.parentFileManager = parentFileManager;
    }

    public JavaClassFileObject getClass(final String name) {
        return this.classes.get(name);
    }

    public Set getClassNames() {
        return Collections.unmodifiableSet(this.classes.keySet());
    }

    public JavaFileObject getFileForOutput(final String name, final Kind kind,
            final JavaFileObject originatingSource) {
        if (originatingSource instanceof JavaSourceFileObject) {
            JavaClassFileObject classObject = new JavaClassFileObject(name);
            this.classes.put(name, classObject);
            return classObject;
        } else {
            throw new UnsupportedOperationException();
        }
    }

    public void close() throws IOException {
        this.parentFileManager.close();
    }

    public void flush() throws IOException {
        this.parentFileManager.flush();
    }

    public void setLocation(final String location, final String path) {
        this.parentFileManager.setLocation(location, path);
    }

    public Iterable list(final String packageName,
            final Set kinds) throws IOException {
        return this.parentFileManager.list(packageName, kinds);
    }

    public JavaFileObject getFileForInput(final String name) {
        throw new UnsupportedOperationException();
    }

    public JavaFileObject getFileForOutput(final String filename,
            final String location, final String pkg) throws IOException {
        throw new UnsupportedOperationException();
    }
}

ClassLoaderImpl.java

package jp.in_vitro.codelets.mustang.compilerapi;

import java.util.HashMap;
import java.util.Map;

public class ClassLoaderImpl extends ClassLoader {
    private Map classes = new HashMap();

    public ClassLoaderImpl() {
        super();
    }

    public ClassLoaderImpl(final ClassLoader parent) {
        super(parent);
    }

    public void addClass(final String name, final byte[] bytecode) {
        this.classes.put(name, bytecode);
    }

    public void addClasses(final Map classes) {
        this.classes.putAll(classes);
    }

    @Override
    public Class< ? > loadClass(final String name)
            throws ClassNotFoundException {
        try {
            return super.loadClass(name);
        } catch (ClassNotFoundException e) {
            byte[] classData = classes.get(name);
            return defineClass(name, classData, 0, classData.length);
        }
    }
}

サンプルの実行結果

↑の Codelet#main を実行した結果は↓

************************************************
*  Compilation souce(s) completed!!
************************************************
HelloWorld.java

************************************************
*  Execute HelloWorld
************************************************
Hello, World!

************************************************
*  Compilation souce(s) completed!!
************************************************
Main.java
Sub.java

************************************************
*  Execute Main
************************************************
Sub was executed!!

************************************************
*  Compilation souce(s) failed!!
************************************************
Uncompilable:1: シンボルを見つけられません。
シンボル: メソッド executeAbEsseMethod()
場所    : Uncompilable の クラス
Exception in thread "main" java.lang.IllegalStateException
	at jp.in_vitro.codelets.mustang.compilerapi.Codelet.compile(Codelet.java:113)
	at jp.in_vitro.codelets.mustang.compilerapi.Codelet.execute03(Codelet.java:81)
	at jp.in_vitro.codelets.mustang.compilerapi.Codelet.execute(Codelet.java:27)
	at jp.in_vitro.codelets.mustang.compilerapi.Codelet.main(Codelet.java:20)

TrackBack ping me at
http://www.in-vitro.jp/blog/index.cgi/Mustang/20051017_01.trackback
Post a comment

writeback message: Ready to post a comment.