S-JIS[2015-07-25] 変更履歴

Asakusa Framework属性ドライバー

Asakusa Framework0.7.3のAttributeDriverのメモ。


概要

Asakusa Frameworkでは、DMDL(dmdlファイル)でデータモデルの記述に属性(@hogeのような名前)を付けることが出来る。

属性は、com.asakusafw.dmdl.spi.AttributeDriverを継承したクラス(実際には、データモデルに付ける属性はModelAttributeDriver、プロパティーに付ける属性はPropertyAttributeDriverを継承する)で管理されている。

これらのドライバーはJavaのサービスプロバイダー(SPI)の仕組みを使って呼ばれている。
したがって、自分でドライバーを作ってSPIに入れれば、独自のクラス(Javaソース)を生成することが出来る。
データモデルドライバーと同様)


属性の管理(属性名の指定や値の取得)はAttributeDriver(ModelAttributeDriver・PropertyAttributeDriver)を継承したクラスで行う。
取得した属性の値は、Traitというクラスを用意し、そこで保持する。
データモデルからファイルを生成するJavaDataModelDriverで、Traitを指定して情報を取得し、ファイル生成に利用する。


以下のような属性が記述できる属性ドライバーを作ってみる。

example.dmdl:

@example.model(name = "abc")
model1 = {

  @example.field(value = "zzz")
  value1 : TEXT;
};

プロジェクトの作成

属性ドライバーを作成するJavaプロジェクトは、データモデルドライバーのプロジェクトと同じ。


Traitの作成

最初に、属性に指定された値を保持するTraitクラスを作っておく。

まずは、モデルに付ける「@example.model」のTrait。

ExampleModelTrait.java:

package jp.hishidama.asakusafw.dmdl.attribute;

import com.asakusafw.dmdl.model.AstNode;
import com.asakusafw.dmdl.semantics.Trait;
//@see com.asakusafw.dmdl.directio.csv.driver.CsvFormatTrait
public class ExampleModelTrait implements Trait<ExampleModelTrait> {

	private final AstNode originalAst;

	private final ExampleModelConfiguration configuration;

	public ExampleModelTrait(AstNode originalAst, ExampleModelConfiguration configuration) {
		if (configuration == null) {
			throw new IllegalArgumentException("configuration must not be null"); //$NON-NLS-1$
		}
		this.originalAst = originalAst;
		this.configuration = configuration;
	}
	@Override
	public AstNode getOriginalAst() {
		return originalAst;
	}

	public ExampleModelConfiguration getConfiguration() {
		return configuration;
	}
	public static class ExampleModelConfiguration {
		private String name;

		public void setName(String name) {
			this.name = name;
		}

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

originalAstは、データモデルを解析した結果の抽象構文木。
エラーがあった場合のエラー箇所を指定するのに使ったりするようだ。

configurationは、属性の値を保持するもの。(値を保持しない属性なら、不要)
このクラスは自分で用意する。単なるJavaBeansでよい。


こちらは、プロパティーに付ける「@example.field」のTrait。

ExampleFieldTrait.java:

package jp.hishidama.asakusafw.dmdl.attribute;

import com.asakusafw.dmdl.model.AstNode;
import com.asakusafw.dmdl.semantics.Trait;
//@see com.asakusafw.dmdl.directio.csv.driver.CsvFieldTrait
public class ExampleFieldTrait implements Trait<ExampleFieldTrait> {

	private final AstNode originalAst;

	private final ExampleFieldConfiguration configuration;

	public ExampleFieldTrait(AstNode originalAst, ExampleFieldConfiguration configuration) {
		if (configuration == null) {
			throw new IllegalArgumentException("configuration must not be null"); //$NON-NLS-1$
		}
		this.originalAst = originalAst;
		this.configuration = configuration;
	}
	@Override
	public AstNode getOriginalAst() {
		return originalAst;
	}

	public ExampleFieldConfiguration getConfiguration() {
		return configuration;
	}
	public static class ExampleFieldConfiguration {
		private String value;

		public void setValue(String value) {
			this.value = value;
		}

