| /******************************************************************************* |
| * Copyright (c) 2000, 2006 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.participants; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| 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.NullProgressMonitor; |
| import org.eclipse.core.runtime.OperationCanceledException; |
| import org.eclipse.core.runtime.PerformanceStats; |
| import org.eclipse.core.runtime.SubProgressMonitor; |
| |
| import org.eclipse.core.resources.IFile; |
| |
| import org.eclipse.ltk.core.refactoring.Change; |
| import org.eclipse.ltk.core.refactoring.CompositeChange; |
| 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 abstract base implementation for refactorings that are split into |
| * one refactoring processor and 0..n participants. |
| * <p> |
| * This class should be subclassed by clients wishing to provide a special |
| * refactoring which uses a processor/participant architecture. |
| * </p> |
| * @since 3.0 |
| */ |
| public abstract 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 List/*<RefactoringParticipant>*/ fParticipants; |
| private SharableParticipants fSharedParticipants= new SharableParticipants(); |
| |
| private Map/*<Object, TextChange>*/ fTextChangeMap; |
| |
| private static final List/*<RefactoringParticipant>*/ EMPTY_PARTICIPANTS= Collections.EMPTY_LIST; |
| |
| private static class ProcessorChange extends CompositeChange { |
| private Map fParticipantMap; |
| public ProcessorChange(String name) { |
| super(name); |
| markAsSynthetic(); |
| } |
| public void setParticipantMap(Map map) { |
| fParticipantMap= map; |
| } |
| protected void internalHandleException(Change change, Throwable e) { |
| if (e instanceof OperationCanceledException) |
| return; |
| |
| RefactoringParticipant participant= (RefactoringParticipant)fParticipantMap.get(change); |
| if (participant != null) { |
| ParticipantDescriptor descriptor= participant.getDescriptor(); |
| descriptor.disable(); |
| RefactoringCorePlugin.logRemovedParticipant(descriptor, e); |
| } |
| } |
| 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. |
| * |
| * @deprecated use {@link #ProcessorBasedRefactoring(RefactoringProcessor)} instead |
| */ |
| protected ProcessorBasedRefactoring() { |
| } |
| |
| /** |
| * Creates a new processor based refactoring. |
| * |
| * @param processor the refactoring's main processor |
| * |
| * @since 3.1 |
| */ |
| protected ProcessorBasedRefactoring(RefactoringProcessor processor) { |
| processor.setRefactoring(this); |
| } |
| |
| /** |
| * Return the processor associated with this refactoring. The |
| * method must not return <code>null</code>. |
| * |
| * @return the processor associated with this refactoring |
| */ |
| public abstract RefactoringProcessor getProcessor(); |
| |
| |
| /** |
| * 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()) { |
| 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(); |
| |
| fParticipants= new ArrayList(Arrays.asList(getProcessor().loadParticipants(result, fSharedParticipants))); |
| if (fParticipants == null) |
| fParticipants= EMPTY_PARTICIPANTS; |
| 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() + 2); //$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 changes= new ArrayList(); |
| Map 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 change= participant.createChange(new SubProgressMonitor(pm, 1)); |
| |
| stats.endRun(); |
| |
| 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.add(processorChange); |
| result.addAll((Change[]) changes.toArray(new Change[changes.size()])); |
| result.setParticipantMap(participantMap); |
| 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 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]); |
| } |
| } |
| } |
| } |