Today, the common way to do cross resource references in Xtext is to do it via resource URIs. That is if you want to reference a model element (EObject) from another resource, you typically put the whole resource on the scope by adding a corresponding import. Example:
import "platform:/resource/my.project/src/othermodel.dsl"The corresponding default scoping is very simplistic. Every object in the current resource and in the referenced resources can be referenced by its simple name (as long as it has a 'name').
//.. refer to elements from othermodel.dsl
Although this is very easy to understand, it has it's limitation when it comes to more sophisticated design. If you for instance want to hide some elements or have duplicate simple names in different packages (this can be the case if you use elements, which are developed by others).
In many programming languages we have the notion of namespaces, which are much more flexible and powerful. Java, for instance, is file system agnostic. Although it forces you to put the files into folders which correspond the packages, it ultimately is just based on namespaces (packages, types).
That said Java's namespace mechanism is also a bit limited. For instance I cannot have imports in nested namespaces but only per file. And I cannot nest packages but only classes and interfaces.
Scala and C# both allow to have multiple nested packages within one file and you can put imports per namespace, so that imported names are only visible within that namespace.
In order to demonstrate how to use the index together with Xtext, I've implemented a DefaultIndexBasedScopeProvider which implements a similar semantic. There's a small example I've prepared, where you can see how it can be used. It is mainly a matter of configuring the different implementations with Guice. Programming is not needed as long as you're happy with the defaults.
Here's how it works
The index registeres a builder, which is invoked on resource changes. In order to make your model elements visible, you'll have to contribute a so called Indexer using an extension point.
<extension point="org.eclipse.emf.index.indexer">Please ignore the ExecutableExtensionFactory, which is declared in order to make any executable extension Guice aware, that is you can use dependency injection. This is a different topic and might be covered by another blog post.
<indexer class="org.eclipse.xtext.example.DomainmodelExecutableExtensionFactory
:org.eclipse.xtext.index.DefaultDeclarativeResourceIndexer"
fileextensions="dmodel"/>
</extension>
The actual class to be instantiated is the one after the colon (':'): The DefaultDeclarativeResourceIndexer, which delegates to an instance of IQualifiedNameProvider, which itself is injected. This means that its implementation can be arbitrarily changed.
The contract of a name provider is very simple: it computes a qualified name for an element, if it returns null, the element is not indexed and hence not referable.
By default we use a DefaultDeclarativeQualifiedNameProvider, which if not otherwise specified looks up a simple name (if there's an attribute 'name') and concatenates it to the qualified name of its parent. It's named 'declarative' because you're able to change the described default behavior per type by just adding a method like this:
String qualifiedName(MyType foo) {It will automatically dispatch to this method as soon as it has to compute a qualified name for an instance of MyType.
// compute different qualified name for MyTypes
// ...
}
With this in place we'll have our elements automatically indexed as long as they are in a project, which have the index nature enabled. Being indexed means that they are globally visible by their qualified name, which is comparable to how public Java elements are globally visible as soon as they are on the classpath.
What's next?
In order to use the index and have it injected into your components (e.g. your scope provider) you'll have to configure the singleton instance from the index bundle into your Guice module. In the example the corresponding binding goes into the UI module and looks like this:
public IndexStore bindIndexStore() {With that in place you can inject the index store by just adding a dependency in your code:
return EmfIndexUIPlugin.getDefault().getIndexStore();
}
@InjectGuice will automatically put the instance into such declared dependencies.
private IndexStore store;
Now that we have a binding for IndexStore we can add the index based scoping to the runtime module:
public IndexStore bindIndexStore() {Note the additional IndexStore binding, which is overridden by the binding we previously added to the UI module, but is needed in order to use this stuff at runtime (i.e. without running within exquinox). So it gets active as soon as you run without UI.
return new PersistableIndexStore();
}
@Override
public Class bindIScopeProvider() {
return DefaultIndexBasedScopeProvider.class;
}
How the DefaultIndexBasedScopeProvider works
The DefaultIndexBasedScopeProvider
- looks up EAttributes with name 'importNamespace'
- and translates the globally unique qualified name into shorter ones using those import statements.
By default qualified names with or without a wildcard at the end are supported. For an import of a qualified name the simple name is made available as we know from e.g. Java, where
import java.util.Set;makes it possible to refer to 'java.util.Set' by its simple name 'Set'.
Contrary to Java the import is not active for the whole file but only for the namespace it is declared in and its child namespaces. That is why you can write the following in the example DSL:
package foo {Of course the declared elements within a package are as well referable by their simple name:
import bar.Foo
entity Bar extends Foo {
}
}
package bar {
entity Foo {}
}
package bar {Of course the following would as well be ok:
entity Bar extends Foo {}
entity Foo {}
}
package bar {Disclaimer
entity Bar extends bar.Foo {}
entity Foo {}
}
All this is in a very early stage. The index is not finished and its architecture is not settled down yet. Also the scope provider implementation might be changed in future (I'm sure it will).
Additionally, there are other things around this which we have to work on before considering this mature.
But as I know that there are a lot of bleeding edge users out there, I wanted to share the current state, so you might find a starting point to play with it. The index is an enabler for more advanced functionality in Xtext and in EMF based development in general. So expect it to become an important part in Eclipse Modeling.
Feedback is highly appreciated and should either go to the newsgroup for Xtext (the scoping part) or to the EMFT newsgroup (the index part), because the index project is a component under EMFT.