		public String getValue() {
			return value;
		}
	}
}

※使用する要素が少ない場合は、わざわざConfigurationクラスを用意せず、直接Trait内に保持してしまっても良い。


ModelAttributeDriverの作成

モデルに付ける「@example.model」を解析するドライバーを作成する。

package jp.hishidama.asakusafw.dmdl.attribute;

import java.util.Map;

import jp.hishidama.asakusafw.dmdl.attribute.ExampleModelTrait.ExampleModelConfiguration;

import com.asakusafw.dmdl.Diagnostic;
import com.asakusafw.dmdl.Diagnostic.Level;
import com.asakusafw.dmdl.model.AstAttribute;
import com.asakusafw.dmdl.model.AstAttributeElement;
import com.asakusafw.dmdl.model.AstLiteral;
import com.asakusafw.dmdl.model.LiteralKind;
import com.asakusafw.dmdl.semantics.DmdlSemantics;
import com.asakusafw.dmdl.semantics.ModelDeclaration;
import com.asakusafw.dmdl.spi.ModelAttributeDriver;
import com.asakusafw.dmdl.util.AttributeUtil;
//@see com.asakusafw.dmdl.directio.csv.driver.CsvFormatDriver
public class ExampleModelDriver extends ModelAttributeDriver {

	/**
	 * The attribute name.
	 */
	public static final String TARGET_NAME = "example.model"; //$NON-NLS-1$

	/**
	 * The element name of {@value}.
	 */
	public static final String ELEMENT_NAME = "name"; //$NON-NLS-1$
	@Override
	public String getTargetName() {
		return TARGET_NAME;
	}

getTargetNameメソッドで、属性の名前(今回の例では、「example.model」)を返す。

	@Override
	public void process(DmdlSemantics environment, ModelDeclaration declaration, AstAttribute attribute) {
		Map<String, AstAttributeElement> elements = AttributeUtil.getElementMap(attribute);
		ExampleModelConfiguration conf = analyzeConfig(environment, attribute, elements);
		if (conf != null) {
			declaration.putTrait(ExampleModelTrait.class, new ExampleModelTrait(attribute, conf));
		}
	}

processメソッドで属性値の取得・チェックを行い、Traitに登録する。
取得・チェック処理本体はanalyzeConfigメソッドで行っている。

	private ExampleModelConfiguration analyzeConfig(DmdlSemantics environment, AstAttribute attribute, Map<String, AstAttributeElement> elements) {
		AstLiteral name = take(environment, elements, ELEMENT_NAME, LiteralKind.STRING);

		// 仕様外の要素名をエラーとして出力する
		environment.reportAll(AttributeUtil.reportInvalidElements(attribute, elements.values()));

		ExampleModelConfiguration result = new ExampleModelConfiguration();
		if (name != null && checkNotEmpty(environment, ELEMENT_NAME, name)) {
			result.setName(name.toStringValue());
		}
		return result;
	}

takeメソッドで値を取得するようになっている。
想定していない要素名があったら、エラーにしている。
ついでに要素の値が空かどうかのチェックもしているが、これが必要かどうかは自分が作る属性の仕様次第。

	private boolean checkNotEmpty(DmdlSemantics environment, String name, AstLiteral stringLiteral) {
		assert environment != null;
		assert name != null;
		assert stringLiteral != null;
		assert stringLiteral.kind == LiteralKind.STRING;
		if (stringLiteral.toStringValue().isEmpty()) {
			environment.report(new Diagnostic(Level.ERROR, stringLiteral, "@{0}({1}) must not be empty", TARGET_NAME, name));
			return false;
		}
		return true;
	}

	private AstLiteral take(DmdlSemantics environment, Map<String, AstAttributeElement> elements, String elementName, LiteralKind kind) {
		assert environment != null;
		assert elements != null;
		assert elementName != null;
		assert kind != null;
		AstAttributeElement element = elements.remove(elementName);
		if (element == null) {
			return null;
		}
		if (!(element.value instanceof AstLiteral)) {
			environment.report(new Diagnostic(Level.ERROR, element, "@{0}({1}) must be a {2} literal", TARGET_NAME, elementName, kind));
			return null;
		}
		AstLiteral literal = (AstLiteral) element.value;
		if (literal.kind != kind) {
			environment.report(new Diagnostic(Level.ERROR, element, "@{0}({1}) must be a {2} literal", TARGET_NAME, elementName, kind));
			return null;
		}
		return literal;
	}
}

checkNotEmptyやtakeメソッドはどの属性でも使う機能なのでユーティリティーで用意しておいて欲しい気もするが、エラーメッセージが属性によって変わるので、共通では用意していないのかもしれない。


PropertyAttributeDriverの作成

プロパティーに付ける「@example.field」を解析するドライバーを作成する。

package jp.hishidama.asakusafw.dmdl.attribute;

import java.util.Map;

import jp.hishidama.asakusafw.dmdl.attribute.ExampleFieldTrait.ExampleFieldConfiguration;

import com.asakusafw.dmdl.model.AstAttribute;
import com.asakusafw.dmdl.model.AstAttributeElement;
import com.asakusafw.dmdl.semantics.DmdlSemantics;
import com.asakusafw.dmdl.semantics.PropertyDeclaration;
import com.asakusafw.dmdl.spi.PropertyAttributeDriver;
import com.asakusafw.dmdl.util.AttributeUtil;
//@see com.asakusafw.dmdl.directio.csv.driver.CsvFieldDriver
public class ExampleFieldDriver extends PropertyAttributeDriver {

