Linking

The linking feature allows for specification of cross references within an Xtext grammar. The following things are needed for the linking:

  1. declaration of a crosslink in the grammar (at least in the Ecore model)

  2. specification of linking semantics

Declaration of crosslinks

In the grammar a cross reference is specified using square brackets.

CrossReference :
  '[' ReferencedEClass ('|' terminal=AbstractTerminal)? ']'
;

Example:

ReferringType :
  'ref' referencedObject=[Entity|(ID|STRING)]
;

The Ecore model inference would create an EClass ‘ReferringType’ with an EReference ‘referencedObject’ of type ‘Entity’ (containment=false). The referenced object would be identified either by an ID or a STRING and the surrounding information in the current context (see scoping).

Example: While parsing a given input string, say

ref Entity01

Xtext produces an instance of ‘ReferringType’. After this parsing step it enters the linking phase and tries to find an instance of '‘Entity’' using the parsed text ‘Entity01’. The input

ref "EntityWithÄÖÜ"

would work analogously. This is not an ID (umlauts are not allowed), but a STRING (as it is apparent from the quotation marks).

Specification of linking semantics

The ILinker implementation that is used by default is the LazyLinker. It installs EObject proxies for all crosslinks, which are then resolved on demand. The actual cross reference resolution is done by LazyLinkingResource.getEObject(String) and delegates to an implementation of the ILinkingService. Although the default linking behavior is appropriate in many cases there might be scenarios where this is not sufficient. For each grammar a custom linking service can be implemented and configured. It fulfills the following interface:

public interface ILinkingService {

  /**
   * Returns all {@link EObject}s referenced by the given link text in the
   * given context. But does not set the references or modifies the passed
   * information somehow
   */
  List<EObject> getLinkedObjects(
    EObject context, EReference reference, 
    AbstractNode node) throws IllegalNodeException;

  /**
   * Returns the textual representation of a given object as it would be
   * serialized in the given context.
   * @return the text representation.
   */
  String getLinkText(EObject object, 
    EReference reference, EObject context);
}

The method getLinkedObjects is directly related to this topic whereas getLinkText addresses complementary functionality: it is used for serialization.

A simple implementation of the linking service (the DefaultLinkingService) is shipped with Xtext and used for any grammar per default. It uses the default implementation of IScopeProvider to compute the linking candidates.

Default linking semantics

The default implementation for all languages looks within the current file for an EObject of the respective type. In the example above this would be an “Entity” which by convention has a name attribute set to ‘Entity01’.

Given the grammar :

Model : 
  (stuff+=(Ref|Entity))*
;

Ref : 
  'ref' referencedObject=[Entity|ID] ';'
;

Entity : 
  'entity' name=ID ';'
;

In the following model:

ref Entity01;
entity Entity01;

the ref would be linked to the declared entity ( entity Entity01;). Nearly any aspect is configurable, especially the name of the identifying attribute may be overridden for a particular type.

Default Imports

There is a default implementation for inter-resource references, which as well uses convention. Each string in a model which is assigned to an EAttribute with the name importURI, will be interpreted as an URI and used to be loaded using the ResourceSet of the current resource.

For example, given the following grammar :

Model : 
  (imports+=Import)*
  (stuff+=(Ref|Entity))*
;

Import :
  'import' importURI=STRING ';'
;

Ref : 
  'ref' referencedObject=[Entity|ID] ';'
;

Entity : 
  'entity' name=ID ';'
;

It would be possible to write three files in that language where the first references the other two, like this:

//file model.dsl
import "model1.dsl";
import "model2.dsl";
 
ref Foo;
entity Bar;

//file model1.dsl 
entity Stuff;

//file model2.dsl
entity Foo;

The linking candidates for the reference Foo will be Bar, Stuff and Foo in that order. They will be computed by the ScopeProvider.