blob: d00184b209713ea84e3fd93b8b88c865440da1f8 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2010 BMW Car IT, Technische Universitaet Muenchen, 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:
* BMW Car IT - Initial API and implementation
* Technische Universitaet Muenchen - Major refactoring and extension
*******************************************************************************/
package org.eclipse.emf.edapt.history.instantiation.ui;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.EventObject;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.common.command.CommandStackListener;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.compare.diff.merge.EMFCompareEObjectCopier;
import org.eclipse.emf.compare.diff.merge.service.MergeService;
import org.eclipse.emf.compare.diff.metamodel.ComparisonResourceSnapshot;
import org.eclipse.emf.compare.diff.metamodel.DiffElement;
import org.eclipse.emf.compare.diff.metamodel.DiffFactory;
import org.eclipse.emf.compare.diff.metamodel.DiffModel;
import org.eclipse.emf.compare.diff.service.DiffService;
import org.eclipse.emf.compare.match.metamodel.Match2Elements;
import org.eclipse.emf.compare.match.metamodel.MatchModel;
import org.eclipse.emf.compare.match.service.MatchService;
import org.eclipse.emf.ecore.EDataType;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.presentation.EcoreEditor;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.edapt.common.LoggingUtils;
import org.eclipse.emf.edapt.common.ResourceUtils;
import org.eclipse.emf.edapt.common.ui.ModelSash;
import org.eclipse.emf.edapt.common.ui.PartAdapter;
import org.eclipse.emf.edapt.common.ui.StructureTreeViewer;
import org.eclipse.emf.edapt.history.HistoryFactory;
import org.eclipse.emf.edapt.history.NoChange;
import org.eclipse.emf.edapt.history.Release;
import org.eclipse.emf.edapt.history.instantiation.BreakingSwitch;
import org.eclipse.emf.edapt.history.presentation.HistoryEditorPlugin;
import org.eclipse.emf.edapt.history.reconstruction.DiffModelFilterUtils;
import org.eclipse.emf.edapt.history.reconstruction.DiffModelOrderFilter;
import org.eclipse.emf.edapt.history.reconstruction.DiffModelResourceFilter;
import org.eclipse.emf.edapt.history.reconstruction.IDiffModelFilter;
import org.eclipse.emf.edapt.history.reconstruction.Mapping;
import org.eclipse.emf.edapt.history.reconstruction.ModelAssert;
import org.eclipse.emf.edapt.history.reconstruction.ui.DiffSelectionAdapter;
import org.eclipse.emf.edapt.history.recorder.ui.EcoreEditorDetector;
import org.eclipse.emf.edit.command.ChangeCommand;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.viewers.DecoratingLabelProvider;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IColorDecorator;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ILabelDecorator;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IPartListener;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.ViewPart;
/**
* View to support the convergence of a source metamodel to a target metamodel
*
* @author herrmama
* @author $Author$
* @version $Rev$
* @levd.rating RED Rev:
*/
public class ConvergenceView extends ViewPart implements CommandStackListener,
ISelectionChangedListener {
/**
* Identifier of the view for convenience
*/
public static final String ID = ConvergenceView.class.getName();
/**
* Viewer for the difference model
*/
private ModelSash differenceSash;
/**
* Viewer for the target metamodel
*/
private ModelSash targetSash;
/**
* Editor with the source metamodel
*/
private EcoreEditor editor;
/**
* Resource containing the source metamodel
*/
private Resource sourceResource;
/**
* Resource containing the target metamodel
*/
private Resource targetResource;
/**
* Adapter to listen to the difference viewer that automatically updates
* source and target metamodel viewer
*/
private DiffSelectionAdapter selectionAdapter;
/**
* Mapping between source and target metamodel
*/
private Mapping mapping;
/**
* Flag whether selection is currently going on (in order to avoid {@link StackOverflowError})
*/
private boolean selecting = false;
/**
* Set of breaking changes
*/
private Set<DiffElement> breakingChanges;
/**
* Whether synchronization is turned on or off
*/
private boolean synchronization = true;
/**
* Whether the view is currently refreshing
*/
private boolean refresh;
/** Check whether the editor is closed. */
private final IPartListener partListener = new PartAdapter() {
/**
* {@inheritDoc}
*/
@Override
public void partClosed(IWorkbenchPart part) {
if (editor == part) {
differenceSash.getStructureViewer().setInput(null);
targetSash.getStructureViewer().setInput(null);
detachOldEditor();
}
}
};
/**
* {@inheritDoc}
*/
@Override
public void createPartControl(Composite parent) {
SashForm sash = new SashForm(parent, SWT.HORIZONTAL);
differenceSash = new ModelSash(sash, SWT.None);
StructureTreeViewer differenceViewer = differenceSash.getStructureViewer();
differenceViewer.setLabelProvider(new DecoratingLabelProvider((ILabelProvider) differenceViewer.getLabelProvider(), new Decorator()));
targetSash = new ModelSash(sash, SWT.None);
targetSash.getStructureViewer().addSelectionChangedListener(new ISelectionChangedListener() {
public void selectionChanged(SelectionChangedEvent event) {
if(selecting) return;
if(event.getSelection() instanceof IStructuredSelection) {
IStructuredSelection selection = (IStructuredSelection) event.getSelection();
if(selection.getFirstElement() instanceof EObject) {
EObject element = (EObject) selection.getFirstElement();
EObject source = mapping.getSource(element);
if(source != null) {
selecting = true;
editor.setSelectionToViewer(Collections.singleton(source));
selecting = false;
}
}
}
}
});
differenceViewer.addDoubleClickListener(new IDoubleClickListener() {
public void doubleClick(DoubleClickEvent event) {
if(event.getSelection() instanceof IStructuredSelection) {
IStructuredSelection selection = (IStructuredSelection) event.getSelection();
if(selection.getFirstElement() instanceof DiffElement) {
final DiffElement element = (DiffElement) selection.getFirstElement();
editor.getEditingDomain().getCommandStack().execute(new ChangeCommand(editor.getEditingDomain().getResourceSet()) {
@Override
protected void doExecute() {
fixCopier();
MergeService.merge(element, true);
}
});
}
}
}
});
getSite().getPage().addPartListener(partListener);
getViewSite().getActionBars().getToolBarManager().add(
new Action("Sync", IAction.AS_CHECK_BOX) {
{
setChecked(true);
setImageDescriptor(PlatformUI.getWorkbench()
.getSharedImages().getImageDescriptor(
ISharedImages.IMG_ELCL_SYNCED));
setDisabledImageDescriptor(PlatformUI.getWorkbench()
.getSharedImages().getImageDescriptor(
ISharedImages.IMG_ELCL_SYNCED_DISABLED));
}
@Override
public void run() {
setSynchronization(isChecked());
}
});
}
/**
* {@inheritDoc}
*/
@Override
public void setFocus() {
if(!differenceSash.isDisposed()) {
differenceSash.setFocus();
}
}
/**
* Initialize the contents of the view
*/
public void init(URI targetURI, EcoreEditor editor) {
detachOldEditor();
attachNewEditor(targetURI, editor);
}
/**
* Detach the view from the old editor
*/
private void detachOldEditor() {
if(this.editor != null) {
this.editor.getEditingDomain().getCommandStack().removeCommandStackListener(this);
this.editor.removeSelectionChangedListener(this);
if(!differenceSash.getStructureViewer().getTree().isDisposed()) {
differenceSash.getStructureViewer().getTree().removeSelectionListener(selectionAdapter);
}
this.editor = null;
breakingChanges = null;
}
}
/**
* Attach the view to the new editor
*/
private void attachNewEditor(URI targetURI, EcoreEditor editor) {
try {
sourceResource = editor.getEditingDomain().getResourceSet().getResources().get(0);
ResourceSet resourceSet = ResourceUtils.loadResourceSet(targetURI);
targetResource = resourceSet.getResources().get(0);
this.editor = editor;
this.editor.getEditingDomain().getCommandStack().addCommandStackListener(this);
selectionAdapter = new DiffSelectionAdapter(editor.getViewer(), null, targetSash.getStructureViewer(), targetSash.getPropertiesViewer()) {
@Override
public void widgetSelected(SelectionEvent e) {
selecting = true;
super.widgetSelected(e);
selecting = false;
}
};
differenceSash.getStructureViewer().getTree().addSelectionListener(selectionAdapter);
this.editor.addSelectionChangedListener(this);
refresh();
targetSash.getStructureViewer().setInput(targetResource);
targetSash.getStructureViewer().expandToLevel(2);
} catch (IOException e) {
LoggingUtils
.logError(HistoryEditorPlugin.getPlugin(), e);
}
}
/**
* {@inheritDoc}
*/
public void commandStackChanged(EventObject event) {
if(!refresh && synchronization) {
refresh();
}
}
/**
* Refresh viewer contents
*/
private void refresh() {
try {
refresh = true;
ComparisonResourceSnapshot snapshot = compare();
if (ModelAssert.numberOfChanges(snapshot.getDiff()) == 0) {
if(targetResource != null) {
URI targetURI = targetResource.getURI();
Integer number = extractNumber(targetURI);
if(number != null) {
addMarker(number+1);
String name = targetURI.lastSegment().replace(number.toString(), "" + (number+1));
try {
targetURI = targetURI.trimSegments(1).appendSegment(name);
ResourceSet resourceSet;
resourceSet = ResourceUtils.loadResourceSet(targetURI);
targetResource = resourceSet.getResources().get(0);
targetSash.getStructureViewer().setInput(targetResource);
targetSash.getStructureViewer().expandToLevel(2);
snapshot = compare();
} catch (IOException e) {
// ignore
}
}
}
}
initMapping(snapshot.getMatch());
calculateBreaking(snapshot.getDiff());
differenceSash.getStructureViewer().setInput(snapshot.getDiff());
differenceSash.getStructureViewer().expandToLevel(3);
} catch (InterruptedException e) {
LoggingUtils
.logError(HistoryEditorPlugin.getPlugin(), e);
} finally {
refresh = false;
}
}
/** Compare source and target resource. */
private ComparisonResourceSnapshot compare() throws InterruptedException {
ComparisonResourceSnapshot snapshot = DiffFactory.eINSTANCE
.createComparisonResourceSnapshot();
MatchModel match = MatchService.doResourceMatch(targetResource,
sourceResource, null);
DiffModel diff = DiffService.doDiff(match);
IDiffModelFilter filter = DiffModelFilterUtils
.and(DiffModelOrderFilter.INSTANCE,
DiffModelResourceFilter.INSTANCE);
DiffModelFilterUtils.filter(diff, filter);
snapshot.setMatch(match);
snapshot.setDiff(diff);
return snapshot;
}
/**
* Add a marker with the current revision number to the history
*/
private void addMarker(final Integer number) {
final Release release = EcoreEditorDetector.getInstance().getListener(
editor).getHistory().getLastRelease();
Command command = new ChangeCommand(release) {
@Override
protected void doExecute() {
NoChange marker = HistoryFactory.eINSTANCE.createNoChange();
marker.setDescription(number.toString());
release.getChanges().add(marker);
}
};
editor.getEditingDomain().getCommandStack().execute(command);
}
/**
* Initialize the mapping
*/
private void initMapping(MatchModel match) {
mapping = new Mapping();
for(Iterator<EObject> i = match.eAllContents(); i.hasNext(); ) {
EObject element = i.next();
if(element instanceof Match2Elements) {
Match2Elements match2Elements = (Match2Elements) element;
mapping.map(match2Elements.getRightElement(), match2Elements
.getLeftElement());
}
}
selectionAdapter.setMapping(mapping);
}
/**
* {@inheritDoc}
*/
@Override
public void dispose() {
getSite().getPage().removePartListener(partListener);
detachOldEditor();
super.dispose();
}
/**
* {@inheritDoc}
*/
public void selectionChanged(SelectionChangedEvent event) {
if(selecting) return;
if(event.getSelection() instanceof IStructuredSelection) {
IStructuredSelection selection = (IStructuredSelection) event.getSelection();
if(selection.getFirstElement() instanceof EObject) {
EObject element = (EObject) selection.getFirstElement();
EObject target = mapping.getTarget(element);
if(target != null) {
selecting = true;
targetSash.getStructureViewer().setSelection(new StructuredSelection(target), true);
selecting = false;
}
}
}
}
/**
* Calculate the breaking changes
*/
public void calculateBreaking(DiffModel model) {
breakingChanges = new HashSet<DiffElement>();
BreakingSwitch s = new BreakingSwitch();
for(Iterator<EObject> i = model.eAllContents(); i.hasNext(); ) {
EObject eObject = i.next();
if(eObject instanceof DiffElement) {
DiffElement element = (DiffElement) eObject;
boolean breaking = s.doSwitch(element);
if(breaking) {
breakingChanges.add(element);
}
}
}
for(DiffElement change : new HashSet<DiffElement>(breakingChanges)) {
while(change.eContainer() != null && change.eContainer() instanceof DiffElement) {
change = (DiffElement) change.eContainer();
breakingChanges.add(change);
}
}
}
/**
* Extract the revision number from the URI
*
* @param targetURI
* @return Revision number
*/
private Integer extractNumber(URI targetURI) {
String name = targetURI.trimFileExtension().lastSegment();
int index = name.indexOf('_');
if(index >= 0) {
name = name.substring(index+1);
index = name.indexOf('.');
if(index >= 0) {
name = name.substring(index+1);
try {
return Integer.parseInt(name);
}
catch(NumberFormatException e) {
return null;
}
}
}
return null;
}
/**
* Enable or disable synchronization
*
* @param synchronization
*/
private void setSynchronization(boolean synchronization) {
this.synchronization = synchronization;
if(synchronization) {
refresh();
}
}
/**
* @return Widget for difference representation
*/
public ModelSash getDifferenceSash() {
return differenceSash;
}
/**
* Fix the copier of EMF Compare
*/
private void fixCopier() {
try {
DiffModel diff = (DiffModel) differenceSash.getStructureViewer()
.getInput();
Field field = MergeService.class
.getDeclaredField("copier");
field.setAccessible(true);
field.set(null, new EMFCompareEObjectCopier(diff) {
@Override
public EObject get(Object key) {
EObject value = super.get(key);
if (value == null) {
if (key instanceof EDataType) {
EDataType type = (EDataType) key;
EPackage ePackage = type.getEPackage();
if (ePackage == EcorePackage.eINSTANCE) {
value = type;
}
} else if (key instanceof EObject) {
value = mapping.getSource((EObject) key);
}
}
return value;
}
});
} catch (SecurityException e) {
// ignore
} catch (NoSuchFieldException e) {
// ignore
} catch (IllegalArgumentException e) {
// ignore
} catch (IllegalAccessException e) {
// ignore
}
}
/**
* Decorator for breaking changes
*
* @author herrmama
* @author $Author$
* @version $Rev$
* @levd.rating RED Rev:
*/
public class Decorator implements ILabelDecorator, IColorDecorator {
/**
* Color for breaking changes
*/
private final Color red = new Color(Display.getDefault(), 255, 0, 0);
/**
* {@inheritDoc}
*/
public Color decorateBackground(Object element) {
return null;
}
/**
* {@inheritDoc}
*/
public Color decorateForeground(Object element) {
if(breakingChanges.contains(element)) {
return red;
}
return null;
}
/**
* {@inheritDoc}
*/
public Image decorateImage(Image image, Object element) {
return null;
}
/**
* {@inheritDoc}
*/
public String decorateText(String text, Object element) {
return null;
}
/**
* {@inheritDoc}
*/
public void addListener(ILabelProviderListener listener) {
// not required
}
/**
* {@inheritDoc}
*/
public void dispose() {
red.dispose();
}
/**
* {@inheritDoc}
*/
public boolean isLabelProperty(Object element, String property) {
return false;
}
/**
* {@inheritDoc}
*/
public void removeListener(ILabelProviderListener listener) {
// not required
}
}
}