Xtext offers powerful cross-reference mechanisms to resolve elements inside the current resource or even from imported resources. External resources can either be imported explicitly or implicitly. My goal was to provide a library for my DSL containing a number of elements which should be referencable from “everywhere”. Consequently the “header” files from my library must be imported implicitly on a global level.
Creating a Library Bundle
To create a bundle for your library, use the New Project Wizard in Eclipse and choose Plug-In Development -> Plug-In Project. In this way as OSGi-based bundle will be created. Create a new folder for your resources to be imported globally (e.g. headers
) and copy your header files (written in your DSL) to this folder.
Registering Implicit Imports in your DSL Bundle
Global implicit import behaviour is achievable using a custom ImportUriGlobalScopeProvider
. Create your class in your DSL bundle in the package <your DSL package prefix>.dsl.scoping
and extend org.eclipse.xtext.scoping.impl.ImportUriGlobalScopeProvider
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | import java.util.LinkedHashSet; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.scoping.impl.ImportUriGlobalScopeProvider; public class MyDSLImportURIGlobalScopeProvider extends ImportUriGlobalScopeProvider { public static final URI HEADER_URI = URI.createURI("platform:/plugin/my.dsl.library/headers/myHeader.ext"); @Override protected LinkedHashSet<URI> getImportedUris(Resource resource) { LinkedHashSet<URI> importedURIs = super.getImportedUris(resource); importedURIs.add(HEADER_URI); return importedURIs; } } |
The method getImportedUris()
is overwritten and extends the set of imports retrieved from the super implementation. Note that I used a platform:/plugin
URI here, which is actually resolved sucessfully in an Eclipse Runtime Workspace. In the itemis blog article about global implicit imports classpath-based URIs are used. A disadvantage of classpath-based library resolving is that it does not work out-of-the-box. In fact you have to create a plug-in project in the runtime workspace and add a dependency to your library bundle in MANIFEST.MF
manually. I successfully avoided this problem by using platform:/plugin
URIs which are resolved as soon as the library bundle is present in the run configuration of the runtime Eclipse instance.
Now your global scope provider has to be bound in the runtime module of your workspace. Open MyDSLRuntimeModule
and add the following binding:
1 2 3 4 5 | @Override public Class<? extends IGlobalScopeProvider> bindIGlobalScopeProvider() { return MyDSLImportURIGlobalScopeProvider.class; } |
If you start your runtime eclipse with the library bundle now, your global implicit imports should be resolved in the editor for your DSL.
Resolving Implicit Global Imports in Standalone Mode
If you rely on the global implicit imports in standalone mode (e.g. in unit tests executed in your development Eclipse instance) the platform:/plugin
URIs can not be resolved. But this can easily be fixed by using URI mappings in a ResourceSet
. The following example shows how to create a standalone parser by injecting a ResourceSet
and creating an URI mapping:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | public class StandaloneXtextParser { @Inject private XtextResourceSet resourceSet; private boolean initialized; public StandaloneXtextParser() { super(); } public EObject parse(URI uri) throws IOException { initializeIfApplicable(); Resource resource = resourceSet.getResource(uri, true); return resource.getContents().get(0); } private void initializeIfApplicable() { // Note: this is not the most elegant way, just for demonstration purposes // Just make sure that setupParser() is called once before you parse if(!initialized) { setupParser(); initialized = true; } } protected void setupParser() { resourceSet.addLoadOption(XtextResource.OPTION_RESOLVE_ALL, Boolean.TRUE); registerURIMappingsForImplicitImports(resourceSet); } private static void registerURIMappingsForImplicitImports(XtextResourceSet resourceSet) { final URIConverter uriConverter = resourceSet.getURIConverter(); final Map<URI, URI> uriMap = uriConverter.getURIMap(); registerPlatformToFileURIMapping(MyDSLImportURIGlobalScopeProvider.HEADER_URI, uriMap); } private static void registerPlatformToFileURIMapping(URI uri, Map<URI, URI> uriMap) { final URI fileURI = createFileURIForHeaderFile(uri); final File file = new File(fileURI.toFileString()); Preconditions.checkArgument(file.exists()); uriMap.put(uri, fileURI); } private static URI createFileURIForHeaderFile(URI uri) { return URI.createFileURI(deriveFilePathFromURI(uri)); } private static String deriveFilePathFromURI(URI uri) { return ".." + uri.path().substring(7); } } |
The trick is to add an URI mapping which resolves the platform:/plugin
URI to a relative file URI. Then the library resources are resolved by means of a relative path in the workspace and the global implicit imports can also be used in unit tests. For more information on standalone parsing please read this blog article.
Other useful resources:
- http://zarnekow.blogspot.de/2012/11/xtext-corner-8-libraries-are-key.html
- https://blogs.itemis.de/leipzig/archives/795