Play!で使っているJDT Compilerを使ってみる。(未完

これは Play! framework Advent Calendar 2011 jp の12月12日のブログです。
※日付変わっているとかは気にしない!


Play!ではJavaソースコードを内部でコンパイルしていると聞いたので
ちょっと読んで試してみました。

さくっと見たところ、Play! でコンパイル処理している箇所は2カ所ほどあり、
(1)Javaファイルをコンパイルするとき
(2)テンプレートファイルをGroovy
でコンパイルする時でそれぞれ使っているコンパイラとクラスローダが違います。

で(1)で使っているコンパイラですが、これは
play.classloading.ApplicationCompiler
を使っているようです。

これはEclipseのJDTコンパイラを利用してコンパイルしているっぽいです。

Play!内部での使い方はだいたい以下の通りです。


【用意するもの】
・org.eclipse.jdt.internal.compiler.env.ICompilationUnitのImplったやつ
・org.eclipse.jdt.internal.compiler.IErrorHandlingPolicyのImplったやつ
・org.eclipse.jdt.internal.compiler.IProblemFactoryのImplったやつ
・org.eclipse.jdt.internal.compiler.env.INameEnvironmentのImplったやつ
・org.eclipse.jdt.internal.compiler.ICompilerRequestorのImplったやつ
・org.eclipse.jdt.internal.compiler.Compiler.Compiler


【説明】
ICompilationUnit
 それぞれのコンパイルする対象です。
 ここにファイル名やらパッケージ名やらソースコードが入ります。
IErrorHandlingPolicy
 エラーが起こった時のポリシーです。
IProblemFactory
 特別なタイプの問題処理や、エラー・メッセージ用に他の言語をサポートする場合に使います。
INameEnvironment
 コンパイラーと外部環境と結合します。クラスパスみたいなものらしいです。
 コンパイラーはこれを使って、パッケージなのかみたいな情報を判別します。
ICompilerRequestor
 コンパイル結果が返ります。
Compiler
 こいつでコンパイルします。


【ざくっとした使い方】

  1. CompilationUnitをコンパイルするクラス分つくります。
  2. ErrorHandlingPolicyとProblemFactoryを設定します。
  3. NameEnvironmentとCompilerRequestorを環境に合わせて設定します。
  4. 2と3で用意したものを使って Compiler を生成します。
  5. 4で生成したCompilerでCompilationUnitをコンパイルします。

まぁ見たところそんなに難しくなさそう。
で、ソースコードを書いてみましたがここでハマり日付変更線を越えた訳です。

そんな訳でソースコードの一部をのせときます。
※全部読みたい方はこちら

package com.plugram.jdt;

import java.util.HashMap;

import java.util.Locale;
import java.util.Map;

import org.eclipse.jdt.internal.compiler.CompilationResult;
import org.eclipse.jdt.internal.compiler.Compiler;
import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies;
import org.eclipse.jdt.internal.compiler.ICompilerRequestor;
import org.eclipse.jdt.internal.compiler.IErrorHandlingPolicy;
import org.eclipse.jdt.internal.compiler.IProblemFactory;
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
import org.eclipse.jdt.internal.compiler.env.INameEnvironment;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;


/**
 * AppCompiler
 * Java 1.6でコンパイルします。
 * 
 * @author tango
 */
public class AppCompiler {

	/**
	 * コンパイルします。
	 * @param classNames
	 */
	@SuppressWarnings("deprecation")
	public void compile(String[] classNames) {
        
        ICompilationUnit[] compilationUnits = new CompilationUnit[classNames.length];
        for (int i = 0; i < classNames.length; i++) {
            compilationUnits[i] = new CompilationUnit(classNames[i]);
        }
        IErrorHandlingPolicy policy = DefaultErrorHandlingPolicies.exitOnFirstError();
        IProblemFactory problemFactory = new DefaultProblemFactory(Locale.JAPANESE);
        
        INameEnvironment nameEnvironment = new NameEnvironment();
        ICompilerRequestor compilerRequestor = new CompilerRequestor();
        
        Compiler jdtCompiler = new Compiler(nameEnvironment, policy, settings(), compilerRequestor, problemFactory) {
            @Override
            protected void handleInternalException(Throwable e, CompilationUnitDeclaration ud, CompilationResult result) {
            }
        };
        jdtCompiler.compile(compilationUnits);
    }

    private Map<String, String> settings() {
        Map<String, String> settings = new HashMap<String, String>();
        settings.put(CompilerOptions.OPTION_ReportMissingSerialVersion, CompilerOptions.IGNORE);
        settings.put(CompilerOptions.OPTION_LineNumberAttribute, CompilerOptions.GENERATE);
        settings.put(CompilerOptions.OPTION_SourceFileAttribute, CompilerOptions.GENERATE);
        settings.put(CompilerOptions.OPTION_ReportDeprecation, CompilerOptions.IGNORE);
        settings.put(CompilerOptions.OPTION_ReportUnusedImport, CompilerOptions.IGNORE);
        settings.put(CompilerOptions.OPTION_Encoding, "UTF-8");
        settings.put(CompilerOptions.OPTION_LocalVariableAttribute, CompilerOptions.GENERATE);
        settings.put(CompilerOptions.OPTION_Source, CompilerOptions.VERSION_1_6);
        settings.put(CompilerOptions.OPTION_TargetPlatform, CompilerOptions.VERSION_1_6);
        settings.put(CompilerOptions.OPTION_PreserveUnusedLocal, CompilerOptions.PRESERVE);
        settings.put(CompilerOptions.OPTION_Compliance, CompilerOptions.VERSION_1_6);
        return settings;
    }
}
package com.plugram.jdt;


/**
 * Mainクラス
 * 
 * @author tango
 */
public class Main {

    public static final String SOURCE_DIR = "srcディレクトリまでのパス";
	
    public static void main( String[] args ) {
        new Main().run();
    }
    
    public void run(){
    	AppCompiler compiler = new AppCompiler();
    	String[] classes = new String[]{"com.plugram.sample.Hello"};
    	compiler.compile(classes);
    	System.out.println("Compiled.");
    }
}


んで、ここまで書いて、以下のコンパイルするソースコードを書いてコンパイルしたら
実行結果が・・・・

package com.plugram.sample;

public class Hello {

    public void say(){
        System.out.println("Hello World");
    }
}
Exception in thread "main" com.plugram.jdt.UnexpectedException: Syntax error, insert "}" to complete ClassBody
	at com.plugram.jdt.CompilerRequestor.acceptResult(CompilerRequestor.java:22)
	at org.eclipse.jdt.internal.compiler.Compiler.handleInternalException(Compiler.java:672)
	at org.eclipse.jdt.internal.compiler.Compiler.compile(Compiler.java:516)
	at com.plugram.jdt.AppCompiler.compile(AppCompiler.java:51)
	at com.plugram.jdt.Main.run(Main.java:20)
	at com.plugram.jdt.Main.main(Main.java:14)


うーん、コンパイルエラー。。。なんでだろ。。
コンパイルできるはずなのになー。


という訳で中途半端に終わってしまいました。


そんなわけで明日(今日)は @takayuki_koba さんです!
よろしくお願いします!






※あわせて読みたい
Compiling Java code
http://help.eclipse.org/helios/index.jsp?topic=%2Forg.eclipse.jdt.doc.isv%2Fguide%2Fjdt_api_compile.htm

EclipseのASTParserを試す
http://www.ibm.com/developerworks/opensource/library/os-ast/