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 の実行環境構築
- Mustang をインストールすれば万事 OK
サンプルコード
↓は以下の処理を行うサンプルコード。
- ハードコーディングされているソースコードをコンパイル
- コンパイルで生成されたバイトコードをオンメモリのまま ClassLoader でローディング
- ClassLoader から Class を取得して main メソッドを実行
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
writeback message: Ready to post a comment.
