S-JIS[2015-11-02] 変更履歴

Xtext Resource

XtextのResource(XtextResource)のメモ。


概要

Xtextで生成されたエディターで扱うファイルを管理するのがResource(実体はXtextResource)だと思う。

Resource全てを扱うのがResourceSet。


ResourceSetとResource

ResourceSetはインジェクションによって取得できる。

import com.google.inject.Injector;

import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
		Injector injector = MyDslActivator.getInstance().getInjector(MyDslActivator.ORG_XTEXT_EXAMPLE_MYDSL_MYDSL);
		ResourceSet resourceSet = injector.getInstance(ResourceSet.class);

		Resource resource = resourceSet.getResource(uri, true);

URIは以下のようにして取得できる。

		URI uri = resource.getURI();

ResourceからResourceSetを取得するのは簡単。

		ResourceSet resourceSet = resource.getResourceSet();

EObjectとResource

EObjectからResourceを取得するのは簡単。

import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
		EObject object = 〜;

		Resource resource = object.eResource();

※EObjectの内容によっては、eResource()がnullになることもあるので注意。


import org.eclipse.emf.common.util.EList;
		EList<EObject> list = resource.getContents();

Resourceの保存

Resource(加工したEObject)をファイルに保存するには、saveメソッドを呼び出せばよい。

import java.util.Map;
import org.eclipse.emf.ecore.resource.Resource;
		if (resource.isModified()) {
			Map<Object, Object> options = null;
			resource.save(options);
		}

ただし、内容にエラーがある(例外が発生する)と、保存先のファイルの中身が空になってしまう!(既存ファイルの中身が消えてしまう)
したがって、事前にダミーの出力先に出力して例外が発生しないことを確認するのが無難かも。

import java.io.ByteArrayOutputStream;
		Map<Object, Object> options = null;
		try {
			ByteArrayOutputStream dummy = new ByteArrayOutputStream(4 * 1024);
			resource.save(dummy, options);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}

		// 例外が発生しなかったら、本当に保存する
		resource.save(options);

保存オプション

Resourceを保存する際は、中のEObjectのバリデーション(精査)が実行される。
バリデーションに失敗すると例外が発生する。

保存時にバリデーションを実行させないようにするには、SaveOptionsを指定する。(デフォルトではバリデーションを実行する)
SaveOptionsでは、整形を行うよう指定することも出来る。(デフォルトでは整形しない)

import org.eclipse.xtext.resource.SaveOptions;
		Map<Object, Object> options = new HashMap<Object, Object>();
		SaveOptions.newBuilder().noValidation().format().getOptions().addTo(options);
		resource.save(options);

IAssignmentQuantityIntervalProvider

保存時に以下のような例外が発生して困っていた。(Xtext2.4.3)

org.eclipse.xtext.validation.IConcreteSyntaxValidator$InvalidConcreteSyntaxException: These errors need to be fixed before the model can be serialized.
Script.list[1]->ModelDefinition'ex_add'.rhs->RecordExpression.terms[1]->RecordTerm: Feature RecordTerm.reference must not be set. Constraint: (properties*|reference) Quantities: reference:1, properties:0

	at org.eclipse.xtext.serializer.impl.Serializer.serialize(Serializer.java:96)
	at org.eclipse.xtext.serializer.impl.Serializer.serialize(Serializer.java:130)
	at org.eclipse.xtext.resource.XtextResource.doSave(XtextResource.java:332)
	at org.eclipse.emf.ecore.resource.impl.ResourceImpl.save(ResourceImpl.java:1430)
〜

(properties*|reference)」のルールに従っているかどうかをチェックしようとして、実体はreferenceが1個でpropertiesが0個だからルールに従っていない、ということらしい。が、合ってるじゃん!
xtextファイルやエラーになった実データは以下のようなもの。

DMDL.xtext(抜粋):

RecordExpression:
    terms+=RecordTerm ('+' terms+=RecordTerm)*;

RecordTerm:
    '{' {RecordTerm} (properties+=PropertyDefinition)* '}'
    | reference=ModelReference;

example.dmdl:

ex_src = {
    zzz : TEXT;
};

ex_add = {
    add : TEXT;
} + ex_src;

どうも、実データとしてreference側を使っているときのバリデーションがおかしいような気がする。

このバリデーションは、IConcreteSyntaxValidator(デフォルトはConcreteSyntaxValidatorクラス)のvalidateRecursiveメソッドで行われている。
で、ConcreteSyntaxValidatorの中のvalidateQuantitiesでルールの範囲(例えば「*」なら最小0個、最大はInteger.MAX_VALUE個)に実体が収まっているかどうかをチェックしている。
上記のルールでは、referenceの最小が0個、最大も0個と判断しており、実体は1個だから範囲外となって精査エラー。
なので、今回の件に関してだけなら、判定用の最小値や最大値を変えてやればエラーにならない。

判定用の最小値最大値はIAssignmentQuantityIntervalProvider(デフォルトはAssignmentQuantityIntervalProviderクラス)によって算出されており、これもインジェクションで指定できる。
したがって、独自のIAssignmentQuantityIntervalProviderを作って回避してやることが出来る。

DMDLAssignmentQuantityIntervalProvider.java:

import java.util.Set;

import jp.hishidama.xtext.dmdl_editor.dmdl.RecordTerm;

import org.eclipse.emf.ecore.EObject;
import org.eclipse.xtext.validation.IAssignmentQuantityAllocator.IQuantities;
import org.eclipse.xtext.validation.IConcreteSyntaxConstraintProvider.ISyntaxConstraint;
import org.eclipse.xtext.validation.impl.AssignmentQuantityIntervalProvider;
public class DMDLAssignmentQuantityIntervalProvider extends AssignmentQuantityIntervalProvider {

	@Override
	public int getMax(IQuantities ctx, ISyntaxConstraint assignment, Set<ISyntaxConstraint> involved, String excludeFeature) {
		int max = super.getMax(ctx, assignment, involved, excludeFeature);

		if (max == 0) {
			EObject object = ctx.getEObject();
			if (object instanceof RecordTerm) {
				String name = assignment.getAssignmentName();
				if ("reference".equals(name)) {
					return 1;
				}
			}
		}

		return max;
	}
}

DMDLRuntimeModule.java:

import jp.hishidama.xtext.dmdl_editor.validation.DMDLAssignmentQuantityIntervalProvider;

import org.eclipse.xtext.validation.IAssignmentQuantityIntervalProvider;
/**
 * Use this class to register components to be used at runtime / without the Equinox extension registry.
 */
public class DMDLRuntimeModule extends jp.hishidama.xtext.dmdl_editor.AbstractDMDLRuntimeModule {

	public Class<? extends IAssignmentQuantityIntervalProvider> bindIAssignmentQuantityIntervalProvider() {
		return DMDLAssignmentQuantityIntervalProvider.class;
	}
}

Xtext目次へ戻る / Eclipseへ戻る / 技術メモへ戻る
メールの送信先:ひしだま