S-JIS[2013-08-27/2015-05-03] 変更履歴

Xtext EObject

XtextのEObjectのメモ。


概要

Xtextで生成されたエディターで入力したDSLは、内部ではEObjectクラスのツリーとして保持される。


EObjectとURIとの変換

Xtextの外部からは、URIを通してEObjectを指し示すことが出来る。
(このURIクラスはjava.net.URIではなく、EMFのURIクラス)

EObjectからURIを取得する方法

import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.util.EcoreUtil;
	EObject object = 〜;

	URI uri1 = object.eResource().getURI();	// 例)platform:/resource/example/src/test.mydsl
	URI uri2 = EcoreUtil.getURI(object);   	// 例)platform:/resource/example/src/test.mydsl#/

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


URIの意味

EObjectを表すURIは、「platform:/resource/example/src/test.mydsl」や「platform:/resource/example/src/test.mydsl#//@greetings.0」という形式をしている。

platform:/resource/」は固定で、その直後の「example」がプロジェクト名、それ以降がファイルのパスを表す。

#」以降(fragment)が付いていないものは、ファイルそのものを指す。
フラグメントが付いているものは、ファイル内の個々のEObjectを指す。

フラグメントは「/」区切りでEObjectの階層を表している。
フラグメントが「#/」のみだと、ファイルのトップにあるEObjectを指す。
フラグメントが「#//@greetings.0」の例だと、トップのEObjectのgreetingsというリストの0番目のEObjectを指す。リストでない場合はピリオドを付けない(「@greeting」の様になる)。


URIの生成方法

URIは以下の様にして作る。

import org.eclipse.emf.common.util.URI;
	String projectName = "example";
	String filePath    = "src/test.mydsl";
	URI uri = URI.createPlatformResourceURI(projectName +"/" + filePath, true);

IFileからはIPath経由でパスを取得できる。

	IFile file = 〜;
	URI uri = URI.createPlatformResourceURI(file.getFullPath().toPortableString(), true);

IStorage(IFile)からURIに変換するクラスを使うことも出来る。

import org.eclipse.xtext.ui.resource.IStorage2UriMapper;
import com.google.inject.Injector;

import org.xtext.example.mydsl.ui.internal.MyDslActivator;
	IFile file = 〜;
	Injector injector = MyDslActivator.getInstance().getInjector(MyDslActivator.ORG_XTEXT_EXAMPLE_MYDSL_MYDSL);
	IStorage2UriMapper mapper = injector.getInstance(IStorage2UriMapper.class);
	URI uri = mapper.getUri(file);

フラグメントは以下の様にして追加する。

	URI uri = 〜;
	URI uri2 = uri.appendFragment("/");
	URI uri3 = uri.appendFragment("//@greetings.0");

URIからEObjectを取得する方法

import java.util.List;

import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import com.google.inject.Injector;

import org.xtext.example.mydsl.ui.internal.MyDslActivator;
	public static EObject getEObject(URI uri) {
		Injector injector = MyDslActivator.getInstance().getInjector(MyDslActivator.ORG_XTEXT_EXAMPLE_MYDSL_MYDSL);
		ResourceSet resourceSet = injector.getInstance(ResourceSet.class);

		return resourceSet.getEObject(uri, true);
	}

resourceSet.getEObject()は、URIにフラグメントが無い状態だとNullPointerExceptionが発生する。 (Xtext 2.4.2)

0    [Worker-6] ERROR org.eclipse.xtext.linking.lazy.LazyLinkingResource  - resolution of uriFragment 'null' failed.
java.lang.NullPointerException
	at org.eclipse.xtext.linking.lazy.LazyURIEncoder.isCrossLinkFragment(LazyURIEncoder.java:195)
	at org.eclipse.xtext.linking.lazy.LazyLinkingResource.getEObject(LazyLinkingResource.java:173)
	at org.eclipse.emf.ecore.resource.impl.ResourceSetImpl.getEObject(ResourceSetImpl.java:223)
	〜

