1 package com
.intellij
.refactoring
;
3 import com
.intellij
.find
.findUsages
.PsiElement2UsageTargetAdapter
;
4 import com
.intellij
.history
.LocalHistory
;
5 import com
.intellij
.history
.LocalHistoryAction
;
6 import com
.intellij
.lang
.Language
;
7 import com
.intellij
.openapi
.application
.ApplicationManager
;
8 import com
.intellij
.openapi
.command
.CommandProcessor
;
9 import com
.intellij
.openapi
.diagnostic
.Logger
;
10 import com
.intellij
.openapi
.extensions
.Extensions
;
11 import com
.intellij
.openapi
.progress
.ProgressManager
;
12 import com
.intellij
.openapi
.progress
.ProcessCanceledException
;
13 import com
.intellij
.openapi
.project
.Project
;
14 import com
.intellij
.openapi
.ui
.Messages
;
15 import com
.intellij
.openapi
.util
.EmptyRunnable
;
16 import com
.intellij
.openapi
.util
.Factory
;
17 import com
.intellij
.openapi
.util
.Ref
;
18 import com
.intellij
.openapi
.wm
.WindowManager
;
19 import com
.intellij
.psi
.PsiDocumentManager
;
20 import com
.intellij
.psi
.PsiElement
;
21 import com
.intellij
.psi
.PsiFile
;
22 import com
.intellij
.refactoring
.listeners
.RefactoringListenerManager
;
23 import com
.intellij
.refactoring
.listeners
.impl
.RefactoringListenerManagerImpl
;
24 import com
.intellij
.refactoring
.listeners
.impl
.RefactoringTransaction
;
25 import com
.intellij
.refactoring
.ui
.ConflictsDialog
;
26 import com
.intellij
.refactoring
.util
.CommonRefactoringUtil
;
27 import com
.intellij
.ui
.GuiUtils
;
28 import com
.intellij
.usageView
.UsageInfo
;
29 import com
.intellij
.usageView
.UsageViewDescriptor
;
30 import com
.intellij
.usageView
.UsageViewUtil
;
31 import com
.intellij
.usages
.*;
32 import com
.intellij
.usages
.rules
.PsiElementUsage
;
33 import com
.intellij
.util
.Processor
;
34 import com
.intellij
.util
.containers
.HashSet
;
35 import gnu
.trove
.THashSet
;
36 import org
.jetbrains
.annotations
.NotNull
;
37 import org
.jetbrains
.annotations
.Nullable
;
39 import java
.lang
.reflect
.InvocationTargetException
;
42 public abstract class BaseRefactoringProcessor
{
43 private static final Logger LOG
= Logger
.getInstance("#com.intellij.refactoring.BaseRefactoringProcessor");
44 public static final Runnable EMPTY_CALLBACK
= EmptyRunnable
.getInstance();
45 protected final Project myProject
;
47 private RefactoringTransaction myTransaction
;
48 private boolean myIsPreviewUsages
;
49 protected Runnable myPrepareSuccessfulSwingThreadCallback
= EMPTY_CALLBACK
;
52 protected BaseRefactoringProcessor(Project project
) {
56 protected BaseRefactoringProcessor(Project project
, @Nullable Runnable prepareSuccessfulCallback
) {
58 myPrepareSuccessfulSwingThreadCallback
= prepareSuccessfulCallback
;
61 protected abstract UsageViewDescriptor
createUsageViewDescriptor(UsageInfo
[] usages
);
64 * Is called inside atomic action.
67 protected abstract UsageInfo
[] findUsages();
70 * is called when usage search is re-run.
72 * @param elements - refreshed elements that are returned by UsageViewDescriptor.getElements()
74 protected abstract void refreshElements(PsiElement
[] elements
);
77 * Is called inside atomic action.
79 * @param refUsages usages to be filtered
80 * @return true if preprocessed successfully
82 protected boolean preprocessUsages(Ref
<UsageInfo
[]> refUsages
) {
88 * Is called inside atomic action.
90 protected boolean isPreviewUsages(UsageInfo
[] usages
) {
91 return myIsPreviewUsages
;
94 protected boolean isPreviewUsages() {
95 return myIsPreviewUsages
;
99 public void setPreviewUsages(boolean isPreviewUsages
) {
100 myIsPreviewUsages
= isPreviewUsages
;
103 public void setPrepareSuccessfulSwingThreadCallback(Runnable prepareSuccessfulSwingThreadCallback
) {
104 myPrepareSuccessfulSwingThreadCallback
= prepareSuccessfulSwingThreadCallback
;
107 protected RefactoringTransaction
getTransaction() {
108 return myTransaction
;
112 * Is called in a command and inside atomic action.
114 protected abstract void performRefactoring(UsageInfo
[] usages
);
116 protected abstract String
getCommandName();
118 protected void doRun() {
119 PsiDocumentManager
.getInstance(myProject
).commitAllDocuments();
120 final Ref
<UsageInfo
[]> refUsages
= new Ref
<UsageInfo
[]>();
121 final Ref
<Language
> refErrorLanguage
= new Ref
<Language
>();
122 final Ref
<Boolean
> refProcessCanceled
= new Ref
<Boolean
>();
124 final Runnable findUsagesRunnable
= new Runnable() {
126 ApplicationManager
.getApplication().runReadAction(new Runnable() {
129 refUsages
.set(findUsages());
131 catch (UnknownReferenceTypeException e
) {
132 refErrorLanguage
.set(e
.getElementLanguage());
134 catch (ProcessCanceledException e
) {
135 refProcessCanceled
.set(Boolean
.TRUE
);
142 if (!ProgressManager
.getInstance().runProcessWithProgressSynchronously(findUsagesRunnable
, RefactoringBundle
.message("progress.text"), true, myProject
)) {
146 if (!refErrorLanguage
.isNull()) {
147 Messages
.showErrorDialog(myProject
, RefactoringBundle
.message("unsupported.refs.found", refErrorLanguage
.get().getDisplayName()), RefactoringBundle
.message("error.title"));
150 if (!refProcessCanceled
.isNull()) {
151 Messages
.showErrorDialog(myProject
, "Index corruption detected. Please retry the refactoring - indexes will be rebuilt automatically",
152 RefactoringBundle
.message("error.title"));
156 assert !refUsages
.isNull(): "Null usages from processor " + this;
157 if (!preprocessUsages(refUsages
)) return;
158 final UsageInfo
[] usages
= refUsages
.get();
159 assert usages
!= null;
160 UsageViewDescriptor descriptor
= createUsageViewDescriptor(usages
);
162 boolean isPreview
= isPreviewUsages(usages
);
164 isPreview
= !ensureElementsWritable(usages
, descriptor
) || UsageViewUtil
.hasReadOnlyUsages(usages
);
166 WindowManager
.getInstance().getStatusBar(myProject
).setInfo(RefactoringBundle
.message("readonly.occurences.found"));
170 previewRefactoring(usages
);
177 protected void previewRefactoring(final UsageInfo
[] usages
) {
178 final UsageViewDescriptor viewDescriptor
= createUsageViewDescriptor(usages
);
179 final PsiElement
[] elements
= viewDescriptor
.getElements();
180 final PsiElement2UsageTargetAdapter
[] targets
= PsiElement2UsageTargetAdapter
.convert(elements
);
181 Factory
<UsageSearcher
> factory
= new Factory
<UsageSearcher
>() {
182 public UsageSearcher
create() {
183 return new UsageSearcher() {
184 public void generate(final Processor
<Usage
> processor
) {
185 ApplicationManager
.getApplication().runReadAction(new Runnable() {
187 for (int i
= 0; i
< elements
.length
; i
++) {
188 elements
[i
] = targets
[i
].getElement();
190 refreshElements(elements
);
193 final Ref
<UsageInfo
[]> refUsages
= new Ref
<UsageInfo
[]>();
194 ApplicationManager
.getApplication().runReadAction(new Runnable() {
196 refUsages
.set(findUsages());
199 final Usage
[] usages
= UsageInfo2UsageAdapter
.convert(refUsages
.get());
201 for (Usage usage
: usages
) {
202 processor
.process(usage
);
209 showUsageView(viewDescriptor
, factory
, usages
);
212 private boolean ensureElementsWritable(@NotNull final UsageInfo
[] usages
, final UsageViewDescriptor descriptor
) {
213 Set
<PsiElement
> elements
= new THashSet
<PsiElement
>();
214 for (UsageInfo usage
: usages
) {
215 assert usage
!= null: "Found null element in usages array";
216 PsiElement element
= usage
.getElement();
217 if (element
!= null) elements
.add(element
);
219 elements
.addAll(getElementsToWrite(descriptor
));
220 return ensureFilesWritable(myProject
, elements
);
223 private static boolean ensureFilesWritable(final Project project
, Collection
<?
extends PsiElement
> elements
) {
224 PsiElement
[] psiElements
= elements
.toArray(new PsiElement
[elements
.size()]);
225 return CommonRefactoringUtil
.checkReadOnlyStatus(project
, psiElements
);
228 void execute(final UsageInfo
[] usages
) {
229 CommandProcessor
.getInstance().executeCommand(myProject
, new Runnable() {
231 ApplicationManager
.getApplication().runWriteAction(new Runnable() {
233 Collection
<UsageInfo
> usageInfos
= new HashSet
<UsageInfo
>(Arrays
.asList(usages
));
234 doRefactoring(usageInfos
);
238 }, getCommandName(), null);
241 private static UsageViewPresentation
createPresentation(UsageViewDescriptor descriptor
, final Usage
[] usages
) {
242 UsageViewPresentation presentation
= new UsageViewPresentation();
243 presentation
.setTabText(RefactoringBundle
.message("usageView.tabText"));
244 presentation
.setTargetsNodeText(descriptor
.getProcessedElementsHeader());
245 presentation
.setShowReadOnlyStatusAsRed(true);
246 presentation
.setShowCancelButton(true);
247 presentation
.setUsagesString(RefactoringBundle
.message("usageView.usagesText"));
248 int codeUsageCount
= 0;
249 int nonCodeUsageCount
= 0;
250 Set
<PsiFile
> codeFiles
= new HashSet
<PsiFile
>();
251 Set
<PsiFile
> nonCodeFiles
= new HashSet
<PsiFile
>();
253 for (Usage usage
: usages
) {
254 if (usage
instanceof PsiElementUsage
) {
255 final PsiElementUsage elementUsage
= (PsiElementUsage
)usage
;
256 if (elementUsage
.isNonCodeUsage()) {
258 nonCodeFiles
.add(elementUsage
.getElement().getContainingFile());
262 codeFiles
.add(elementUsage
.getElement().getContainingFile());
266 codeFiles
.remove(null);
267 nonCodeFiles
.remove(null);
269 presentation
.setCodeUsagesString(descriptor
.getCodeReferencesText(codeUsageCount
, codeFiles
.size()));
270 presentation
.setNonCodeUsagesString(descriptor
.getCommentReferencesText(nonCodeUsageCount
, nonCodeFiles
.size()));
274 private void showUsageView(final UsageViewDescriptor viewDescriptor
, final Factory
<UsageSearcher
> factory
, final UsageInfo
[] usageInfos
) {
275 UsageViewManager viewManager
= UsageViewManager
.getInstance(myProject
);
277 final PsiElement
[] initialElements
= viewDescriptor
.getElements();
278 final UsageTarget
[] targets
= PsiElement2UsageTargetAdapter
.convert(initialElements
);
279 final Usage
[] usages
= UsageInfo2UsageAdapter
.convert(usageInfos
);
281 final UsageViewPresentation presentation
= createPresentation(viewDescriptor
, usages
);
283 final UsageView usageView
= viewManager
.showUsages(targets
, usages
, presentation
, factory
);
285 final Runnable refactoringRunnable
= new Runnable() {
287 ApplicationManager
.getApplication().runWriteAction(new Runnable() {
289 Set
<UsageInfo
> usagesToRefactor
= getUsageInfosToRefactor(usageView
);
290 if (ensureElementsWritable(usagesToRefactor
.toArray(new UsageInfo
[usagesToRefactor
.size()]), viewDescriptor
)) {
291 doRefactoring(usagesToRefactor
);
298 String canNotMakeString
= RefactoringBundle
.message("usageView.need.reRun");
300 usageView
.addPerformOperationAction(refactoringRunnable
, getCommandName(), canNotMakeString
, RefactoringBundle
.message("usageView.doAction"));
303 private static Set
<UsageInfo
> getUsageInfosToRefactor(final UsageView usageView
) {
304 Set
<Usage
> excludedUsages
= usageView
.getExcludedUsages();
306 Set
<UsageInfo
> usageInfos
= new HashSet
<UsageInfo
>();
307 for (Usage usage
: usageView
.getUsages()) {
308 if (usage
instanceof UsageInfo2UsageAdapter
&& !excludedUsages
.contains(usage
)) {
309 UsageInfo usageInfo
= ((UsageInfo2UsageAdapter
)usage
).getUsageInfo();
310 usageInfos
.add(usageInfo
);
316 private void doRefactoring(@NotNull Collection
<UsageInfo
> usageInfoSet
) {
317 ApplicationManager
.getApplication().assertWriteAccessAllowed();
319 for (Iterator
<UsageInfo
> iterator
= usageInfoSet
.iterator(); iterator
.hasNext();) {
320 UsageInfo usageInfo
= iterator
.next();
321 final PsiElement element
= usageInfo
.getElement();
322 if (element
== null || !element
.isWritable()) {
327 LocalHistoryAction action
= LocalHistory
.startAction(myProject
, getCommandName());
329 final UsageInfo
[] writableUsageInfos
= usageInfoSet
.toArray(new UsageInfo
[usageInfoSet
.size()]);
331 PsiDocumentManager
.getInstance(myProject
).commitAllDocuments();
332 RefactoringListenerManagerImpl listenerManager
= (RefactoringListenerManagerImpl
)RefactoringListenerManager
.getInstance(myProject
);
333 myTransaction
= listenerManager
.startTransaction();
334 Map
<RefactoringHelper
, Object
> preparedData
= new HashMap
<RefactoringHelper
, Object
>();
335 for(RefactoringHelper helper
: Extensions
.getExtensions(RefactoringHelper
.EP_NAME
)) {
336 preparedData
.put(helper
, helper
.prepareOperation(writableUsageInfos
));
338 performRefactoring(writableUsageInfos
);
339 for(Map
.Entry
<RefactoringHelper
, Object
> e
: preparedData
.entrySet()) {
340 //noinspection unchecked
341 e
.getKey().performOperation(myProject
, e
.getValue());
343 myTransaction
.commit();
344 performPsiSpoilingRefactoring();
350 int count
= writableUsageInfos
.length
;
352 WindowManager
.getInstance().getStatusBar(myProject
).setInfo(RefactoringBundle
.message("statusBar.refactoring.result", count
));
355 if (!isPreviewUsages(writableUsageInfos
)) {
356 WindowManager
.getInstance().getStatusBar(myProject
).setInfo(RefactoringBundle
.message("statusBar.noUsages"));
362 * Refactorings that spoil PSI (write something directly to documents etc.) should
363 * do that in this method.<br>
364 * This method is called immediately after
365 * <code>{@link #performRefactoring(UsageInfo[])}</code>.
367 protected void performPsiSpoilingRefactoring() {
371 protected void prepareSuccessful() {
372 if (myPrepareSuccessfulSwingThreadCallback
!= null) {
373 // make sure that dialog is closed in swing thread
375 GuiUtils
.runOrInvokeAndWait(myPrepareSuccessfulSwingThreadCallback
);
377 catch (InterruptedException e
) {
380 catch (InvocationTargetException e
) {
387 * Override in subclasses
389 protected void prepareTestRun() {
393 public final void run() {
394 if (ApplicationManager
.getApplication().isUnitTestMode()) {
402 private void testRun() {
403 PsiDocumentManager
.getInstance(myProject
).commitAllDocuments();
405 Ref
<UsageInfo
[]> refUsages
= new Ref
<UsageInfo
[]>(findUsages());
406 preprocessUsages(refUsages
);
408 final UsageInfo
[] usages
= refUsages
.get();
409 UsageViewDescriptor descriptor
= createUsageViewDescriptor(usages
);
410 if (!ensureElementsWritable(usages
, descriptor
)) return;
412 RefactoringListenerManagerImpl listenerManager
= (RefactoringListenerManagerImpl
)RefactoringListenerManager
.getInstance(myProject
);
413 myTransaction
= listenerManager
.startTransaction();
414 Map
<RefactoringHelper
, Object
> preparedData
= new HashMap
<RefactoringHelper
, Object
>();
415 for(RefactoringHelper helper
: Extensions
.getExtensions(RefactoringHelper
.EP_NAME
)) {
416 preparedData
.put(helper
, helper
.prepareOperation(usages
));
418 performRefactoring(usages
);
419 for(Map
.Entry
<RefactoringHelper
, Object
> e
: preparedData
.entrySet()) {
420 //noinspection unchecked
421 e
.getKey().performOperation(myProject
, e
.getValue());
423 myTransaction
.commit();
424 performPsiSpoilingRefactoring();
427 protected boolean showConflicts(final List
<String
> conflicts
) {
428 if (!conflicts
.isEmpty() && myPrepareSuccessfulSwingThreadCallback
!= null) {
429 final ConflictsDialog conflictsDialog
= new ConflictsDialog(myProject
, conflicts
);
430 conflictsDialog
.show();
431 if (!conflictsDialog
.isOK()) return false;
439 protected Collection
<?
extends PsiElement
> getElementsToWrite(@NotNull UsageViewDescriptor descriptor
) {
440 return Arrays
.asList(descriptor
.getElements());
443 public static class UnknownReferenceTypeException
extends RuntimeException
{
444 private final Language myElementLanguage
;
446 public UnknownReferenceTypeException(final Language elementLanguage
) {
447 myElementLanguage
= elementLanguage
;
450 public Language
getElementLanguage() {
451 return myElementLanguage
;