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;
204 String message = null;
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")) {
216 RenameUtil.checkRename(entry.getKey(), entry.getValue());
219 catch (IncorrectOperationException e) {
220 message = e.getMessage();
223 if (message != null) {
224 CommonRefactoringUtil.showErrorMessage(RefactoringBundle.message("rename.title"), message, getHelpID(), myProject);
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);
234 refUsages.set(usagesSet.toArray(new UsageInfo[usagesSet.size()]));
237 return PsiElementRenameHandler.canRename(myProject, null, myPrimaryElement);
240 public static void assertNonCompileElement(PsiElement element) {
241 LOG.assertTrue(!(element instanceof PsiCompiledElement), element);
244 private void assertValidName(PsiElement element, String newName) {
245 LOG.assertTrue(RenameUtil.isValidName(myProject, element, newName), "element: " + element + ", newName: " + newName);
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)) {
257 final Runnable runnable = () -> ApplicationManager.getApplication().runReadAction(() -> {
258 for (final AutomaticRenamer renamer : myRenamers) {
259 renamer.findUsages(variableUsages, mySearchInComments, mySearchTextOccurrences, mySkippedUsages, myAllRenames);
263 return ProgressManager.getInstance()
264 .runProcessWithProgressSynchronously(runnable, RefactoringBundle.message("searching.for.variables"), true, myProject);
267 protected boolean showAutomaticRenamingDialog(AutomaticRenamer automaticVariableRenamer) {
268 if (ApplicationManager.getApplication().isUnitTestMode()) {
269 for (PsiNamedElement element : automaticVariableRenamer.getElements()) {
270 automaticVariableRenamer.setRename(element, automaticVariableRenamer.getNewName(element));
274 final AutomaticRenamingDialog dialog = new AutomaticRenamingDialog(myProject, automaticVariableRenamer);
275 return dialog.showAndGet();
278 public void addElement(@NotNull PsiElement element, @NotNull String newName) {
279 assertNonCompileElement(element);
280 myAllRenames.put(element, newName);
283 private void setNewName(@NotNull String newName) {
285 myAllRenames.put(myPrimaryElement, newName);
286 myCommandName = RefactoringBundle
287 .message("renaming.0.1.to.2", UsageViewUtil.getType(myPrimaryElement), DescriptiveNameUtil.getDescriptiveName(myPrimaryElement), newName);
292 protected UsageViewDescriptor createUsageViewDescriptor(@NotNull UsageInfo[] usages) {
293 return new RenameViewDescriptor(myAllRenames);
298 public UsageInfo[] findUsages() {
300 ArrayList<UsageInfo> result = new ArrayList<>();
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);
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);
315 for (AutomaticRenamerFactory factory : myRenamerFactories) {
316 if (factory.isApplicable(element)) {
317 myRenamers.add(factory.createRenamer(element, newName, usagesList));
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));
327 UsageInfo[] usageInfos = result.toArray(new UsageInfo[result.size()]);
328 usageInfos = UsageViewUtil.removeDuplicatedUsages(usageInfos);
333 protected void refreshElements(@NotNull PsiElement[] elements) {
334 LOG.assertTrue(elements.length > 0);
335 myPrimaryElement = elements[0];
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());
342 myAllRenames.clear();
343 myAllRenames.putAll(newAllRenames);
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;
356 protected String getRefactoringId() {
357 return "refactoring.rename";
362 protected RefactoringEventData getBeforeData() {
363 final RefactoringEventData data = new RefactoringEventData();
364 data.addElement(myPrimaryElement);
370 protected RefactoringEventData getAfterData(@NotNull UsageInfo[] usages) {
371 final RefactoringEventData data = new RefactoringEventData();
372 data.addElement(myPrimaryElement);
377 public void performRefactoring(@NotNull UsageInfo[] usages) {
378 List<Runnable> postRenameCallbacks = new ArrayList<>();
380 final MultiMap<PsiElement, UsageInfo> classified = classifyUsages(myAllRenames.keySet(), usages);
381 for (final PsiElement element : myAllRenames.keySet()) {
382 String newName = myAllRenames.get(element);
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);
389 RenameUtil.doRename(element, newName, infos.toArray(new UsageInfo[infos.size()]), myProject, elementListener);
391 catch (final IncorrectOperationException e) {
392 RenameUtil.showErrorMessage(e, element, myProject);
395 if (postRenameCallback != null) {
396 postRenameCallbacks.add(postRenameCallback);
400 for (Runnable runnable : postRenameCallbacks) {
404 List<NonCodeUsageInfo> nonCodeUsages = new ArrayList<>();
405 for (UsageInfo usage : usages) {
406 if (usage instanceof NonCodeUsageInfo) {
407 nonCodeUsages.add((NonCodeUsageInfo)usage);
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) {
417 StatusBarEx statusBar = (StatusBarEx)ideFrame.getStatusBar();
418 HyperlinkListener listener = new HyperlinkListener() {
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);
427 statusBar.notifyProgressByBalloon(MessageType.WARNING, "<html><body>Unable to rename certain usages. <a href=\"\">Browse</a></body></html>", null, listener);
429 }, ModalityState.NON_MODAL);
435 protected void performPsiSpoilingRefactoring() {
436 RenameUtil.renameNonCodeUsages(myProject, myNonCodeUsages);
440 protected String getCommandName() {
441 return myCommandName;
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)
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);
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);
473 public Collection<String> getNewNames() {
474 return myAllRenames.values();
477 public void setSearchInComments(boolean value) {
478 mySearchInComments = value;
481 public void setSearchTextOccurrences(boolean searchTextOccurrences) {
482 mySearchTextOccurrences = searchTextOccurrences;
485 public boolean isSearchInComments() {
486 return mySearchInComments;
489 public boolean isSearchTextOccurrences() {
490 return mySearchTextOccurrences;
493 public void setCommandName(final String commandName) {
494 myCommandName = commandName;