EA-91810 - assert: NoSwingUnderWriteAction.lambda$watchForEvents$
[idea/community.git] / platform / lang-impl / src / com / intellij / refactoring / rename / RenameProcessor.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 package com.intellij.refactoring.rename;
18
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;
58
59 import javax.swing.event.HyperlinkEvent;
60 import javax.swing.event.HyperlinkListener;
61 import java.util.*;
62
63 public class RenameProcessor extends BaseRefactoringProcessor {
64   private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.rename.RenameProcessor");
65
66   protected final LinkedHashMap<PsiElement, String> myAllRenames = new LinkedHashMap<>();
67
68   private @NotNull PsiElement myPrimaryElement;
69   private String myNewName = null;
70
71   private boolean mySearchInComments;
72   private boolean mySearchTextOccurrences;
73   protected boolean myForceShowPreview;
74
75   private String myCommandName;
76
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<>();
81
82   public RenameProcessor(Project project,
83                          @NotNull PsiElement element,
84                          @NotNull @NonNls String newName,
85                          boolean isSearchInComments,
86                          boolean isSearchTextOccurrences) {
87     super(project);
88     myPrimaryElement = element;
89
90     assertNonCompileElement(element);
91     //assertValidName(element, newName);
92
93     mySearchInComments = isSearchInComments;
94     mySearchTextOccurrences = isSearchTextOccurrences;
95
96     setNewName(newName);
97   }
98
99   public Set<PsiElement> getElements() {
100     return Collections.unmodifiableSet(myAllRenames.keySet());
101   }
102
103   public String getNewName(PsiElement element) {
104     return myAllRenames.get(element);
105   }
106
107   public void addRenamerFactory(AutomaticRenamerFactory factory) {
108     if (!myRenamerFactories.contains(factory)) {
109       myRenamerFactories.add(factory);
110     }
111   }
112
113   public void removeRenamerFactory(AutomaticRenamerFactory factory) {
114     myRenamerFactories.remove(factory);
115   }
116
117   @Override
118   public void doRun() {
119     if (!myPrimaryElement.isValid()) return;
120     prepareRenaming(myPrimaryElement, myNewName, myAllRenames);
121
122     super.doRun();
123   }
124
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();
132       }
133     }
134   }
135
136   @Nullable
137   private String getHelpID() {
138     return RenamePsiElementProcessor.forElement(myPrimaryElement).getHelpID(myPrimaryElement);
139   }
140
141   @Override
142   public boolean preprocessUsages(@NotNull final Ref<UsageInfo[]> refUsages) {
143     UsageInfo[] usagesIn = refUsages.get();
144     MultiMap<PsiElement, String> conflicts = new MultiMap<>();
145
146     RenameUtil.addConflictDescriptions(usagesIn, conflicts);
147     RenamePsiElementProcessor.forElement(myPrimaryElement).findExistingNameConflicts(myPrimaryElement, myNewName, conflicts, myAllRenames);
148     if (!conflicts.isEmpty()) {
149
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);
154
155       if (ApplicationManager.getApplication().isUnitTestMode()) {
156         throw new ConflictsInTestsException(conflicts.values());
157       }
158       ConflictsDialog conflictsDialog = prepareConflictsDialog(conflicts, refUsages.get());
159       if (!conflictsDialog.showAndGet()) {
160         if (conflictsDialog.isShowConflicts()) prepareSuccessful();
161         return false;
162       }
163     }
164
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);
176           }
177         }
178       }
179       if (!renames.isEmpty()) {
180         for (PsiElement element : renames.keySet()) {
181           assertNonCompileElement(element);
182         }
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[]>() {
188                 @Override
189                 public UsageInfo[] compute() {
190                   return RenameUtil.findUsages(entry.getKey(), entry.getValue(), mySearchInComments, mySearchTextOccurrences, myAllRenames);
191                 }
192               });
193             Collections.addAll(variableUsages, usages);
194           }
195         };
196         if (!ProgressManager.getInstance()
197           .runProcessWithProgressSynchronously(runnable, RefactoringBundle.message("searching.for.variables"), true, myProject)) {
198           return false;
199         }
200       }
201     }
202
203     final int[] choice = myAllRenames.size() > 1 ? new int[]{-1} : null;
204     String message = null;
205     try {
206       for (Iterator<Map.Entry<PsiElement, String>> iterator = myAllRenames.entrySet().iterator(); iterator.hasNext(); ) {
207         Map.Entry<PsiElement, String> entry = iterator.next();
208         if (entry.getKey() instanceof PsiFile) {
209           final PsiFile file = (PsiFile)entry.getKey();
210           final PsiDirectory containingDirectory = file.getContainingDirectory();
211           if (CopyFilesOrDirectoriesHandler.checkFileExist(containingDirectory, choice, file, entry.getValue(), "Rename")) {
212             iterator.remove();
213             continue;
214           }
215         }
216         RenameUtil.checkRename(entry.getKey(), entry.getValue());
217       }
218     }
219     catch (IncorrectOperationException e) {
220       message = e.getMessage();
221     }
222
223     if (message != null) {
224       CommonRefactoringUtil.showErrorMessage(RefactoringBundle.message("rename.title"), message, getHelpID(), myProject);
225       return false;
226     }
227
228     final Set<UsageInfo> usagesSet = ContainerUtil.newLinkedHashSet(usagesIn);
229     usagesSet.addAll(variableUsages);
230     final List<UnresolvableCollisionUsageInfo> conflictUsages = RenameUtil.removeConflictUsages(usagesSet);
231     if (conflictUsages != null) {
232       mySkippedUsages.addAll(conflictUsages);
233     }
234     refUsages.set(usagesSet.toArray(new UsageInfo[usagesSet.size()]));
235
236     prepareSuccessful();
237     return PsiElementRenameHandler.canRename(myProject, null, myPrimaryElement);
238   }
239
240   public static void assertNonCompileElement(PsiElement element) {
241     LOG.assertTrue(!(element instanceof PsiCompiledElement), element);
242   }
243   
244   private void assertValidName(PsiElement element, String newName) {
245     LOG.assertTrue(RenameUtil.isValidName(myProject, element, newName), "element: " + element + ", newName: " + newName);
246   }
247
248   private boolean findRenamedVariables(final List<UsageInfo> variableUsages) {
249     for (Iterator<AutomaticRenamer> iterator = myRenamers.iterator(); iterator.hasNext(); ) {
250       AutomaticRenamer automaticVariableRenamer = iterator.next();
251       if (!automaticVariableRenamer.hasAnythingToRename()) continue;
252       if (!showAutomaticRenamingDialog(automaticVariableRenamer)) {
253         iterator.remove();
254       }
255     }
256
257     final Runnable runnable = () -> ApplicationManager.getApplication().runReadAction(() -> {
258       for (final AutomaticRenamer renamer : myRenamers) {
259         renamer.findUsages(variableUsages, mySearchInComments, mySearchTextOccurrences, mySkippedUsages, myAllRenames);
260       }
261     });
262
263     return ProgressManager.getInstance()
264       .runProcessWithProgressSynchronously(runnable, RefactoringBundle.message("searching.for.variables"), true, myProject);
265   }
266
267   protected boolean showAutomaticRenamingDialog(AutomaticRenamer automaticVariableRenamer) {
268     if (ApplicationManager.getApplication().isUnitTestMode()) {
269       for (PsiNamedElement element : automaticVariableRenamer.getElements()) {
270         automaticVariableRenamer.setRename(element, automaticVariableRenamer.getNewName(element));
271       }
272       return true;
273     }
274     final AutomaticRenamingDialog dialog = new AutomaticRenamingDialog(myProject, automaticVariableRenamer);
275     return dialog.showAndGet();
276   }
277
278   public void addElement(@NotNull PsiElement element, @NotNull String newName) {
279     assertNonCompileElement(element);
280     myAllRenames.put(element, newName);
281   }
282
283   private void setNewName(@NotNull String newName) {
284     myNewName = newName;
285     myAllRenames.put(myPrimaryElement, newName);
286     myCommandName = RefactoringBundle
287       .message("renaming.0.1.to.2", UsageViewUtil.getType(myPrimaryElement), DescriptiveNameUtil.getDescriptiveName(myPrimaryElement), newName);
288   }
289
290   @Override
291   @NotNull
292   protected UsageViewDescriptor createUsageViewDescriptor(@NotNull UsageInfo[] usages) {
293     return new RenameViewDescriptor(myAllRenames);
294   }
295
296   @Override
297   @NotNull
298   public UsageInfo[] findUsages() {
299     myRenamers.clear();
300     ArrayList<UsageInfo> result = new ArrayList<>();
301
302     List<PsiElement> elements = new ArrayList<>(myAllRenames.keySet());
303     //noinspection ForLoopReplaceableByForEach
304     for (int i = 0; i < elements.size(); i++) {
305       PsiElement element = elements.get(i);
306       if (element == null) {
307         LOG.error("primary: " + myPrimaryElement + "; renamers: " + myRenamers);
308         continue;
309       }
310       final String newName = myAllRenames.get(element);
311       final UsageInfo[] usages = RenameUtil.findUsages(element, newName, mySearchInComments, mySearchTextOccurrences, myAllRenames);
312       final List<UsageInfo> usagesList = Arrays.asList(usages);
313       result.addAll(usagesList);
314
315       for (AutomaticRenamerFactory factory : myRenamerFactories) {
316         if (factory.isApplicable(element)) {
317           myRenamers.add(factory.createRenamer(element, newName, usagesList));
318         }
319       }
320
321       for (AutomaticRenamerFactory factory : Extensions.getExtensions(AutomaticRenamerFactory.EP_NAME)) {
322         if (factory.getOptionName() == null && factory.isApplicable(element)) {
323           myRenamers.add(factory.createRenamer(element, newName, usagesList));
324         }
325       }
326     }
327     UsageInfo[] usageInfos = result.toArray(new UsageInfo[result.size()]);
328     usageInfos = UsageViewUtil.removeDuplicatedUsages(usageInfos);
329     return usageInfos;
330   }
331
332   @Override
333   protected void refreshElements(@NotNull PsiElement[] elements) {
334     LOG.assertTrue(elements.length > 0);
335     myPrimaryElement = elements[0];
336
337     final Iterator<String> newNames = myAllRenames.values().iterator();
338     LinkedHashMap<PsiElement, String> newAllRenames = new LinkedHashMap<>();
339     for (PsiElement resolved : elements) {
340       newAllRenames.put(resolved, newNames.next());
341     }
342     myAllRenames.clear();
343     myAllRenames.putAll(newAllRenames);
344   }
345
346   @Override
347   protected boolean isPreviewUsages(@NotNull UsageInfo[] usages) {
348     if (myForceShowPreview) return true;
349     if (super.isPreviewUsages(usages)) return true;
350     if (UsageViewUtil.reportNonRegularUsages(usages, myProject)) return true;
351     return false;
352   }
353
354   @Nullable
355   @Override
356   protected String getRefactoringId() {
357     return "refactoring.rename";
358   }
359
360   @Nullable
361   @Override
362   protected RefactoringEventData getBeforeData() {
363     final RefactoringEventData data = new RefactoringEventData();
364     data.addElement(myPrimaryElement);
365     return data;
366   }
367
368   @Nullable
369   @Override
370   protected RefactoringEventData getAfterData(@NotNull UsageInfo[] usages) {
371     final RefactoringEventData data = new RefactoringEventData();
372     data.addElement(myPrimaryElement);
373     return data;
374   }
375
376   @Override
377   public void performRefactoring(@NotNull UsageInfo[] usages) {
378     List<Runnable> postRenameCallbacks = new ArrayList<>();
379
380     final MultiMap<PsiElement, UsageInfo> classified = classifyUsages(myAllRenames.keySet(), usages);
381     for (final PsiElement element : myAllRenames.keySet()) {
382       String newName = myAllRenames.get(element);
383
384       final RefactoringElementListener elementListener = getTransaction().getElementListener(element);
385       final RenamePsiElementProcessor renamePsiElementProcessor = RenamePsiElementProcessor.forElement(element);
386       Runnable postRenameCallback = renamePsiElementProcessor.getPostRenameCallback(element, newName, elementListener);
387       final Collection<UsageInfo> infos = classified.get(element);
388       try {
389         RenameUtil.doRename(element, newName, infos.toArray(new UsageInfo[infos.size()]), myProject, elementListener);
390       }
391       catch (final IncorrectOperationException e) {
392         RenameUtil.showErrorMessage(e, element, myProject);
393         return;
394       }
395       if (postRenameCallback != null) {
396         postRenameCallbacks.add(postRenameCallback);
397       }
398     }
399
400     for (Runnable runnable : postRenameCallbacks) {
401       runnable.run();
402     }
403
404     List<NonCodeUsageInfo> nonCodeUsages = new ArrayList<>();
405     for (UsageInfo usage : usages) {
406       if (usage instanceof NonCodeUsageInfo) {
407         nonCodeUsages.add((NonCodeUsageInfo)usage);
408       }
409     }
410     myNonCodeUsages = nonCodeUsages.toArray(new NonCodeUsageInfo[nonCodeUsages.size()]);
411     if (!mySkippedUsages.isEmpty()) {
412       if (!ApplicationManager.getApplication().isUnitTestMode() && !ApplicationManager.getApplication().isHeadlessEnvironment()) {
413         ApplicationManager.getApplication().invokeLater(() -> {
414           final IdeFrame ideFrame = WindowManager.getInstance().getIdeFrame(myProject);
415           if (ideFrame != null) {
416
417             StatusBarEx statusBar = (StatusBarEx)ideFrame.getStatusBar();
418             HyperlinkListener listener = new HyperlinkListener() {
419               @Override
420               public void hyperlinkUpdate(HyperlinkEvent e) {
421                 if (e.getEventType() != HyperlinkEvent.EventType.ACTIVATED) return;
422                 Messages.showMessageDialog("<html>Following usages were safely skipped:<br>" +
423                                            StringUtil.join(mySkippedUsages, unresolvableCollisionUsageInfo -> unresolvableCollisionUsageInfo.getDescription(), "<br>") +
424                                            "</html>", "Not All Usages Were Renamed", null);
425               }
426             };
427             statusBar.notifyProgressByBalloon(MessageType.WARNING, "<html><body>Unable to rename certain usages. <a href=\"\">Browse</a></body></html>", null, listener);
428           }
429         }, ModalityState.NON_MODAL);
430       }
431     }
432   }
433
434   @Override
435   protected void performPsiSpoilingRefactoring() {
436     RenameUtil.renameNonCodeUsages(myProject, myNonCodeUsages);
437   }
438
439   @Override
440   protected String getCommandName() {
441     return myCommandName;
442   }
443
444   public static MultiMap<PsiElement, UsageInfo> classifyUsages(Collection<? extends PsiElement> elements, UsageInfo[] usages) {
445     final MultiMap<PsiElement, UsageInfo> result = new MultiMap<>();
446     for (UsageInfo usage : usages) {
447       LOG.assertTrue(usage instanceof MoveRenameUsageInfo);
448       if (usage.getReference() instanceof LightElement) {
449         continue; //filter out implicit references (e.g. from derived class to super class' default constructor)
450       }
451       MoveRenameUsageInfo usageInfo = (MoveRenameUsageInfo)usage;
452       if (usage instanceof RelatedUsageInfo) {
453         final PsiElement relatedElement = ((RelatedUsageInfo)usage).getRelatedElement();
454         if (elements.contains(relatedElement)) {
455           result.putValue(relatedElement, usage);
456         }
457       } else {
458         PsiElement referenced = usageInfo.getReferencedElement();
459         if (elements.contains(referenced)) {
460           result.putValue(referenced, usage);
461         } else if (referenced != null) {
462           PsiElement indirect = referenced.getNavigationElement();
463           if (elements.contains(indirect)) {
464             result.putValue(indirect, usage);
465           }
466         }
467
468       }
469     }
470     return result;
471   }
472
473   public Collection<String> getNewNames() {
474     return myAllRenames.values();
475   }
476
477   public void setSearchInComments(boolean value) {
478     mySearchInComments = value;
479   }
480
481   public void setSearchTextOccurrences(boolean searchTextOccurrences) {
482     mySearchTextOccurrences = searchTextOccurrences;
483   }
484
485   public boolean isSearchInComments() {
486     return mySearchInComments;
487   }
488
489   public boolean isSearchTextOccurrences() {
490     return mySearchTextOccurrences;
491   }
492
493   public void setCommandName(final String commandName) {
494     myCommandName = commandName;
495   }
496 }