The linking feature allows for specification of cross references within an Xtext grammar. The following things are needed for the linking:
declaration of a crosslink in the grammar (at least in the Ecore model)
specification of linking semantics
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).
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.
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.
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.