| /*=============================================================================# |
| # Copyright (c) 2008, 2019 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; |
| } |
| |
| } |