package com.sap.ide.refactoring.core.execution;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.CompositeChange;
import org.eclipse.ltk.core.refactoring.NullChange;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import com.sap.ide.refactoring.Activator;
import com.sap.ide.refactoring.core.AbstractRefactoringCommand;
import com.sap.ide.refactoring.core.RefactoringCoreException;
import com.sap.ide.refactoring.core.execution.participation.AbstractCommandExecutionParticipant;
import com.sap.ide.refactoring.core.execution.participation.AbstractCommandExecutionParticipant.ParticipantDescriptor;
import com.sap.ide.refactoring.core.execution.participation.ContributionInfo;
import com.sap.ide.refactoring.core.execution.participation.ContributionMap;
import com.sap.ide.refactoring.core.execution.participation.MetaModelConstraintCheckingParticipant;
import com.sap.ide.refactoring.core.execution.participation.ReferenceReEvaluationObservingParticipant;
import com.sap.ide.refactoring.core.execution.participation.TextBlockSynchronizationParticipant;
import com.sap.ide.refactoring.core.textual.RefactoringEditorFacade;
import com.sap.ide.refactoring.core.textual.TextBlockInChangeCalculator;
import com.sap.ide.refactoring.core.textual.TextBlocksNeedingPrettyPrintChangeListener;
import com.sap.ide.refactoring.core.textual.TextBlocksSynchronizationCommand;
import com.sap.ide.refactoring.ui.NamedSubProgressMonitor;
import com.sap.mi.textual.parsing.textblocks.TbValidationUtil;
import com.sap.tc.moin.repository.commands.CommandHandle;
/**
* Class used to execute {@link AbstractRefactoringCommand}s, while making sure that TextBlocks are
* updated accordingly (for that, see {@link TextBlocksSynchronizationCommand},
* {@link TextBlocksNeedingPrettyPrintChangeListener} and {@link TextBlockInChangeCalculator}
*
* @author Stephan Erb (d049157)
*
*/
public class RefactoringCommandExecutor {
protected final RefactoringEditorFacade facade;
private final AbstractRefactoringCommand refactoringCommand;
private final Collection<AbstractCommandExecutionParticipant> participants;
public RefactoringCommandExecutor(RefactoringEditorFacade facade, AbstractRefactoringCommand cmd) {
this.facade = facade;
this.refactoringCommand = cmd;
this.participants = createParticipants();
}
/**
* Run the refactoring.
*
* @return A change object describing the performed changes.
* The workspace remains unchanged until {@link Change#perform} is called.
* @throws RefactoringCoreException
*/
public RefactoringResult runRefactoring(IProgressMonitor pm) throws RefactoringCoreException {
pm.beginTask("Refactoring: ", 100);
try {
setupExecutionParticipants(new NamedSubProgressMonitor(pm, 10));
RefactoringResult result = runRefactoringInternal(refactoringCommand, pm);
tearDownExecutionParticipants(new NamedSubProgressMonitor(pm, 5));
undoRefactoring(result, new NamedSubProgressMonitor(pm, 10));
return result;
} finally {
pm.done();
}
}
private RefactoringResult runRefactoringInternal(AbstractRefactoringCommand cmd, IProgressMonitor pm) {
CommandUndoRedoHelper helper = new CommandUndoRedoHelper(facade.getEditorConnection());
RefactoringResult result = null;
RefactoringStatus status = new RefactoringStatus();
try {
status.merge(cmd.preValidate(new NamedSubProgressMonitor(pm, 5)));
cmd.setExecutionProgressMonitor(new NamedSubProgressMonitor(pm, 25));
CommandHandle refactoringCmdHandle = cmd.execute();
// here (synchronously, directly after the refactoring) several update/change handlers perform their
// work. This includes delayed reference resolving.
ContributionMap contributions = letParticipantsContribute(new SubProgressMonitor(pm, 40));
status.merge(cmd.postValidate(contributions, new NamedSubProgressMonitor(pm, 5)));
CommandHandle dependentCommandHandle = helper.peekUndoStack(); //captures the handle of the last of these commands
Change change = createCollectiveCompositeChange(status, refactoringCmdHandle, dependentCommandHandle, contributions);
result = RefactoringResult.createResult(refactoringCmdHandle, change, status);
} catch (Exception e) {
Activator.logError(e, "Refactoring exeuction failed " + cmd.getDescription() );
status.merge(RefactoringStatus.createFatalErrorStatus("Failed to execute " + cmd.getDescription() + ": " + e.getMessage()));
result = RefactoringResult.createFailingResult(status);
}
return result;
}
/**
* Create a change
*
* @param refactoringCommandHandle The executed refactoring.
* @param dependentCommandHandle The newest command directly or indirectly triggered by the command
* (e.g. reference re-evaluations)
* @param contributions
* @return
*/
private Change createCollectiveCompositeChange(RefactoringStatus status, CommandHandle refactoringCommandHandle, CommandHandle dependentCommandHandle, ContributionMap contributions) {
CompositeChange change = new CompositeChange(refactoringCommandHandle.getDescription());
change.markAsSynthetic();
change.add(new ModelChange(facade, status, refactoringCommandHandle, dependentCommandHandle));
for (ContributionInfo contributionInfo : contributions.getAllContributions()) {
status.merge(contributionInfo.status);
// NullChanged become an empty UI entry. Prevent that.
if (! (contributionInfo.change instanceof NullChange)) {
change.add(contributionInfo.change);
}
}
return change;
}
private void undoRefactoring(RefactoringResult result, IProgressMonitor pm) throws RefactoringCoreException {
if (result.refactoringCommandHandle != null) {
// command could be executed; undo it
CommandUndoRedoHelper helper = new CommandUndoRedoHelper(facade.getEditorConnection());
helper.undoRefactoring(result.refactoringCommandHandle, pm);
}
// post undo check. State should be as it were before the refactoring (pre state)
TbValidationUtil.assertTextBlockConsistencyRecursive(facade.getTextBlocksModel().getRoot());
}
/**
* Can be overwritten in tests if the executor shall run without participants,
* or if a refactoring requires a specific set of participants.
*/
protected Collection<AbstractCommandExecutionParticipant> createParticipants() {
Collection<AbstractCommandExecutionParticipant> participants = new ArrayList<AbstractCommandExecutionParticipant>();
participants.add(new TextBlockSynchronizationParticipant(facade));
participants.add(new MetaModelConstraintCheckingParticipant(facade));
participants.add(new ReferenceReEvaluationObservingParticipant(facade));
return participants;
}
private void setupExecutionParticipants(IProgressMonitor pm) {
for (AbstractCommandExecutionParticipant participant : participants) {
try {
participant.setup(new NamedSubProgressMonitor(pm, 1));
} catch (Exception e) {
Activator.logError(e, "Participant setup failed");
}
}
pm.done();
}
private ContributionMap letParticipantsContribute(IProgressMonitor pm) {
pm.beginTask("", participants.size());
Map<ParticipantDescriptor, Collection<ContributionInfo>> results = new HashMap<ParticipantDescriptor, Collection<ContributionInfo>>();
for (AbstractCommandExecutionParticipant participant : participants) {
try {
results.put(participant.getDescriptor(), participant.contribute(new NamedSubProgressMonitor(pm, 1)));
} catch (Exception e) {
Activator.logError(e, "Participant setup failed");
}
}
pm.done();
return new ContributionMap(results);
}
private void tearDownExecutionParticipants(IProgressMonitor pm) {
for (AbstractCommandExecutionParticipant participant : participants) {
try {
participant.teardown(new NamedSubProgressMonitor(pm, 1));
} catch (Exception e) {
Activator.logError(e, "Participant teardown failed");
}
}
pm.done();
}
}