/******************************************************************************* * Copyright (c) 2010, 2011 Technical University of Denmark. * 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: * Patrick Koenemann, DTU Informatics - initial API and implementation *******************************************************************************/ package org.eclipse.emf.compare.mpatch.common.util; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.common.util.TreeIterator; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.compare.diff.metamodel.ComparisonResourceSetSnapshot; import org.eclipse.emf.compare.diff.metamodel.ComparisonResourceSnapshot; import org.eclipse.emf.compare.diff.metamodel.ComparisonSnapshot; import org.eclipse.emf.compare.diff.metamodel.DiffElement; import org.eclipse.emf.compare.diff.metamodel.DiffFactory; import org.eclipse.emf.compare.diff.metamodel.DiffGroup; import org.eclipse.emf.compare.diff.metamodel.DiffModel; import org.eclipse.emf.compare.diff.metamodel.ReferenceOrderChange; import org.eclipse.emf.compare.diff.service.DiffService; import org.eclipse.emf.compare.match.MatchOptions; import org.eclipse.emf.compare.match.metamodel.Match2Elements; import org.eclipse.emf.compare.match.metamodel.MatchElement; import org.eclipse.emf.compare.match.metamodel.MatchModel; import org.eclipse.emf.compare.match.service.MatchService; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.part.FileEditorInput; /** * Helper class for miscellanous functions. * * @author Patrick Koenemann (pk@imm.dtu.dk) * */ public class CommonUtils { /** See {@link CommonUtils#analyzeDiff(ComparisonSnapshot, int)} for documentation. */ public static final int DIFF_EMPTY = 1; /** See {@link CommonUtils#analyzeDiff(ComparisonSnapshot, int)} for documentation. */ public static final int DIFF_ORDERINGS = 2; /** * Create an {@link IFile} pointing to the given fileName in the same location as the * referenceURI. * * @param referenceURI * An arbitrary {@link URI}. * @param fileName * A fileName. * @return An {@link IFile} having the name fileName located in the location of * referenceURI. */ public static IFile createNewIFile(final URI referenceURI, final String fileName) { final IPath referencePath = new Path(referenceURI.toPlatformString(true)); final IPath path = referencePath.removeLastSegments(1); final IPath newPath = path.append(fileName); final IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(newPath); return file; } /** * Create an {@link IFile} pointing to the given fileName in the same location as the * referenceFile. * * @param referenceFile * An arbitrary {@link IFile}. * @param fileName * A fileName. * @return An {@link IFile} having the name fileName located in the location of * referenceFile. */ public static IFile createNewIFile(IFile referenceFile, String fileName) { final IPath path = referenceFile.getFullPath().removeLastSegments(1); final IPath newPath = path.append(fileName); final IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(newPath); return file; } /** * This extracts the referenced model from an emfdiff {@link ComparisonSnapshot} - to be more precise from the * (single) {@link DiffModel}. If leftModel is true, then the left (modified) version of * the model is returned; if false, then the right (unmodified) version is returned. * * Note: This extraction fails if the comparison contains more than just one original model. * * @param emfdiff * An emfdiff {@link ComparisonSnapshot}. * @param leftModel * Indicates whether the left (changed) or right (unchanged) version of the model should be returned. * @return A reference to the original model. */ public static EObject getModelFromEmfdiff(ComparisonSnapshot emfdiff, boolean leftModel) { if (emfdiff instanceof ComparisonResourceSnapshot) { final ComparisonResourceSnapshot resourceSnapshot = (ComparisonResourceSnapshot) emfdiff; final EList roots = leftModel ? resourceSnapshot.getMatch().getLeftRoots() : resourceSnapshot .getMatch().getRightRoots(); if (roots.size() > 1) { throw new UnsupportedOperationException("This is not supported yet. Please implement it!"); } else if (roots.size() == 0) { return null; } else { return roots.get(0); } } else if (emfdiff instanceof ComparisonResourceSetSnapshot) { final ComparisonResourceSetSnapshot resourceSetSnapshot = (ComparisonResourceSetSnapshot) emfdiff; if (resourceSetSnapshot.getMatchResourceSet().getMatchModels().size() > 1) { throw new UnsupportedOperationException("This is not supported yet. Please implement it!"); } else { final EList roots = leftModel ? resourceSetSnapshot.getMatchResourceSet().getMatchModels() .get(0).getLeftRoots() : resourceSetSnapshot.getMatchResourceSet().getMatchModels().get(0) .getRightRoots(); if (roots.size() > 1) { throw new UnsupportedOperationException("This is not supported yet. Please implement it!"); } else if (roots.size() == 0) { return null; } else { return roots.get(0); } } } else { throw new UnsupportedOperationException("Invalid emfdiff type detected!"); } } /** * Create an emfdiff ({@link ComparisonResourceSnapshot}) from two {@link EObject}s. This is done by first creating * a {@link MatchModel} and then a {@link DiffModel}. * * @param leftModel * The left model. * @param rightModel * The right model. * @param useIdsIfAvailable * Tell EMF Compare to use IDs if available. * @return The emfdiff. */ public static ComparisonResourceSnapshot createEmfdiff(EObject leftModel, EObject rightModel, boolean useIdsIfAvailable) { MatchModel matchModel = null; try { final Map options = new HashMap(); // use ids? options.put(MatchOptions.OPTION_IGNORE_ID, !useIdsIfAvailable); options.put(MatchOptions.OPTION_IGNORE_XMI_ID, !useIdsIfAvailable); // usually we operate on the same meta model options.put(MatchOptions.OPTION_DISTINCT_METAMODELS, false); matchModel = MatchService.doMatch(leftModel, rightModel, options); } catch (final InterruptedException e) { } final DiffModel diffModel = DiffService.doDiff(matchModel, false); return wrapInComparisonSnapshot(matchModel, diffModel); } /** * Create an emfdiff ({@link ComparisonResourceSnapshot}) from two {@link EObject}s. This is done by first creating * a {@link MatchModel} and then a {@link DiffModel}. * * @param leftModel * The left model. * @param rightModel * The right model. * @return The emfdiff. */ public static ComparisonResourceSnapshot createEmfdiff(EObject leftModel, EObject rightModel) { MatchModel matchModel = null; try { matchModel = MatchService.doMatch(leftModel, rightModel, Collections. emptyMap()); } catch (final InterruptedException e) { } final DiffModel diffModel = DiffService.doDiff(matchModel, false); return wrapInComparisonSnapshot(matchModel, diffModel); } private static ComparisonResourceSnapshot wrapInComparisonSnapshot(MatchModel matchModel, DiffModel diffModel) { final ComparisonResourceSnapshot comparison = DiffFactory.eINSTANCE.createComparisonResourceSnapshot(); comparison.setDiff(diffModel); comparison.setMatch(matchModel); comparison.setDate(new Date()); return comparison; } /** * Analyze an EMF Compare snapshot for some criteria: *
    *
  1. {@link CommonUtils#DIFF_EMPTY}: The given diff does not contain any {@link DiffElement} except for * {@link DiffGroup}s. *
  2. {@link CommonUtils#DIFF_ORDERINGS}: The given diff does only contain ordering changes multi-valued references * (not yet implemented for attributes in EMF Compare). *
* * @param snapshot * An EMF Compare snapshot. * @param criteria * The criteria which should be checked. * @return A collection of {@link DiffElement}s which do not satisfy the given criteria, an empty collection * otherwise. */ public static Collection analyzeDiff(ComparisonSnapshot snapshot, int criteria) { final Collection violations = new ArrayList(); // maybe a bit inefficient, but the easiest implementation! for (final TreeIterator i = snapshot.eAllContents(); i.hasNext();) { final EObject obj = i.next(); if (obj instanceof DiffElement) { // we break if our criteria is violated! switch (criteria) { case DIFF_EMPTY: if (!(obj instanceof DiffGroup)) { violations.add((DiffElement) obj); } break; case DIFF_ORDERINGS: if (!(obj instanceof DiffGroup) && !(obj instanceof ReferenceOrderChange)) { violations.add((DiffElement) obj); } break; default: } } } return violations; // probably more efficient but more implementation effort // if (snapshot instanceof ComparisonResourceSetSnapshot) { // throw new UnsupportedOperationException("Support for ComparisonResourceSetSnapshot not yet implemented!"); // } else if (snapshot instanceof ComparisonResourceSnapshot) { // final ComparisonResourceSnapshot resourceSnapshop = (ComparisonResourceSnapshot) snapshot; // if (resourceSnapshop.getDiff() != null && resourceSnapshop.getDiff().getOwnedElements().size() > 0) { // // // check if there exist any changes at all // if (criteria == DIFF_EMPTY) { // for (final DiffElement diff : resourceSnapshop.getDiff().getOwnedElements()) { // if (diff instanceof DiffGroup) { // if (((DiffGroup)diff).getSubchanges() > 0) // return false; // } else { // return true; // } // } // return true; // // // check if the only diffelements are about ordering // } else if (criteria == DIFF_ORDERINGS) { // for (final TreeIterator i = resourceSnapshop.getDiff().eAllContents(); i.hasNext();) { // final EObject obj = i.next(); // // TODO // } // // // unknown criteria! // } else { // throw new IllegalArgumentException("Unknown criteria: " + criteria); // } // // } else { // return true; // } // } else { // throw new UnsupportedOperationException("Unknown snapshot type: " + snapshot); // } } /** * Get the input of the current editor, if there is one. * * @return The URI of the currently selected file or null, if there is none. */ public static URI getCurrentEditorFileInputUri() { final IEditorPart editor = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage() .getActiveEditor(); if (editor != null) { try { final IEditorInput editorInput = editor.getEditorInput(); if (editorInput instanceof FileEditorInput) { final IFile file = ((FileEditorInput) editorInput).getFile(); if (file != null) { final URI uri = URI.createPlatformResourceURI(file.getFullPath().toString(), true); // lets check if we got an EMF model here final ResourceSet resourceSet = new ResourceSetImpl(); final Resource resource = resourceSet.getResource(uri, true); if (resource != null && !resource.getContents().isEmpty()) { final EObject root = resource.getContents().get(0); // maybe we got a GMF diagram here? // since we don't want a dependency to GMF, use reflection to check if root is a diagram. final EObject element = getDiagramElement(root); if (element != null && element.eResource() != null) { final URI uri2 = element.eResource().getURI(); return uri2; } // return the uri of the editor input otherwise return uri; } } } else { // maybe GMF is loaded and it is a gmf diagram input? try { final URI uri = CommonGmfUtils.getUriFromEditorInput(editorInput); if (uri != null) return uri; } catch (NoClassDefFoundError e) { // do nothing.. } } } catch (Exception e) { // do nothing... just return null } } return null; } /** * Match all elements in the contents of the two given EObjects. * * This method makes use of EMF Compare's {@link MatchService} to find matches. * * @param obj1 * One EObject. * @param obj2 * Another EObject. * @return A map containing all matched objects in both content trees. */ public static Map getMatchingObjects(EObject obj1, EObject obj2) { if (obj1 == null || obj2 == null) throw new IllegalArgumentException("Parameters must not be null!"); // ignore ids; we operate on the same meta model final Map options = new HashMap(); options.put(MatchOptions.OPTION_IGNORE_ID, true); options.put(MatchOptions.OPTION_IGNORE_XMI_ID, true); options.put(MatchOptions.OPTION_DISTINCT_METAMODELS, false); final MatchModel match; try { match = MatchService.doContentMatch(obj1, obj2, options); } catch (InterruptedException e) { throw new RuntimeException("Comparing two EObjects failed!", e); } // built result map final Map result = new HashMap(); final Queue queue = new LinkedList(); queue.addAll(match.getMatchedElements()); while (!queue.isEmpty()) { final MatchElement matchElement = queue.poll(); final Match2Elements match2Elements = (Match2Elements) matchElement; result.put(match2Elements.getLeftElement(), match2Elements.getRightElement()); result.put(match2Elements.getRightElement(), match2Elements.getLeftElement()); queue.addAll(matchElement.getSubMatchElements()); } return result; } /** * Extract the element of a GMF diagram using EMF reflection. * * @param diagram * The diagram. * @return The element if diagram is really a GMF diagram. */ private static EObject getDiagramElement(EObject diagram) { final EClass eClass = diagram.eClass(); if (eClass == null) return null; if ("org.eclipse.gmf.runtime.notation.Diagram".equals(eClass.getInstanceClassName())) { final EStructuralFeature feature = eClass.getEStructuralFeature("element"); if (feature != null) { final Object object = diagram.eGet(feature); if (object instanceof EObject) { return (EObject) object; } } } return null; } /** * Opposite of {@link String#split(String)}. */ public static T join(String[] src, CharSequence pattern, T dst) throws IOException { for (int i = 0; i < src.length; i++) { if (i > 0) dst.append(pattern); dst.append(src[i]); } return dst; } /** * Opposite of {@link String#split(String)}. */ public static String join(String[] src, CharSequence pattern) { try { return join(src, pattern, new StringBuilder()).toString(); } catch (IOException e) { throw new Error("StringBuilder should not throw IOExceptions!"); } } /** * Opposite of {@link String#split(String)}. */ public static String join(List src, CharSequence pattern) { try { return join((String[]) src.toArray(new String[src.size()]), pattern, new StringBuilder()).toString(); } catch (IOException e) { throw new Error("StringBuilder should not throw IOExceptions!"); } } /** * Filter map elements by their value. The values are compared using {@link Object#equals(Object)}. * * @param map * A map. * @param value * The value to filter. * @return A list of elements (subset of keys of map) which have the given value. */ public static List filterByValue(Map map, V value) { final ArrayList result = new ArrayList(); if (value != null) { for (K key : map.keySet()) { if (value.equals(map.get(key))) result.add(key); } } return result; } }