Thursday, November 27, 2008

How to make an xtext language reference another by nsURI

In principle, Xtext allows two languages to refer to each other by means of the importMetamodel feature. If you specify the imported meta-model by means of a file URI, everything works out of the box. Unfortunately, file URIs are not suitable in many scenarios, so something less physical would be appropriate.

Due to a certain mismatch between the way openArchitectureWare (and thereby all the language's Xtend and Check files) and EMF access files and handle EPackage registration. The following describes the way to go in the version of Xtext that is shipped with oAW 4.3.1. Note that in TMF/Xtext we will provide easier support by means of classpath relative URIs.

Example

We consider the following two DSLs asl and bsl, where asl references bsl:

asl:
importMetamodel "http://www.example.org/my/bsl" as mybsl;
A:
(imports+=Import)*
(bs+=BRef)*;
Import:
"import" file=URI;
BRef:
"a" name=ID "b" b=[mybsl::B];

bsl:
B:
"b" name=ID;
To allow such cross-language reference, you have bsl has to be installed in the workbench you use for editing asl.

Install an xtext language

This usually boils down to deploying the language plug-in into the Eclipse workbench, e.g. by exporting it as deployable plug-ins and fragments into the dropins folder of the Eclipse installation.

There is one issue with the way xtext registers its meta-model. The EPackage is added to the EPackage.Registry programatically in the generated MetaModelRegistration class, such that we can call it inside or outside a running Eclipse. If running inside Eclipse, the MetaModelRegistration is called by the Activator of the language plug-in. This can be too late, as other plug-ins might not use the language but the meta-model, and rely on it to be accessible from the EPackage.Registry.

We cannot use EMFs dynamic_package extension point, as it requires the ecore file to be in a plug-in neutral folder. This is not the case in Xtext, as it stores the generated file in a source folder, which is no longer present once the plug-in is deployed.

There are two ways to solve this problem
1) Run the EMF generator to generate code from the ecore model. This will also register the generated EPackage Java class to the generated_package extension point of EMF.
2) Register a proxy class to the generated_package extension point, that implements EPackage (empty methods) and returns MetaModelRegistration.getEPackage() as its eINSTANCE. To ensure that the model is really loaded, you should delete the registered proxy in the static initializer and set the right resource loader before registering the package.

org.example.bsl.EPackageProxy:
package org.example.bsl;
...
public class EPackageProxy implements EPackage {
static {
ResourceLoader cl = ResourceLoaderFactory.createResourceLoader();
try {
ResourceLoaderFactory
.setCurrentThreadResourceLoader(new ResourceLoaderImpl(
EPackageProxy.class.getClassLoader()));
EPackage.Registry.INSTANCE.remove("http://www.example.org/my/bsl");
MetaModelRegistration.register();
} finally {
ResourceLoaderFactory.setCurrentThreadResourceLoader(cl);
}
}

public static final EPackage eINSTANCE = MetaModelRegistration
.getEPackage();

public EClassifier getEClassifier(String name) {
throw new UnsupportedOperationException("Method not implemented");
}

// remaining methods of EPackage
...
plugin.xml:
  <extension point="org.eclipse.emf.ecore.generated_package">
<package class="org.example.bsl.EPackageProxy" uri="http://www.example.org/my/bsl">
</package>
</extension>

Referencing an installed xtext language by nsURI

The xtext generator starts a new plain Java VM, and therefore does not have access to EPackages registered via extension points. That's why you have to explicitly register the referenced metamodel of bsl in the generator workflow of asl. Into the bargain, we have to register bsl's resource factory. Write a class

RegisterBslHelper:
package org.example.asl;
...
public class RegisterBslHelper {

public RegisterBslHelper() {
EPackage package1 = org.example.bsl.MetaModelRegistration.getEPackage();
package1.eResource().setURI(URI.createURI(package1.getNsURI()));
org.example.bsl.ResourceFactoryRegistration.register();
}
}
and call it in the generator workflow generator.oaw:
<workflow>
<property file="'generate.properties'/">
<bean class="org.example.asl.RegisterBslHelper">
<component file="'org/openarchitectureware/xtext/Generator.oaw'" inheritall="'true'/">
</component>
</workflow>
Also make sure, that the my.asl plug-in has a dependency on my.bsl and reexports that.