/* * Copyright 2000-2009 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.rename; import com.intellij.lang.findUsages.DescriptiveNameUtil; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.MessageType; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.wm.IdeFrame; import com.intellij.openapi.wm.WindowManager; import com.intellij.openapi.wm.ex.StatusBarEx; import com.intellij.psi.*; import com.intellij.psi.impl.light.LightElement; import com.intellij.refactoring.BaseRefactoringProcessor; import com.intellij.refactoring.RefactoringBundle; import com.intellij.refactoring.copy.CopyFilesOrDirectoriesHandler; import com.intellij.refactoring.listeners.RefactoringElementListener; import com.intellij.refactoring.listeners.RefactoringEventData; import com.intellij.refactoring.listeners.RefactoringEventListener; import com.intellij.refactoring.rename.naming.AutomaticRenamer; import com.intellij.refactoring.rename.naming.AutomaticRenamerFactory; import com.intellij.refactoring.ui.ConflictsDialog; import com.intellij.refactoring.util.*; import com.intellij.usageView.UsageInfo; import com.intellij.usageView.UsageViewDescriptor; import com.intellij.usageView.UsageViewUtil; import com.intellij.util.Function; import com.intellij.util.IncorrectOperationException; import com.intellij.util.containers.HashSet; import com.intellij.util.containers.MultiMap; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.event.HyperlinkEvent; import javax.swing.event.HyperlinkListener; import java.util.*; public class RenameProcessor extends BaseRefactoringProcessor { private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.rename.RenameProcessor"); protected final LinkedHashMap myAllRenames = new LinkedHashMap(); private PsiElement myPrimaryElement; private String myNewName = null; private boolean mySearchInComments; private boolean mySearchTextOccurrences; protected boolean myForceShowPreview; private String myCommandName; private NonCodeUsageInfo[] myNonCodeUsages = new NonCodeUsageInfo[0]; private final List myRenamerFactories = new ArrayList(); private final List myRenamers = new ArrayList(); private final List mySkippedUsages = new ArrayList(); public RenameProcessor(Project project, PsiElement element, @NotNull @NonNls String newName, boolean isSearchInComments, boolean isSearchTextOccurrences) { super(project); myPrimaryElement = element; assertNonCompileElement(element); mySearchInComments = isSearchInComments; mySearchTextOccurrences = isSearchTextOccurrences; setNewName(newName); } @Deprecated public RenameProcessor(Project project) { this(project, null, "", false, false); } public Set getElements() { return Collections.unmodifiableSet(myAllRenames.keySet()); } public String getNewName(PsiElement element) { return myAllRenames.get(element); } public void addRenamerFactory(AutomaticRenamerFactory factory) { if (!myRenamerFactories.contains(factory)) { myRenamerFactories.add(factory); } } public void removeRenamerFactory(AutomaticRenamerFactory factory) { myRenamerFactories.remove(factory); } @Override public void doRun() { prepareRenaming(myPrimaryElement, myNewName, myAllRenames); super.doRun(); } public void prepareRenaming(@NotNull final PsiElement element, final String newName, final LinkedHashMap allRenames) { final List processors = RenamePsiElementProcessor.allForElement(element); myForceShowPreview = false; for (RenamePsiElementProcessor processor : processors) { if (processor.canProcessElement(element)) { processor.prepareRenaming(element, newName, allRenames); myForceShowPreview |= processor.forcesShowPreview(); } } } @Nullable private String getHelpID() { return RenamePsiElementProcessor.forElement(myPrimaryElement).getHelpID(myPrimaryElement); } @Override public boolean preprocessUsages(final Ref refUsages) { UsageInfo[] usagesIn = refUsages.get(); MultiMap conflicts = new MultiMap(); RenameUtil.addConflictDescriptions(usagesIn, conflicts); RenamePsiElementProcessor.forElement(myPrimaryElement).findExistingNameConflicts(myPrimaryElement, myNewName, conflicts, myAllRenames); if (!conflicts.isEmpty()) { final RefactoringEventData conflictData = new RefactoringEventData(); conflictData.putUserData(RefactoringEventData.CONFLICTS_KEY, conflicts.values()); myProject.getMessageBus().syncPublisher(RefactoringEventListener.REFACTORING_EVENT_TOPIC).conflictsDetected("refactoring.rename", conflictData); if (ApplicationManager.getApplication().isUnitTestMode()) { throw new ConflictsInTestsException(conflicts.values()); } ConflictsDialog conflictsDialog = prepareConflictsDialog(conflicts, refUsages.get()); conflictsDialog.show(); if (!conflictsDialog.isOK()) { if (conflictsDialog.isShowConflicts()) prepareSuccessful(); return false; } } final List variableUsages = new ArrayList(); if (!myRenamers.isEmpty()) { if (!findRenamedVariables(variableUsages)) return false; final LinkedHashMap renames = new LinkedHashMap(); for (final AutomaticRenamer renamer : myRenamers) { final List variables = renamer.getElements(); for (final PsiNamedElement variable : variables) { final String newName = renamer.getNewName(variable); if (newName != null) { addElement(variable, newName); prepareRenaming(variable, newName, renames); } } } if (!renames.isEmpty()) { for (PsiElement element : renames.keySet()) { assertNonCompileElement(element); } myAllRenames.putAll(renames); final Runnable runnable = new Runnable() { @Override public void run() { for (Map.Entry entry : renames.entrySet()) { final UsageInfo[] usages = RenameUtil.findUsages(entry.getKey(), entry.getValue(), mySearchInComments, mySearchTextOccurrences, myAllRenames); Collections.addAll(variableUsages, usages); } } }; if (!ProgressManager.getInstance() .runProcessWithProgressSynchronously(runnable, RefactoringBundle.message("searching.for.variables"), true, myProject)) { return false; } } } final Set usagesSet = new HashSet(Arrays.asList(usagesIn)); usagesSet.addAll(variableUsages); final List conflictUsages = RenameUtil.removeConflictUsages(usagesSet); if (conflictUsages != null) { mySkippedUsages.addAll(conflictUsages); } refUsages.set(usagesSet.toArray(new UsageInfo[usagesSet.size()])); prepareSuccessful(); return PsiElementRenameHandler.canRename(myProject, null, myPrimaryElement); } public static void assertNonCompileElement(PsiElement element) { LOG.assertTrue(!(element instanceof PsiCompiledElement), element); } private boolean findRenamedVariables(final List variableUsages) { for (final AutomaticRenamer automaticVariableRenamer : myRenamers) { if (!automaticVariableRenamer.hasAnythingToRename()) continue; if (!showAutomaticRenamingDialog(automaticVariableRenamer)) return false; } final Runnable runnable = new Runnable() { @Override public void run() { for (final AutomaticRenamer renamer : myRenamers) { renamer.findUsages(variableUsages, mySearchInComments, mySearchTextOccurrences, mySkippedUsages, myAllRenames); } } }; return ProgressManager.getInstance() .runProcessWithProgressSynchronously(runnable, RefactoringBundle.message("searching.for.variables"), true, myProject); } protected boolean showAutomaticRenamingDialog(AutomaticRenamer automaticVariableRenamer) { if (ApplicationManager.getApplication().isUnitTestMode()){ for (PsiNamedElement element : automaticVariableRenamer.getElements()) { automaticVariableRenamer.setRename(element, automaticVariableRenamer.getNewName(element)); } return true; } final AutomaticRenamingDialog dialog = new AutomaticRenamingDialog(myProject, automaticVariableRenamer); dialog.show(); return dialog.isOK(); } public void addElement(@NotNull PsiElement element, @NotNull String newName) { assertNonCompileElement(element); myAllRenames.put(element, newName); } private void setNewName(@NotNull String newName) { if (myPrimaryElement == null) { myCommandName = RefactoringBundle.message("renaming.something"); return; } myNewName = newName; myAllRenames.put(myPrimaryElement, newName); myCommandName = RefactoringBundle .message("renaming.0.1.to.2", UsageViewUtil.getType(myPrimaryElement), DescriptiveNameUtil.getDescriptiveName(myPrimaryElement), newName); } @Override @NotNull protected UsageViewDescriptor createUsageViewDescriptor(UsageInfo[] usages) { return new RenameViewDescriptor(myAllRenames); } @Override @NotNull public UsageInfo[] findUsages() { myRenamers.clear(); ArrayList result = new ArrayList(); List elements = new ArrayList(myAllRenames.keySet()); //noinspection ForLoopReplaceableByForEach for (int i = 0; i < elements.size(); i++) { PsiElement element = elements.get(i); final String newName = myAllRenames.get(element); final UsageInfo[] usages = RenameUtil.findUsages(element, newName, mySearchInComments, mySearchTextOccurrences, myAllRenames); final List usagesList = Arrays.asList(usages); result.addAll(usagesList); for (AutomaticRenamerFactory factory : myRenamerFactories) { if (factory.isApplicable(element)) { myRenamers.add(factory.createRenamer(element, newName, usagesList)); } } for (AutomaticRenamerFactory factory : Extensions.getExtensions(AutomaticRenamerFactory.EP_NAME)) { if (factory.getOptionName() == null && factory.isApplicable(element)) { myRenamers.add(factory.createRenamer(element, newName, usagesList)); } } } UsageInfo[] usageInfos = result.toArray(new UsageInfo[result.size()]); usageInfos = UsageViewUtil.removeDuplicatedUsages(usageInfos); return usageInfos; } @Override protected void refreshElements(PsiElement[] elements) { LOG.assertTrue(elements.length > 0); if (myPrimaryElement != null) { myPrimaryElement = elements[0]; } final Iterator newNames = myAllRenames.values().iterator(); LinkedHashMap newAllRenames = new LinkedHashMap(); for (PsiElement resolved : elements) { newAllRenames.put(resolved, newNames.next()); } myAllRenames.clear(); myAllRenames.putAll(newAllRenames); } @Override protected boolean isPreviewUsages(UsageInfo[] usages) { if (myForceShowPreview) return true; if (super.isPreviewUsages(usages)) return true; if (UsageViewUtil.reportNonRegularUsages(usages, myProject)) return true; return false; } @Nullable @Override protected String getRefactoringId() { return "refactoring.rename"; } @Nullable @Override protected RefactoringEventData getBeforeData() { final RefactoringEventData data = new RefactoringEventData(); data.addElement(myPrimaryElement); return data; } @Nullable @Override protected RefactoringEventData getAfterData(UsageInfo[] usages) { final RefactoringEventData data = new RefactoringEventData(); data.addElement(myPrimaryElement); return data; } @Override public void performRefactoring(UsageInfo[] usages) { final int[] choice = myAllRenames.size() > 1 ? new int[]{-1} : null; String message = null; try { for (Iterator> iterator = myAllRenames.entrySet().iterator(); iterator.hasNext(); ) { Map.Entry entry = iterator.next(); if (entry.getKey() instanceof PsiFile) { final PsiFile file = (PsiFile)entry.getKey(); final PsiDirectory containingDirectory = file.getContainingDirectory(); if (CopyFilesOrDirectoriesHandler.checkFileExist(containingDirectory, choice, file, entry.getValue(), "Rename")) { iterator.remove(); continue; } } RenameUtil.checkRename(entry.getKey(), entry.getValue()); } } catch (IncorrectOperationException e) { message = e.getMessage(); } if (message != null) { CommonRefactoringUtil.showErrorMessage(RefactoringBundle.message("rename.title"), message, getHelpID(), myProject); return; } List postRenameCallbacks = new ArrayList(); final MultiMap classified = classifyUsages(myAllRenames.keySet(), usages); for (final PsiElement element : myAllRenames.keySet()) { String newName = myAllRenames.get(element); final RefactoringElementListener elementListener = getTransaction().getElementListener(element); final RenamePsiElementProcessor renamePsiElementProcessor = RenamePsiElementProcessor.forElement(element); Runnable postRenameCallback = renamePsiElementProcessor.getPostRenameCallback(element, newName, elementListener); final Collection infos = classified.get(element); try { RenameUtil.doRename(element, newName, infos.toArray(new UsageInfo[infos.size()]), myProject, elementListener); } catch (final IncorrectOperationException e) { RenameUtil.showErrorMessage(e, element, myProject); return; } if (postRenameCallback != null) { postRenameCallbacks.add(postRenameCallback); } } for (Runnable runnable : postRenameCallbacks) { runnable.run(); } List nonCodeUsages = new ArrayList(); for (UsageInfo usage : usages) { if (usage instanceof NonCodeUsageInfo) { nonCodeUsages.add((NonCodeUsageInfo)usage); } } myNonCodeUsages = nonCodeUsages.toArray(new NonCodeUsageInfo[nonCodeUsages.size()]); if (!mySkippedUsages.isEmpty()) { if (!ApplicationManager.getApplication().isUnitTestMode() && !ApplicationManager.getApplication().isHeadlessEnvironment()) { ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { final IdeFrame ideFrame = WindowManager.getInstance().getIdeFrame(myProject); if (ideFrame != null) { StatusBarEx statusBar = (StatusBarEx)ideFrame.getStatusBar(); HyperlinkListener listener = new HyperlinkListener() { @Override public void hyperlinkUpdate(HyperlinkEvent e) { if (e.getEventType() != HyperlinkEvent.EventType.ACTIVATED) return; Messages.showMessageDialog("Following usages were safely skipped:
" + StringUtil.join(mySkippedUsages, new Function() { @Override public String fun(UnresolvableCollisionUsageInfo unresolvableCollisionUsageInfo) { return unresolvableCollisionUsageInfo.getDescription(); } }, "
") + "", "Not All Usages Were Renamed", null); } }; statusBar.notifyProgressByBalloon(MessageType.WARNING, "Unable to rename certain usages. Browse", null, listener); } } }, ModalityState.NON_MODAL); } } } @Override protected void performPsiSpoilingRefactoring() { RenameUtil.renameNonCodeUsages(myProject, myNonCodeUsages); } @Override protected String getCommandName() { return myCommandName; } public static MultiMap classifyUsages(Collection elements, UsageInfo[] usages) { final MultiMap result = new MultiMap(); for (UsageInfo usage : usages) { LOG.assertTrue(usage instanceof MoveRenameUsageInfo); if (usage.getReference() instanceof LightElement) { continue; //filter out implicit references (e.g. from derived class to super class' default constructor) } MoveRenameUsageInfo usageInfo = (MoveRenameUsageInfo)usage; if (usage instanceof RelatedUsageInfo) { final PsiElement relatedElement = ((RelatedUsageInfo)usage).getRelatedElement(); if (elements.contains(relatedElement)) { result.putValue(relatedElement, usage); } } else { PsiElement referenced = usageInfo.getReferencedElement(); if (elements.contains(referenced)) { result.putValue(referenced, usage); } else if (referenced != null) { PsiElement indirect = referenced.getNavigationElement(); if (elements.contains(indirect)) { result.putValue(indirect, usage); } } } } return result; } public Collection getNewNames() { return myAllRenames.values(); } public void setSearchInComments(boolean value) { mySearchInComments = value; } public void setSearchTextOccurrences(boolean searchTextOccurrences) { mySearchTextOccurrences = searchTextOccurrences; } public boolean isSearchInComments() { return mySearchInComments; } public boolean isSearchTextOccurrences() { return mySearchTextOccurrences; } public void setCommandName(final String commandName) { myCommandName = commandName; } }