| /******************************************************************************* |
| * Copyright (c) 2000, 2015 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * 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.RefactoringStatus; |
| import org.eclipse.ltk.core.refactoring.TextChange; |
| import org.eclipse.ltk.core.refactoring.TextFileChange; |
| 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 |
| */ |
| public class ProcessorBasedRefactoring extends Refactoring { |
| |
| 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.emptyList(); |
| |
| 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; |
| } |
| |
| @Override |
| protected void internalHandleException(Change change, Throwable e) { |
| if (e instanceof OperationCanceledException) |
| return; |
| |
| RefactoringParticipant participant= 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<RefactoringParticipant> it= fPreChangeParticipants.iterator(); |
| while (it.hasNext()) { |
| participant= it.next(); |
| disableParticipant(participant, null); |
| } |
| } |
| } |
| |
| @Override |
| protected boolean internalContinueOnCancel() { |
| return true; |
| } |
| |
| @Override |
| protected boolean internalProcessOnCancel(Change change) { |
| RefactoringParticipant participant= 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 |
| */ |
| @Deprecated |
| 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(); |
| } |
| |
| @Override |
| public String getName() { |
| return getProcessor().getProcessorName(); |
| } |
| |
| @Override |
| 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()) { |
| pm.done(); |
| return result; |
| } |
| pm.done(); |
| return result; |
| } |
| |
| @Override |
| 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 (RefactoringParticipant loadedParticipant : loadedParticipants) { |
| fParticipants.add(loadedParticipant); |
| } |
| } |
| if (result.hasFatalError()) { |
| pm.done(); |
| return result; |
| } |
| IProgressMonitor sm= new SubProgressMonitor(pm, 2); |
| |
| sm.beginTask("", fParticipants.size()); //$NON-NLS-1$ |
| for (Iterator<RefactoringParticipant> iter= fParticipants.iterator(); iter.hasNext() && !result.hasFatalError(); ) { |
| |
| RefactoringParticipant participant= 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 (OperationCanceledException e) { |
| throw e; |
| } 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; |
| } |
| |
| @Override |
| 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<RefactoringParticipant> iter= fParticipants.iterator(); iter.hasNext();) { |
| final RefactoringParticipant participant= 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 (OperationCanceledException e) { |
| throw e; |
| } catch (RuntimeException e) { |
| disableParticipant(participant, e); |
| throw e; |
| } |
| if (pm.isCanceled()) |
| throw new OperationCanceledException(); |
| } |
| |
| fTextChangeMap= null; |
| |
| Change postChange= getProcessor().postCreateChange( |
| changes.toArray(new Change[changes.size()]), |
| new SubProgressMonitor(pm, 1)); |
| |
| ProcessorChange result= new ProcessorChange(getName()); |
| result.addAll(preChanges.toArray(new Change[preChanges.size()])); |
| result.add(processorChange); |
| result.addAll(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 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. |
| */ |
| @SuppressWarnings("unchecked") |
| @Override |
| public <T> T getAdapter(Class<T> clazz) { |
| if (clazz.isInstance(this)) { |
| return (T) this; |
| } |
| RefactoringProcessor processor= getProcessor(); |
| if (clazz.isInstance(processor)) |
| return (T) processor; |
| return super.getAdapter(clazz); |
| } |
| |
| /* non java-doc |
| * for debugging only |
| */ |
| @Override |
| 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, (TextChange) 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)) { |
| TextFileChange textFileChange= (TextFileChange) change; |
| IFile file= textFileChange.getFile(); |
| fTextChangeMap.put(file, textFileChange); |
| } |
| } else if (change instanceof CompositeChange) { |
| Change[] children= ((CompositeChange) change).getChildren(); |
| for (Change child : children) { |
| addToTextChangeMap(child); |
| } |
| } |
| } |
| } |