blob: 34ba9ac6a02cba53dd40ebc1236b26d4b5246be4 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2008, 2020 Stephan Wahlbrink and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.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 org.eclipse.statet.internal.ltk.ui.refactoring.Messages;
import org.eclipse.statet.ltk.model.core.elements.ISourceUnit;
import org.eclipse.statet.ltk.refactoring.core.IScheduledRefactoring;
import org.eclipse.statet.ltk.refactoring.core.SourceUnitChange;
/**
* 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 refactoring;
private final IProgressService execContext;
private final Shell parent;
private final int stopSeverity;
private ISourceUnit insertPositionSourceUnit;
private Position insertPosition;
/**
* @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);
this.refactoring= refactoring;
this.stopSeverity= stopSeverity;
this.parent= parent;
this.execContext= 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 {
this.execContext.busyCursorWhile(new IRunnableWithProgress() {
@Override
public void run(final IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
final SubMonitor m= SubMonitor.convert(monitor, RefactoringExecutionHelper.this.refactoring.getName(), 10);
final IJobManager manager= Job.getJobManager();
final Thread workingThread= Thread.currentThread();
final ISchedulingRule rule= (RefactoringExecutionHelper.this.refactoring instanceof IScheduledRefactoring) ?
((IScheduledRefactoring) RefactoringExecutionHelper.this.refactoring).getSchedulingRule() :
ResourcesPlugin.getWorkspace().getRoot();
manager.beginRule(rule, m.newChild(1));
PerformChangeOperation operation= null;
try {
if (cancelable && monitor.isCanceled()) {
throw new InterruptedException();
}
RefactoringExecutionHelper.this.refactoring.setValidationContext(RefactoringExecutionHelper.this.parent);
final RefactoringStatus status= RefactoringExecutionHelper.this.refactoring.checkAllConditions(
m.newChild(1, SubMonitor.SUPPRESS_NONE));
if (status.getSeverity() >= RefactoringExecutionHelper.this.stopSeverity) {
final AtomicBoolean canceled= new AtomicBoolean();
RefactoringExecutionHelper.this.parent.getDisplay().syncExec(new Runnable() {
@Override
public void run() {
final Dialog dialog= RefactoringUI.createRefactoringStatusDialog(status, RefactoringExecutionHelper.this.parent, RefactoringExecutionHelper.this.refactoring.getName(), false);
final int selection= dialog.open();
canceled.set(selection == IDialogConstants.CANCEL_ID);
}
});
if (canceled.get()) {
throw new OperationCanceledException();
}
}
final Change change= RefactoringExecutionHelper.this.refactoring.createChange(
m.newChild(2, SubMonitor.SUPPRESS_NONE) );
change.initializeValidationData(
m.newChild(1, SubMonitor.SUPPRESS_NONE) );
final SourceUnitChange insertTracker= (RefactoringExecutionHelper.this.insertPositionSourceUnit != null) ?
search(change) : null;
operation= new PerformChangeOperation(change);
operation.setUndoManager(RefactoringCore.getUndoManager(), RefactoringExecutionHelper.this.refactoring.getName());
operation.setSchedulingRule(rule);
if (cancelable && monitor.isCanceled()) {
throw new InterruptedException();
}
op.set(operation);
if (forkChangeExecution) {
operation.run(m.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(m.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= RefactoringExecutionHelper.this.parent.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(RefactoringExecutionHelper.this.parent, RefactoringExecutionHelper.this.refactoring.getName(), NLS.bind(
Messages.ExecutionHelper_CannotExecute_message,
validationStatus.getMessageMatchingSeverity(RefactoringStatus.FATAL) ));
return;
}
if (insertTracker != null) {
RefactoringExecutionHelper.this.insertPosition= 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);
RefactoringExecutionHelper.this.refactoring.setValidationContext(null);
}
}
});
}
catch (final InvocationTargetException e) {
final PerformChangeOperation operation= op.get();
if (operation != null && operation.changeExecutionFailed()) {
final ChangeExceptionHandler handler= new ChangeExceptionHandler(this.parent, this.refactoring);
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) {
this.insertPositionSourceUnit= su;
}
public Position getInsertPosition() {
return this.insertPosition;
}
private SourceUnitChange search(final Change change) {
if (change instanceof SourceUnitChange) {
if (((SourceUnitChange) change).getSourceUnit() == this.insertPositionSourceUnit) {
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;
}
}