Skip to main content

Notice: this Wiki will be going read only early in 2024 and edits will no longer be possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.

Jump to: navigation, search

EMF Compare/Developer Guide

< EMF Compare
Revision as of 04:11, 10 January 2013 by Laurent.goubet.obeo.fr (Talk | contribs) (Project Architecture)


EMF Compare
Website
Download
Community
Mailing List
Forums
Bugzilla
Open
Create New
Contribute
Browse Source


Architecture

Comparison Process

EMF Compare Process Full.png

This is the overview of the comparison process as a whole. Each of the six phases of the comparison process of EMF Compare are briefly defined on the Overview, and a much more in-depth explanation will be given below, in our explanations of the default behavior of EMF Compare.

Project Architecture

EMF Compare 2 Architecture.png

EMF Compare is built on top of the Eclipse platform. We depend on the Eclipse Modeling Framework (EMF), the Eclipse Compare framework and, finally, Eclipse Team, the framework upon which the repository providers (EGit, CVS, Subversive...) are built.

The EMF Compare extensions target specific extensions of the modeling framework : UML, the Graphical Modeling Framework (and its own extensions, papyrus, ecoretools, ...).

Whilst we are built atop bricks that are tightly coupled with the eclipse platform, it should be noted that the core of EMF Compare can be run in a standalone application with no runtime dependencies towards Eclipse; as can EMF itself.

Core Concepts

Proxy Resolution

PENDING

Equality Helper

PENDING

Longest Common Subsequence

PENDING

Default behavior and extensibility

All main components of EMF Compare have been designed for extensibility. Some are only extensible when comparing models through your own actions, some can be customized globally for a given kind of model or metamodel... We'll outline the customization options of all 6 comparison phases in this section. (Any dead link? Report them on the forum!)

Model Resolving

PENDING

Match

Before we can compute differences between two versions of a same Object, we must determine which are actually the "same" Object. For example, let's consider that my first model contains a Package P1 which itself contains a class C1; and that my second model contains a package P1 which contains a class C1. It may seem obvious for a human reader that "P1" and "C1" are the same object in both models. However, since their features might have changed in-between the two versions (for example, the "C1" might now be abstract, or it could have been converted to an Interface), this "equality" is not that obvious for a computer.

The goal of the "Match" phase is to discover which of the objects from model 2 match with which objects of model 1. In other words, this is when we'll say that two objects are one and the same, and that any difference between the two sides of this couple is actually a difference that should be reported as such to the user.

By default, EMF Compare browses through elements that are within the scope, and matches them through their identifier if they have one, r through a distance mechanism for all elements that have none. If the scope contains resources, EMF Compare will first match those two-by-two before browsing through all of their contained objects.

EMF Compare "finds" the identifier of given object through a basic function that can be found in IdentifierEObjectMatcher.DefaultIDFunction. In short, if the object is a proxy, its identifier is its URI fragment. Otherwise its functional ID (in ecore, an attribute that serves as an identifier) takes precedence over its XMI ID (the identifier it was given in the XMI file). If the object is not a proxy and has neither functional nor XMI identifier, then the default behavior will simply pass that object over to the proximity algorithms so that it can be matched through its distance with other objects.

PENDING : brief description of the proximity algorithm

This behavior can be customized in a number of ways.

Overriding the Match engine

The most powerful (albeit most cumbersome) customization you can implement is to override the match engine EMF Compare uses. To this end you can either implement the whole contract, IMatchEngine, in which case you will have to carefully follow the javadoc's recommandations, or extend the default implementation, DefaultMatchEngine.

A custom match engine can be used for your model comparison needs :

IMatchEngine customMatchEngine = new MyMatchEngine(...);
EMFCompare.builder().setMatchEngine(customMatchEngine).build().compare(scope);

Changing how resources are matched

By default, the logic EMF Compare uses to match resources together is very simple : if two resources have the same name (strict equality on the name, without considering folders), they match. When this is not sufficient, EMF Compare will look at the XMI ID of the resources' root(s). If the two resources share at least one root with an equal XMI ID, they match.

This can be changed only by implementing your own subclass of the DefaultMatchEngine and overriding its resource matcher. The method of interest here is DefaultMatchEngine#createResourceMatcher().

