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);
}
});
}