T2+GAE+guice のPageクラス用GuiceModuleをAnnotation Processing Toolで作成します。

T2 Guice連携だとPageクラスをModuleで一つずつ読み込む必要があります。
意外とこれが面倒な作業で、Page作ったけどModuleに書き忘れてもっかいApplicationServer再起動なんてことはよくあります。

てことでめんどくさい作業はToolを作ってしまえと思いました。そこで最近よく見るようになってきたAnnotation Processing Tool(以下 APT)で
Pageクラス用Moduleを作成してみます。
まずAnnotationを処理するProcessorクラス

package jp.stk.tools.apt.processor;

import java.util.List;
import java.util.Map;

import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedOptions;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;

import jp.stk.tools.apt.AptConst;
import jp.stk.tools.apt.generator.T2GuiceModuleGenerator;

import org.t2framework.t2.tool.apt.PageMirror;
import org.t2framework.t2.tool.apt.PageSettingsGenerator;
import org.t2framework.t2.tool.apt.PageSettingsProcessor;

/**
 * t2 page module processor
 * <pre>
 * t2のPageクラス用Moduleクラスを作成するProcessorクラスです。
 * </pre>
 * @author soundTricker
 *
 */
@SupportedSourceVersion(SourceVersion.RELEASE_6)
@SupportedAnnotationTypes("org.t2framework.t2.annotation.core.Page")
@SupportedOptions({AptConst.OPTIONS_GENERATE_MODULE_NAME , AptConst.OPTIONS_OUTPUT_PACKAGE})
public class T2GuiceModuleProcessor extends PageSettingsProcessor {

	/**
	 * GuiceModuleを作成します。
	 * <pre>
	 * Pageクラスのメタデータより、GuiceModuleを作成します。
	 * </pre>
	 * @see org.t2framework.t2.tool.apt.PageSettingsProcessor#generate(java.util.Map<java.lang.String,java.util.List<org.t2framework.t2.tool.apt.PageMirror>>, javax.annotation.processing.ProcessingEnvironment)
	 */
	@Override
	protected void generate(Map<String, List<PageMirror>> pageMetaMap,ProcessingEnvironment processingEnv) {

		PageSettingsGenerator generator = new T2GuiceModuleGenerator(pageMetaMap, processingEnv);

		generator.generate();
	}
}

PageSettingsProcessor はT2framework 0.6.3-cr1に入っていたpage.propertiesを作るAPTのProcessorクラスです。
ほとんどの処理はこれがやってくれちゃってますね。。。
でもバージョンがcrなのでなくなっちゃったらどうしよう。
次に実際にGuiceModuleを作成するGeneratorクラス

package jp.stk.tools.apt.generator;

import java.io.IOException;

/**
 * Pageクラス用のGuiceModuleを作成するGeneratorクラスです。
 * @author soundTricker
 *
 */
public class T2GuiceModuleGenerator implements PageSettingsGenerator {
	//======================================================================
	//                                                            プロパティ
	//======================================================================
	/**
	 * Pageクラスのメタデータマップ
	 * <pre>
	 * key:package名
	 * value:keyのパッケージ内Pageクラスメタデータのリスト
	 */
	protected Map<String, List<PageMirror>> pageMetaMap;

	/** APTの実行情報 */
	protected ProcessingEnvironment processingEnv;

	/** writer */
	protected PrintWriter writer;

	/** GuiceModuleの出力先パッケージ */
	protected String outputPackage = "";

	/** 作成するGuiceModule名 */
	protected String generateModuleName = AptConst.DEFAULT_MODULE_NAME;

	//======================================================================
	//                                                        コンストラクタ
	//======================================================================
	/**
	 * デフォルトコンストラクタ
	 * <pre>
	 * writerなどの設定を行います。
	 * </pre>
	 * @param pageMetaMap Pageクラスのメタデータマップ
	 * @param processingEnv APTの実行情報
	 */
	public T2GuiceModuleGenerator(Map<String, List<PageMirror>> pageMetaMap,ProcessingEnvironment processingEnv) {

		this.pageMetaMap = pageMetaMap;

		this.processingEnv = processingEnv;

		Filer filer = this.processingEnv.getFiler();

		Map<String , String> options = this.processingEnv.getOptions();

		if(options.containsKey(AptConst.OPTIONS_OUTPUT_PACKAGE)){

			outputPackage = options.get(AptConst.OPTIONS_OUTPUT_PACKAGE);
		}

		if(options.containsKey(AptConst.OPTIONS_GENERATE_MODULE_NAME)){

			generateModuleName = options.get(AptConst.OPTIONS_GENERATE_MODULE_NAME);
		}

		FileObject fileObject = null;
		try {
			fileObject = filer.createSourceFile(outputPackage + AptConst.PACKAGE_SEPARATOR + generateModuleName);

			final Writer filerWriter = FilerUtil.openWriter(fileObject);

			this.writer = WriterUtil.createPrintWriter(filerWriter);

		} catch (IOException e) {

			CloseableUtil.close(this.writer);

			throw new IORuntimeException(e);
		}
	}

