/*******************************************************************************
* Copyright (c) 2000, 2008 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
* Oakland Software (Francis Upton) <francisu@ieee.org> -
* Fix for Bug 63149 [ltk] allow changes to be executed after the 'main' change during an undo [refactoring]
*******************************************************************************/
package org.eclipse.ltk.core.refactoring.participants;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.PerformanceStats;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.CompositeChange;
import org.eclipse.ltk.core.refactoring.IRefactoringCoreStatusCodes;
import org.eclipse.ltk.core.refactoring.Refactoring;
import org.eclipse.ltk.core.refactoring.RefactoringDescriptor;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.ltk.core.refactoring.TextChange;
import org.eclipse.ltk.core.refactoring.TextFileChange;
import org.eclipse.ltk.core.refactoring.codingspectator.IWatchedProcessor;
import org.eclipse.ltk.core.refactoring.codingspectator.IWatchedRefactoring;
import org.eclipse.ltk.core.refactoring.codingspectator.Logger;
import org.eclipse.ltk.internal.core.refactoring.Messages;
import org.eclipse.ltk.internal.core.refactoring.ParticipantDescriptor;
import org.eclipse.ltk.internal.core.refactoring.RefactoringCoreMessages;
import org.eclipse.ltk.internal.core.refactoring.RefactoringCorePlugin;
/**
* An base implementation for refactorings that are split into one refactoring processor and 0..n
* participants.
* <p>
* This class can be subclassed by clients wishing to provide a special refactoring which uses a
* processor/participant architecture.
* </p>
* <p>
* Since 3.4, this class is non abstract and can be instantiated. {@link #getProcessor()} will
* return the processor passed in {@link #ProcessorBasedRefactoring(RefactoringProcessor)} or the
* processor set by {@link #setProcessor(RefactoringProcessor)}.
*
* @since 3.0
*
* @author Mohsen Vakilian, nchen - Logged refactorings with fatal errors while checking initial
* conditions.
*/
public class ProcessorBasedRefactoring extends Refactoring implements IWatchedRefactoring {
private static final String PERF_CHECK_CONDITIONS= "org.eclipse.ltk.core.refactoring/perf/participants/checkConditions"; //$NON-NLS-1$
private static final String PERF_CREATE_CHANGES= "org.eclipse.ltk.core.refactoring/perf/participants/createChanges"; //$NON-NLS-1$
private RefactoringProcessor fProcessor;
private List/*<RefactoringParticipant>*/fParticipants;
private List/*<RefactoringParticipant>*/fPreChangeParticipants; // can be null
private Map/*<Object, TextChange>*/fTextChangeMap;
private static final List/*<RefactoringParticipant>*/EMPTY_PARTICIPANTS= Collections.EMPTY_LIST;
private static class ProcessorChange extends CompositeChange {
private Map/*<Change, RefactoringParticipant>*/fParticipantMap;
private List/*<RefactoringParticipant>*/fPreChangeParticipants; // can be null
public ProcessorChange(String name) {
super(name);
markAsSynthetic();
}
public void setParticipantMap(Map/*<Change, RefactoringParticipant>*/map) {
fParticipantMap= map;
}
public void setPreChangeParticipants(List/*<RefactoringParticipant>*/list) {
fPreChangeParticipants= list;
}
protected void internalHandleException(Change change, Throwable e) {
if (e instanceof OperationCanceledException)
return;
RefactoringParticipant participant= (RefactoringParticipant)fParticipantMap.get(change);
if (participant != null) {
disableParticipant(participant, e);
} else if (fPreChangeParticipants != null) {
// The main refactoring, get rid of any participants with pre changes
IStatus status= new Status(
IStatus.ERROR, RefactoringCorePlugin.getPluginId(),
IRefactoringCoreStatusCodes.REFACTORING_EXCEPTION_DISABLED_PARTICIPANTS,
RefactoringCoreMessages.ProcessorBasedRefactoring_prechange_participants_removed,
e);
ResourcesPlugin.getPlugin().getLog().log(status);
Iterator it= fPreChangeParticipants.iterator();
while (it.hasNext()) {
participant= (RefactoringParticipant)it.next();
disableParticipant(participant, null);
}
}
}
protected boolean internalContinueOnCancel() {
return true;
}
protected boolean internalProcessOnCancel(Change change) {
RefactoringParticipant participant= (RefactoringParticipant)fParticipantMap.get(change);
if (participant == null)
return false;
return participant.getDescriptor().processOnCancel();
}
}
/**
* Creates a new processor based refactoring. Clients must override {@link #getProcessor()} to
* return a processor or set the processor with {@link #setProcessor(RefactoringProcessor)}.
*
* @deprecated use {@link #ProcessorBasedRefactoring(RefactoringProcessor)} instead
*/
protected ProcessorBasedRefactoring() {
}
/**
* Creates a new processor based refactoring.
*
* @param processor the refactoring's main processor
*
* @since 3.4 public, was added in 3.1 as protected method
*/
public ProcessorBasedRefactoring(RefactoringProcessor processor) {
setProcessor(processor);
}
/**
* Return the processor associated with this refactoring. The method must not return
* <code>null</code>. Implementors can override this method to return the processor to be used
* by this refactoring. Since 3.4, this method returns the processor passed in
* {@link #ProcessorBasedRefactoring(RefactoringProcessor)} or by
* {@link #setProcessor(RefactoringProcessor)}.
*
* @return the processor associated with this refactoring
*/
public RefactoringProcessor getProcessor() {
return fProcessor;
}
/**
* Sets the processor associated with this refactoring. The processor must not be
* <code>null</code>.
*
* @param processor the processor associated with this refactoring
*
* @since 3.4
*/
public void setProcessor(RefactoringProcessor processor) {
processor.setRefactoring(this);
fProcessor= processor;
}
/**
* Checks whether the refactoring is applicable to the elements to be refactored or not.
* <p>
* This default implementation forwards the call to the refactoring processor.
* </p>
*
* @return <code>true</code> if the refactoring is applicable to the elements; otherwise
* <code>false</code> is returned.
* @throws CoreException if the test fails
*/
public final boolean isApplicable() throws CoreException {
return getProcessor().isApplicable();
}
/**
* {@inheritDoc}
*/
public String getName() {
return getProcessor().getProcessorName();
}
/**
* {@inheritDoc}
*/
public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException {
if (pm == null)
pm= new NullProgressMonitor();
RefactoringStatus result= new RefactoringStatus();
pm.beginTask("", 10); //$NON-NLS-1$
pm.setTaskName(RefactoringCoreMessages.ProcessorBasedRefactoring_initial_conditions);
result.merge(getProcessor().checkInitialConditions(new SubProgressMonitor(pm, 8)));
if (result.hasFatalError()) {
//CODINGSPECTATOR
logUnavailableRefactoring(result);
pm.done();
return result;
}
pm.done();
return result;
}
/**
* {@inheritDoc}
*/
public RefactoringStatus checkFinalConditions(IProgressMonitor pm) throws CoreException {
if (pm == null)
pm= new NullProgressMonitor();
RefactoringStatus result= new RefactoringStatus();
CheckConditionsContext context= createCheckConditionsContext();
pm.beginTask("", 9); //$NON-NLS-1$
pm.setTaskName(RefactoringCoreMessages.ProcessorBasedRefactoring_final_conditions);
result.merge(getProcessor().checkFinalConditions(new SubProgressMonitor(pm, 5), context));
if (result.hasFatalError()) {
pm.done();
return result;
}
if (pm.isCanceled())
throw new OperationCanceledException();
SharableParticipants sharableParticipants= new SharableParticipants(); // must not be shared when checkFinalConditions is called again
RefactoringParticipant[] loadedParticipants= getProcessor().loadParticipants(result, sharableParticipants);
if (loadedParticipants == null || loadedParticipants.length == 0) {
fParticipants= EMPTY_PARTICIPANTS;
} else {
fParticipants= new ArrayList();
for (int i= 0; i < loadedParticipants.length; i++) {
fParticipants.add(loadedParticipants[i]);
}
}
if (result.hasFatalError()) {
pm.done();
return result;
}
IProgressMonitor sm= new SubProgressMonitor(pm, 2);
sm.beginTask("", fParticipants.size()); //$NON-NLS-1$
for (Iterator iter= fParticipants.iterator(); iter.hasNext() && !result.hasFatalError();) {
RefactoringParticipant participant= (RefactoringParticipant)iter.next();
final PerformanceStats stats= PerformanceStats.getStats(PERF_CHECK_CONDITIONS, getName() + ", " + participant.getName()); //$NON-NLS-1$
stats.startRun();
try {
result.merge(participant.checkConditions(new SubProgressMonitor(sm, 1), context));
} catch (RuntimeException e) {
// remove the participant so that it will be ignored during change execution.
RefactoringCorePlugin.log(e);
result.merge(RefactoringStatus.createErrorStatus(Messages.format(
RefactoringCoreMessages.ProcessorBasedRefactoring_check_condition_participant_failed,
participant.getName())));
iter.remove();
}
stats.endRun();
if (sm.isCanceled())
throw new OperationCanceledException();
}
sm.done();
if (result.hasFatalError()) {
pm.done();
return result;
}
result.merge(context.check(new SubProgressMonitor(pm, 1)));
pm.done();
return result;
}
/**
* {@inheritDoc}
*/
public Change createChange(IProgressMonitor pm) throws CoreException {
if (pm == null)
pm= new NullProgressMonitor();
pm.beginTask("", fParticipants.size() + 3); //$NON-NLS-1$
pm.setTaskName(RefactoringCoreMessages.ProcessorBasedRefactoring_create_change);
Change processorChange= getProcessor().createChange(new SubProgressMonitor(pm, 1));
if (pm.isCanceled())
throw new OperationCanceledException();
fTextChangeMap= new HashMap();
addToTextChangeMap(processorChange);
List/*<Change>*/changes= new ArrayList();
List/*<Change>*/preChanges= new ArrayList();
Map/*<Change, RefactoringParticipant>*/participantMap= new HashMap();
for (Iterator iter= fParticipants.iterator(); iter.hasNext();) {
final RefactoringParticipant participant= (RefactoringParticipant)iter.next();
try {
final PerformanceStats stats= PerformanceStats.getStats(PERF_CREATE_CHANGES, getName() + ", " + participant.getName()); //$NON-NLS-1$
stats.startRun();
Change preChange= participant.createPreChange(new SubProgressMonitor(pm, 1));
Change change= participant.createChange(new SubProgressMonitor(pm, 1));
stats.endRun();
if (preChange != null) {
if (fPreChangeParticipants == null)
fPreChangeParticipants= new ArrayList();
fPreChangeParticipants.add(participant);
preChanges.add(preChange);
participantMap.put(preChange, participant);
addToTextChangeMap(preChange);
}
if (change != null) {
changes.add(change);
participantMap.put(change, participant);
addToTextChangeMap(change);
}
} catch (CoreException e) {
disableParticipant(participant, e);
throw e;
} catch (RuntimeException e) {
disableParticipant(participant, e);
throw e;
}
if (pm.isCanceled())
throw new OperationCanceledException();
}
fTextChangeMap= null;
Change postChange= getProcessor().postCreateChange(
(Change[])changes.toArray(new Change[changes.size()]),
new SubProgressMonitor(pm, 1));
ProcessorChange result= new ProcessorChange(getName());
result.addAll((Change[])preChanges.toArray(new Change[preChanges.size()]));
result.add(processorChange);
result.addAll((Change[])changes.toArray(new Change[changes.size()]));
result.setParticipantMap(participantMap);
result.setPreChangeParticipants(fPreChangeParticipants);
if (postChange != null)
result.add(postChange);
return result;
}
/**
* Returns the text change for the given element or <code>null</code> if a text change doesn't
* exist. This method only returns a valid result during change creation. Outside of change
* creation always <code>null</code> is returned.
*
* @param element the element to be modified for which a text change is requested
*
* @return the text change or <code>null</code> if no text change exists for the element
*
* @since 3.1
*/
public TextChange getTextChange(Object element) {
if (fTextChangeMap == null)
return null;
return (TextChange)fTextChangeMap.get(element);
}
/**
* Adapts the refactoring to the given type. The adapter is resolved as follows:
* <ol>
* <li>the refactoring itself is checked whether it is an instance of the requested type.</li>
* <li>its processor is checked whether it is an instance of the requested type.</li>
* <li>the request is delegated to the super class.</li>
* </ol>
*
* @param clazz the adapter class to look up
*
* @return the requested adapter or <code>null</code>if no adapter exists.
*/
public Object getAdapter(Class clazz) {
if (clazz.isInstance(this))
return this;
if (clazz.isInstance(getProcessor()))
return getProcessor();
return super.getAdapter(clazz);
}
/* non java-doc
* for debugging only
*/
public String toString() {
return getName();
}
//---- Helper methods ---------------------------------------------------------------------
private CheckConditionsContext createCheckConditionsContext() throws CoreException {
CheckConditionsContext result= new CheckConditionsContext();
result.add(new ValidateEditChecker(getValidationContext()));
result.add(new ResourceChangeChecker());
return result;
}
private static void disableParticipant(final RefactoringParticipant participant, Throwable e) {
ParticipantDescriptor descriptor= participant.getDescriptor();
descriptor.disable();
RefactoringCorePlugin.logRemovedParticipant(descriptor, e);
}
private void addToTextChangeMap(Change change) {
if (change instanceof TextChange) {
Object element= ((TextChange)change).getModifiedElement();
if (element != null) {
fTextChangeMap.put(element, change);
}
// check if we have a subclass of TextFileChange. If so also put the change
// under the file resource into the hash table if possible.
if (change instanceof TextFileChange && !change.getClass().equals(TextFileChange.class)) {
IFile file= ((TextFileChange)change).getFile();
fTextChangeMap.put(file, change);
}
} else if (change instanceof CompositeChange) {
Change[] children= ((CompositeChange)change).getChildren();
for (int i= 0; i < children.length; i++) {
addToTextChangeMap(children[i]);
}
}
}
/////////////////
//CODINGSPECTATOR
/////////////////
protected void logUnavailableRefactoring(RefactoringStatus refactoringStatus) {
if (isRefWizOpenOpCheckedInitConds()) {
if (fProcessor instanceof IWatchedProcessor) {
IWatchedProcessor watchedProcessor= (IWatchedProcessor)fProcessor;
Logger.logUnavailableRefactoringEvent(watchedProcessor.getDescriptorID(), watchedProcessor.getJavaProjectName(), watchedProcessor.getCodeSnippetInformation(),
refactoringStatus.getMessageMatchingSeverity(RefactoringStatus.FATAL));
unsetRefWizOpenOpCheckedInitConds();
}
}
}
public RefactoringDescriptor getSimpleRefactoringDescriptor(RefactoringStatus refactoringStatus) {
if (!isWatched())
throw new UnsupportedOperationException();
return ((IWatchedProcessor)fProcessor).getSimpleRefactoringDescriptor(refactoringStatus);
}
public boolean isWatched() {
return fProcessor instanceof IWatchedProcessor;
}
}