Sunday, June 20, 2010

Semantic Model Access in Xtext

In Xtext, the parser creates the EMF-based semantic model or abstract syntax tree from the textual representation of the model. Many components, e.g. validation or outline, but also an interpreter or code generator work on this semantic model rather than on the textual representation. In this post, I want to introduce you to Xtext's APIs to safely read and modify the semantic model of an Xtext document.

Why do we need model synchronization at all?

In a running Xtext Workbench, there are a number of components which access the semantic model, i.e. the parser, the linker, the validator, the outline, the index builder etc. While some of these components are executed by the display thread, others like the parser or the indexer use different concurrent threads to not deteriorate the editing experience. If you for example want to have a consistent outline of your model, it is essential to keep other threads from modifying the model while the outline component reads it.

Why not use EMF Transaction?

Editors that use EMF Transaction usually employ a so called TransactionalEditingDomain that shields a ResourceSet with a lock and manages a transactional command stack. The Resources in the ResourceSet can be read safely - i.e. excluding any concurrent write operation - inside a Runnable that is passed to the TransactionalEditingDomain.runExclusive() method. Write access is only possible by issuing a command. A change listener throws an Exception if write operations are executed outside of write command.

This quite heavy-weight mechanism can be appropriate for pure model editors, i.e. editors modifying the model directly like the generated EMF tree editor, but experience shows that it is not easily integrated with other command frameworks. (If you don't believe this, just look at the way GMF deals with EMF Transactions, GEF commands and the Eclipse Operation history.)

Architecturally, an Xtext editor is a text editor in the first place. The commands on its undo/redo stack are the usual commands used in text editors. So instead of inventing yet another adapter technology, we headed for an easier synchronization mechanism.

How do I read a model safely?

Each XtextEditor uses an IXtextDocument to store its model. The IXtextDocument allows reentrant read/write access to the underlying semantic model by means of the two methods
readOnly() and modify(). Both take an argument of type IUnitOfWork(<T>, IXtextResource) which defines a method <T> exec(IXtextResource) that contains what you want to do with the model and allows to deliver a result of arbitrary type.

So here is an example of safely reading a model:
IXtextDocument myDocument = ...;
String rootElementName = myDocument.readOnly(
new IUnitOfWork(){
public String exec(IXtextResource resource) {
MyType type = (MyType)resource.getContents().get(0);
return myType.getName();
}
});

How do I modify a model safely?

Direct write-access on the document is usually only performed inside the framework. If you want to change a document by means of its semantic model, you should rather use an IDocumentEditor which uses the modify() method internally but takes care of synchronizing the node model, too:
@Inject
private IDocumentEditor documentEditor;

public void setRootName(IXtextDocument myDocument,
final String newName) {
documentEditor.process(
new IUnitOfWork.Void() {
public void process(IXtextResource resource) {
MyType type = (MyType)resource.getContents().get(0);
myType.setName(newName);
}
}, myDocument);
}

BTW, the QuickFix-API internally uses an IDocumentEditor, too.

PS:

In Xtext 2.0 we have changed the semantics of XtextDocument.modify() to use the former IDocumentEditor functionality. So the model write example now symmetrically becomes
public void setRootName(IXtextDocument myDocument,
final String newName) {
myDocument.modify(
new IUnitOfWork.Void(){
public void process(IXtextResource resource) {
MyType type = (MyType)resource.getContents().get(0);
myType.setName(newName);
}
});
}

6 comments:

betto said...

Interesting post Jan.

but for modifying the model, how can I get the

@Inject
private IDocumentEditor documentEditor;

?

Is there any example implementing, e.g., a UI action on the editor for instance?

thanks in advance
Lorenzo

Unknown said...

This is just the Google Guice code defining a field that is injected. Make sure the class declaring this is itself created with Guice. For more details, I recommend reading the section on DI in the Xtext docs.

Anonymous said...

Does this still work with xtext2.0? I did not mangage to get it working.
How can I access the model in xtext2.0?

Unknown said...

Sure, look at the PS.

Unknown said...

Hi all ,

I try this code in Xtext 2.4.1.but not works.it works in the Xtext 2.4.1.

Unknown said...

If you follow the procedure for read and the one in the PS section for write this is supposed to work. If it doesn't, please open a new thread in the TMF forum and describe what exactly the problem is.