× platform:/resource/example/src/test.mydsl
platform:/resource/example/src/test.mydsl#/


	public static List<EObject> getEObjectList(URI uri) {
		Injector injector = MyDslActivator.getInstance().getInjector(MyDslActivator.ORG_XTEXT_EXAMPLE_MYDSL_MYDSL);
		ResourceSet resourceSet = injector.getInstance(ResourceSet.class);

		Resource r = resourceSet.getResource(uri, true);
		return r.getContents();
	}

resourceSet.getResource()は、URIにフラグメントが有っても無くてもリソース(ファイル)を返す。
したがって、Resource#getContents()はファイル内のトップのEObjectを返す。

resourceSet.getResource(uri, true)で「プロローグにはコンテンツを指定できません(Content is not allowed in prolog)」という例外が発生するときは、Xtextの初期化がされていないらしい。[2015-04-28]

Caused by: org.eclipse.emf.ecore.resource.impl.ResourceSetImpl$1DiagnosticWrappedException: org.xml.sax.SAXParseExceptionpublicId: platform:/resource/example/src/main/example.mydsl; systemId: platform:/resource/example/src/main/example.mydsl; lineNumber: 1; columnNumber: 1; プロローグにはコンテンツを指定できません。
	at org.eclipse.emf.ecore.resource.impl.ResourceSetImpl.handleDemandLoadException(ResourceSetImpl.java:319)
	at org.eclipse.emf.ecore.resource.impl.ResourceSetImpl.demandLoadHelper(ResourceSetImpl.java:278)
	at org.eclipse.xtext.resource.XtextResourceSet.getResource(XtextResourceSet.java:201)
	at org.eclipse.xtext.resource.SynchronizedXtextResourceSet.getResource(SynchronizedXtextResourceSet.java:26)
	〜

以下のように初期化処理を自分で呼び出せば大丈夫。

		MyDslStandaloneSetup.doSetup();
		Injector injector = MyDslActivator.getInstance().getInjector(MyDslActivator.ORG_XTEXT_EXAMPLE_MYDSL_MYDSL);
		〜

参考: Eclipse Community ForumsのContent is not allowed in prolog.

しかし初期化処理が呼ばれないのは、初期化処理(plugin.xml等)の記述に誤りがある可能性が高い。[2015-05-03]
特に作成対象となる拡張子を後から変更した場合、Javaソース内やplugin.xmlの各所に拡張子の指定があるので、全部変える必要がある。


EObjectがプロキシーの場合は、中身が空っぽの場合がある。[2013-09-03]
EcoreUtil.resolve()で解決させることが出来る。

		EObject object = 〜;
		if (object.eIsProxy()) {
			Injector injector = MyDslActivator.getInstance().getInjector(MyDslActivator.ORG_XTEXT_EXAMPLE_MYDSL_MYDSL);
			ResourceSet resourceSet = injector.getInstance(ResourceSet.class);
			object = EcoreUtil.resolve(model, resourceSet);
		}

IFileからEObjectを取得する方法

IFileからEObjectを取得するには、基本的にはIFileからURIを生成して、URIからEObjectを取得する。


エディターからEObjectを取得する方法

IFileのエディターが開いている場合はXtextDocumentUtilを使ってEObjectを取得できる。

import org.eclipse.xtext.ui.editor.model.IXtextDocument;
import org.eclipse.xtext.ui.editor.model.XtextDocumentUtil;
import org.eclipse.xtext.ui.editor.utils.EditorUtils;
		IFile file = 〜;
		List<EObject> list = getEObject(file);

		XtextEditor editor = EditorUtils.getActiveXtextEditor();
		List<EObject> list = getEObject(file);
	private static List<EObject> getEObject(Object obj) {
		IXtextDocument doc = XtextDocumentUtil.get(obj);
		if (doc == null) {
			return null;
		}

		EList<EObject> list = doc.readOnly(new IUnitOfWork<EList<EObject>, XtextResource>() {
			@Override
			public EList<EObject> exec(XtextResource state) throws Exception {
				return state.getContents();
			}
		});

		return list;
	}