Defining custom identifiers

In some cases, there might be ways to identify your objects via the use of "identifiers" that cannot be identified as such by the default mechanism. For example, you might want each of your objects to be matched through their name alone, or through the composition of their name and their type... This can be achieved through code by simply redefining the function EMF Compare uses to find the ID of an object. The following code will tell EMF Compare that the identifier of all "MyEObject" elements is their name, and that any other element should go through the default behavior.

Function<EObject, String> idFunction = new Function<EObject, String>() {
	public String apply(EObject input) {
		if (input instanceof MyEObject) {
			return ((MyEObject)input).getName();
		}
		// a null return here tells the match engine to fall back to the other matchers
		return null;
	}
};
// Using this matcher as fall back, EMF Compare will still search for XMI IDs on EObjects
// for which we had no custom id function.
IEObjectMatcher fallBackMatcher = DefaultMatchEngine.createDefaultEObjectMatcher(UseIdentifiers.WHEN_AVAILABLE);
IEObjectMatcher customIDMatcher = new IdentifierEObjectMatcher(fallBackMatcher, idFunction);
 
IComparisonFactory comparisonFactory = new DefaultComparisonFactory(new DefaultEqualityHelperFactory());
 
IMatchEngine matchEngine = new DefaultMatchEngine(customIDMatcher, comparisonFactory);
EMFCompare.builder().setMatchEngine(matchEngine).build().compare(scope);

Ignoring identifiers

There are some cases where you do not want the identifiers of your elements to be taken into account when matching the objects. This can easily be done when calling for comparisons programmatically :

Through code

IEObjectMatcher matcher = DefaultMatchEngine.createDefaultEObjectMatcher(UseIdentifiers.NEVER);
IComparisonFactory comparisonFactory = new DefaultComparisonFactory(new DefaultEqualityHelperFactory());
 
IMatchEngine matchEngine = new DefaultMatchEngine(matcher , comparisonFactory);
EMFCompare.builder().setMatchEngine(matchEngine).build().compare(scope);

From the user interface

PENDING : preference page

Refine the default Match result

If you are happy with most of what the default behavior does, but would like to refine some of it, you can do so by post-processing the result of the match phase. The original models are only used when matching, and will never be queried again afterwards. All remaining phases are incremental refinings of the "Comparison" model that's been created by the matching phase.

As such, you can impact all of the differencing process through this. Within this post-processing implementation, you can :

  • Remove Match elements
no difference will be detected on those : neither additions, nor deletions, nor conflicts... They'll simply be entirely ignored by the remaining process. Do note that elements for which we have no match will be considered "distinct" by the innards of EMF Compare : if a couple "B<->B'" references a couple "C<->C'" through one of their references, but you have removed the Match "C<->C'", we will considered that this reference has been "changed" from C to C' and this difference within the references of B will be shown as such.
  • Add new Match element
the new couples of elements will be considered by the remaining comparison process and difference may be detected on them.
  • Change existing Match elements
unmatched elements have two or three associated Match objects. For example if you are comparing three version of a model which all contain a different version of a given package, and all three version change the name of this package : version 1 has package "P1", version 2 has package "P2" and version three has package "P3". This package is actually the same, but EMF Compare did not manage to match it. We will thus have three Match objects : one that references "P1" as left, one that references "P2" as right and one that references "P3" as origin.
You may remove two of those three elements and change the third one so that it references P1 as left, P2 as right and P3 as origin. In such a case, those three will be considered to Match for the remainder of the comparison process. Make sure that there are not two different Match referencing the same object though, as this would yield unspecified results.

Defining a custom post-processor requires you to implement IPostProcessor and registering this sub-class against EMF Compare. The latter can be done via either an extension point, in which case it will be considered for all comparisons on models that match its enablement, or programmatically if you only want it active for your own actions :

Through code

The following registers a post-processor for all UML models. This post-processor will not be triggered if there are no UML models (matching the given namespace URI) within the compared scope. Take note that the NsURI provided here is treated as a regular expression.

IPostProcessor customPostProcessor = new CustomPostProcessor();
 
PostProcessorRegistry registry = new PostProcessorRegistry();
registry.addPostProcessor(new PostProcessorDescriptor("http://www.eclipse.org/uml2/\\d\\.0\\.0/UML", null,
		"my.custom.post.processor.id", customPostProcessor ));
