Friday, June 24, 2011

Using Xbase to Define a Generic Graphical View

While textual representations such as code are perfectly suited to process the details of an object model, a graphical view can be very helpful to display the relationships between objects.

Most graphics frameworks in the Eclipse ecosystem provide graphical editors. Usually, the semantic model is mapped to its graphical representation using a hard wired transformation, be it in code (GEF, Graphiti) or in models and a code generator (GMF, the upcoming Spray project). There are two issues here: First, as opposed to a graphical view, a graphical editor requires the mapping to be bidirectional. That constrains the transformation a lot, e.g. requires having the same structure on the graphical as on the semantic side. Second, hard wiring the mapping introduces extra compilation/generation/deployment turnarounds.

I've created a prototype of a graphical view that is configured using two textual DSLs: A mapping DSL that declaratively describes the transformation from semantic objects to graphical elements, and a styling DSL to modify the graphical representation. The graphical view interprets the mapping and the stylesheet on a given input model, giving immediate feedback on changes of these live models.

Both DSLs are implemented in Xtext 2.0 and make heavy use of the new Xbase expression language library. As Xbase uses Java's typesystem, we can refer to any POJO as an input model. Xbase's rich semantics allow comfortably navigating the models references, and its interpreter can be customized easily to fit our needs. The graphics are implemented using plain GEF and Zest layouts.

The following screencast gives you a short demo. (Sorry for the poor video quality, it gets better if you got to full screen mode)

Monday, June 6, 2011

Rename Refactoring in Xtext 2.0

The upcoming Eclipse Indigo version of Xtext ships with experimental support for generic rename refactoring. It allows you to rename model elements and automatically fix all links to these elements. This blog entry describes the main components behind this new feature. The following screencast shows refactoring for a domainmodel language:



Enabling Refactoring


To enable refactoring support for your language, you have to add the RefactorElementNameFragment in the fragment section of the MWE workflow of your language, e.g.

// rename refactoring
fragment = refactoring.RefactorElementNameFragment {}

The fragment has an additional flag useJdtRefactoring which can be used to delegate to JDT's refactorings for languages using the JVM types compiling to Java (i.e. the domain model example or Xtend). Usually users are fine with the defaults.

After running the workflow, you will have working refactoring support - at least if you have stuck to the defaults with regard to naming, cross-referencing. and indexing. Give it a try. If it doesn't work yet you have to adapt the infrastructure a bit more.

Customizing


The most likely component you want to customize is the IRenameStrategy. This component defines how the declaration of the target element is performed. It has two major responsibilities:

  • Apply and revert the declaration change on the semantic model (methods applyDeclarationChange and revertDeclarationChange))

    The default is to look for an EAttribute 'name' on the target object and set its value using EMFs reflective API.

  • Create the LTK Change objects of the declaration change. These changes will be aggregated, checked for overlaps, presented to you in the preview and finally executed if you apply the refactoring.

    The default is to use the ILocationInFileProvider to locate the text range representing the name and create a ReplaceEdit for it.


As the IRenameStrategy is a stateful object, you have to bind a custom IRenameStrategy.Factory instead of the strategy itself to integrate your changes.

The other component you might want to customize is the IDependentElementsCalculator. Dependent elements are those elements whose qualified name changes when the target element is renamed. E.g. when you rename a Java class the qualified names of its inner classes change, too, thus references to these have to be updated. This calculation is performed by the IDependentElementsCalculator. By default, all elements contained in the target element are added. This matches Xtext's default strategy of qualified name computation.

The Internals


The following describe the steps taken in the refactoring and the responsible components

Refactoring UI


In terms of UI, the refactoring of Xtext looks pretty much the same as in JDT. When the user has triggered the refactoring - by a keystroke or using a context menu entry - the editor goes into a linked editing mode allowing to edit all occurrences of the element in the current document at once. Pressing the keys again or using the popup menu, the user switched to a dialog wizard or a preview wizard. Thanks a lot to Holger who did the major work on the UI.

The UI parts pass an element of type IRenameElementContext to the non-UI components, holding information on the target element, its EClass, the current editor etc.

Non-UI


The non-UI part and thereby the actual refactoring is mainly managed by the RenameElementProcessor.

As a first step, it loads the model declaring the element to be renamed into a new ResourceSet that is configured with respect to the declaring language. This allows to easily abandon all changes as well as avoiding concurrency issues. Some basic checks are performed like 'is the element available' or 'is the resource writeable' and the IRenameStrategy is initialized.

Then we calculate the dependent elements.

After that the URIs of the elements that are affected by the refactoring are calculated. Those URIs may change, e.g. if you use a name-based fragment strategy. So we calculate the original URIs, apply the change and recalculate the new URIs. The result is a Map<URI, URI>. This task is the responsibility of the IRenamedElementTracker.
In the end, the changes are reverted to leave the resource in the same state as before.

The final step is to handle the cross-references to the renamed elements. We have to consider resource internal cross-references as well as the ones from other resources. The latter can be retrieved from Xtext's index. Note that if your external cross-references are not indexed, they won't be found and consequently won't be updated by the refactoring.

As references can come from any other language, we use a ReferenceUpdaterDispatcher that searches for local and indexed references and dispatches to the IReferenceUpdater of the referring language. We have two implementations of this interface: The DefaultReferenceUpdater works with Xtext resources and uses the language's serializer to update the reference text. OTOH, the EmfResourceReferenceUpdater works for all EMF based languages and re-serializes the whole resource after applying the semantic change.

Rename Participants


Finally, one refactoring can trigger another. E.g. when renaming a rule in an Xtext grammar, the returned EClass should be renamed, too. For these cases, you can register a RenameParticipant by the common means of LTK. If the target of the participant is Xtext based, you can use a AbstractProcessorBasedRenameParticipant in order to reuse lots of the already described classes.