/* * Copyright 2000-2015 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.refactoring.changeSignature; import com.intellij.ide.actions.CopyReferenceAction; import com.intellij.lang.findUsages.DescriptiveNameUtil; import com.intellij.openapi.command.undo.BasicUndoableAction; import com.intellij.openapi.command.undo.UndoManager; import com.intellij.openapi.command.undo.UndoableAction; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Ref; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiManager; import com.intellij.refactoring.BaseRefactoringProcessor; import com.intellij.refactoring.RefactoringBundle; import com.intellij.refactoring.listeners.RefactoringElementListener; import com.intellij.refactoring.listeners.RefactoringEventData; import com.intellij.refactoring.listeners.UndoRefactoringElementListener; import com.intellij.refactoring.listeners.impl.RefactoringTransaction; import com.intellij.refactoring.rename.ResolveSnapshotProvider; import com.intellij.refactoring.rename.inplace.VariableInplaceRenamer; import com.intellij.refactoring.util.MoveRenameUsageInfo; import com.intellij.usageView.UsageInfo; import com.intellij.util.IncorrectOperationException; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.MultiMap; import com.intellij.util.containers.hash.HashMap; import com.intellij.util.containers.hash.HashSet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; /** * @author Maxim.Medvedev */ public abstract class ChangeSignatureProcessorBase extends BaseRefactoringProcessor { private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.changeSignature.ChangeSignatureProcessorBase"); protected static final String REFACTORING_ID = "refactoring.changeSignature"; protected final ChangeInfo myChangeInfo; protected final PsiManager myManager; protected ChangeSignatureProcessorBase(Project project, ChangeInfo changeInfo) { super(project); myChangeInfo = changeInfo; myManager = PsiManager.getInstance(project); } protected ChangeSignatureProcessorBase(Project project, @Nullable Runnable prepareSuccessfulCallback, ChangeInfo changeInfo) { super(project, prepareSuccessfulCallback); myChangeInfo = changeInfo; myManager = PsiManager.getInstance(project); } @Override @NotNull protected UsageInfo[] findUsages() { return findUsages(myChangeInfo); } public static void collectConflictsFromExtensions(@NotNull Ref<UsageInfo[]> refUsages, MultiMap<PsiElement, String> conflictDescriptions, ChangeInfo changeInfo) { for (ChangeSignatureUsageProcessor usageProcessor : ChangeSignatureUsageProcessor.EP_NAME.getExtensions()) { final MultiMap<PsiElement, String> conflicts = usageProcessor.findConflicts(changeInfo, refUsages); for (PsiElement key : conflicts.keySet()) { Collection<String> collection = conflictDescriptions.get(key); if (collection.isEmpty()) collection = new com.intellij.util.containers.HashSet<>(); collection.addAll(conflicts.get(key)); conflictDescriptions.put(key, collection); } } } @NotNull public static UsageInfo[] findUsages(ChangeInfo changeInfo) { List<UsageInfo> infos = new ArrayList<>(); final ChangeSignatureUsageProcessor[] processors = ChangeSignatureUsageProcessor.EP_NAME.getExtensions(); for (ChangeSignatureUsageProcessor processor : processors) { for (UsageInfo info : processor.findUsages(changeInfo)) { LOG.assertTrue(info != null, processor); infos.add(info); } } infos = filterUsages(infos); return infos.toArray(new UsageInfo[infos.size()]); } protected static List<UsageInfo> filterUsages(List<UsageInfo> infos) { Map<PsiElement, MoveRenameUsageInfo> moveRenameInfos = new HashMap<>(); Set<PsiElement> usedElements = new HashSet<>(); List<UsageInfo> result = new ArrayList<>(infos.size() / 2); for (UsageInfo info : infos) { LOG.assertTrue(info != null); PsiElement element = info.getElement(); if (info instanceof MoveRenameUsageInfo) { if (usedElements.contains(element)) continue; moveRenameInfos.put(element, (MoveRenameUsageInfo)info); } else { moveRenameInfos.remove(element); usedElements.add(element); if (!(info instanceof PossiblyIncorrectUsage) || ((PossiblyIncorrectUsage)info).isCorrect()) { result.add(info); } } } result.addAll(moveRenameInfos.values()); return result; } @Override protected boolean isPreviewUsages(@NotNull UsageInfo[] usages) { for (ChangeSignatureUsageProcessor processor : ChangeSignatureUsageProcessor.EP_NAME.getExtensions()) { if (processor.shouldPreviewUsages(myChangeInfo, usages)) return true; } return super.isPreviewUsages(usages); } @Nullable @Override protected String getRefactoringId() { return REFACTORING_ID; } @Nullable @Override protected RefactoringEventData getBeforeData() { RefactoringEventData data = new RefactoringEventData(); data.addElement(getChangeInfo().getMethod()); return data; } @Nullable @Override protected RefactoringEventData getAfterData(@NotNull UsageInfo[] usages) { RefactoringEventData data = new RefactoringEventData(); data.addElement(getChangeInfo().getMethod()); return data; } @Override protected void performRefactoring(@NotNull UsageInfo[] usages) { RefactoringTransaction transaction = getTransaction(); final ChangeInfo changeInfo = myChangeInfo; final RefactoringElementListener elementListener = transaction == null ? null : transaction.getElementListener(changeInfo.getMethod()); final String fqn = CopyReferenceAction.elementToFqn(changeInfo.getMethod()); if (fqn != null) { UndoableAction action = new BasicUndoableAction() { @Override public void undo() { if (elementListener instanceof UndoRefactoringElementListener) { ((UndoRefactoringElementListener)elementListener).undoElementMovedOrRenamed(changeInfo.getMethod(), fqn); } } @Override public void redo() { } }; UndoManager.getInstance(myProject).undoableActionPerformed(action); } try { doChangeSignature(changeInfo, usages); final PsiElement method = changeInfo.getMethod(); LOG.assertTrue(method.isValid()); if (elementListener != null && changeInfo.isNameChanged()) { elementListener.elementRenamed(method); } } catch (IncorrectOperationException e) { LOG.error(e); } } public static void doChangeSignature(ChangeInfo changeInfo, @NotNull UsageInfo[] usages) { final ChangeSignatureUsageProcessor[] processors = ChangeSignatureUsageProcessor.EP_NAME.getExtensions(); final ResolveSnapshotProvider resolveSnapshotProvider = changeInfo.isParameterNamesChanged() ? VariableInplaceRenamer.INSTANCE.forLanguage(changeInfo.getMethod().getLanguage()) : null; final List<ResolveSnapshotProvider.ResolveSnapshot> snapshots = new ArrayList<>(); for (ChangeSignatureUsageProcessor processor : processors) { if (resolveSnapshotProvider != null) { processor.registerConflictResolvers(snapshots, resolveSnapshotProvider, usages, changeInfo); } } for (UsageInfo usage : usages) { for (ChangeSignatureUsageProcessor processor : processors) { if (processor.processUsage(changeInfo, usage, true, usages)) break; } } LOG.assertTrue(changeInfo.getMethod().isValid()); for (ChangeSignatureUsageProcessor processor : processors) { if (processor.processPrimaryMethod(changeInfo)) break; } for (UsageInfo usage : usages) { for (ChangeSignatureUsageProcessor processor : processors) { if (processor.processUsage(changeInfo, usage, false, usages)) break; } } if (!snapshots.isEmpty()) { for (ParameterInfo parameterInfo : changeInfo.getNewParameters()) { for (ResolveSnapshotProvider.ResolveSnapshot snapshot : snapshots) { snapshot.apply(parameterInfo.getName()); } } } } @Override protected String getCommandName() { return RefactoringBundle.message("changing.signature.of.0", DescriptiveNameUtil.getDescriptiveName(myChangeInfo.getMethod())); } public ChangeInfo getChangeInfo() { return myChangeInfo; } }