2 * Copyright 2000-2009 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
;
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
.PlatformDataKeys
;
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
.diagnostic
.Logger
;
29 import com
.intellij
.openapi
.extensions
.Extensions
;
30 import com
.intellij
.openapi
.progress
.ProcessCanceledException
;
31 import com
.intellij
.openapi
.progress
.ProgressManager
;
32 import com
.intellij
.openapi
.project
.DumbService
;
33 import com
.intellij
.openapi
.project
.IndexNotReadyException
;
34 import com
.intellij
.openapi
.project
.Project
;
35 import com
.intellij
.openapi
.ui
.Messages
;
36 import com
.intellij
.openapi
.util
.EmptyRunnable
;
37 import com
.intellij
.openapi
.util
.Factory
;
38 import com
.intellij
.openapi
.util
.Ref
;
39 import com
.intellij
.openapi
.util
.text
.StringUtil
;
40 import com
.intellij
.openapi
.wm
.WindowManager
;
41 import com
.intellij
.psi
.PsiDocumentManager
;
42 import com
.intellij
.psi
.PsiElement
;
43 import com
.intellij
.psi
.PsiFile
;
44 import com
.intellij
.refactoring
.listeners
.RefactoringListenerManager
;
45 import com
.intellij
.refactoring
.listeners
.impl
.RefactoringListenerManagerImpl
;
46 import com
.intellij
.refactoring
.listeners
.impl
.RefactoringTransaction
;
47 import com
.intellij
.refactoring
.ui
.ConflictsDialog
;
48 import com
.intellij
.refactoring
.util
.CommonRefactoringUtil
;
49 import com
.intellij
.ui
.GuiUtils
;
50 import com
.intellij
.usageView
.UsageInfo
;
51 import com
.intellij
.usageView
.UsageViewDescriptor
;
52 import com
.intellij
.usageView
.UsageViewUtil
;
53 import com
.intellij
.usages
.*;
54 import com
.intellij
.usages
.rules
.PsiElementUsage
;
55 import com
.intellij
.util
.Processor
;
56 import com
.intellij
.util
.containers
.HashSet
;
57 import com
.intellij
.util
.containers
.MultiMap
;
58 import gnu
.trove
.THashSet
;
59 import org
.jetbrains
.annotations
.NotNull
;
60 import org
.jetbrains
.annotations
.Nullable
;
62 import java
.lang
.reflect
.InvocationTargetException
;
65 public abstract class BaseRefactoringProcessor
{
66 private static final Logger LOG
= Logger
.getInstance("#com.intellij.refactoring.BaseRefactoringProcessor");
67 public static final Runnable EMPTY_CALLBACK
= EmptyRunnable
.getInstance();
68 protected final Project myProject
;
70 private RefactoringTransaction myTransaction
;
71 private boolean myIsPreviewUsages
;
72 protected Runnable myPrepareSuccessfulSwingThreadCallback
= EMPTY_CALLBACK
;
75 protected BaseRefactoringProcessor(Project project
) {
79 protected BaseRefactoringProcessor(Project project
, @Nullable Runnable prepareSuccessfulCallback
) {
81 myPrepareSuccessfulSwingThreadCallback
= prepareSuccessfulCallback
;
84 protected abstract UsageViewDescriptor
createUsageViewDescriptor(UsageInfo
[] usages
);
87 * Is called inside atomic action.
90 protected abstract UsageInfo
[] findUsages();
93 * is called when usage search is re-run.
95 * @param elements - refreshed elements that are returned by UsageViewDescriptor.getElements()
97 protected abstract void refreshElements(PsiElement
[] elements
);
100 * Is called inside atomic action.
102 * @param refUsages usages to be filtered
103 * @return true if preprocessed successfully
105 protected boolean preprocessUsages(Ref
<UsageInfo
[]> refUsages
) {
111 * Is called inside atomic action.
113 protected boolean isPreviewUsages(UsageInfo
[] usages
) {
114 return myIsPreviewUsages
;
117 protected boolean isPreviewUsages() {
118 return myIsPreviewUsages
;
122 public void setPreviewUsages(boolean isPreviewUsages
) {
123 myIsPreviewUsages
= isPreviewUsages
;
126 public void setPrepareSuccessfulSwingThreadCallback(Runnable prepareSuccessfulSwingThreadCallback
) {
127 myPrepareSuccessfulSwingThreadCallback
= prepareSuccessfulSwingThreadCallback
;
130 protected RefactoringTransaction
getTransaction() {
131 return myTransaction
;
135 * Is called in a command and inside atomic action.
137 protected abstract void performRefactoring(UsageInfo
[] usages
);
139 protected abstract String
getCommandName();
141 protected void doRun() {
142 PsiDocumentManager
.getInstance(myProject
).commitAllDocuments();
143 final Ref
<UsageInfo
[]> refUsages
= new Ref
<UsageInfo
[]>();
144 final Ref
<Language
> refErrorLanguage
= new Ref
<Language
>();
145 final Ref
<Boolean
> refProcessCanceled
= new Ref
<Boolean
>();
146 final Ref
<Boolean
> dumbModeOccured
= new Ref
<Boolean
>();
148 final Runnable findUsagesRunnable
= new Runnable() {
150 ApplicationManager
.getApplication().runReadAction(new Runnable() {
153 refUsages
.set(findUsages());
155 catch (UnknownReferenceTypeException e
) {
156 refErrorLanguage
.set(e
.getElementLanguage());
158 catch (ProcessCanceledException e
) {
159 refProcessCanceled
.set(Boolean
.TRUE
);
161 catch (IndexNotReadyException e
) {
162 dumbModeOccured
.set(Boolean
.TRUE
);
169 if (!ProgressManager
.getInstance().runProcessWithProgressSynchronously(findUsagesRunnable
, RefactoringBundle
.message("progress.text"), true, myProject
)) {
173 if (!refErrorLanguage
.isNull()) {
174 Messages
.showErrorDialog(myProject
, RefactoringBundle
.message("unsupported.refs.found", refErrorLanguage
.get().getDisplayName()), RefactoringBundle
.message("error.title"));
177 if (!dumbModeOccured
.isNull()) {
178 DumbService
.getInstance(myProject
).showDumbModeNotification("Usage search is not available until indices are ready");
181 if (!refProcessCanceled
.isNull()) {
182 Messages
.showErrorDialog(myProject
, "Index corruption detected. Please retry the refactoring - indexes will be rebuilt automatically",
183 RefactoringBundle
.message("error.title"));
187 assert !refUsages
.isNull(): "Null usages from processor " + this;
188 if (!preprocessUsages(refUsages
)) return;
189 final UsageInfo
[] usages
= refUsages
.get();
190 assert usages
!= null;
191 UsageViewDescriptor descriptor
= createUsageViewDescriptor(usages
);
193 boolean isPreview
= isPreviewUsages(usages
);
195 isPreview
= !ensureElementsWritable(usages
, descriptor
) || UsageViewUtil
.hasReadOnlyUsages(usages
);
197 WindowManager
.getInstance().getStatusBar(myProject
).setInfo(RefactoringBundle
.message("readonly.occurences.found"));
201 previewRefactoring(usages
);
208 protected void previewRefactoring(final UsageInfo
[] usages
) {
209 final UsageViewDescriptor viewDescriptor
= createUsageViewDescriptor(usages
);
210 final PsiElement
[] elements
= viewDescriptor
.getElements();
211 final PsiElement2UsageTargetAdapter
[] targets
= PsiElement2UsageTargetAdapter
.convert(elements
);
212 Factory
<UsageSearcher
> factory
= new Factory
<UsageSearcher
>() {
213 public UsageSearcher
create() {
214 return new UsageSearcher() {
215 public void generate(final Processor
<Usage
> processor
) {
216 ApplicationManager
.getApplication().runReadAction(new Runnable() {
218 for (int i
= 0; i
< elements
.length
; i
++) {
219 elements
[i
] = targets
[i
].getElement();
221 refreshElements(elements
);
224 final Ref
<UsageInfo
[]> refUsages
= new Ref
<UsageInfo
[]>();
225 ApplicationManager
.getApplication().runReadAction(new Runnable() {
227 refUsages
.set(findUsages());
230 final Usage
[] usages
= UsageInfo2UsageAdapter
.convert(refUsages
.get());
232 for (Usage usage
: usages
) {
233 processor
.process(usage
);
240 showUsageView(viewDescriptor
, factory
, usages
);
243 private boolean ensureElementsWritable(@NotNull final UsageInfo
[] usages
, final UsageViewDescriptor descriptor
) {
244 Set
<PsiElement
> elements
= new THashSet
<PsiElement
>();
245 for (UsageInfo usage
: usages
) {
246 assert usage
!= null: "Found null element in usages array";
247 PsiElement element
= usage
.getElement();
248 if (element
!= null) elements
.add(element
);
250 elements
.addAll(getElementsToWrite(descriptor
));
251 return ensureFilesWritable(myProject
, elements
);
254 private static boolean ensureFilesWritable(final Project project
, Collection
<?
extends PsiElement
> elements
) {
255 PsiElement
[] psiElements
= elements
.toArray(new PsiElement
[elements
.size()]);
256 return CommonRefactoringUtil
.checkReadOnlyStatus(project
, psiElements
);
259 void execute(final UsageInfo
[] usages
) {
260 CommandProcessor
.getInstance().executeCommand(myProject
, new Runnable() {
262 ApplicationManager
.getApplication().runWriteAction(new Runnable() {
264 Collection
<UsageInfo
> usageInfos
= new HashSet
<UsageInfo
>(Arrays
.asList(usages
));
265 doRefactoring(usageInfos
);
266 if (isGlobalUndoAction()) CommandProcessor
.getInstance().markCurrentCommandAsGlobal(myProject
);
270 }, getCommandName(), null, getUndoConfirmationPolicy());
273 protected boolean isGlobalUndoAction() {
274 return PlatformDataKeys
.EDITOR
.getData(DataManager
.getInstance().getDataContext()) == null;
277 protected UndoConfirmationPolicy
getUndoConfirmationPolicy() {
278 return UndoConfirmationPolicy
.DEFAULT
;
281 private static UsageViewPresentation
createPresentation(UsageViewDescriptor descriptor
, final Usage
[] usages
) {
282 UsageViewPresentation presentation
= new UsageViewPresentation();
283 presentation
.setTabText(RefactoringBundle
.message("usageView.tabText"));
284 presentation
.setTargetsNodeText(descriptor
.getProcessedElementsHeader());
285 presentation
.setShowReadOnlyStatusAsRed(true);
286 presentation
.setShowCancelButton(true);
287 presentation
.setUsagesString(RefactoringBundle
.message("usageView.usagesText"));
288 int codeUsageCount
= 0;
289 int nonCodeUsageCount
= 0;
290 Set
<PsiFile
> codeFiles
= new HashSet
<PsiFile
>();
291 Set
<PsiFile
> nonCodeFiles
= new HashSet
<PsiFile
>();
293 for (Usage usage
: usages
) {
294 if (usage
instanceof PsiElementUsage
) {
295 final PsiElementUsage elementUsage
= (PsiElementUsage
)usage
;
296 if (elementUsage
.isNonCodeUsage()) {
298 nonCodeFiles
.add(elementUsage
.getElement().getContainingFile());
302 codeFiles
.add(elementUsage
.getElement().getContainingFile());
306 codeFiles
.remove(null);
307 nonCodeFiles
.remove(null);
309 presentation
.setCodeUsagesString(descriptor
.getCodeReferencesText(codeUsageCount
, codeFiles
.size()));
310 presentation
.setNonCodeUsagesString(descriptor
.getCommentReferencesText(nonCodeUsageCount
, nonCodeFiles
.size()));
314 private void showUsageView(final UsageViewDescriptor viewDescriptor
, final Factory
<UsageSearcher
> factory
, final UsageInfo
[] usageInfos
) {
315 UsageViewManager viewManager
= UsageViewManager
.getInstance(myProject
);
317 final PsiElement
[] initialElements
= viewDescriptor
.getElements();
318 final UsageTarget
[] targets
= PsiElement2UsageTargetAdapter
.convert(initialElements
);
319 final Usage
[] usages
= UsageInfo2UsageAdapter
.convert(usageInfos
);
321 final UsageViewPresentation presentation
= createPresentation(viewDescriptor
, usages
);
323 final UsageView usageView
= viewManager
.showUsages(targets
, usages
, presentation
, factory
);
325 final Runnable refactoringRunnable
= new Runnable() {
327 ApplicationManager
.getApplication().runWriteAction(new Runnable() {
329 Set
<UsageInfo
> usagesToRefactor
= getUsageInfosToRefactor(usageView
);
330 if (ensureElementsWritable(usagesToRefactor
.toArray(new UsageInfo
[usagesToRefactor
.size()]), viewDescriptor
)) {
331 doRefactoring(usagesToRefactor
);
338 String canNotMakeString
= RefactoringBundle
.message("usageView.need.reRun");
340 usageView
.addPerformOperationAction(refactoringRunnable
, getCommandName(), canNotMakeString
, RefactoringBundle
.message("usageView.doAction"));
343 private static Set
<UsageInfo
> getUsageInfosToRefactor(final UsageView usageView
) {
344 Set
<Usage
> excludedUsages
= usageView
.getExcludedUsages();
346 Set
<UsageInfo
> usageInfos
= new HashSet
<UsageInfo
>();
347 for (Usage usage
: usageView
.getUsages()) {
348 if (usage
instanceof UsageInfo2UsageAdapter
&& !excludedUsages
.contains(usage
)) {
349 UsageInfo usageInfo
= ((UsageInfo2UsageAdapter
)usage
).getUsageInfo();
350 usageInfos
.add(usageInfo
);
356 private void doRefactoring(@NotNull Collection
<UsageInfo
> usageInfoSet
) {
357 ApplicationManager
.getApplication().assertWriteAccessAllowed();
359 for (Iterator
<UsageInfo
> iterator
= usageInfoSet
.iterator(); iterator
.hasNext();) {
360 UsageInfo usageInfo
= iterator
.next();
361 final PsiElement element
= usageInfo
.getElement();
362 if (element
== null || !element
.isWritable()) {
367 LocalHistoryAction action
= LocalHistory
.startAction(myProject
, getCommandName());
369 final UsageInfo
[] writableUsageInfos
= usageInfoSet
.toArray(new UsageInfo
[usageInfoSet
.size()]);
371 PsiDocumentManager
.getInstance(myProject
).commitAllDocuments();
372 RefactoringListenerManagerImpl listenerManager
= (RefactoringListenerManagerImpl
)RefactoringListenerManager
.getInstance(myProject
);
373 myTransaction
= listenerManager
.startTransaction();
374 Map
<RefactoringHelper
, Object
> preparedData
= new HashMap
<RefactoringHelper
, Object
>();
375 for(RefactoringHelper helper
: Extensions
.getExtensions(RefactoringHelper
.EP_NAME
)) {
376 preparedData
.put(helper
, helper
.prepareOperation(writableUsageInfos
));
378 performRefactoring(writableUsageInfos
);
379 for(Map
.Entry
<RefactoringHelper
, Object
> e
: preparedData
.entrySet()) {
380 //noinspection unchecked
381 e
.getKey().performOperation(myProject
, e
.getValue());
383 myTransaction
.commit();
384 performPsiSpoilingRefactoring();
390 int count
= writableUsageInfos
.length
;
392 WindowManager
.getInstance().getStatusBar(myProject
).setInfo(RefactoringBundle
.message("statusBar.refactoring.result", count
));
395 if (!isPreviewUsages(writableUsageInfos
)) {
396 WindowManager
.getInstance().getStatusBar(myProject
).setInfo(RefactoringBundle
.message("statusBar.noUsages"));
402 * Refactorings that spoil PSI (write something directly to documents etc.) should
403 * do that in this method.<br>
404 * This method is called immediately after
405 * <code>{@link #performRefactoring(UsageInfo[])}</code>.
407 protected void performPsiSpoilingRefactoring() {
411 protected void prepareSuccessful() {
412 if (myPrepareSuccessfulSwingThreadCallback
!= null) {
413 // make sure that dialog is closed in swing thread
415 GuiUtils
.runOrInvokeAndWait(myPrepareSuccessfulSwingThreadCallback
);
417 catch (InterruptedException e
) {
420 catch (InvocationTargetException e
) {
427 * Override in subclasses
429 protected void prepareTestRun() {
433 public final void run() {
434 if (ApplicationManager
.getApplication().isUnitTestMode()) {
442 private void testRun() {
443 PsiDocumentManager
.getInstance(myProject
).commitAllDocuments();
445 Ref
<UsageInfo
[]> refUsages
= new Ref
<UsageInfo
[]>(findUsages());
446 preprocessUsages(refUsages
);
448 final UsageInfo
[] usages
= refUsages
.get();
449 UsageViewDescriptor descriptor
= createUsageViewDescriptor(usages
);
450 if (!ensureElementsWritable(usages
, descriptor
)) return;
452 RefactoringListenerManagerImpl listenerManager
= (RefactoringListenerManagerImpl
)RefactoringListenerManager
.getInstance(myProject
);
453 myTransaction
= listenerManager
.startTransaction();
454 Map
<RefactoringHelper
, Object
> preparedData
= new HashMap
<RefactoringHelper
, Object
>();
455 for(RefactoringHelper helper
: Extensions
.getExtensions(RefactoringHelper
.EP_NAME
)) {
456 preparedData
.put(helper
, helper
.prepareOperation(usages
));
458 performRefactoring(usages
);
459 for(Map
.Entry
<RefactoringHelper
, Object
> e
: preparedData
.entrySet()) {
460 //noinspection unchecked
461 e
.getKey().performOperation(myProject
, e
.getValue());
463 myTransaction
.commit();
464 performPsiSpoilingRefactoring();
467 public static class ConflictsInTestsException
extends RuntimeException
{
468 private final Collection
<?
extends String
> messages
;
470 private static boolean myTestIgnore
= false;
472 public ConflictsInTestsException(Collection
<?
extends String
> messages
) {
473 this.messages
= messages
;
476 public static void setTestIgnore(boolean myIgnore
) {
477 myTestIgnore
= myIgnore
;
480 public static boolean isTestIgnore() {
484 public Collection
<String
> getMessages() {
485 List
<String
> result
= new ArrayList
<String
>(messages
);
486 for (int i
= 0; i
< messages
.size(); i
++) {
487 result
.set(i
, result
.get(i
).replaceAll("<[^>]+>", ""));
493 public String
getMessage() {
494 return StringUtil
.join(messages
, "\n");
498 protected boolean showConflicts(final MultiMap
<PsiElement
,String
> conflicts
) {
499 if (!conflicts
.isEmpty() && ApplicationManager
.getApplication().isUnitTestMode()) {
500 throw new ConflictsInTestsException(conflicts
.values());
503 if (myPrepareSuccessfulSwingThreadCallback
!= null && !conflicts
.isEmpty()) {
504 final ConflictsDialog conflictsDialog
= new ConflictsDialog(myProject
, conflicts
);
505 conflictsDialog
.show();
506 if (!conflictsDialog
.isOK()) {
507 if (conflictsDialog
.isShowConflicts()) prepareSuccessful();
517 protected Collection
<?
extends PsiElement
> getElementsToWrite(@NotNull UsageViewDescriptor descriptor
) {
518 return Arrays
.asList(descriptor
.getElements());
521 public static class UnknownReferenceTypeException
extends RuntimeException
{
522 private final Language myElementLanguage
;
524 public UnknownReferenceTypeException(final Language elementLanguage
) {
525 myElementLanguage
= elementLanguage
;
528 public Language
getElementLanguage() {
529 return myElementLanguage
;