/*=============================================================================#
# Copyright (c) 2008-2016 Stephan Wahlbrink (WalWare.de) 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:
# Stephan Wahlbrink - initial API and implementation
#=============================================================================*/
package de.walware.ecommons.ltk.ui.refactoring;
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.jobs.IJobManager;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.text.Position;
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.CompositeChange;
import org.eclipse.ltk.core.refactoring.PerformChangeOperation;
import org.eclipse.ltk.core.refactoring.Refactoring;
import org.eclipse.ltk.core.refactoring.RefactoringCore;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.ltk.internal.ui.refactoring.ChangeExceptionHandler;
import org.eclipse.ltk.ui.refactoring.RefactoringUI;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.progress.IProgressService;
import de.walware.ecommons.ltk.core.model.ISourceUnit;
import de.walware.ecommons.ltk.core.refactoring.IScheduledRefactoring;
import de.walware.ecommons.ltk.core.refactoring.SourceUnitChange;
import de.walware.ecommons.ltk.internal.ui.refactoring.Messages;
/**
* Helper to execute a refactoring. The class takes care of pushing the
* undo change onto the undo stack and folding editor edits into one editor
* undo object.
*/
public class RefactoringExecutionHelper {
private final Refactoring fRefactoring;
private final IProgressService fExecContext;
private final Shell fParent;
private final int fStopSeverity;
private ISourceUnit fInsertPositionSourceUnit;
private Position fInsertPosition;
/**
* @param refactoring
* @param stopSeverity a refactoring status constant from {@link RefactoringStatus}
* @param parent
* @param context
*/
public RefactoringExecutionHelper(final Refactoring refactoring, final int stopSeverity, final Shell parent, final IProgressService context) {
assert (refactoring != null);
assert (parent != null);
assert (context != null);
fRefactoring = refactoring;
fStopSeverity = stopSeverity;
fParent = parent;
fExecContext = context;
}
/**
* Must be called in the UI thread.<br>
* <strong>Use {@link #perform(boolean, boolean)} unless you know exactly what you are doing!</strong>
*
* @param forkChangeExecution if the change should not be executed in the UI thread: This may not work in any case
* @param cancelable if set, the operation will be cancelable
* @throws InterruptedException thrown when the operation is cancelled
* @throws InvocationTargetException thrown when the operation failed to execute
*/
public void perform(final boolean forkChangeExecution, final boolean cancelable) throws InterruptedException, InvocationTargetException {
final AtomicReference<PerformChangeOperation> op = new AtomicReference<>();
try {
fExecContext.busyCursorWhile(new IRunnableWithProgress() {
@Override
public void run(final IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
final SubMonitor progress = SubMonitor.convert(monitor, fRefactoring.getName(), 10);
final IJobManager manager = Job.getJobManager();
final Thread workingThread = Thread.currentThread();
final ISchedulingRule rule = (fRefactoring instanceof IScheduledRefactoring) ?
((IScheduledRefactoring) fRefactoring).getSchedulingRule() :
ResourcesPlugin.getWorkspace().getRoot();
manager.beginRule(rule, progress.newChild(1));
PerformChangeOperation operation = null;
try {
if (cancelable && monitor.isCanceled()) {
throw new InterruptedException();
}
fRefactoring.setValidationContext(fParent);
final RefactoringStatus status = fRefactoring.checkAllConditions(
progress.newChild(1, SubMonitor.SUPPRESS_NONE));
if (status.getSeverity() >= fStopSeverity) {
final AtomicBoolean canceled = new AtomicBoolean();
fParent.getDisplay().syncExec(new Runnable() {
@Override
public void run() {
final Dialog dialog = RefactoringUI.createRefactoringStatusDialog(status, fParent, fRefactoring.getName(), false);
final int selection = dialog.open();
canceled.set(selection == IDialogConstants.CANCEL_ID);
}
});
if (canceled.get()) {
throw new OperationCanceledException();
}
}
final Change change = fRefactoring.createChange(
progress.newChild(2, SubMonitor.SUPPRESS_NONE) );
change.initializeValidationData(
progress.newChild(1, SubMonitor.SUPPRESS_NONE) );
final SourceUnitChange insertTracker = (fInsertPositionSourceUnit != null) ?
search(change) : null;
operation = new PerformChangeOperation(change);
operation.setUndoManager(RefactoringCore.getUndoManager(), fRefactoring.getName());
operation.setSchedulingRule(rule);
if (cancelable && monitor.isCanceled()) {
throw new InterruptedException();
}
op.set(operation);
if (forkChangeExecution) {
operation.run(progress.newChild(4, SubMonitor.SUPPRESS_NONE));
}
else {
final AtomicReference<Exception> opException = new AtomicReference<>();
final Runnable runnable = new Runnable() {
@Override
public void run() {
try {
final PerformChangeOperation operation = op.get();
operation.run(progress.newChild(4, SubMonitor.SUPPRESS_NONE));
}
catch (final CoreException e) {
opException.set(e);
}
catch (final RuntimeException e) {
opException.set(e);
}
finally {
manager.transferRule(rule, workingThread);
}
}
};
final Display display = fParent.getDisplay();
manager.transferRule(rule, display.getThread());
display.syncExec(runnable);
if (opException.get() != null) {
final Exception e = opException.get();
if (e instanceof CoreException) {
throw (CoreException) e;
}
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
}
}
}
final RefactoringStatus validationStatus = operation.getValidationStatus();
if (validationStatus != null && validationStatus.hasFatalError()) {
MessageDialog.openError(fParent, fRefactoring.getName(), NLS.bind(
Messages.ExecutionHelper_CannotExecute_message,
validationStatus.getMessageMatchingSeverity(RefactoringStatus.FATAL) ));
return;
}
if (insertTracker != null) {
fInsertPosition = insertTracker.getInsertPosition();
}
}
catch (final OperationCanceledException e) {
throw new InterruptedException(e.getMessage());
}
catch (final CoreException e) {
throw new InvocationTargetException(e);
}
catch (final RuntimeException e) {
throw new InvocationTargetException(e);
}
finally {
manager.endRule(rule);
fRefactoring.setValidationContext(null);
}
}
});
}
catch (final InvocationTargetException e) {
final PerformChangeOperation operation = op.get();
if (operation != null && operation.changeExecutionFailed()) {
final ChangeExceptionHandler handler = new ChangeExceptionHandler(fParent, fRefactoring);
final Throwable inner = e.getTargetException();
if (inner instanceof RuntimeException) {
handler.handle(operation.getChange(), (RuntimeException) inner);
}
else if (inner instanceof CoreException) {
handler.handle(operation.getChange(), (CoreException) inner);
}
else {
throw e;
}
}
else {
throw e;
}
}
}
public void enableInsertPosition(final ISourceUnit su) {
fInsertPositionSourceUnit = su;
}
public Position getInsertPosition() {
return fInsertPosition;
}
private SourceUnitChange search(final Change change) {
if (change instanceof SourceUnitChange) {
if (((SourceUnitChange) change).getSourceUnit() == fInsertPositionSourceUnit) {
return (SourceUnitChange) change;
}
}
if (change instanceof CompositeChange) {
final Change[] children = ((CompositeChange) change).getChildren();
for (int i = 0; i < children.length; i++) {
final SourceUnitChange child = search(children[i]);
if (child != null) {
return child;
}
}
}
return null;
}
}