/*******************************************************************************
* Copyright (c) 2013, 2015 Obeo and others.
* 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
* Philip Langer - bug 482404
*******************************************************************************/
package org.eclipse.emf.compare.diagram.internal.factories.extensions;
import static com.google.common.collect.Collections2.filter;
import static org.eclipse.emf.compare.DifferenceKind.CHANGE;
import static org.eclipse.emf.compare.utils.EMFComparePredicates.fromSide;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterators;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.eclipse.emf.compare.AttributeChange;
import org.eclipse.emf.compare.Comparison;
import org.eclipse.emf.compare.Diff;
import org.eclipse.emf.compare.DifferenceKind;
import org.eclipse.emf.compare.Match;
import org.eclipse.emf.compare.ReferenceChange;
import org.eclipse.emf.compare.diagram.internal.CompareDiagramConfiguration;
import org.eclipse.emf.compare.diagram.internal.extensions.CoordinatesChange;
import org.eclipse.emf.compare.diagram.internal.extensions.DiagramDiff;
import org.eclipse.emf.compare.diagram.internal.extensions.ExtensionsFactory;
import org.eclipse.emf.compare.utils.MatchUtil;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.gmf.runtime.notation.Bounds;
import org.eclipse.gmf.runtime.notation.Location;
import org.eclipse.gmf.runtime.notation.NotationPackage;
/**
* Factory of coordinates changes.
*
* @author <a href="mailto:cedric.notot@obeo.fr">Cedric Notot</a>
*/
public class CoordinatesChangeFactory extends NodeChangeFactory {
/** Configuration of the diagram comparison. */
private final CompareDiagramConfiguration configuration;
/**
* Constructor.
*
* @param configuration
* The configuration of the diagram comparison.
*/
public CoordinatesChangeFactory(CompareDiagramConfiguration configuration) {
this.configuration = configuration;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.internal.postprocessor.factories.AbstractChangeFactory#getExtensionKind()
*/
@Override
public Class<? extends Diff> getExtensionKind() {
return CoordinatesChange.class;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.internal.postprocessor.factories.AbstractChangeFactory#createExtension()
*/
@Override
public DiagramDiff createExtension() {
return ExtensionsFactory.eINSTANCE.createCoordinatesChange();
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.diagram.internal.factories.extensions.NodeChangeFactory#setRefiningChanges(org.eclipse.emf.compare.Diff,
* org.eclipse.emf.compare.DifferenceKind, org.eclipse.emf.compare.Diff)
*/
@Override
public void setRefiningChanges(Diff extension, DifferenceKind extensionKind, Diff refiningDiff) {
if (extensionKind == DifferenceKind.CHANGE) {
extension.getRefinedBy().addAll(
filter(getAllDifferencesForChange(refiningDiff), fromSide(extension.getSource())));
}
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.diagram.internal.factories.extensions.NodeChangeFactory#getAllDifferencesForChange(org.eclipse.emf.compare.Diff)
*/
@Override
protected Collection<Diff> getAllDifferencesForChange(final Diff input) {
final Collection<Diff> diffs = super.getAllDifferencesForChange(input);
return filter(diffs, new Predicate<Diff>() {
public boolean apply(Diff diff) {
return diff instanceof AttributeChange && isCoordinatesChange((AttributeChange)diff)
&& fromSide(input.getSource()).apply(diff);
}
});
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.internal.postprocessor.factories.AbstractChangeFactory#isRelatedToAnExtensionMove(org.eclipse.emf.compare.AttributeChange)
*/
@Override
protected boolean isRelatedToAnExtensionChange(AttributeChange input) {
return isCoordinatesChange(input) && filter(input.getRefines(), isExtensionKind(CHANGE)).isEmpty()
&& isOverThreshold(input) && !isLeadedByMoveNode(input);
}
/**
* It returns the predicate to check that the given difference concerns a move of node.
*
* @param scannedMatches
* List of browsed matches when looking for the match ancestor which contains a matching move
* change.
* @return The predicate.
*/
private static Predicate<? super Diff> isRelatedToMove(final List<Match> scannedMatches) {
return new Predicate<Diff>() {
public boolean apply(Diff difference) {
if (!scannedMatches.isEmpty()) {
return difference instanceof ReferenceChange
&& isRelatedToAMoveNode((ReferenceChange)difference)
&& scannedMatches.contains(scannedMatches.get(0).getComparison()
.getMatch(((ReferenceChange)difference).getValue()));
} else {
return false;
}
}
};
}
/**
* It checks that the given attribute change concerns a change of coordinates.
*
* @param input
* The difference.
* @return True if it is a change of coordinates.
*/
private static boolean isCoordinatesChange(AttributeChange input) {
return input.getAttribute() == NotationPackage.Literals.LOCATION__X
|| input.getAttribute() == NotationPackage.Literals.LOCATION__Y;
}
/**
* It checks that the given attribute change is leaded by a move of the same object.
*
* @param input
* The attribute change.
* @return True if it is leaded by a move, False otherwise.
*/
private boolean isLeadedByMoveNode(AttributeChange input) {
List<Match> scannedMatches = new ArrayList<Match>();
boolean result = false;
EObject match = input.getMatch();
while (!result && match instanceof Match) {
scannedMatches.add((Match)match);
result = Iterators.any(((Match)match).getDifferences().iterator(),
isRelatedToMove(scannedMatches));
match = match.eContainer();
}
return result;
}
/**
* Check if the moving of the node is over the threshold (in pixels) specified in the emf compare
* preference page.
*
* @param diff
* The difference.
* @return True if it is over the threshold.
*/
private boolean isOverThreshold(AttributeChange diff) {
final Comparison comparison = diff.getMatch().getComparison();
final EObject left = MatchUtil.getContainer(comparison, diff);
final EObject right = MatchUtil.getOriginContainer(comparison, diff);
final CoordinateProvider leftCoordinateProvider = new CoordinateProvider(left);
final CoordinateProvider rightCoordinateProvider = new CoordinateProvider(right);
if (leftCoordinateProvider.hasCoordinates() && rightCoordinateProvider.hasCoordinates()) {
final int leftX = leftCoordinateProvider.getX();
final int leftY = leftCoordinateProvider.getY();
final int rightX = rightCoordinateProvider.getX();
final int rightY = rightCoordinateProvider.getY();
final int deltaX = Math.abs(leftX - rightX);
final int deltaY = Math.abs(leftY - rightY);
int threshold = 0;
if (configuration != null) {
threshold = configuration.getMoveThreshold();
}
return deltaX + deltaY > threshold;
}
return false;
}
/**
* A provider for coordinates.
* <p>
* This provider can return the coordinates of either a shape with {@link Bounds} or a decoration node
* with a {@link Location}.
* </p>
*
* @author Philip Langer <planger@eclipsesource.com>
*/
private static class CoordinateProvider {
/** The element this provider should return the coordinates of. */
private EObject element;
/**
* Creates a provider for the given {@code element}.
*
* @param element
* The element this provider should return the coordinates of.
*/
CoordinateProvider(EObject element) {
this.element = element;
}
/**
* Specifies whether this provider can provide coordinates for its element.
*
* @return <code>true</code> if it can provide coordinates, <code>false</code> otherwise.
*/
public boolean hasCoordinates() {
return element instanceof Bounds || element instanceof Location;
}
/**
* Returns the X value.
*
* @return The X value.
*/
public int getX() {
final int x;
if (element instanceof Bounds) {
x = ((Bounds)element).getX();
} else if (element instanceof Location) {
x = ((Location)element).getX();
} else {
x = -1;
}
return x;
}
/**
* Returns the Y value.
*
* @return The Y value.
*/
public int getY() {
final int y;
if (element instanceof Bounds) {
y = ((Bounds)element).getY();
} else if (element instanceof Location) {
y = ((Location)element).getY();
} else {
y = -1;
}
return y;
}
}
}