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