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