refactoring find suages: wait for smart mode (EA-63973)
[idea/community.git] / platform / lang-impl / src / com / intellij / refactoring / BaseRefactoringProcessor.java
1 /*
2  * Copyright 2000-2014 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;
18
19 import com.intellij.find.findUsages.PsiElement2UsageTargetAdapter;
20 import com.intellij.history.LocalHistory;
21 import com.intellij.history.LocalHistoryAction;
22 import com.intellij.ide.DataManager;
23 import com.intellij.lang.Language;
24 import com.intellij.openapi.actionSystem.CommonDataKeys;
25 import com.intellij.openapi.application.ApplicationManager;
26 import com.intellij.openapi.command.CommandProcessor;
27 import com.intellij.openapi.command.UndoConfirmationPolicy;
28 import com.intellij.openapi.command.undo.BasicUndoableAction;
29 import com.intellij.openapi.command.undo.UndoManager;
30 import com.intellij.openapi.command.undo.UndoableAction;
31 import com.intellij.openapi.diagnostic.Logger;
32 import com.intellij.openapi.extensions.Extensions;
33 import com.intellij.openapi.progress.ProcessCanceledException;
34 import com.intellij.openapi.progress.ProgressManager;
35 import com.intellij.openapi.project.DumbService;
36 import com.intellij.openapi.project.IndexNotReadyException;
37 import com.intellij.openapi.project.Project;
38 import com.intellij.openapi.ui.Messages;
39 import com.intellij.openapi.util.Computable;
40 import com.intellij.openapi.util.EmptyRunnable;
41 import com.intellij.openapi.util.Factory;
42 import com.intellij.openapi.util.Ref;
43 import com.intellij.openapi.util.text.StringUtil;
44 import com.intellij.openapi.wm.impl.status.StatusBarUtil;
45 import com.intellij.psi.PsiDocumentManager;
46 import com.intellij.psi.PsiElement;
47 import com.intellij.psi.PsiFile;
48 import com.intellij.psi.util.PsiUtilCore;
49 import com.intellij.refactoring.listeners.RefactoringEventData;
50 import com.intellij.refactoring.listeners.RefactoringEventListener;
51 import com.intellij.refactoring.listeners.RefactoringListenerManager;
52 import com.intellij.refactoring.listeners.impl.RefactoringListenerManagerImpl;
53 import com.intellij.refactoring.listeners.impl.RefactoringTransaction;
54 import com.intellij.refactoring.ui.ConflictsDialog;
55 import com.intellij.refactoring.util.CommonRefactoringUtil;
56 import com.intellij.refactoring.util.MoveRenameUsageInfo;
57 import com.intellij.ui.GuiUtils;
58 import com.intellij.usageView.UsageInfo;
59 import com.intellij.usageView.UsageViewDescriptor;
60 import com.intellij.usageView.UsageViewUtil;
61 import com.intellij.usages.*;
62 import com.intellij.usages.rules.PsiElementUsage;
63 import com.intellij.util.Processor;
64 import com.intellij.util.containers.HashSet;
65 import com.intellij.util.containers.MultiMap;
66 import com.intellij.util.ui.UIUtil;
67 import gnu.trove.THashSet;
68 import gnu.trove.TObjectHashingStrategy;
69 import org.jetbrains.annotations.NotNull;
70 import org.jetbrains.annotations.Nullable;
71
72 import java.lang.reflect.InvocationTargetException;
73 import java.util.*;
74
75 public abstract class BaseRefactoringProcessor implements Runnable {
76   private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.BaseRefactoringProcessor");
77
78   @NotNull
79   protected final Project myProject;
80
81   private RefactoringTransaction myTransaction;
82   private boolean myIsPreviewUsages;
83   protected Runnable myPrepareSuccessfulSwingThreadCallback = EmptyRunnable.INSTANCE;
84
85   protected BaseRefactoringProcessor(@NotNull Project project) {
86     this(project, null);
87   }
88
89   protected BaseRefactoringProcessor(@NotNull Project project, @Nullable Runnable prepareSuccessfulCallback) {
90     myProject = project;
91     myPrepareSuccessfulSwingThreadCallback = prepareSuccessfulCallback;
92   }
93
94   @NotNull
95   protected abstract UsageViewDescriptor createUsageViewDescriptor(UsageInfo[] usages);
96
97   /**
98    * Is called inside atomic action.
99    */
100   @NotNull
101   protected abstract UsageInfo[] findUsages();
102
103   /**
104    * is called when usage search is re-run.
105    *
106    * @param elements - refreshed elements that are returned by UsageViewDescriptor.getElements()
107    */
108   protected void refreshElements(PsiElement[] elements) {}
109
110   /**
111    * Is called inside atomic action.
112    *
113    * @param refUsages usages to be filtered
114    * @return true if preprocessed successfully
115    */
116   protected boolean preprocessUsages(Ref<UsageInfo[]> refUsages) {
117     prepareSuccessful();
118     return true;
119   }
120
121   /**
122    * Is called inside atomic action.
123    */
124   protected boolean isPreviewUsages(UsageInfo[] usages) {
125     return myIsPreviewUsages;
126   }
127
128   protected boolean isPreviewUsages() {
129     return myIsPreviewUsages;
130   }
131
132
133   public void setPreviewUsages(boolean isPreviewUsages) {
134     myIsPreviewUsages = isPreviewUsages;
135   }
136
137   public void setPrepareSuccessfulSwingThreadCallback(Runnable prepareSuccessfulSwingThreadCallback) {
138     myPrepareSuccessfulSwingThreadCallback = prepareSuccessfulSwingThreadCallback;
139   }
140
141   protected RefactoringTransaction getTransaction() {
142     return myTransaction;
143   }
144
145   /**
146    * Is called in a command and inside atomic action.
147    */
148   protected abstract void performRefactoring(UsageInfo[] usages);
149
150   protected abstract String getCommandName();
151
152   protected void doRun() {
153     PsiDocumentManager.getInstance(myProject).commitAllDocuments();
154     final Ref<UsageInfo[]> refUsages = new Ref<UsageInfo[]>();
155     final Ref<Language> refErrorLanguage = new Ref<Language>();
156     final Ref<Boolean> refProcessCanceled = new Ref<Boolean>();
157     final Ref<Boolean> dumbModeOccurred = new Ref<Boolean>();
158     final Ref<Boolean> anyException = new Ref<Boolean>();
159
160     final Runnable findUsagesRunnable = new Runnable() {
161       @Override
162       public void run() {
163         try {
164           refUsages.set(DumbService.getInstance(myProject).runReadActionInSmartMode(new Computable<UsageInfo[]>() {
165             @Override
166             public UsageInfo[] compute() {
167               return findUsages();
168             }
169           }));
170         }
171         catch (UnknownReferenceTypeException e) {
172           refErrorLanguage.set(e.getElementLanguage());
173         }
174         catch (ProcessCanceledException e) {
175           refProcessCanceled.set(Boolean.TRUE);
176         }
177         catch (IndexNotReadyException e) {
178           dumbModeOccurred.set(Boolean.TRUE);
179         }
180         catch (Throwable e) {
181           anyException.set(Boolean.TRUE);
182           LOG.error(e);
183         }
184       }
185     };
186
187     if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(findUsagesRunnable, RefactoringBundle.message("progress.text"),
188                                                                            true, myProject)) {
189       return;
190     }
191
192     if (!refErrorLanguage.isNull()) {
193       Messages.showErrorDialog(myProject, RefactoringBundle.message("unsupported.refs.found", refErrorLanguage.get().getDisplayName()), RefactoringBundle.message("error.title"));
194       return;
195     }
196     if (!dumbModeOccurred.isNull()) {
197       DumbService.getInstance(myProject).showDumbModeNotification("Usage search is not available until indices are ready");
198       return;
199     }
200     if (!refProcessCanceled.isNull()) {
201       Messages.showErrorDialog(myProject, "Index corruption detected. Please retry the refactoring - indexes will be rebuilt automatically", RefactoringBundle.message("error.title"));
202       return;
203     }
204
205     if (!anyException.isNull()) {
206       //do not proceed if find usages fails
207       return;
208     }
209     assert !refUsages.isNull(): "Null usages from processor " + this;
210     if (!preprocessUsages(refUsages)) return;
211     final UsageInfo[] usages = refUsages.get();
212     assert usages != null;
213     UsageViewDescriptor descriptor = createUsageViewDescriptor(usages);
214
215     boolean isPreview = isPreviewUsages(usages);
216     if (!isPreview) {
217       isPreview = !ensureElementsWritable(usages, descriptor) || UsageViewUtil.hasReadOnlyUsages(usages);
218       if (isPreview) {
219         StatusBarUtil.setStatusBarInfo(myProject, RefactoringBundle.message("readonly.occurences.found"));
220       }
221     }
222     if (isPreview) {
223       previewRefactoring(usages);
224     }
225     else {
226       execute(usages);
227     }
228   }
229
230   protected void previewRefactoring(final UsageInfo[] usages) {
231     if (ApplicationManager.getApplication().isUnitTestMode()) {
232       execute(usages);
233       return;
234     }
235     final UsageViewDescriptor viewDescriptor = createUsageViewDescriptor(usages);
236     final PsiElement[] elements = viewDescriptor.getElements();
237     final PsiElement2UsageTargetAdapter[] targets = PsiElement2UsageTargetAdapter.convert(elements);
238     Factory<UsageSearcher> factory = new Factory<UsageSearcher>() {
239       @Override
240       public UsageSearcher create() {
241         return new UsageInfoSearcherAdapter() {
242           @Override
243           public void generate(@NotNull final Processor<Usage> processor) {
244             ApplicationManager.getApplication().runReadAction(new Runnable() {
245               @Override
246               public void run() {
247                 for (int i = 0; i < elements.length; i++) {
248                   elements[i] = targets[i].getElement();
249                 }
250                 refreshElements(elements);
251               }
252             });
253             processUsages(processor, myProject);
254           }
255
256           @Override
257           protected UsageInfo[] findUsages() {
258             return BaseRefactoringProcessor.this.findUsages();
259           }
260         };
261       }
262     };
263
264     showUsageView(viewDescriptor, factory, usages);
265   }
266
267   protected boolean skipNonCodeUsages() {
268     return false;
269   }
270
271   private boolean ensureElementsWritable(@NotNull final UsageInfo[] usages, final UsageViewDescriptor descriptor) {
272     Set<PsiElement> elements = new THashSet<PsiElement>(TObjectHashingStrategy.IDENTITY); // protect against poorly implemented equality
273     for (UsageInfo usage : usages) {
274       assert usage != null: "Found null element in usages array";
275       if (skipNonCodeUsages() && usage.isNonCodeUsage()) continue;
276       PsiElement element = usage.getElement();
277       if (element != null) elements.add(element);
278     }
279     elements.addAll(getElementsToWrite(descriptor));
280     return ensureFilesWritable(myProject, elements);
281   }
282
283   private static boolean ensureFilesWritable(final Project project, Collection<? extends PsiElement> elements) {
284     PsiElement[] psiElements = PsiUtilCore.toPsiElementArray(elements);
285     return CommonRefactoringUtil.checkReadOnlyStatus(project, psiElements);
286   }
287
288   protected void execute(final UsageInfo[] usages) {
289     CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
290       @Override
291       public void run() {
292         Collection<UsageInfo> usageInfos = new LinkedHashSet<UsageInfo>(Arrays.asList(usages));
293         doRefactoring(usageInfos);
294         if (isGlobalUndoAction()) CommandProcessor.getInstance().markCurrentCommandAsGlobal(myProject);
295       }
296     }, getCommandName(), null, getUndoConfirmationPolicy());
297   }
298
299   protected boolean isGlobalUndoAction() {
300     return CommonDataKeys.EDITOR.getData(DataManager.getInstance().getDataContext()) == null;
301   }
302
303   @SuppressWarnings("MethodMayBeStatic")
304   protected UndoConfirmationPolicy getUndoConfirmationPolicy() {
305     return UndoConfirmationPolicy.DEFAULT;
306   }
307
308   private static UsageViewPresentation createPresentation(UsageViewDescriptor descriptor, final Usage[] usages) {
309     UsageViewPresentation presentation = new UsageViewPresentation();
310     presentation.setTabText(RefactoringBundle.message("usageView.tabText"));
311     presentation.setTargetsNodeText(descriptor.getProcessedElementsHeader());
312     presentation.setShowReadOnlyStatusAsRed(true);
313     presentation.setShowCancelButton(true);
314     presentation.setUsagesString(RefactoringBundle.message("usageView.usagesText"));
315     int codeUsageCount = 0;
316     int nonCodeUsageCount = 0;
317     int dynamicUsagesCount = 0;
318     Set<PsiFile> codeFiles = new HashSet<PsiFile>();
319     Set<PsiFile> nonCodeFiles = new HashSet<PsiFile>();
320     Set<PsiFile> dynamicUsagesCodeFiles = new HashSet<PsiFile>();
321
322     for (Usage usage : usages) {
323       if (usage instanceof PsiElementUsage) {
324         final PsiElementUsage elementUsage = (PsiElementUsage)usage;
325         final PsiElement element = elementUsage.getElement();
326         if (element == null) continue;
327         final PsiFile containingFile = element.getContainingFile();
328         if (elementUsage.isNonCodeUsage()) {
329           nonCodeUsageCount++;
330           nonCodeFiles.add(containingFile);
331         }
332         else {
333           codeUsageCount++;
334           codeFiles.add(containingFile);
335         }
336         if (usage instanceof UsageInfo2UsageAdapter) {
337           final UsageInfo usageInfo = ((UsageInfo2UsageAdapter)usage).getUsageInfo();
338           if (usageInfo instanceof MoveRenameUsageInfo && usageInfo.isDynamicUsage()) {
339             dynamicUsagesCount++;
340             dynamicUsagesCodeFiles.add(containingFile);
341           }
342         }
343       }
344     }
345     codeFiles.remove(null);
346     nonCodeFiles.remove(null);
347     dynamicUsagesCodeFiles.remove(null);
348
349     String codeReferencesText = descriptor.getCodeReferencesText(codeUsageCount, codeFiles.size());
350     presentation.setCodeUsagesString(codeReferencesText);
351     final String commentReferencesText = descriptor.getCommentReferencesText(nonCodeUsageCount, nonCodeFiles.size());
352     if (commentReferencesText != null) {
353       presentation.setNonCodeUsagesString(commentReferencesText);
354     }
355     presentation.setDynamicUsagesString("Dynamic " + StringUtil.decapitalize(descriptor.getCodeReferencesText(dynamicUsagesCount, dynamicUsagesCodeFiles.size())));
356     String generatedCodeString;
357     if (codeReferencesText.contains("in code")) {
358       generatedCodeString = StringUtil.replace(codeReferencesText, "in code", "in generated code");
359     }
360     else {
361       generatedCodeString = codeReferencesText + " in generated code";
362     }
363     presentation.setUsagesInGeneratedCodeString(generatedCodeString);
364     return presentation;
365   }
366
367   private void showUsageView(final UsageViewDescriptor viewDescriptor, final Factory<UsageSearcher> factory, final UsageInfo[] usageInfos) {
368     UsageViewManager viewManager = UsageViewManager.getInstance(myProject);
369
370     final PsiElement[] initialElements = viewDescriptor.getElements();
371     final UsageTarget[] targets = PsiElement2UsageTargetAdapter.convert(initialElements);
372     final Ref<Usage[]> convertUsagesRef = new Ref<Usage[]>();
373     if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() {
374       @Override
375       public void run() {
376         ApplicationManager.getApplication().runReadAction(new Runnable() {
377           @Override
378           public void run() {
379             convertUsagesRef.set(UsageInfo2UsageAdapter.convert(usageInfos));
380           }
381         });
382       }
383     }, "Preprocess usages", true, myProject)) return;
384
385     if (convertUsagesRef.isNull()) return;
386
387     final Usage[] usages = convertUsagesRef.get();
388
389     final UsageViewPresentation presentation = createPresentation(viewDescriptor, usages);
390
391     final UsageView usageView = viewManager.showUsages(targets, usages, presentation, factory);
392
393     final Runnable refactoringRunnable = new Runnable() {
394       @Override
395       public void run() {
396         Set<UsageInfo> usagesToRefactor = UsageViewUtil.getNotExcludedUsageInfos(usageView);
397         final UsageInfo[] infos = usagesToRefactor.toArray(new UsageInfo[usagesToRefactor.size()]);
398         if (ensureElementsWritable(infos, viewDescriptor)) {
399           execute(infos);
400         }
401       }
402     };
403
404     String canNotMakeString = RefactoringBundle.message("usageView.need.reRun");
405
406     addDoRefactoringAction(usageView, refactoringRunnable, canNotMakeString);
407   }
408
409   protected void addDoRefactoringAction(UsageView usageView, Runnable refactoringRunnable, String canNotMakeString) {
410     usageView.addPerformOperationAction(refactoringRunnable, getCommandName(), canNotMakeString,
411                                         RefactoringBundle.message("usageView.doAction"), false);
412   }
413
414   private void doRefactoring(@NotNull final Collection<UsageInfo> usageInfoSet) {
415    for (Iterator<UsageInfo> iterator = usageInfoSet.iterator(); iterator.hasNext();) {
416       UsageInfo usageInfo = iterator.next();
417       final PsiElement element = usageInfo.getElement();
418       if (element == null || !isToBeChanged(usageInfo)) {
419         iterator.remove();
420       }
421     }
422
423     LocalHistoryAction action = LocalHistory.getInstance().startAction(getCommandName());
424
425     final UsageInfo[] writableUsageInfos = usageInfoSet.toArray(new UsageInfo[usageInfoSet.size()]);
426     try {
427       PsiDocumentManager.getInstance(myProject).commitAllDocuments();
428       RefactoringListenerManagerImpl listenerManager = (RefactoringListenerManagerImpl)RefactoringListenerManager.getInstance(myProject);
429       myTransaction = listenerManager.startTransaction();
430       final Map<RefactoringHelper, Object> preparedData = new LinkedHashMap<RefactoringHelper, Object>();
431       final Runnable prepareHelpersRunnable = new Runnable() {
432         @Override
433         public void run() {
434           for (final RefactoringHelper helper : Extensions.getExtensions(RefactoringHelper.EP_NAME)) {
435             Object operation = ApplicationManager.getApplication().runReadAction(new Computable<Object>() {
436               @Override
437               public Object compute() {
438                 return helper.prepareOperation(writableUsageInfos);
439               }
440             });
441             preparedData.put(helper, operation);
442           }
443         }
444       };
445
446       ProgressManager.getInstance().runProcessWithProgressSynchronously(prepareHelpersRunnable, "Prepare ...", false, myProject);
447
448       ApplicationManager.getApplication().runWriteAction(new Runnable() {
449         @Override
450         public void run() {
451           final String refactoringId = getRefactoringId();
452           if (refactoringId != null) {
453             RefactoringEventData data = getBeforeData();
454             if (data != null) {
455               data.addUsages(usageInfoSet);
456             }
457             myProject.getMessageBus().syncPublisher(RefactoringEventListener.REFACTORING_EVENT_TOPIC).refactoringStarted(refactoringId, data);
458           }
459
460           try {
461             if (refactoringId != null) {
462               UndoableAction action = new BasicUndoableAction() {
463                 @Override
464                 public void undo() {
465                   myProject.getMessageBus().syncPublisher(RefactoringEventListener.REFACTORING_EVENT_TOPIC).undoRefactoring(refactoringId);
466                 }
467   
468                 @Override
469                 public void redo() {
470                 }
471               };
472               UndoManager.getInstance(myProject).undoableActionPerformed(action);
473             }
474
475             performRefactoring(writableUsageInfos);
476           }
477           finally {
478             if (refactoringId != null) {
479               myProject.getMessageBus()
480                 .syncPublisher(RefactoringEventListener.REFACTORING_EVENT_TOPIC).refactoringDone(refactoringId, getAfterData(writableUsageInfos));
481             }
482           }
483         }
484       });
485
486       for(Map.Entry<RefactoringHelper, Object> e: preparedData.entrySet()) {
487         //noinspection unchecked
488         e.getKey().performOperation(myProject, e.getValue());
489       }
490       myTransaction.commit();
491       ApplicationManager.getApplication().runWriteAction(new Runnable() {
492         @Override
493         public void run() {
494           performPsiSpoilingRefactoring();
495         }
496       });
497     }
498     finally {
499       action.finish();
500     }
501
502     int count = writableUsageInfos.length;
503     if (count > 0) {
504       StatusBarUtil.setStatusBarInfo(myProject, RefactoringBundle.message("statusBar.refactoring.result", count));
505     }
506     else {
507       if (!isPreviewUsages(writableUsageInfos)) {
508         StatusBarUtil.setStatusBarInfo(myProject, RefactoringBundle.message("statusBar.noUsages"));
509       }
510     }
511   }
512
513   protected boolean isToBeChanged(UsageInfo usageInfo) {
514     return usageInfo.isWritable();
515   }
516
517   /**
518    * Refactorings that spoil PSI (write something directly to documents etc.) should
519    * do that in this method.<br>
520    * This method is called immediately after
521    * <code>{@link #performRefactoring(UsageInfo[])}</code>.
522    */
523   protected void performPsiSpoilingRefactoring() {
524   }
525
526   protected void prepareSuccessful() {
527     if (myPrepareSuccessfulSwingThreadCallback != null) {
528       // make sure that dialog is closed in swing thread
529       try {
530         GuiUtils.runOrInvokeAndWait(myPrepareSuccessfulSwingThreadCallback);
531       }
532       catch (InterruptedException e) {
533         LOG.error(e);
534       }
535       catch (InvocationTargetException e) {
536         LOG.error(e);
537       }
538     }
539   }
540
541   @Override
542   public final void run() {
543     if (ApplicationManager.getApplication().isUnitTestMode()) {
544       ApplicationManager.getApplication().assertIsDispatchThread();
545       doRun();
546       UIUtil.dispatchAllInvocationEvents();
547       UIUtil.dispatchAllInvocationEvents();
548       return;
549     }
550     if (ApplicationManager.getApplication().isWriteAccessAllowed()) {
551       DumbService.getInstance(myProject).smartInvokeLater(new Runnable() {
552         @Override
553         public void run() {
554           doRun();
555         }
556       });
557     }
558     else {
559       doRun();
560     }
561   }
562
563   public static class ConflictsInTestsException extends RuntimeException {
564     private final Collection<? extends String> messages;
565
566     private static boolean myTestIgnore = false;
567
568     public ConflictsInTestsException(Collection<? extends String> messages) {
569       this.messages = messages;
570     }
571
572     public static void setTestIgnore(boolean myIgnore) {
573       myTestIgnore = myIgnore;
574     }
575
576     public static boolean isTestIgnore() {
577       return myTestIgnore;
578     }
579
580     public Collection<String> getMessages() {
581         List<String> result = new ArrayList<String>(messages);
582         for (int i = 0; i < messages.size(); i++) {
583           result.set(i, result.get(i).replaceAll("<[^>]+>", ""));
584         }
585         return result;
586       }
587
588     @Override
589     public String getMessage() {
590       return StringUtil.join(messages, "\n");
591     }
592   }
593
594   @Deprecated
595   protected boolean showConflicts(final MultiMap<PsiElement, String> conflicts) {
596     return showConflicts(conflicts, null);
597   }
598
599   protected boolean showConflicts(final MultiMap<PsiElement, String> conflicts, @Nullable final UsageInfo[] usages) {
600     if (!conflicts.isEmpty() && ApplicationManager.getApplication().isUnitTestMode()) {
601       throw new ConflictsInTestsException(conflicts.values());
602     }
603
604     if (myPrepareSuccessfulSwingThreadCallback != null && !conflicts.isEmpty()) {
605       final String refactoringId = getRefactoringId();
606       if (refactoringId != null) {
607         RefactoringEventData conflictUsages = new RefactoringEventData();
608         conflictUsages.putUserData(RefactoringEventData.CONFLICTS_KEY, conflicts.values());
609         myProject.getMessageBus().syncPublisher(RefactoringEventListener.REFACTORING_EVENT_TOPIC)
610           .conflictsDetected(refactoringId, conflictUsages);
611       }
612       final ConflictsDialog conflictsDialog = prepareConflictsDialog(conflicts, usages);
613       if (!conflictsDialog.showAndGet()) {
614         if (conflictsDialog.isShowConflicts()) prepareSuccessful();
615         return false;
616       }
617     }
618
619     prepareSuccessful();
620     return true;
621   }
622
623   @NotNull
624   protected ConflictsDialog prepareConflictsDialog(MultiMap<PsiElement, String> conflicts, @Nullable final UsageInfo[] usages) {
625     final ConflictsDialog conflictsDialog = createConflictsDialog(conflicts, usages);
626     conflictsDialog.setCommandName(getCommandName());
627     return conflictsDialog;
628   }
629
630   @Nullable
631   protected RefactoringEventData getBeforeData() {
632     return null;
633   }
634
635   @Nullable
636   protected RefactoringEventData getAfterData(UsageInfo[] usages) {
637     return null;
638   }
639
640   @Nullable
641   protected String getRefactoringId() {
642     return null;
643   }
644   
645   @NotNull
646   protected ConflictsDialog createConflictsDialog(MultiMap<PsiElement, String> conflicts, @Nullable final UsageInfo[] usages) {
647     return new ConflictsDialog(myProject, conflicts, usages == null ? null : new Runnable() {
648         @Override
649         public void run() {
650           execute(usages);
651         }
652       }, false, true);
653   }
654
655   @NotNull
656   protected Collection<? extends PsiElement> getElementsToWrite(@NotNull UsageViewDescriptor descriptor) {
657     return Arrays.asList(descriptor.getElements());
658   }
659
660   public static class UnknownReferenceTypeException extends RuntimeException {
661     private final Language myElementLanguage;
662
663     public UnknownReferenceTypeException(final Language elementLanguage) {
664       myElementLanguage = elementLanguage;
665     }
666
667     public Language getElementLanguage() {
668       return myElementLanguage;
669     }
670   }
671 }