Asakusa Framework0.7.3のJavaDataModelDriverのメモ。
|
|
|
|
|
Asakusa Frameworkでは、DMDL(dmdlファイル)でデータモデルを記述すると、Modelクラス(やio/Input・io/Outputクラス)のJavaソースファイルが生成される。
これらはcom.asakusafw.dmdl.java.spi.JavaDataModelDriverを継承したクラスで生成されている。
(特にInput・OutputクラスはModelInputDriver・ModelOutputDriverで作られている。ModelクラスはConcreteModelEmitterを始めとする色々なDriverで分散して作っている模様)
これらのドライバーはJavaのサービスプロバイダー(SPI)の仕組みを使って呼ばれている。
したがって、自分でドライバーを作ってSPIに入れれば、独自のクラス(Javaソース)を生成することが出来る。
AsakusaFWのソースコードリーディングでSPIを使っていると聞いていたので、AsakusaFWのjarファイルの中からSPIのファイルを探したら それらしいクラス(JavaDataModelDriver)を見つけたので、試してみたもの。
まずはSPIで自分のDriverが呼び出せる状態にする必要がある。
最初に、Driverを記述する為のEclipseのプロジェクトを新たに作成する。[2015-07-19]
apply plugin: 'java' apply plugin: 'eclipse' apply plugin: 'maven' defaultTasks 'jar' group = 'com.example.asakusafw.dmdl' version = '0.1-SNAPSHOT' sourceCompatibility = 1.7 targetCompatibility = 1.7 def defaultEncoding = 'UTF-8' [compileJava, compileTestJava]*.options*.encoding = defaultEncoding repositories { mavenCentral() maven { url 'http://asakusafw.s3.amazonaws.com/maven/releases' } } dependencies { compile group: 'com.asakusafw', name: 'asakusa-dmdl-java', version: "0.7.3-hadoop1" runtime group: 'org.apache.hadoop', name: 'hadoop-core', version: "1.2.1" testCompile group: 'ch.qos.logback', name: 'logback-classic', version: "1.1.3" } task wrapper(type: Wrapper) { gradleVersion '2.2.1' jarFile file('.buildtools/gradlew.jar') } eclipse.classpath.file { whenMerged { classpath -> classpath.entries.findAll { entry -> entry.kind == 'output' }*.path = 'classes' } }
$ gradle wrapper $ mkdir -p src/main/java $ mkdir -p src/main/resources $ ./gradle eclipse
テスト時のログ出力をコンソールに出力する為、以下のファイルを用意しておく。[2015-07-27]
<configuration> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <target>System.out</target> <encoder> <pattern>%d{yyyy/MM/dd HH:mm:ss} %-5level %msg%n</pattern> </encoder> </appender> <root> <level value="INFO" /> <appender-ref ref="STDOUT" /> </root> </configuration>
まずは空っぽのDriverを作成してみる。
package jp.hishidama.asakusafw.dmdl;
import java.io.IOException; import com.asakusafw.dmdl.java.emitter.EmitContext; import com.asakusafw.dmdl.java.spi.JavaDataModelDriver; import com.asakusafw.dmdl.semantics.ModelDeclaration;
public class EmptyDriver extends JavaDataModelDriver {
@Override public void generateResources(EmitContext context, ModelDeclaration model) throws IOException { System.out.println("+++EmptyDriver#generateResources\t" + model); } }
JavaDataModelDriverにはアノテーション定義用に呼ばれるメソッドやフィールド定義用に呼ばれるメソッド・メソッド定義用に呼ばれるメソッド等があるが、必ず1回だけ呼ばれるメソッドとしてgenerateResourcesメソッドが用意されている。[/2015-07-25]
どんなものが引数で渡されるのか、とりあえずコンソールに表示してみる。
SPIを使う際は、META-INF/servicesというディレクトリーの下に親クラスの名前をしたテキストファイルを作成し、その中に具象クラスを記述する。
jp.hishidama.asakusafw.dmdl.EmptyDriver
モデルの生成にはcom.asakusafw.dmdl.java.Mainを実行する。
これを実行する為の「実行の構成」は以下の通り。
タブ | 設定内容 | 備考 | |
---|---|---|---|
メイン | プロジェクト(P) | afw-driver |
自分のプロジェクト |
メイン・クラス(M) | com.asakusafw.dmdl.java.Main |
実行するクラス名 | |
引数 | プログラムの引数(A) | -output C:/cygwin/tmp/afw/ |
sourceで指定した場所にあるdmdlファイルを読み込み、 outputで指定した場所にJavaソースを出力する。 参考: DMDLユーザーガイド#DMDLコンパイラの実行 |
※普通、SPIを使う際はjarファイル化するのだが、今回はsrc/main/resourcesにSPIのファイルを入れてある(厳密には、それがclassesディレクトリーに移送され、classesがクラスパスに含まれている)のでjarファイルを作らなくても動作する。[/2015-07-19]
〜 +++EmptyDriver#generateResources RECORD word_count_model +++EmptyDriver#generateResources RECORD line_model
dmdlファイル内のモデル毎にDriverが呼ばれている。
→Gradleコマンドによるモデル生成時に自分のドライバーを実行させる方法
空っぽのJavaソースを生成してみる。
package jp.hishidama.asakusafw.dmdl;
import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import com.asakusafw.dmdl.java.emitter.EmitContext; import com.asakusafw.dmdl.java.spi.JavaDataModelDriver; import com.asakusafw.dmdl.semantics.ModelDeclaration; import com.asakusafw.utils.java.model.syntax.Attribute; import com.asakusafw.utils.java.model.syntax.ClassDeclaration; import com.asakusafw.utils.java.model.syntax.Javadoc; import com.asakusafw.utils.java.model.syntax.ModelFactory; import com.asakusafw.utils.java.model.syntax.SimpleName; import com.asakusafw.utils.java.model.syntax.Type; import com.asakusafw.utils.java.model.syntax.TypeBodyDeclaration; import com.asakusafw.utils.java.model.syntax.TypeParameterDeclaration; import com.asakusafw.utils.java.model.util.AttributeBuilder; import com.asakusafw.utils.java.model.util.JavadocBuilder;
※0.2.1の頃はcom.ashigeru.lang
パッケージを使っていたが、いつの間にかcom.asakusafw.utils
に替わっていた。[2015-07-19]
このソースは、ModelInputDriverを参考にしている。
なぜなら、Input(やOutput)はモデルとは別のソースファイルを生成しているから。
今回は新しいソースを生成したい。
/** * @see com.asakusafw.dmdl.java.emitter.driver.ModelInputDriver */ public class SimpleDriver extends JavaDataModelDriver {
@Override public void generateResources(EmitContext context, ModelDeclaration model) throws IOException { String categoryName = "simple"; String typeNamePattern = "{0}Simple"; EmitContext next = new EmitContext(context.getSemantics(), context.getConfiguration(), model, categoryName, typeNamePattern); Generator.emit(next, model); }
ここで、新しく生成するソースファイルを設定している。
categoryName(
カテゴリー名)は、パッケージ名の一部。
パッケージ名は「Mainの引数で指定されたパッケージ名」+「dmdl」+「カテゴリー名」になるので、今回は「com.example.modelgen.dmdl.simple
」になる。
typeNamePatternはクラス名のパターン。
「{0}」がモデル名に置換される。今回だと「{0}Simple」なので、「LineModelSimple」「WordCountModelSimple」というクラスが生成される。
実際にクラスの中身を定義するのがGenerator。
Javaの文法要素を表すクラス群が用意されており、それを使ってクラス(の抽象構文木)を生成する。[2015-07-19]
各文法要素はModelFactoryを使って生成し、最終的にそれを出力する。
SPI用のservicesにSimpleDriverを追加しておく。
#jp.hishidama.asakusafw.dmdl.EmptyDriver jp.hishidama.asakusafw.dmdl.SimpleDriver
↓生成されたファイル
package com.example.modelgen.
dmdl.simple;
/**
* とりあえずword_count_modelのクラスを生成してみる。
*/
public class WordCountModelSimple {
}
java.modelを使うソース生成方法だと、生成したいクラスの構文に沿ってインスタンスを色々用意しないといけないので、けっこう面倒。[2015-07-20]
(抽象構文木を直接扱うのが慣れている人にとっては便利なんだろうけど^^;)
そこで、(無理矢理かもしれないが)PrintWriterを使ってソースを生成してみる。
package jp.hishidama.asakusafw.dmdl;
import java.io.IOException; import java.io.PrintWriter; import com.asakusafw.dmdl.java.Configuration; import com.asakusafw.dmdl.java.emitter.EmitContext; import com.asakusafw.dmdl.java.spi.JavaDataModelDriver; import com.asakusafw.dmdl.semantics.ModelDeclaration; import com.asakusafw.utils.java.model.syntax.ModelFactory; import com.asakusafw.utils.java.model.syntax.PackageDeclaration; import com.asakusafw.utils.java.model.util.Emitter;
/** * @see com.asakusafw.dmdl.java.emitter.driver.ModelInputDriver */ public class SimpleDriver extends JavaDataModelDriver {
@Override public void generateResources(EmitContext context, ModelDeclaration model) throws IOException { String categoryName = "simple"; String typeNamePattern = "{0}Simple"; EmitContext next = new EmitContext(context.getSemantics(), context.getConfiguration(), model, categoryName, typeNamePattern); Generator.emit(next, model); }
↑ここまでは、java.modelを使う方法と同じ。
private static class Generator { private final EmitContext context; private final ModelDeclaration model; private final ModelFactory f; private Generator(EmitContext context, ModelDeclaration model) { assert context != null; assert model != null; this.context = context; this.model = model; this.f = context.getModelFactory(); }
static void emit(EmitContext context, ModelDeclaration model) throws IOException { assert context != null; assert model != null; Generator emitter = new Generator(context, model); emitter.emit(); }
private void emit() throws IOException { PackageDeclaration packageName = f.newPackageDeclaration(context.getQualifiedTypeName().getQualifier()); String fileName = context.getTypeName().getToken() + ".java"; Configuration config = context.getConfiguration(); Emitter emitter = config.getOutput(); try (PrintWriter writer = emitter.openFor(packageName, fileName)) { writer.printf("package %s;%n%n", packageName.getName().toNameString()); writer.println("/**"); writer.printf(" * %sのクラスを生成してみる。%n", model.getName().identifier); writer.println(" */"); writer.printf("public class %s {%n", context.getTypeName().toNameString()); writer.println("}"); } } } }
openForメソッドでPrintWriterを取得できる。
この出力先のルートパスは、DMDLのコンパイルを実行するMainクラスの-outputで指定したディレクトリーになる。
※属性の値を取得したい場合は、model.getTraitメソッドを使用する。[2015-07-25]
→独自の属性を使用するには、属性ドライバーを作成する。
実行方法は、java.modelの場合の実行方法と同じ。
先の例では、ドライバーを起動するMainクラスを直接実行して独自ドライバーを呼び出した。[2011-08-28]
通常、DMDLからモデルクラスを生成するにはGradleのコマンドでcompileDMDL等を実行する。[/2015-07-19]
このときに自分のドライバーを実行させるには、Asakusaプロジェクトの依存関係に自分のドライバーを追加する。
$ ./gradlew install
repositories { mavenLocal() // Mavenローカルリポジトリーを使用する } dependencies { 〜 compile group: 'com.example.asakusafw.dmdl', name: 'afw-driver', version: '0.1-SNAPSHOT' }
これで、モデルクラスの生成を行ったときに自分のドライバーも呼ばれるようになる。
または、Mavenローカルリポジトリーを使用せず、生成されたjarファイルを直接指定することも出来る。[2015-07-21]
$ ./gradlew jarこれで、ワークスペースのプロジェクトディレクトリーのbuild/libsの下にjarファイル(
afw-driver
-0.1-SNAPSHOT.jar
)が作られる。dependencies {
〜
compile files('D:/workspace/afw-driver/build/libs/afw-driver-0.1-SNAPSHOT.jar')
}