	//======================================================================
	//                                                                メイン
	//======================================================================
	/**
	 * ソースを作成します。
	 * @see org.t2framework.t2.tool.apt.PageSettingsGenerator#generate()
	 */
	@Override
	public void generate() {
		try{
			makeClassDefinition();

			makeConfigureMethodDefinition();

			for (Entry<String, List<PageMirror>> pageMetaEntry : pageMetaMap.entrySet()) {
				for (PageMirror pageMirror : pageMetaEntry.getValue()) {
					makeBindLogic(pageMirror);
				}
			}
			makeConfigureMethodFutter();

			makeClassFutter();

			FlushableUtil.flush(writer);
		}finally{
			CloseableUtil.close(writer);
		}

	}

	//======================================================================
	//                                                                下請け
	//======================================================================
	/**
	 * クラス定義を作成します。
	 */
	private void makeClassDefinition() {
		writer.format("package %s;\n" , outputPackage);

		writer.format("public class %s extends %s {\n", generateModuleName , AbstractModule.class.getName());

	}

	/**
	 * メソッド定義を作成します。
	 */
	private void makeConfigureMethodDefinition(){
		writer.format("	@Override\n");

		writer.format("	protected void configure() {\n");
	}

	/**
	 * Pageクラスのメタデータよりbindロジックを作成します。
	 * @param pageMirror
	 */
	private void makeBindLogic(PageMirror pageMirror) {
		writer.format("		bind(%s.%s.class).in(com.google.inject.servlet.ServletScopes.REQUEST);\n" , pageMirror.getPackageName() , pageMirror.getPageClassName());
	}

	/**
	 * メソッドのフッターを作成します。
	 */
	private void makeConfigureMethodFutter() {
		writer.format("	}\n");
	}

	/**
	 * クラスのフッターを作成します。
	 */
	private void makeClassFutter() {
		writer.format("}\n");
	}

}

あと定数定義クラス。

package jp.stk.tools.apt;

public class AptConst {
	public static final String OPTIONS_GENERATE_MODULE_NAME = "generateModuleName";

	public static final String OPTIONS_OUTPUT_PACKAGE = "outputPackage";

	public static final String DEFAULT_MODULE_NAME = "PageModule";

	public static final String PACKAGE_SEPARATOR = ".";
}

ただテストの仕方がわからなくて、
調べていたら、SeasarのsandboxにaptinaというAPT用のプロジェクトがありましたので、
こちらのhttp://aptina.sandbox.seasar.org/aptina-unit/index.html:Aptina Unitを使ってみました。

でこれがTestクラス

package jp.stk.tools.apt.processor;

import java.io.IOException;

import javax.tools.ToolProvider;

import jp.stk.tools.apt.AptConst;
import jp.stk.tools.apt.testpage.TestPage;

import org.seasar.aptina.unit.AptinaTestCase;

public class T2GuiceModuleProcessorTest extends AptinaTestCase {

	private static final String OPTION_FORMAT = "-A%s=%s";

	protected void setUp() throws Exception {
		super.setUp();
		addSourcePath("src/test/java");
	}

	public void testProcess() throws IOException {
		ToolProvider.getSystemJavaCompiler().getClass();
		T2GuiceModuleProcessor processor = new T2GuiceModuleProcessor();
		addProcessor(processor);
		setCharset("UTF-8");
		addOption(String.format(OPTION_FORMAT, AptConst.OPTIONS_OUTPUT_PACKAGE , "jp.stk.gae.module"));
		addCompilationUnit(TestPage.class);
		compile();
//TODO めんどくさいので表示だけ確認。。。ちゃんと作らなくちゃ。。。
		System.out.println(getGeneratedSource("jp.stk.gae.module." + AptConst.DEFAULT_MODULE_NAME));
	}

}

これでテストしました。

今回はAPTをやってみました。
私適当なSVNを持っていないのでjarを公開できませんが、
質問がありましたらご連絡ください。

ちなみに最初Velocityでソースをつくるの(テンプレートの変更も可能)も書いていたのですが、
わざわざAPTのためにライブラリを落とすのも嫌かなと思ってべた書き版を載せました。

要望があったら載せます。


追加:
このソースだとAPTに引数を与えないとこけます。。。
時間があったら治します。