	/**
	 * The attribute name.
	 */
	public static final String TARGET_NAME = "example.field"; //$NON-NLS-1$

	/**
	 * The element name of {@value}.
	 */
	public static final String ELEMENT_VALUE = "value"; //$NON-NLS-1$
	@Override
	public String getTargetName() {
		return TARGET_NAME;
	}

getTargetNameメソッドで、属性名(今回の例では、「example.field」)を返す。

	@Override
	public void process(DmdlSemantics environment, PropertyDeclaration declaration, AstAttribute attribute) {
		Map<String, AstAttributeElement> elements = AttributeUtil.getElementMap(attribute);
		ExampleFieldConfiguration conf = analyzeConfig(environment, attribute, elements);
		if (conf != null) {
			declaration.putTrait(ExampleFieldTrait.class, new ExampleFieldTrait(attribute, conf));
		}
	}

	private ExampleFieldConfiguration analyzeConfig(DmdlSemantics environment, AstAttribute attribute, Map<String, AstAttributeElement> elements) {
		String value = AttributeUtil.takeString(environment, attribute, elements, ELEMENT_VALUE, true);

		// 仕様外の要素名をエラーとして出力する
		environment.reportAll(AttributeUtil.reportInvalidElements(attribute, elements.values()));

		ExampleFieldConfiguration result = new ExampleFieldConfiguration();
		result.setValue(value);
		return result;
	}
}

AttributeUtilに要素の値を取り出すtakeStringメソッドがあるので、それを使っている。
最後の引数のboolean mandatoryをtrueにしておくと、必須チェック(要素が指定されていなかったらエラーにする)を行ってくれる。


JavaDataModelDriverの作成

データモデルドライバーで属性を取得し、使用する。

package jp.hishidama.asakusafw.dmdl.attribute;

import java.io.IOException;

import jp.hishidama.asakusafw.dmdl.attribute.ExampleModelTrait.ExampleModelConfiguration;

import com.asakusafw.dmdl.java.emitter.EmitContext;
import com.asakusafw.dmdl.java.spi.JavaDataModelDriver;
import com.asakusafw.dmdl.semantics.ModelDeclaration;
import com.asakusafw.dmdl.semantics.PropertyDeclaration;
//@see com.asakusafw.dmdl.directio.csv.driver.CsvFormatEmitter
public class ExampleEmitter extends JavaDataModelDriver {

	@Override
	public void generateResources(EmitContext context, ModelDeclaration model) throws IOException {
		ExampleModelTrait modelTrait = model.getTrait(ExampleModelTrait.class);
		if (modelTrait == null) {
			return;
		}

		ExampleModelConfiguration config = modelTrait.getConfiguration();
		System.out.printf("+++example.model(name = %s)%n", config.getName());

		for (PropertyDeclaration property : model.getDeclaredProperties()) {
			ExampleFieldTrait trait = property.getTrait(ExampleFieldTrait.class);
			String value = (trait != null) ? trait.getConfiguration().getValue() : null;
			System.out.printf("%s's value=%s%n", property.getName(), value);
		}
	}
}

SPIの定義

ドライバーを実行させるために、SPIのservicesのファイルを用意する。

src/main/resources/META-INF/services/com.asakusafw.dmdl.spi.AttributeDriver:

jp.hishidama.asakusafw.dmdl.attribute.ExampleModelDriver
jp.hishidama.asakusafw.dmdl.attribute.ExampleFieldDriver

src/main/resources/META-INF/services/com.asakusafw.dmdl.java.spi.JavaDataModelDriver:

jp.hishidama.asakusafw.dmdl.attribute.ExampleEmitter

実行方法

データモデルドライバーの実行方法と同じ。


データモデルドライバーへ戻る / AsakusaFW目次へ戻る / 技術メモへ戻る
メールの送信先:ひしだま