Comparison comparison = EMFCompare.builder().setPostProcessorRegistry(registry).build().compare(scope);

Through extension point

This accomplishes the exact same task, but it registers the post-processor globally. Any comparison through EMF Compare on a scope that contains models matching the given namespace URI will trigger that post-processor.

<extension point="org.eclipse.emf.compare.postProcessor">
      <postProcessor class="my.package.CustomPostProcessor">
         <nsURI value="http://www.eclipse.org/uml2/\\d\\.0\\.0/UML">
         </nsURI>
      </postProcessor>

Diff

Now that the Matching phase has finished completed and that we know how our objects are coupled together, EMF Compare no longer requires the two (or three) input models. It will no longer iterate over them or the comparison's input scope. From this point onward, only the result of our comparison, the Comparison object, will be refined through the successive remaining phases, starting by the Diff.

The goal of this phase is to iterate over all of our Match elements, be they unmatched (only one side has this object), couples (two of the three sides contain this object) or trios (all three sides have this object) and compute any difference that may appear between the sides. For example, an object that is only on one side of the comparison is an object that has been added, or deleted. But a couple might also represent a deletion : during three way comparisons, if we have an object in the common ancestor (origin) and in the left side, but not in the right side, then it has been deleted from the right version. However, this latter example might also be a conflict : we have determined that the object has been removed from the right side... but there might also be differences between the original version and the "left" version.

The differencing phase does not care about conflicts though : all it does is refine the comparison to tell that this particular Match has n diffs : one DELETE difference on the right side, and n differences on the left. Detecting conflicts between these differences will come at a later time, during the conflict resolution phase.

There are a little fewer customization options for this phase.

Overriding the Diff engine

Changing the Diff Processor

Refine the default Diff result

Using the Compare Services


Introduction



All the following examples will use a simple (some would say "stupid") domain model used to keep personal information, here 

contact lists.


Here is the diagram:

AddressBookMM.png


The whole comparison process is divided in two phases :

 

  1.  matching both versions of the elements and providing a match model.
  2. computing the diff (delta) model from this match.


When comparing (using the user interface) two models conformed to the AddressBook metamodel you'll get:


AddressBookComparaison1.png


This is an emfdiff model displayed using the emf compare editor. Both match and delta information are kept in this model, let's have 

a further look on the match part:


AddressBookMatch.png

The match model weave elements from both versions of the model.


AddressBookMatchDetail.png


From this match model EMF compare is able to compute the delta (diff):


AddressBookDiffDetail.png



Getting the differences of two models


Getting these differences using some code is easy, here is the snippet::


private DiffModel getDiff(AddressBook v1, AddressBook v2) throws Exception {
        Map options = new HashMap();
        MatchModel match = MatchService.doContentMatch(v1, v2, options);
        // ...

And you'll get the Match model which you can browse like any other EMF model.

Let's now produce the diff from this match information::


  DiffModel diff =  DiffService.doDiff(match);
        return diff;
    }

  

Both Diff and Match services leverage the Eclipse architecture to find the best match/diff engine from the file extension.

But EMF Compare can be used to get the differences of two models within Eclipse or out of Eclipse (standalone mode) and using it in standalone mode you'll loose the ability of "auto-picking" the right match/diff engine considering the file extension : you'll have to call the engines yourself. Here the generic one::


private DiffModel getDiff(AddressBook v1, AddressBook v2) throws Exception {
        Map options = new HashMap();
        MatchModel match = new GenericMatchEngine().contentMatch(v1,v2,options);        
        DiffModel diff =  new GenericDiffEngine().doDiff(match);
        return diff;
    }


The generic match engine can be parameterized thanks to some options available in the MatchOptions interface:


Name Description Type
OPTION_DISTINCT_METAMODELS If set to true, the engine will be able to compare two models from different metamodel and find similarities. Boolean
OPTION_IGNORE_ID If set to true, the engine will ignore the Ecore ID's and will consider the elements data to match them. Boolean
OPTION_IGNORE_XMI_ID If set to true, the engine will ignore the XMI ID's  and will consider the elements data to match them.  Boolean
OPTION_SEARCH_WINDOW The search window is the number of elements the engine will consider at the same time, the bigger it is, more precise is the result, but slower is the process. Integer
OPTION_PROGRESS_MONITOR If set with an IProgressMonitor instance, this one will| IProgressMonitor be used to monitor the match process. IProgressMonitor

 

  please note that all these calls can be done with 3 models, left, right and the ancestor one. Then you'll be able to detect conflicts.


