This article shows the necessary steps to enable cross-references between Xtext models and other EMF based models. It focusses on the linking aspects, and keeps things like the synchronization, transactions, singleton editing domains, dirty-state handling etc. aside. So for a full integration, let’s say with Sirius, this is only one part of the story.
There are often good reasons to describe different parts of an EMF model in different notations, e.g. textual and graphical. To connect resources with different notations we can use EMF cross-references. Via the XtextResource, Xtext hides the entire process of parsing (text to EMF) and serialization (EMF to text) behind EMF’s resource API. So in theory, cross-references between Xtext and other EMF-based models should work out-of-the-box, shouldn’t they?
Unfortunately, there is one big difference: Xtext uses names to refer to an element, while EMF uses URIs. This blogpost is about how to get cross-references working anyway.
In our setup, we can define tree models either in XMI using the generated EMF tree editor (file extension tree) or textually in an Xtext (file extension xtree), and establish cross-references between the models of both notations. The screenshot shows an XMI-based model on the left and an Xtext-based on the right refering to each other.
Cross-References from Xtext to XMI
To resolve a cross-reference to an element, Xtext takes the name given in the text to look up the referred element in a scope. You can think of a scope as a table of all candidates for a certain cross-reference with their name in a specific context. Scopes are usually chained, such that if the current scope does not yield a result for a given name, it asks its parent scope and so on. The top-most parent scope is called the global scope. It provides all elements from all reachable resources that would be referable at that specific location. It is usually backed by the Xtext index, which stores descriptions of all externally referable elements for each resource. The index is populated by the Xtext builder, which automatically syncs the index data on file changes. This is why you should never deactivate automatic builds for an Xtext project. More information on scoping can be found in the Xtext documentation.
Given the above, in order to refer to XMI from Xtext we have to create index entries for the elements of the XMI-based model. This is achieved by registering a new language for the *.tree resources to the Xtext infrastructure, thus providing services like indexing and name computation. In the example, we created a separate plug-in project for the Xtext language registration. You could of course put that code in an existing plug-in as well. or you might want to put runtime and UI parts into separate plug-ins.
- Implement a TreeRuntimeModule inheriting from AbstractGenericResourceRuntimeModule and implement the missing methods. This class is used to configure the runtime dependency injection (DI) container for this language. If you want to override non-UI services you can do this here.
In our example, we override the IQualifiedNameProvider to yield fully qualified names, i.e. Root.Child instead of just Child in the above example to avoid name collisions.
- Implement a TreeUiModule inheriting from EmfUiModule. This is the DI config for all Eclipse-based services.
In the example, we added an editor opener that opens the EMF tree editor when the user follows a reference to an XMI-defined tree element in the Xtext editor.
- Implement a plug-in Activator (inheriting from AbstractUIPlugin) that creates the injector based on the TreeUiModule, the TreeRuntimeModule and the common SharedStateModule on start(). Make sure to register the Activator in the MANIFEST.MF.
- Implement an TreeExecutableExtensionFactory that extends AbstractGuiceAwareExecutableExtensionFactory and delivers both the bundle and the injector from the Activator.
- In the plugin.xml, register your language to the extension point org.eclipse.xtext.extension_resourceServiceProvider with the uriExtension tree and an instance of EmfResourceUIServiceProvider created via the TreeExecutableExtensionFactory from the previous step.
You could skip step 2 to 5 if you don’t need Eclipse support. If you want to have the same functionality in a plain Java process, you have to manually create the injector and initialize the EMF registries, as we did in the TreeStandaloneSetup.
Cross-References from XMI to Xtext
As opposed to names in Xtext, EMF uses URIs to refer to elements. In XMI, the standard serialization format for EMF models, a cross-ref becomes an href with the URI of the referred element. The URI consists of the URI of the resource where the element is defined in, followed by the fragment which is a resource unique string identifier of the element, e.g.
The XtextResource delivers such URIs for all contained elements by default. These URIs are picked up by referring XMI resources, so it seems like this works out-of-the-box. But the problems begin as soon as you start modifying the referred Xtext resource.
The default algorithm for computing the fragments uses an Xpath expression, navigating the containment features from the root element by name (children) and index (1). This approach delivers unique fragments for all elements in a resource without relying on a name or a unique ID attribute. The disadvantage is, that it assumes that the position of an element in the content tree is fixed. When we switch the order of Bar and Baz in the example, their path fragments would be switched as well, screwing up existing URI references to them.
If you want the same linking semantics as in Xtext, the fragment should encode the fully qualified name of the element. Xtext allows you to customise that by implementing your own IFragmentProvider. In the example, we have added our own XtreeFragmentProvider and bound it in the XtreeRuntimeModule.
Additionally we might want to include the element’s EClass, because elements of different types in the same model could have the same name. Then, there can be multiple EClasses in different EPackages with the same name, so a complete generic solution for the fragment would be a fully qualified EClass name followed by a fully qualified element name , that is
href="[resource URI]#[EPackage nsURI][separator][EClass name][separator][EObject FQN]"
This is a lot of information to be packed into a string. We also must sure we don’t break encoding rules. So it may be better to go for a less general, domain-specific solution as we did in the example.
Another problem is the resource part of the URI: In Xtext, a referrer does not care in which resource the cross-referenced element is. Moving an element to a different Xtext resource would break all URI-based links to it, while the name-based links stay intact. A possible solution would be to implement a move refactoring for Xtext elements. That is beyond the scope of this article.
A Word on Rename Refactoring
In URI-based linking, renaming an element will not change a cross reference, as long as the fragments don’t involve the name. With our approach, the links to the Xtext resource are susceptible to such renames.
The good news is that by registering the tree language to Xtext as we did above, cross-references to Xtext elements will be automatically updated when the user triggers a rename refactoring on them.
The bad news is that the links from Xtext to XMI will break when an XMI element is renamed, let’s say in the EMF tree editor. It is up to the implementor of the editor to trigger the a rename refactoring for referring Xtext resources on such user actions if that is the intended behavior. Luckily, broken links from Xtext to XMI will just be marked as errors and can be easily fixed by hand.