2 * Copyright 2000-2016 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.intellij.refactoring.rename;
19 import com.intellij.lang.findUsages.DescriptiveNameUtil;
20 import com.intellij.openapi.application.ApplicationManager;
21 import com.intellij.openapi.application.ModalityState;
22 import com.intellij.openapi.diagnostic.Logger;
23 import com.intellij.openapi.extensions.Extensions;
24 import com.intellij.openapi.progress.ProgressManager;
25 import com.intellij.openapi.project.Project;
26 import com.intellij.openapi.ui.MessageType;
27 import com.intellij.openapi.ui.Messages;
28 import com.intellij.openapi.util.Computable;
29 import com.intellij.openapi.util.Ref;
30 import com.intellij.openapi.util.text.StringUtil;
31 import com.intellij.openapi.wm.IdeFrame;
32 import com.intellij.openapi.wm.WindowManager;
33 import com.intellij.openapi.wm.ex.StatusBarEx;
34 import com.intellij.psi.*;
35 import com.intellij.psi.impl.light.LightElement;
36 import com.intellij.refactoring.BaseRefactoringProcessor;
37 import com.intellij.refactoring.RefactoringBundle;
38 import com.intellij.refactoring.copy.CopyFilesOrDirectoriesHandler;
39 import com.intellij.refactoring.listeners.RefactoringElementListener;
40 import com.intellij.refactoring.listeners.RefactoringEventData;
41 import com.intellij.refactoring.listeners.RefactoringEventListener;
42 import com.intellij.refactoring.rename.naming.AutomaticRenamer;
43 import com.intellij.refactoring.rename.naming.AutomaticRenamerFactory;
44 import com.intellij.refactoring.ui.ConflictsDialog;
45 import com.intellij.refactoring.util.CommonRefactoringUtil;
46 import com.intellij.refactoring.util.MoveRenameUsageInfo;
47 import com.intellij.refactoring.util.NonCodeUsageInfo;
48 import com.intellij.refactoring.util.RelatedUsageInfo;
49 import com.intellij.usageView.UsageInfo;
50 import com.intellij.usageView.UsageViewDescriptor;
51 import com.intellij.usageView.UsageViewUtil;
52 import com.intellij.util.IncorrectOperationException;
53 import com.intellij.util.containers.ContainerUtil;
54 import com.intellij.util.containers.MultiMap;
55 import org.jetbrains.annotations.NonNls;
56 import org.jetbrains.annotations.NotNull;
57 import org.jetbrains.annotations.Nullable;
59 import javax.swing.event.HyperlinkEvent;
60 import javax.swing.event.HyperlinkListener;
63 public class RenameProcessor extends BaseRefactoringProcessor {
64 private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.rename.RenameProcessor");
66 protected final LinkedHashMap<PsiElement, String> myAllRenames = new LinkedHashMap<>();
68 private @NotNull PsiElement myPrimaryElement;
69 private String myNewName = null;
71 private boolean mySearchInComments;
72 private boolean mySearchTextOccurrences;
73 protected boolean myForceShowPreview;
75 private String myCommandName;
77 private NonCodeUsageInfo[] myNonCodeUsages = new NonCodeUsageInfo[0];
78 private final List<AutomaticRenamerFactory> myRenamerFactories = new ArrayList<>();
79 private final List<AutomaticRenamer> myRenamers = new ArrayList<>();
80 private final List<UnresolvableCollisionUsageInfo> mySkippedUsages = new ArrayList<>();
82 public RenameProcessor(Project project,
83 @NotNull PsiElement element,
84 @NotNull @NonNls String newName,
85 boolean isSearchInComments,
86 boolean isSearchTextOccurrences) {
88 myPrimaryElement = element;
90 assertNonCompileElement(element);
91 //assertValidName(element, newName);
93 mySearchInComments = isSearchInComments;
94 mySearchTextOccurrences = isSearchTextOccurrences;
99 public Set<PsiElement> getElements() {
100 return Collections.unmodifiableSet(myAllRenames.keySet());
103 public String getNewName(PsiElement element) {
104 return myAllRenames.get(element);
107 public void addRenamerFactory(AutomaticRenamerFactory factory) {
108 if (!myRenamerFactories.contains(factory)) {
109 myRenamerFactories.add(factory);
113 public void removeRenamerFactory(AutomaticRenamerFactory factory) {
114 myRenamerFactories.remove(factory);
118 public void doRun() {
119 if (!myPrimaryElement.isValid()) return;
120 prepareRenaming(myPrimaryElement, myNewName, myAllRenames);
125 public void prepareRenaming(@NotNull final PsiElement element, final String newName, final LinkedHashMap<PsiElement, String> allRenames) {
126 final List<RenamePsiElementProcessor> processors = RenamePsiElementProcessor.allForElement(element);
127 myForceShowPreview = false;
128 for (RenamePsiElementProcessor processor : processors) {
129 if (processor.canProcessElement(element)) {
130 processor.prepareRenaming(element, newName, allRenames);
131 myForceShowPreview |= processor.forcesShowPreview();
137 private String getHelpID() {
138 return RenamePsiElementProcessor.forElement(myPrimaryElement).getHelpID(myPrimaryElement);
142 public boolean preprocessUsages(@NotNull final Ref<UsageInfo[]> refUsages) {
143 UsageInfo[] usagesIn = refUsages.get();
144 MultiMap<PsiElement, String> conflicts = new MultiMap<>();
146 RenameUtil.addConflictDescriptions(usagesIn, conflicts);
147 RenamePsiElementProcessor.forElement(myPrimaryElement).findExistingNameConflicts(myPrimaryElement, myNewName, conflicts, myAllRenames);
148 if (!conflicts.isEmpty()) {
150 final RefactoringEventData conflictData = new RefactoringEventData();
151 conflictData.putUserData(RefactoringEventData.CONFLICTS_KEY, conflicts.values());
152 myProject.getMessageBus().syncPublisher(RefactoringEventListener.REFACTORING_EVENT_TOPIC)
153 .conflictsDetected("refactoring.rename", conflictData);
155 if (ApplicationManager.getApplication().isUnitTestMode()) {
156 throw new ConflictsInTestsException(conflicts.values());
158 ConflictsDialog conflictsDialog = prepareConflictsDialog(conflicts, refUsages.get());
159 if (!conflictsDialog.showAndGet()) {
160 if (conflictsDialog.isShowConflicts()) prepareSuccessful();
165 final List<UsageInfo> variableUsages = new ArrayList<>();
166 if (!myRenamers.isEmpty()) {
167 if (!findRenamedVariables(variableUsages)) return false;
168 final LinkedHashMap<PsiElement, String> renames = new LinkedHashMap<>();
169 for (final AutomaticRenamer renamer : myRenamers) {
170 final List<? extends PsiNamedElement> variables = renamer.getElements();
171 for (final PsiNamedElement variable : variables) {
172 final String newName = renamer.getNewName(variable);
173 if (newName != null) {
174 addElement(variable, newName);
175 prepareRenaming(variable, newName, renames);
179 if (!renames.isEmpty()) {
180 for (PsiElement element : renames.keySet()) {
181 assertNonCompileElement(element);
183 myAllRenames.putAll(renames);
184 final Runnable runnable = () -> {
185 for (final Map.Entry<PsiElement, String> entry : renames.entrySet()) {
186 final UsageInfo[] usages =
187 ApplicationManager.getApplication().runReadAction(new Computable<UsageInfo[]>() {
189 public UsageInfo[] compute() {
190 return RenameUtil.findUsages(entry.getKey(), entry.getValue(), mySearchInComments, mySearchTextOccurrences, myAllRenames);
193 Collections.addAll(variableUsages, usages);
196 if (!ProgressManager.getInstance()
197 .runProcessWithProgressSynchronously(runnable, RefactoringBundle.message("searching.for.variables"), true, myProject)) {
203 final int[] choice = myAllRenames.size() > 1 ? new int[]{-1} : null;
205 for (Iterator<Map.Entry<PsiElement, String>> iterator = myAllRenames.entrySet().iterator(); iterator.hasNext(); ) {
206 Map.Entry<PsiElement, String> entry = iterator.next();
207 if (entry.getKey() instanceof PsiFile) {
208 final PsiFile file = (PsiFile)entry.getKey();
209 final PsiDirectory containingDirectory = file.getContainingDirectory();
210 if (CopyFilesOrDirectoriesHandler.checkFileExist(containingDirectory, choice, file, entry.getValue(), "Rename")) {
215 RenameUtil.checkRename(entry.getKey(), entry.getValue());
218 catch (IncorrectOperationException e) {
219 CommonRefactoringUtil.showErrorMessage(RefactoringBundle.message("rename.title"), e.getMessage(), getHelpID(), myProject);
223 final Set<UsageInfo> usagesSet = ContainerUtil.newLinkedHashSet(usagesIn);
224 usagesSet.addAll(variableUsages);
225 final List<UnresolvableCollisionUsageInfo> conflictUsages = RenameUtil.removeConflictUsages(usagesSet);
226 if (conflictUsages != null) {
227 mySkippedUsages.addAll(conflictUsages);
229 refUsages.set(usagesSet.toArray(new UsageInfo[usagesSet.size()]));
232 return PsiElementRenameHandler.canRename(myProject, null, myPrimaryElement);
235 public static void assertNonCompileElement(PsiElement element) {
236 LOG.assertTrue(!(element instanceof PsiCompiledElement), element);
239 private void assertValidName(PsiElement element, String newName) {
240 LOG.assertTrue(RenameUtil.isValidName(myProject, element, newName), "element: " + element + ", newName: " + newName);
243 private boolean findRenamedVariables(final List<UsageInfo> variableUsages) {
244 for (Iterator<AutomaticRenamer> iterator = myRenamers.iterator(); iterator.hasNext(); ) {
245 AutomaticRenamer automaticVariableRenamer = iterator.next();
246 if (!automaticVariableRenamer.hasAnythingToRename()) continue;
247 if (!showAutomaticRenamingDialog(automaticVariableRenamer)) {
252 final Runnable runnable = () -> ApplicationManager.getApplication().runReadAction(() -> {
253 for (final AutomaticRenamer renamer : myRenamers) {
254 renamer.findUsages(variableUsages, mySearchInComments, mySearchTextOccurrences, mySkippedUsages, myAllRenames);
258 return ProgressManager.getInstance()
259 .runProcessWithProgressSynchronously(runnable, RefactoringBundle.message("searching.for.variables"), true, myProject);
262 protected boolean showAutomaticRenamingDialog(AutomaticRenamer automaticVariableRenamer) {
263 if (ApplicationManager.getApplication().isUnitTestMode()) {
264 for (PsiNamedElement element : automaticVariableRenamer.getElements()) {
265 automaticVariableRenamer.setRename(element, automaticVariableRenamer.getNewName(element));
269 final AutomaticRenamingDialog dialog = new AutomaticRenamingDialog(myProject, automaticVariableRenamer);
270 return dialog.showAndGet();
273 public void addElement(@NotNull PsiElement element, @NotNull String newName) {
274 assertNonCompileElement(element);
275 myAllRenames.put(element, newName);
278 private void setNewName(@NotNull String newName) {
280 myAllRenames.put(myPrimaryElement, newName);
281 myCommandName = RefactoringBundle
282 .message("renaming.0.1.to.2", UsageViewUtil.getType(myPrimaryElement), DescriptiveNameUtil.getDescriptiveName(myPrimaryElement), newName);
287 protected UsageViewDescriptor createUsageViewDescriptor(@NotNull UsageInfo[] usages) {
288 return new RenameViewDescriptor(myAllRenames);
293 public UsageInfo[] findUsages() {
295 ArrayList<UsageInfo> result = new ArrayList<>();
297 List<PsiElement> elements = new ArrayList<>(myAllRenames.keySet());
298 //noinspection ForLoopReplaceableByForEach
299 for (int i = 0; i < elements.size(); i++) {
300 PsiElement element = elements.get(i);
301 if (element == null) {
302 LOG.error("primary: " + myPrimaryElement + "; renamers: " + myRenamers);
305 final String newName = myAllRenames.get(element);
306 final UsageInfo[] usages = RenameUtil.findUsages(element, newName, mySearchInComments, mySearchTextOccurrences, myAllRenames);
307 final List<UsageInfo> usagesList = Arrays.asList(usages);
308 result.addAll(usagesList);
310 for (AutomaticRenamerFactory factory : myRenamerFactories) {
311 if (factory.isApplicable(element)) {
312 myRenamers.add(factory.createRenamer(element, newName, usagesList));
316 for (AutomaticRenamerFactory factory : Extensions.getExtensions(AutomaticRenamerFactory.EP_NAME)) {
317 if (factory.getOptionName() == null && factory.isApplicable(element)) {
318 myRenamers.add(factory.createRenamer(element, newName, usagesList));
322 UsageInfo[] usageInfos = result.toArray(new UsageInfo[result.size()]);
323 usageInfos = UsageViewUtil.removeDuplicatedUsages(usageInfos);
328 protected void refreshElements(@NotNull PsiElement[] elements) {
329 LOG.assertTrue(elements.length > 0);
330 myPrimaryElement = elements[0];
332 final Iterator<String> newNames = myAllRenames.values().iterator();
333 LinkedHashMap<PsiElement, String> newAllRenames = new LinkedHashMap<>();
334 for (PsiElement resolved : elements) {
335 newAllRenames.put(resolved, newNames.next());
337 myAllRenames.clear();
338 myAllRenames.putAll(newAllRenames);
342 protected boolean isPreviewUsages(@NotNull UsageInfo[] usages) {
343 if (myForceShowPreview) return true;
344 if (super.isPreviewUsages(usages)) return true;
345 if (UsageViewUtil.reportNonRegularUsages(usages, myProject)) return true;
351 protected String getRefactoringId() {
352 return "refactoring.rename";
357 protected RefactoringEventData getBeforeData() {
358 final RefactoringEventData data = new RefactoringEventData();
359 data.addElement(myPrimaryElement);
365 protected RefactoringEventData getAfterData(@NotNull UsageInfo[] usages) {
366 final RefactoringEventData data = new RefactoringEventData();
367 data.addElement(myPrimaryElement);
372 public void performRefactoring(@NotNull UsageInfo[] usages) {
373 List<Runnable> postRenameCallbacks = new ArrayList<>();
375 final MultiMap<PsiElement, UsageInfo> classified = classifyUsages(myAllRenames.keySet(), usages);
376 for (final PsiElement element : myAllRenames.keySet()) {
377 String newName = myAllRenames.get(element);
379 final RefactoringElementListener elementListener = getTransaction().getElementListener(element);
380 final RenamePsiElementProcessor renamePsiElementProcessor = RenamePsiElementProcessor.forElement(element);
381 Runnable postRenameCallback = renamePsiElementProcessor.getPostRenameCallback(element, newName, elementListener);
382 final Collection<UsageInfo> infos = classified.get(element);
384 RenameUtil.doRename(element, newName, infos.toArray(new UsageInfo[infos.size()]), myProject, elementListener);
386 catch (final IncorrectOperationException e) {
387 RenameUtil.showErrorMessage(e, element, myProject);
390 if (postRenameCallback != null) {
391 postRenameCallbacks.add(postRenameCallback);
395 for (Runnable runnable : postRenameCallbacks) {
399 List<NonCodeUsageInfo> nonCodeUsages = new ArrayList<>();
400 for (UsageInfo usage : usages) {
401 if (usage instanceof NonCodeUsageInfo) {
402 nonCodeUsages.add((NonCodeUsageInfo)usage);
405 myNonCodeUsages = nonCodeUsages.toArray(new NonCodeUsageInfo[nonCodeUsages.size()]);
406 if (!mySkippedUsages.isEmpty()) {
407 if (!ApplicationManager.getApplication().isUnitTestMode() && !ApplicationManager.getApplication().isHeadlessEnvironment()) {
408 ApplicationManager.getApplication().invokeLater(() -> {
409 final IdeFrame ideFrame = WindowManager.getInstance().getIdeFrame(myProject);
410 if (ideFrame != null) {
412 StatusBarEx statusBar = (StatusBarEx)ideFrame.getStatusBar();
413 HyperlinkListener listener = new HyperlinkListener() {
415 public void hyperlinkUpdate(HyperlinkEvent e) {
416 if (e.getEventType() != HyperlinkEvent.EventType.ACTIVATED) return;
417 Messages.showMessageDialog("<html>Following usages were safely skipped:<br>" +
418 StringUtil.join(mySkippedUsages, unresolvableCollisionUsageInfo -> unresolvableCollisionUsageInfo.getDescription(), "<br>") +
419 "</html>", "Not All Usages Were Renamed", null);
422 statusBar.notifyProgressByBalloon(MessageType.WARNING, "<html><body>Unable to rename certain usages. <a href=\"\">Browse</a></body></html>", null, listener);
424 }, ModalityState.NON_MODAL);
430 protected void performPsiSpoilingRefactoring() {
431 RenameUtil.renameNonCodeUsages(myProject, myNonCodeUsages);
435 protected String getCommandName() {
436 return myCommandName;
439 public static MultiMap<PsiElement, UsageInfo> classifyUsages(Collection<? extends PsiElement> elements, UsageInfo[] usages) {
440 final MultiMap<PsiElement, UsageInfo> result = new MultiMap<>();
441 for (UsageInfo usage : usages) {
442 LOG.assertTrue(usage instanceof MoveRenameUsageInfo);
443 if (usage.getReference() instanceof LightElement) {
444 continue; //filter out implicit references (e.g. from derived class to super class' default constructor)
446 MoveRenameUsageInfo usageInfo = (MoveRenameUsageInfo)usage;
447 if (usage instanceof RelatedUsageInfo) {
448 final PsiElement relatedElement = ((RelatedUsageInfo)usage).getRelatedElement();
449 if (elements.contains(relatedElement)) {
450 result.putValue(relatedElement, usage);
453 PsiElement referenced = usageInfo.getReferencedElement();
454 if (elements.contains(referenced)) {
455 result.putValue(referenced, usage);
456 } else if (referenced != null) {
457 PsiElement indirect = referenced.getNavigationElement();
458 if (elements.contains(indirect)) {
459 result.putValue(indirect, usage);
468 public Collection<String> getNewNames() {
469 return myAllRenames.values();
472 public void setSearchInComments(boolean value) {
473 mySearchInComments = value;
476 public void setSearchTextOccurrences(boolean searchTextOccurrences) {
477 mySearchTextOccurrences = searchTextOccurrences;
480 public boolean isSearchInComments() {
481 return mySearchInComments;
484 public boolean isSearchTextOccurrences() {
485 return mySearchTextOccurrences;
488 public void setCommandName(final String commandName) {
489 myCommandName = commandName;