Merging the differences



Once you get the differences you can merge them. You can merge every detected delta from the diff model using the merge service::


AddModelElement add = DiffHelper.isAdded(alice, changes);
MergeService.merge(add, false);

Here is a quick view of all the diff elements:


!images/DiffMM.jpg!


Using EMF Compare in standalone mode



You can setup your environment to use EMF Compare in standalone mode. The will looks like this ::


/**
	 * This application will try and launch an headless model comparison.
	 * 
	 * @author Cedric Brun <a href="mailto:cedric.brun@obeo.fr">cedric.brun@obeo.fr</a>
	 */
	public final class ExampleLauncher {
		/**
		 * This class doesn't need to be instantiated.
		 */
		private ExampleLauncher() {
			// prevents instantiation
		}
	
		/**
		 * Launcher of this application.
		 * 
		 * @param args
		 *            Arguments of the launch.
		 */
		public static void main(String[] args) {
			if (args.length == 2 && new File(args[0]).canRead() && new File(args[1]).canRead()) {
				// Creates the resourceSet where we'll load the models
				final ResourceSet resourceSet = new ResourceSetImpl();
				// Register additionnal packages here. For UML2 for instance :
	//			Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put(UMLResource.FILE_EXTENSION,
	//					UMLResource.Factory.INSTANCE);
	//			resourceSet.getPackageRegistry().put(UMLPackage.eNS_URI, UMLPackage.eINSTANCE);
	
				try {
					System.out.println("Loading resources.\n"); //$NON-NLS-1$
					// Loads the two models passed as arguments
					final EObject model1 = ModelUtils.load(new File(args[0]), resourceSet);
					final EObject model2 = ModelUtils.load(new File(args[1]), resourceSet);
	
					// Creates the match then the diff model for those two models
					System.out.println("Matching models.\n"); //$NON-NLS-1$
					final MatchModel match = MatchService.doMatch(model1, model2, Collections
							.<String, Object> emptyMap());
					System.out.println("Differencing models.\n"); //$NON-NLS-1$
					final DiffModel diff = DiffService.doDiff(match, false);
					
					System.out.println("Merging difference to args[1].\n"); //$NON-NLS-1$
					final List<DiffElement> differences = new ArrayList<DiffElement>(diff.getOwnedElements());
					// This will merge all references to the right model (second argument).
					MergeService.merge(differences, true);
	
					// Prints the results
					try {
						System.out.println("MatchModel :\n"); //$NON-NLS-1$
						System.out.println(ModelUtils.serialize(match));
						System.out.println("DiffModel :\n"); //$NON-NLS-1$
						System.out.println(ModelUtils.serialize(diff));
					} catch (IOException e) {
						e.printStackTrace();
					}
	
					// Serializes the result as "result.emfdiff" in the directory this class has been called from.
					System.out.println("saving emfdiff as \"result.emfdiff\""); //$NON-NLS-1$
					final ModelInputSnapshot snapshot = DiffFactory.eINSTANCE.createModelInputSnapshot();
					snapshot.setDate(Calendar.getInstance().getTime());
					snapshot.setMatch(match);
					snapshot.setDiff(diff);
					ModelUtils.save(snapshot, "result.emfdiff"); //$NON-NLS-1$
				} catch (IOException e) {
					// shouldn't be thrown
					e.printStackTrace();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			} else {
				System.out.println("usage : ExampleLauncher <Model1> <Model2>"); //$NON-NLS-1$
			}
		}
	}


Tutorials

You'll find a few more tutorials on the Eclipse Online Help once you installed EMF Compare

  • Architecture
  • Using the Compare Services
  • Adapting the Comparison Process to your Ecore Model
  • Adding new actions to the export menu

Additional UI APIs:

Feel free to add any tutorial or documentation on the wiki, we'll integrate them back in the online help.

Back to the top