XtextDocumentUtilはuiプロジェクトで使用できる。
XtextDocumentUtil.get()にはIFileやXtextEditor等が渡せる。
ただし該当ファイルのエディターが開いていないとnullが返る。

IXtextDocument#readOnly()は、ドキュメント内のオブジェクトにアクセスするメソッド。
引数にはIUnitOfWorkというクラスを渡す。型引数は、1個目が戻り値の型、2個目はXtextResourceで固定。


IFileからIXtextDocumentを取得する方法

IFileのエディターが開いている場合はXtextDocumentUtilでIXtextDocumentを取得できるが、開いていない場合は以下のようにして取得できる。[2013-09-01]

import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.xtext.resource.XtextResource;
import org.eclipse.xtext.ui.editor.model.XtextDocument;

import com.google.inject.Injector;

import org.xtext.example.mydsl.ui.internal.MyDslActivator;
	public static getXtextDocument(IFile file) {
		Injector injector = MyDslActivator.getInstance().getInjector(MyDslActivator.ORG_XTEXT_EXAMPLE_MYDSL_MYDSL);

		URI uri = URI.createPlatformResourceURI(file.getFullPath().toPortableString(), true);
		ResourceSet resourceSet = injector.getInstance(ResourceSet.class);
		Resource resource = resourceSet.getResource(uri, true);
		XtextDocument document = injector.getInstance(XtextDocument.class);
		document.setInput((XtextResource) resource);

		return document;
	}

参考: GamlUtils


エディターで選択されているEObjectの取得

イベントハンドラー内でXtextEditorを取得し、選択されているEObjectを取得する例。[2014-11-19]

import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.jface.text.ITextSelection;

import org.eclipse.emf.ecore.EObject;

import org.eclipse.xtext.resource.EObjectAtOffsetHelper;
import org.eclipse.xtext.resource.XtextResource;
import org.eclipse.xtext.ui.editor.XtextEditor;
import org.eclipse.xtext.ui.editor.model.IXtextDocument;
import org.eclipse.xtext.ui.editor.utils.EditorUtils;
import org.eclipse.xtext.util.concurrent.IUnitOfWork;

import com.google.inject.Inject;
public class ExampleHandler extends AbstractHandler {

	@Inject
	private EObjectAtOffsetHelper helper;
//	private EObjectAtOffsetHelper helper = new EObjectAtOffsetHelper();
	// @Override
	public Object execute(ExecutionEvent event) throws ExecutionException {
		XtextEditor editor = (XtextEditor) EditorUtils.getActiveXtextEditor(event);
		if (editor == null) {
			return;
		}

		ITextSelection selection = (ITextSelection) editor.getSelectionProvider().getSelection();
		final int offset = selection.getOffset();

		IXtextDocument document = editor.getDocument();
		EObject object = document.readOnly(new IUnitOfWork<EObject, XtextResource>() {
			// @Override
			public EObject exec(XtextResource state) throws Exception {
				return helper.resolveContainedElementAt(state, offset);
			}
		});

		return null;
	}

参考: Java Code Examples for org.eclipse.xtext.ui.editor.XtextEditor


IProjectからEObject一覧を取得する方法

IProjectからIFile一覧を取得すれば、IFileからEObjectを取得することは出来るが。
IXtextEObjectSearchを使うと、ワークスペース全体のEObjectを取得することが出来る。
IXtextEObjectSearchをカスタマイズして該当プロジェクトのEObjectだけ抽出すればいい。

import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.resources.IProject;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.xtext.resource.IEObjectDescription;
import org.eclipse.xtext.ui.search.IXtextEObjectSearch;

import com.google.inject.Injector;

import org.xtext.example.mydsl.ui.internal.MyDslActivator;
	public static List<EObject> getEObjectList(IProject project) {
		List<EObject> list = new ArrayList<EObject>();

		IXtextEObjectSearch search = new MySearch(project.getName());
		Injector injector = MyDslActivator.getInstance().getInjector(MyDslActivator.ORG_XTEXT_EXAMPLE_MYDSL_MYDSL);
		injector.injectMembers(search);

		Iterable<IEObjectDescription> matches = search.findMatches("", "");
		for (IEObjectDescription desc : matches) {
			EObject object = desc.getEObjectOrProxy();
			list.add(object);
		}

		return list;
	}

findMatches()には、フィルター(名称での絞り込み)が指定できる。
第1引数がname要素の条件、第2引数はルールクラス名の条件となる。

XtextEObjectSearchDialog


import java.util.Collections;

import org.eclipse.emf.common.util.URI;
import org.eclipse.xtext.resource.IEObjectDescription;
import org.eclipse.xtext.resource.IResourceDescription;
import org.eclipse.xtext.ui.search.IXtextEObjectSearch;

import com.google.common.base.Function;
import com.google.common.collect.Iterables;
public class MySearch extends IXtextEObjectSearch.Default {

