/* * Copyright (c) 2012, 2013, 2015, 2016 Eike Stepper (Berlin, Germany) 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: * Eike Stepper - initial API and implementation */ package org.eclipse.emf.cdo.ui.compare; import org.eclipse.emf.cdo.CDOObject; import org.eclipse.emf.cdo.CDOState; import org.eclipse.emf.cdo.common.branch.CDOBranchPoint; import org.eclipse.emf.cdo.common.commit.CDOCommitInfo; import org.eclipse.emf.cdo.common.id.CDOID; import org.eclipse.emf.cdo.common.id.CDOIDUtil; import org.eclipse.emf.cdo.common.revision.delta.CDORevisionDelta; import org.eclipse.emf.cdo.compare.CDOCompare; import org.eclipse.emf.cdo.compare.CDOCompareUtil; import org.eclipse.emf.cdo.compare.CDOComparisonScope; import org.eclipse.emf.cdo.session.CDORepositoryInfo; import org.eclipse.emf.cdo.session.CDOSession; import org.eclipse.emf.cdo.spi.common.branch.CDOBranchUtil; import org.eclipse.emf.cdo.spi.common.revision.CDOIDMapper; import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevision; import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevisionDelta; import org.eclipse.emf.cdo.transaction.CDOCommitContext; import org.eclipse.emf.cdo.transaction.CDODefaultTransactionHandler2; import org.eclipse.emf.cdo.transaction.CDOTransaction; import org.eclipse.emf.cdo.transaction.CDOTransactionOpener; import org.eclipse.emf.cdo.ui.CDOItemProvider; import org.eclipse.emf.cdo.ui.internal.compare.bundle.OM; import org.eclipse.emf.cdo.util.CDOUtil; import org.eclipse.emf.cdo.view.CDOView; import org.eclipse.emf.cdo.view.CDOViewOpener; import org.eclipse.net4j.util.lifecycle.ILifecycle; import org.eclipse.net4j.util.lifecycle.LifecycleEventAdapter; import org.eclipse.net4j.util.lifecycle.LifecycleUtil; import org.eclipse.net4j.util.registry.IRegistry; import org.eclipse.net4j.util.ui.UIUtil; import org.eclipse.emf.common.CommonPlugin; import org.eclipse.emf.common.notify.Adapter; import org.eclipse.emf.common.notify.AdapterFactory; import org.eclipse.emf.common.util.BasicEList; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.compare.Comparison; import org.eclipse.emf.compare.Diff; import org.eclipse.emf.compare.DifferenceState; import org.eclipse.emf.compare.Match; import org.eclipse.emf.compare.domain.ICompareEditingDomain; import org.eclipse.emf.compare.domain.impl.EMFCompareEditingDomain; import org.eclipse.emf.compare.scope.IComparisonScope; import org.eclipse.emf.ecore.EObject; 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.emf.edit.EMFEditPlugin; import org.eclipse.emf.edit.provider.ComposedAdapterFactory; import org.eclipse.emf.spi.cdo.InternalCDOObject; import org.eclipse.emf.spi.cdo.InternalCDOTransaction; import org.eclipse.emf.spi.cdo.InternalCDOView; import org.eclipse.compare.CompareConfiguration; import org.eclipse.compare.CompareUI; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IPartListener; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.IWorkbenchPart; import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; /** * Static methods to open an EMF Compare dialog. * * @author Eike Stepper * @since 4.2 */ public class CDOCompareEditorUtil { /** * @since 4.4 */ public static final String PROP_COMPARISON_LABEL = "comparison.label"; /** * @since 4.4 */ public static final String PROP_COMPARISON_IMAGE = "comparison.image"; private static final ThreadLocal<String> COMPARISON_TITLE = new ThreadLocal<String>(); private static final ThreadLocal<Boolean> ACTIVATE_EDITOR = new ThreadLocal<Boolean>(); private static final ThreadLocal<Boolean> SUPPRESS_COMMIT = new ThreadLocal<Boolean>(); private static final ThreadLocal<List<Runnable>> DISPOSE_RUNNABLES = new ThreadLocal<List<Runnable>>(); /** * @since 4.4 */ public static void closeTransactionAfterCommit(final CDOTransaction transaction) { transaction.addTransactionHandler(new CDODefaultTransactionHandler2() { @Override public void rolledBackTransaction(CDOTransaction transaction) { closeTransaction(transaction); } @Override public void committedTransaction(CDOTransaction transaction, CDOCommitContext commitContext) { closeTransaction(transaction); } private void closeTransaction(final CDOTransaction transaction) { UIUtil.getDisplay().asyncExec(new Runnable() { public void run() { transaction.close(); } }); } }); } /** * @since 4.4 */ public static void closeEditorWithTransaction(final CDOTransaction transaction) { final IEditorPart[] result = { null }; final IWorkbenchPage page = UIUtil.getActiveWorkbenchPage(); final IPartListener listener = new IPartListener() { @SuppressWarnings("restriction") public void partOpened(IWorkbenchPart part) { if (part instanceof org.eclipse.compare.internal.CompareEditor) { result[0] = (IEditorPart)part; } } public void partDeactivated(IWorkbenchPart part) { // Do nothing. } public void partClosed(IWorkbenchPart part) { if (part == result[0]) { transaction.close(); page.removePartListener(this); } } public void partBroughtToTop(IWorkbenchPart part) { // Do nothing. } public void partActivated(IWorkbenchPart part) { // Do nothing. } }; page.addPartListener(listener); transaction.addListener(new LifecycleEventAdapter() { @Override protected void onDeactivated(ILifecycle lifecycle) { if (result[0] != null) { UIUtil.getDisplay().asyncExec(new Runnable() { public void run() { page.closeEditor(result[0], false); } }); } } }); } /** * @since 4.3 */ public static boolean openEditor(CDOViewOpener viewOpener, CDOBranchPoint leftPoint, CDOBranchPoint rightPoint, CDOView[] originView, boolean activate) { return openEditor(viewOpener, null, leftPoint, rightPoint, originView, activate); } /** * @since 4.3 */ public static boolean openEditor(CDOViewOpener viewOpener, CDOTransactionOpener transactionOpener, CDOBranchPoint leftPoint, CDOBranchPoint rightPoint, CDOView[] originView, boolean activate) { ACTIVATE_EDITOR.set(activate); try { return openDialog(viewOpener, transactionOpener, leftPoint, rightPoint, originView); } finally { ACTIVATE_EDITOR.remove(); } } /** * @since 4.3 */ public static boolean openEditor(CDOCommitInfo rightCommitInfo, CDOBranchPoint leftPoint, boolean activate) { ACTIVATE_EDITOR.set(activate); try { return openDialog(rightCommitInfo, leftPoint); } finally { ACTIVATE_EDITOR.remove(); } } /** * @since 4.3 */ public static boolean openEditor(CDOCommitInfo commitInfo, boolean activate) { ACTIVATE_EDITOR.set(activate); try { return openDialog(commitInfo); } finally { ACTIVATE_EDITOR.remove(); } } /** * @since 4.3 */ public static boolean openEditor(CDOView leftView, CDOView rightView, CDOView[] originView, boolean activate) { ACTIVATE_EDITOR.set(activate); try { return openDialog(leftView, rightView, originView); } finally { ACTIVATE_EDITOR.remove(); } } /** * @since 4.4 */ public static boolean openEditor(CDOView leftView, CDOView rightView, Set<CDOID> affectedIDs, boolean activate) { ACTIVATE_EDITOR.set(activate); try { return openDialog(leftView, rightView, affectedIDs); } finally { ACTIVATE_EDITOR.remove(); } } /** * @since 4.3 */ public static boolean openDialog(CDOSession session, CDOBranchPoint leftPoint, CDOBranchPoint rightPoint) { return openDialog(session, leftPoint, rightPoint, null); } /** * @since 4.3 */ public static boolean openDialog(CDOViewOpener viewOpener, CDOBranchPoint leftPoint, CDOBranchPoint rightPoint, CDOView[] originView) { return openDialog(viewOpener, null, leftPoint, rightPoint, originView); } /** * @since 4.3 */ public static boolean openDialog(CDOViewOpener viewOpener, CDOTransactionOpener transactionOpener, CDOBranchPoint leftPoint, CDOBranchPoint rightPoint, CDOView[] originView) { final Boolean activateEditor = ACTIVATE_EDITOR.get(); final CDOView[] leftAndRightView = { null, null }; addDisposeRunnables(new Runnable() { public void run() { LifecycleUtil.deactivate(leftAndRightView[0]); LifecycleUtil.deactivate(leftAndRightView[1]); } }); try { leftAndRightView[0] = viewOpener.openView(leftPoint, new ResourceSetImpl()); ResourceSet rightResourceSet = new ResourceSetImpl(); if (transactionOpener != null) { rightPoint = rightPoint.getBranch().getHead(); leftAndRightView[1] = transactionOpener.openTransaction(rightPoint, rightResourceSet); } else { leftAndRightView[1] = viewOpener.openView(rightPoint, rightResourceSet); } return openDialog(leftAndRightView[0], leftAndRightView[1], originView); } finally { if (activateEditor == null) { List<Runnable> list = removeDisposeRunnables(); runDisposeRunnables(list); } } } public static boolean openDialog(CDOCommitInfo rightCommitInfo, CDOBranchPoint leftPoint) { CDORepositoryInfo repositoryInfo = (CDORepositoryInfo)rightCommitInfo.getCommitInfoManager().getRepository(); CDOSession session = repositoryInfo.getSession(); return openDialog(session, leftPoint, rightCommitInfo); } public static boolean openDialog(CDOCommitInfo commitInfo) { long previousTimeStamp = commitInfo.getPreviousTimeStamp(); if (previousTimeStamp == CDOBranchPoint.UNSPECIFIED_DATE) { return false; } CDOBranchPoint previous = CDOBranchUtil.normalizeBranchPoint(commitInfo.getBranch(), previousTimeStamp); return openDialog(commitInfo, previous); } public static boolean openDialog(CDOView leftView, CDOView rightView, CDOView[] originView) { return openDialog(leftView, rightView, originView, CDOCompareUtil.DEFAULT_VIEW_OPENER); } /** * @since 4.3 */ public static boolean openDialog(CDOView leftView, CDOView rightView, CDOView[] originView, CDOViewOpener viewOpener) { Input input = createComparisonInput(leftView, rightView, originView, viewOpener); return openDialog(input, rightView); } /** * @since 4.4 */ public static boolean openDialog(CDOView leftView, CDOView rightView, Set<CDOID> affectedIDs) { Input input = createComparisonInput(leftView, rightView, affectedIDs); return openDialog(input, rightView); } /** * @since 4.4 */ public static boolean openDialog(final Input input, final CDOView rightView) { if (input == null) { UIUtil.getDisplay().syncExec(new Runnable() { public void run() { Shell shell = UIUtil.getShell(); MessageDialog.openInformation(shell, "Compare", "There are no differences between the selected inputs."); } }); return false; } final Boolean activateEditor = ACTIVATE_EDITOR.get(); if (activateEditor != null) { List<Runnable> disposeRunnables = removeDisposeRunnables(); input.setDisposeRunnables(disposeRunnables); UIUtil.getDisplay().asyncExec(new Runnable() { public void run() { CompareUI.openCompareEditor(input, activateEditor); } }); } else { final EList<Diff> differences = new BasicEList<Diff>(); UIUtil.getDisplay().syncExec(new Runnable() { public void run() { CompareUI.openCompareDialog(input); if (rightView instanceof InternalCDOTransaction) { Comparison comparison = input.getComparison(); differences.addAll(comparison.getDifferences()); } } }); if (!differences.isEmpty() && rightView instanceof InternalCDOTransaction) { if (!handleMerges((InternalCDOTransaction)rightView, differences)) { return false; } } } return input.isOK(); } /** * @since 4.3 */ public static Input createComparisonInput(CDOView leftView, CDOView rightView, CDOView[] originView, CDOViewOpener viewOpener) { Comparison comparison = CDOCompareUtil.compare(leftView, rightView, originView, viewOpener); return createComparisonInput(leftView, rightView, comparison); } /** * @since 4.4 */ public static Input createComparisonInput(CDOView leftView, CDOView rightView, Set<CDOID> affectedIDs) { CDOComparisonScope scope = new CDOComparisonScope.Minimal(leftView, rightView, null, affectedIDs); Comparison comparison = CDOCompareUtil.compare(scope); return createComparisonInput(leftView, rightView, comparison); } /** * @since 4.4 */ public static Input createComparisonInput(CDOView leftView, CDOView rightView, Comparison comparison) { if (comparison.getDifferences().isEmpty()) { return null; } IComparisonScope scope = CDOCompare.getScope(comparison); ICompareEditingDomain editingDomain = EMFCompareEditingDomain.create(scope.getLeft(), scope.getRight(), scope.getOrigin()); ComposedAdapterFactory.Descriptor.Registry registry = EMFEditPlugin.getComposedAdapterFactoryDescriptorRegistry(); ComposedAdapterFactory adapterFactory = new ComposedAdapterFactory(registry); CDOBranchPoint leftBranchPoint = CDOBranchUtil.copyBranchPoint(leftView); CDOBranchPoint rightBranchPoint = CDOBranchUtil.copyBranchPoint(rightView); CDOItemProvider itemProvider = new CDOItemProvider(null) { @Override public boolean useFullPath(Object object) { if (object instanceof CDOBranchPoint) { return true; } return super.useFullPath(object); } }; IRegistry<String, Object> leftProperties = leftView.properties(); Image leftImage = (Image)leftProperties.get(PROP_COMPARISON_IMAGE); if (leftImage == null) { leftImage = itemProvider.getImage(leftBranchPoint); } String leftLabel = (String)leftProperties.get(PROP_COMPARISON_LABEL); if (leftLabel == null) { leftLabel = itemProvider.getText(leftBranchPoint); } IRegistry<String, Object> rightProperties = rightView.properties(); Image rightImage = (Image)rightProperties.get(PROP_COMPARISON_IMAGE); if (rightImage == null) { rightImage = itemProvider.getImage(rightBranchPoint); } String rightLabel = (String)rightProperties.get(PROP_COMPARISON_LABEL); if (rightLabel == null) { rightLabel = itemProvider.getText(rightBranchPoint); } itemProvider.dispose(); boolean leftEditable = !leftView.isReadOnly(); boolean rightEditable = !rightView.isReadOnly(); String title = COMPARISON_TITLE.get(); if (title == null) { String repositoryName = ((InternalCDOView)leftView).getRepositoryName(); boolean merge = leftEditable || rightEditable; if (merge) { title = "Merge " + repositoryName + " from " + leftLabel + " into " + rightLabel; leftLabel = "From " + leftLabel; rightLabel = "Into " + rightLabel; } else { title = "Compare " + repositoryName + " between " + leftLabel + " and " + rightLabel; } } CompareConfiguration configuration = new CompareConfiguration(); configuration.setLeftImage(leftImage); configuration.setLeftLabel(leftLabel); configuration.setLeftEditable(leftEditable); configuration.setRightImage(rightImage); configuration.setRightLabel(rightLabel); configuration.setRightEditable(rightEditable); Input input = new Input(leftView, rightView, configuration, comparison, editingDomain, adapterFactory); input.setTitle(title); workaroundEMFCompareBug(leftView, leftLabel); workaroundEMFCompareBug(rightView, rightLabel); return input; } /** * See https://git.eclipse.org/r/#/c/55404/ */ private static void workaroundEMFCompareBug(CDOView view, String label) { try { Class<?> c = CommonPlugin.loadClass("org.eclipse.emf.compare.ide", "org.eclipse.emf.compare.ide.internal.utils.StoragePathAdapter"); Constructor<?> constructor = c.getConstructor(String.class, boolean.class); Adapter adapter = (Adapter)constructor.newInstance(label, false); for (Resource resource : view.getResourceSet().getResources()) { resource.eAdapters().add(adapter); } } catch (Throwable ex) { //$FALL-THROUGH$ } } /** * @since 4.4 */ public static void runWithTitle(String title, Runnable runnable) { COMPARISON_TITLE.set(title); try { runnable.run(); } finally { COMPARISON_TITLE.remove(); } } /** * @since 4.3 */ public static boolean isSuppressCommit() { return Boolean.TRUE.equals(SUPPRESS_COMMIT.get()); } /** * @since 4.3 */ public static void setSuppressCommit(boolean suppressCommit) { if (suppressCommit) { SUPPRESS_COMMIT.set(true); } else { SUPPRESS_COMMIT.remove(); } } /** * @since 4.3 */ public static void addDisposeRunnables(Runnable... disposeRunnables) { List<Runnable> list = DISPOSE_RUNNABLES.get(); if (list == null) { list = new ArrayList<Runnable>(); DISPOSE_RUNNABLES.set(list); } list.addAll(Arrays.asList(disposeRunnables)); } private static List<Runnable> removeDisposeRunnables() { List<Runnable> list = DISPOSE_RUNNABLES.get(); DISPOSE_RUNNABLES.remove(); return list; } private static void runDisposeRunnables(List<Runnable> disposeRunnables) { if (disposeRunnables != null) { for (Runnable disposeRunnable : disposeRunnables) { try { disposeRunnable.run(); } catch (Exception ex) { OM.LOG.error(ex); } } } } private static boolean handleMerges(InternalCDOTransaction transaction, EList<Diff> differences) { Map<InternalCDOObject, InternalCDORevision> cleanRevisions = transaction.getCleanRevisions(); Map<CDOID, CDORevisionDelta> revisionDeltas = transaction.getLastSavepoint().getRevisionDeltas2(); boolean unmergedConflicts = false; for (Diff diff : differences) { if (diff.getState() != DifferenceState.MERGED) { unmergedConflicts = true; } else { Match match = diff.getMatch(); EObject left = match.getLeft(); EObject right = match.getRight(); if (left != null && right != null) { InternalCDOObject leftObject = (InternalCDOObject)CDOUtil.getCDOObject(left); InternalCDOObject rightObject = (InternalCDOObject)CDOUtil.getCDOObject(right); InternalCDORevision leftRevision = leftObject.cdoRevision(); cleanRevisions.put(rightObject, leftRevision); int remoteVersion = leftRevision.getVersion(); InternalCDORevision rightRevision = rightObject.cdoRevision(); rightRevision.setBranchPoint(leftRevision); rightRevision.setVersion(remoteVersion); InternalCDORevisionDelta revisionDelta = (InternalCDORevisionDelta)revisionDeltas.get(rightRevision.getID()); if (revisionDelta != null) { revisionDelta.setVersion(remoteVersion); } transaction.removeConflict(rightObject); rightObject.cdoInternalSetState(CDOState.DIRTY); } } } return !unmergedConflicts; } /** * @author Eike Stepper * @since 4.4 */ @SuppressWarnings("restriction") public static final class Input extends org.eclipse.emf.compare.ide.ui.internal.editor.ComparisonEditorInput { private static final Image COMPARE_IMAGE = OM.getImage("icons/compare.gif"); private final CDOView sourceView; private final CDOView targetView; private final Comparison comparison; private List<Runnable> disposeRunnables; private boolean ok; private boolean suppressCommit; private Input(CDOView sourceView, CDOView targetView, CompareConfiguration configuration, Comparison comparison, ICompareEditingDomain editingDomain, AdapterFactory adapterFactory) { super(new org.eclipse.emf.compare.ide.ui.internal.configuration.EMFCompareConfiguration(configuration), comparison, editingDomain, adapterFactory); this.sourceView = sourceView; this.targetView = targetView; this.comparison = comparison; suppressCommit = isSuppressCommit(); SUPPRESS_COMMIT.remove(); } private void dispose() { AdapterFactory adapterFactory = getAdapterFactory(); if (adapterFactory instanceof ComposedAdapterFactory) { ComposedAdapterFactory composedAdapterFactory = (ComposedAdapterFactory)adapterFactory; composedAdapterFactory.dispose(); } runDisposeRunnables(disposeRunnables); disposeRunnables = null; } public final Comparison getComparison() { return comparison; } @Override public Image getTitleImage() { return COMPARE_IMAGE; } public void setDisposeRunnables(List<Runnable> disposeRunnables) { this.disposeRunnables = disposeRunnables; } @Override public void saveChanges(IProgressMonitor monitor) throws CoreException { if (targetView instanceof InternalCDOTransaction) { InternalCDOTransaction transaction = (InternalCDOTransaction)targetView; if (transaction.isDirty()) { Collection<CDOObject> values = transaction.getNewObjects().values(); if (!values.isEmpty()) { Map<CDOID, CDOID> idMappings = CDOIDUtil.createMap(); CDOObject[] rightObjects = values.toArray(new CDOObject[values.size()]); for (CDOObject rightObject : rightObjects) { Match match = comparison.getMatch(rightObject); if (match != null) { EObject left = match.getLeft(); if (left != null) { CDOObject leftObject = CDOUtil.getCDOObject(left); if (leftObject != null) { CDOID id = leftObject.cdoID(); idMappings.put(rightObject.cdoID(), id); org.eclipse.emf.internal.cdo.transaction.CDOTransactionImpl.resurrectObject(rightObject, id); } } } } if (!idMappings.isEmpty()) { CDOIDMapper idMapper = new CDOIDMapper(idMappings); for (CDOObject newObject : values) { InternalCDORevision revision = (InternalCDORevision)newObject.cdoRevision(); revision.adjustReferences(idMapper); } for (CDOObject dirtyObject : transaction.getDirtyObjects().values()) { InternalCDORevision revision = (InternalCDORevision)dirtyObject.cdoRevision(); revision.adjustReferences(idMapper); } } } } try { if (!suppressCommit) { CDOBranchPoint mergeSource = sourceView.isHistorical() ? CDOBranchUtil.copyBranchPoint(sourceView) : sourceView.getBranch().getPoint(sourceView.getLastUpdateTime()); transaction.setCommitMergeSource(mergeSource); transaction.commit(monitor); setDirty(false); } } catch (Exception ex) { OM.BUNDLE.coreException(ex); } } } public boolean isOK() { return ok; } @Override public boolean okPressed() { try { ok = true; return super.okPressed(); } finally { dispose(); } } @Override public void removePropertyChangeListener(IPropertyChangeListener listener) { try { super.removePropertyChangeListener(listener); } finally { dispose(); } } } /** * @author Eike Stepper * @since 4.4 */ public static final class TransactionOpenerAndEditorCloser implements CDOTransactionOpener { private final CDOTransactionOpener delegate; private final boolean closeTransactionAfterCommit; public TransactionOpenerAndEditorCloser(CDOTransactionOpener delegate, boolean closeTransactionAfterCommit) { this.delegate = delegate; this.closeTransactionAfterCommit = closeTransactionAfterCommit; } public boolean isCloseTransactionAfterCommit() { return closeTransactionAfterCommit; } public CDOTransaction openTransaction(String durableLockingID, ResourceSet resourceSet) { return wrap(delegate.openTransaction(durableLockingID, resourceSet)); } public CDOTransaction openTransaction(CDOBranchPoint target, ResourceSet resourceSet) { return wrap(delegate.openTransaction(target, resourceSet)); } private CDOTransaction wrap(CDOTransaction transaction) { if (closeTransactionAfterCommit) { closeTransactionAfterCommit(transaction); } CDOCompareEditorUtil.closeEditorWithTransaction(transaction); return transaction; } } }