blob: 58502212f3100649e7d1eebdbeda0a6489b96442 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2007 IBM Corporation 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.ltk.core.refactoring;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.ltk.internal.core.refactoring.NotCancelableProgressMonitor;
/**
* Operation that, when run, performs a {@link Change} object. The operation
* can be created in two different ways: with a given change or with a
* {@link CreateChangeOperation}. If created the second way the given create
* change operation will be used to create the actual change to perform.
* <p>
* If the change has been performed successfully (e.g. {@link #changeExecuted()} returns
* <code>true</code>) then the operation has called {@link Change#dispose()} as well
* to clear-up internal state in the change object. If it hasn't been executed the
* change, the change is still intact and the client is responsible to dispose the
* change object.
* </p>
* <p>
* If an undo change has been provided by the change to execute then the operation
* calls {@link Change#initializeValidationData(IProgressMonitor)} to initialize the
* undo change's validation data.
* </p>
* <p>
* If an undo manager has been set via the method {@link #setUndoManager(IUndoManager, String)}
* then the undo object, if any has been provided, will be pushed onto the manager's
* undo stack.
* </p>
* <p>
* The operation should be executed via the run method offered by
* <code>IWorkspace</code> to achieve proper delta batching.
* </p>
* <p>
* Note: this class is not intended to be extended outside of the refactoring framework.
* </p>
*
* @since 3.0
*/
public class PerformChangeOperation implements IWorkspaceRunnable {
private Change fChange;
private CreateChangeOperation fCreateChangeOperation;
private RefactoringStatus fValidationStatus;
private Change fUndoChange;
private String fUndoName;
private IUndoManager fUndoManager;
private boolean fChangeExecuted;
private boolean fChangeExecutionFailed;
private ISchedulingRule fSchedulingRule;
/**
* Creates a new perform change operation instance for the given change.
*
* @param change the change to be applied to the workbench
*/
public PerformChangeOperation(Change change) {
Assert.isNotNull(change);
fChange= change;
fSchedulingRule= ResourcesPlugin.getWorkspace().getRoot();
}
/**
* Creates a new <code>PerformChangeOperation</code> for the given {@link
* CreateChangeOperation}. The create change operation is used to create
* the actual change to execute.
*
* @param op the <code>CreateChangeOperation</code> used to create the
* actual change object
*/
public PerformChangeOperation(CreateChangeOperation op) {
Assert.isNotNull(op);
fCreateChangeOperation= op;
fSchedulingRule= ResourcesPlugin.getWorkspace().getRoot();
}
/**
* Returns <code>true</code> if the change execution failed.
*
* @return <code>true</code> if the change execution failed;
* <code>false</code> otherwise
*
*/
public boolean changeExecutionFailed() {
return fChangeExecutionFailed;
}
/**
* Returns <code>true</code> if the change has been executed. Otherwise <code>
* false</code> is returned.
*
* @return <code>true</code> if the change has been executed, otherwise
* <code>false</code>
*/
public boolean changeExecuted() {
return fChangeExecuted;
}
/**
* Returns the status of the condition checking. Returns <code>null</code> if
* no condition checking has been requested.
*
* @return the status of the condition checking
*/
public RefactoringStatus getConditionCheckingStatus() {
if (fCreateChangeOperation != null)
return fCreateChangeOperation.getConditionCheckingStatus();
return null;
}
/**
* Returns the change used by this operation. This is either the change passed to
* the constructor or the one create by the <code>CreateChangeOperation</code>.
* Method returns <code>null</code> if the create operation did not create
* a corresponding change or hasn't been executed yet.
*
* @return the change used by this operation or <code>null</code> if no change
* has been created
*/
public Change getChange() {
return fChange;
}
/**
* Returns the undo change of the change performed by this operation. Returns
* <code>null</code> if the change hasn't been performed yet or if the change
* doesn't provide a undo.
*
* @return the undo change of the performed change or <code>null</code>
*/
public Change getUndoChange() {
return fUndoChange;
}
/**
* Returns the refactoring status returned from the call <code>IChange#isValid()</code>.
* Returns <code>null</code> if the change has not been executed.
*
* @return the change's validation status
*/
public RefactoringStatus getValidationStatus() {
return fValidationStatus;
}
/**
* Sets the undo manager. If the executed change provides an undo change,
* then the undo change is pushed onto this manager.
*
* @param manager the undo manager to use or <code>null</code> if no
* undo recording is desired
* @param undoName the name used to present the undo change on the undo
* stack. Must be a human-readable string. Must not be <code>null</code>
* if manager is unequal <code>null</code>
*/
public void setUndoManager(IUndoManager manager, String undoName) {
if (manager != null) {
Assert.isNotNull(undoName);
}
fUndoManager= manager;
fUndoName= undoName;
}
/**
* Sets the scheduling rule used to execute this operation. If
* not set then the workspace root is used. The Change operation
* must be able to be performed in the provided scheduling rule.
*
* @param rule the Rule to use, not null
* @since 3.3
*/
public void setSchedulingRule(ISchedulingRule rule) {
Assert.isNotNull(rule);
fSchedulingRule= rule;
}
/**
* {@inheritDoc}
*/
public void run(IProgressMonitor pm) throws CoreException {
if (pm == null)
pm= new NullProgressMonitor();
try {
fChangeExecuted= false;
if (createChange()) {
pm.beginTask("", 4); //$NON-NLS-1$
pm.subTask(""); //$NON-NLS-1$
fCreateChangeOperation.run(new SubProgressMonitor(pm, 3));
// Check for cancellation before executing the change, since canceling
// during change execution is not supported
// (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=187265 ):
if (pm.isCanceled())
throw new OperationCanceledException();
fChange= fCreateChangeOperation.getChange();
if (fChange != null) {
executeChange(new SubProgressMonitor(pm, 1));
} else {
pm.worked(1);
}
} else {
executeChange(pm);
}
} finally {
pm.done();
}
}
/**
* Actually executes the change.
*
* @param pm a progress monitor to report progress
*
* @throws CoreException if an unexpected error occurs during
* change execution
*/
protected void executeChange(IProgressMonitor pm) throws CoreException {
fChangeExecuted= false;
if (!fChange.isEnabled())
return;
IWorkspaceRunnable runnable= new IWorkspaceRunnable() {
public void run(IProgressMonitor monitor) throws CoreException {
boolean undoInitialized= false;
try {
monitor.beginTask("", 10); //$NON-NLS-1$
fValidationStatus= fChange.isValid(new SubProgressMonitor(monitor, 1));
if (fValidationStatus.hasFatalError())
return;
boolean aboutToPerformChangeCalled= false;
try {
if (fUndoManager != null) {
ResourcesPlugin.getWorkspace().checkpoint(false);
fUndoManager.aboutToPerformChange(fChange);
aboutToPerformChangeCalled= true;
}
fChangeExecutionFailed= true;
fUndoChange= fChange.perform(new SubProgressMonitor(monitor, 9));
fChangeExecutionFailed= false;
fChangeExecuted= true;
} finally {
if (fUndoManager != null) {
ResourcesPlugin.getWorkspace().checkpoint(false);
if (aboutToPerformChangeCalled)
fUndoManager.changePerformed(fChange, !fChangeExecutionFailed);
}
}
fChange.dispose();
if (fUndoChange != null) {
fUndoChange.initializeValidationData(new NotCancelableProgressMonitor(
new SubProgressMonitor(monitor, 1)));
undoInitialized= true;
}
if (fUndoManager != null) {
if (fUndoChange != null) {
fUndoManager.addUndo(fUndoName, fUndoChange);
} else {
fUndoManager.flush();
}
}
} catch (CoreException e) {
if (fUndoManager != null)
fUndoManager.flush();
if (fUndoChange != null && undoInitialized) {
Change ch= fUndoChange;
fUndoChange= null;
ch.dispose();
}
fUndoChange= null;
throw e;
} catch (RuntimeException e) {
if (fUndoManager != null)
fUndoManager.flush();
if (fUndoChange != null && undoInitialized) {
Change ch= fUndoChange;
fUndoChange= null;
ch.dispose();
}
fUndoChange= null;
throw e;
} finally {
monitor.done();
}
}
};
ResourcesPlugin.getWorkspace().run(runnable, fSchedulingRule, IWorkspace.AVOID_UPDATE, pm);
}
private boolean createChange() {
return fCreateChangeOperation != null;
}
}