/******************************************************************************* * Copyright (c) 2012 Obeo. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Obeo - initial API and implementation *******************************************************************************/ package org.eclipse.mylyn.docs.intent.compare.match; import com.google.common.collect.Maps; import java.util.Map; import org.eclipse.emf.compare.Comparison; import org.eclipse.emf.ecore.EObject; import org.eclipse.mylyn.docs.intent.compare.match.EditionDistance.CountingDiffEngine; import org.eclipse.mylyn.docs.intent.compare.utils.LocationDistanceUtils; import org.eclipse.mylyn.docs.intent.compare.utils.StringDistanceUtils; import org.eclipse.mylyn.docs.intent.core.document.IntentDocument; import org.eclipse.mylyn.docs.intent.core.document.IntentStructuredElement; import org.eclipse.mylyn.docs.intent.core.document.descriptionunit.DescriptionBloc; import org.eclipse.mylyn.docs.intent.core.document.descriptionunit.DescriptionUnit; import org.eclipse.mylyn.docs.intent.core.modelingunit.ContributionInstruction; import org.eclipse.mylyn.docs.intent.core.modelingunit.InstanciationInstruction; import org.eclipse.mylyn.docs.intent.core.modelingunit.ModelingUnit; import org.eclipse.mylyn.docs.intent.core.modelingunit.StructuralFeatureAffectation; import org.eclipse.mylyn.docs.intent.markup.markup.BlockContent; import org.eclipse.mylyn.docs.intent.markup.markup.MarkupPackage; import org.eclipse.mylyn.docs.intent.markup.markup.Text; import org.eclipse.mylyn.docs.intent.markup.serializer.WikiTextSerializer; import org.eclipse.mylyn.docs.intent.serializer.IntentSerializer; /** * An implementation of a diff engine which count and measure the detected changes into Intent documents. * * @author <a href="mailto:william.piers@obeo.fr">William Piers</a> */ public class IntentCountingDiffEngine extends CountingDiffEngine { /** * Weight of location differences. */ private static final double LOCALIZATION_DISTANCE_WEIGHT = 0.15; /** * Weight of identifier differences. */ private static final double IDENTIFIER_DISTANCE_WEIGHT = 0.85; /** * A map keeping a cache of each element and its serialized form. */ private Map<EObject, String> serializationCache = Maps.newLinkedHashMap(); /** * A map keeping a cache of each element and its complete level. */ private Map<EObject, String> levelCache = Maps.newLinkedHashMap(); /** * Constructor. * * @param editionDistance * for instanciation * @param maxDistance * the max distance */ public IntentCountingDiffEngine( org.eclipse.mylyn.docs.intent.compare.match.EditionDistance editionDistance, double maxDistance) { editionDistance.super(maxDistance, editionDistance.fakeComparison); } /** * {@inheritDoc} * * @see org.eclipse.mylyn.docs.intent.compare.match.EditionDistance.CountingDiffEngine#measureDifferences(org.eclipse.emf.compare.Comparison, * org.eclipse.emf.ecore.EObject, org.eclipse.emf.ecore.EObject) */ @Override public double measureDifferences(Comparison comparisonInProgress, EObject a, EObject b) { if (a instanceof IntentDocument && b instanceof IntentDocument) { return 0; // root element } Double distance = null; // the default localization distance Double locationDistance = getLocationDistance(a, b); // the semantic distance: in the best case, a title or feature id. If not available, the // element serialization Double identifierDistance = getIdentifierDistance(a, b); if (identifierDistance == null) { identifierDistance = getSerializationDistance(a, b); } if (identifierDistance != null && locationDistance != null) { distance = (double)(identifierDistance * IDENTIFIER_DISTANCE_WEIGHT + locationDistance * LOCALIZATION_DISTANCE_WEIGHT); } else if (identifierDistance != null) { distance = identifierDistance; } else if (locationDistance != null) { distance = locationDistance; } else { distance = super.measureDifferences(comparisonInProgress, a, b); } return distance; } /** * Returns the distance between document elements by comparing their locations. * * @param a * the first element * @param b * the second element * @return the distance between two strings */ private Double getLocationDistance(EObject a, EObject b) { Double distance = null; String fragmentA = LocationDistanceUtils.computeLevel(a); String fragmentB = LocationDistanceUtils.computeLevel(b); levelCache.put(a, fragmentA); levelCache.put(b, fragmentB); if (fragmentA != null && fragmentB != null) { distance = StringDistanceUtils.getStringDistance(fragmentA, fragmentB); } return distance; } /** * Returns the distance between document elements by comparing their identifiers. * * @param a * the first element * @param b * the second element * @return the distance between two strings */ private Double getIdentifierDistance(EObject a, EObject b) { Double distance = null; String identifierA = null; String identifierB = null; if (a instanceof IntentStructuredElement && b instanceof IntentStructuredElement) { identifierA = getTitle((IntentStructuredElement)a); identifierB = getTitle((IntentStructuredElement)b); } else if (a instanceof StructuralFeatureAffectation && b instanceof StructuralFeatureAffectation) { identifierA = ((StructuralFeatureAffectation)a).getName(); identifierB = ((StructuralFeatureAffectation)b).getName(); } else if (a instanceof InstanciationInstruction && b instanceof InstanciationInstruction) { identifierA = ((InstanciationInstruction)a).getName(); identifierB = ((InstanciationInstruction)b).getName(); } else if (a instanceof ContributionInstruction && b instanceof ContributionInstruction) { identifierA = ((ContributionInstruction)a).getContributionReference().getIntentHref(); identifierB = ((ContributionInstruction)b).getContributionReference().getIntentHref(); } if (identifierA != null && identifierB != null) { distance = StringDistanceUtils.getStringDistance(identifierA, identifierB); } return distance; } /** * Returns the distance between document elements by comparing their serialization. * * @param a * the first element * @param b * the second element * @return the distance between two strings */ private Double getSerializationDistance(EObject a, EObject b) { Double distance = null; String serializedA = serialize(a); String serializedB = serialize(b); if (serializedA != null && serializedB != null) { distance = StringDistanceUtils.getStringDistance(serializedA, serializedB); } return distance; } /** * Serializes the given element. * * @param root * the element to serialize * @return the serialized version */ private String serialize(EObject root) { if (serializationCache.get(root) == null) { String res = ""; if (root.eClass().getEPackage().equals(MarkupPackage.eINSTANCE)) { res = new WikiTextSerializer().serialize(root); } else if (root instanceof ModelingUnit || root instanceof DescriptionUnit || root instanceof IntentStructuredElement) { res = new IntentSerializer().serialize(root); } else if (root instanceof DescriptionBloc) { DescriptionBloc bloc = (DescriptionBloc)root; res = serialize(bloc.getDescriptionBloc()); } serializationCache.put(root, res); } return serializationCache.get(root); } /** * Returns the formatted title of the given element. * * @param element * the formatted title * @return the formatted title of the given element */ private static String getTitle(IntentStructuredElement element) { String title = null; if (element.getTitle() != null && !element.getTitle().getContent().isEmpty()) { BlockContent content = element.getTitle().getContent().get(0); if (content instanceof Text) { title = ((Text)content).getData(); } } return title; } }