	private String projectName;
	public MySearch(String projectName) {
		this.projectName = projectName;
	}
	@Override
	protected Iterable<IEObjectDescription> getSearchScope() {
		return Iterables.concat(Iterables.transform(getResourceDescriptions().getAllResourceDescriptions(),
			new Function<IResourceDescription, Iterable<IEObjectDescription>>() {
				@Override
				public Iterable<IEObjectDescription> apply(IResourceDescription from) {
					URI uri = from.getURI();
					if (isTarget(uri)) {
						return from.getExportedObjects();
					}
					return Collections.emptyList();
				}
			}));
	}
	private boolean isTarget(URI uri) {
		if (uri.fileExtension().equals("mydsl")) {	//ファイルの拡張子
			String[] seg = uri.segments();
			if (seg.length >= 2 && projectName.equals(seg[1])) {	//プロジェクト名
				return true;
			}
		}
		return false;
	}
}

デフォルトのIXtextEObjectSearch(IXtextEObjectSearch.Default)では、Xtextで作られた全DSLファイルが対象になる。
そこで、MySearchではプロジェクト名やファイルの拡張子で絞り込むようにしている。


EObjectの位置へジャンプする方法

EObjectの位置へジャンプする(エディターを開く)ことが出来る。
ジャンプ先はURIで指定する。

import org.eclipse.emf.common.util.URI;
import org.eclipse.ui.IEditorPart;
import org.eclipse.xtext.ui.editor.GlobalURIEditorOpener;
import com.google.inject.Injector;

import org.xtext.example.mydsl.ui.internal.MyDslActivator;
	public static boolean jump(URI uri) {
		Injector injector = MyDslActivator.getInstance().getInjector(MyDslActivator.ORG_XTEXT_EXAMPLE_MYDSL_MYDSL);
//		GlobalURIEditorOpener opener = new GlobalURIEditorOpener();
//		injector.injectMembers(opener);
		GlobalURIEditorOpener opener = injector.getInstance(GlobalURIEditorOpener.class);

		IEditorPart editor = opener.open(uri, true);
		return editor != null;
	}

EObjectの生成・削除

(ファイル等とは無関係に)EObjectを新規に生成する方法。[2013-09-02]

import org.eclipse.emf.ecore.util.EcoreUtil;

import org.xtext.example.mydsl.myDsl.Greeting;
import org.xtext.example.mydsl.myDsl.Model;
import org.xtext.example.mydsl.myDsl.MyDslPackage;
	Greeting greeting = (Greeting) EcoreUtil.create(MyDslPackage.Literals.GREETING);

	Model model 〜;
	EList<Greeting> list = model.getGreetings();
	list.add(greeting);

EObjectを複製する方法。[2013-09-02]

	Greeting greeting = 〜;
	Greeting dup = EcoreUtil.copy(greeting);

親オブジェクトからEObjectを削除する方法。[2013-09-02]

	Greeting greeting = 〜;
	EcoreUtil.remove(greeting);

greetingが属している親オブジェクトを探して、そこからgreetingを削除してくれる。

※EObjectはコメントも含んでいるので、EObjectを削除するとコメントも消えるので注意。


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