mardi 18 octobre 2011

Model Transformation Preview

Let's say you have a model to model transformation, and you want to provide the ability, for the end-user, to see and control what is going to be applied on the target model. How could you do that ? EMF compare might do the trick..

Here is a trivial model transformation, renaming all Classes which are "abstract" by adding a prefix to their name :




public class ModelTransformer {


public void process(Resource res) {
Iterator it = EcoreUtil.getAllProperContents(res, true);
while (it.hasNext()) {
EObject eobj = it.next();
if (eobj instanceof Class) {
renameifAbstract((Class)eobj);
}
}
}


private void renameifAbstract(Class eobj) {
if (eobj.isAbstract() && !eobj.getName().startsWith("Abstract")) {
eobj.setName("Abstract" + eobj.getName());
}
}


}


The orchestrator of this process has the following responsabilities : loading the original models, transforming those, and then opening the comparison preview, here is the code coming from an action :

protected void transformModelsAndOpenComparison() throws InterruptedException, PartInitException,
InvocationTargetException {
ResourceSet future = new ResourceSetImpl();
for (URI uri : selectedURI) {
future.getResource(uri, true);
}
ModelTransformer transformer = new ModelTransformer();
for (Resource res : future.getResources()) {
transformer.process(res);
}
new DifferencePreview(future).compareWithExisting();
}

Future contains all the models after they have been transformed.  Now the interesting part is in the DifferencePreview class :

public class DifferencePreview {

private ResourceSet now = new ResourceSetImpl();

private ResourceSet future;

public DifferencePreview(ResourceSet output) {
future = output;
}

public void compareWithExisting() throws InterruptedException, PartInitException,
InvocationTargetException {

for (Resource futureRes : future.getResources()) {
now.getResource(futureRes.getURI(), true);
}

MatchResourceSet match = MatchService.doResourceSetMatch(future, now, Collections.EMPTY_MAP);
DiffResourceSet diff = DiffService.doDiff(match);
ComparisonResourceSetSnapshot snap = DiffFactory.eINSTANCE.createComparisonResourceSetSnapshot();
snap.setDiffResourceSet(diff);
snap.setMatchResourceSet(match);
ModelCompareEditorInput input = new ModelCompareEditorInput(snap);
CompareServices.openEditor(input, Collections.EMPTY_LIST);
}
}

Now contains the state of the models as it's serialized on the filesystem.  We starts by constructing the Now resourceset getting all the resources which are present in the Future one. Then we call emf compare to compute the required match and diff , forge an editor input and open it.

How does it look like then ? 



Pretty easy huh ? 

Now let's say you want to allow the end user to customize your output model and allow him to see when the changes coming from the transformation are in conflict with his customizations. Its getting more interesting :  to do so you will need to have a version of the model which has been untouched by the end user and use it as the ancestor. You need to decide where to keep this data and how to prevent the end user to edit it. It's up to your use case, it might be a file next to the output file, it can be in the Eclipse metadata.. Here for the example we'll just use files with a ".ancestor" suffix (see getAncestorURI) .

Anyway, then you'll need to move to three way comparison :

public class DifferencePreviewWithConflictDetection {

private ResourceSet now = new ResourceSetImpl();
private ResourceSet ancestor = new ResourceSetImpl();

private ResourceSet future;

public DifferencePreviewWithConflictDetection(ResourceSet output) {
future = output;
}

public void compareWithExisting() throws InterruptedException, PartInitException,
InvocationTargetException {

for (Resource futureRes : future.getResources()) {
now.getResource(futureRes.getURI(), true);
ancestor.getResource(getAncestorURI(futureRes.getURI()),true);
}
MatchResourceSet match = MatchService.doResourceSetMatch(now, future, ancestor, Collections.EMPTY_MAP);
DiffResourceSet diff = DiffService.doDiff(match,true);
ComparisonResourceSetSnapshot snap = DiffFactory.eINSTANCE.createComparisonResourceSetSnapshot();
snap.setDiffResourceSet(diff);
snap.setMatchResourceSet(match);
ModelCompareEditorInput input = new ModelCompareEditorInput(snap);
CompareServices.openEditor(input, Collections.EMPTY_LIST);
}

private URI getAncestorURI(URI uri) {
return uri.appendFileExtension("ancestor");
}
}

And now what happens if the user decides to update the name of an abstract class, here LibraryElement got renamed in AnyElement ...


That's right, we have a conflict

It's a pretty simple example of what you can achieve using emf compare and how you can reuse it in your tooling. The model comparison editor which opens then is slightly different from the one you have  using SCM operations and has less features (it does not provide the latest capabilities regarding diff filtering). It is also probably not the best fit for end users if many conflicts have to be handled, you might want a wizard in these cases. These are open subjects left as an exercice to the reader.  We are clearly missing building blocks regarding ui so far.

On the newsgroup, bugzilla or during conferences we are often amazed to see how adopters are re-using the technology for their use case, keep telling us, we like to know !

Next steps ? Plugging this with an ATL transformation or using the scoping mechanisms (IMatchScope) to ignore parts of the model we don't want to check, we'll see.. Stay tuned.

Enregistrer un commentaire