extensibility for Pull Up and Push Down
[fedora-idea.git] / lang-impl / src / com / intellij / refactoring / BaseRefactoringProcessor.java
blob3e07a3bea3cd21c740b5587e3fe3aca7a3488956
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;
40 import java.util.*;
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) {
53 this(project, null);
56 protected BaseRefactoringProcessor(Project project, @Nullable Runnable prepareSuccessfulCallback) {
57 myProject = project;
58 myPrepareSuccessfulSwingThreadCallback = prepareSuccessfulCallback;
61 protected abstract UsageViewDescriptor createUsageViewDescriptor(UsageInfo[] usages);
63 /**
64 * Is called inside atomic action.
66 @NotNull
67 protected abstract UsageInfo[] findUsages();
69 /**
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);
76 /**
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) {
83 prepareSuccessful();
84 return true;
87 /**
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() {
125 public void run() {
126 ApplicationManager.getApplication().runReadAction(new Runnable() {
127 public void run() {
128 try {
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)) {
143 return;
146 if (!refErrorLanguage.isNull()) {
147 Messages.showErrorDialog(myProject, RefactoringBundle.message("unsupported.refs.found", refErrorLanguage.get().getDisplayName()), RefactoringBundle.message("error.title"));
148 return;
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"));
153 return;
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);
163 if (!isPreview) {
164 isPreview = !ensureElementsWritable(usages, descriptor) || UsageViewUtil.hasReadOnlyUsages(usages);
165 if (isPreview) {
166 WindowManager.getInstance().getStatusBar(myProject).setInfo(RefactoringBundle.message("readonly.occurences.found"));
169 if (isPreview) {
170 previewRefactoring(usages);
172 else {
173 execute(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() {
186 public void run() {
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() {
195 public void run() {
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() {
230 public void run() {
231 ApplicationManager.getApplication().runWriteAction(new Runnable() {
232 public void run() {
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()) {
257 nonCodeUsageCount++;
258 nonCodeFiles.add(elementUsage.getElement().getContainingFile());
260 else {
261 codeUsageCount++;
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()));
271 return presentation;
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() {
286 public void run() {
287 ApplicationManager.getApplication().runWriteAction(new Runnable() {
288 public void run() {
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);
313 return usageInfos;
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()) {
323 iterator.remove();
327 LocalHistoryAction action = LocalHistory.startAction(myProject, getCommandName());
329 final UsageInfo[] writableUsageInfos = usageInfoSet.toArray(new UsageInfo[usageInfoSet.size()]);
330 try {
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();
346 finally {
347 action.finish();
350 int count = writableUsageInfos.length;
351 if (count > 0) {
352 WindowManager.getInstance().getStatusBar(myProject).setInfo(RefactoringBundle.message("statusBar.refactoring.result", count));
354 else {
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
374 try {
375 GuiUtils.runOrInvokeAndWait(myPrepareSuccessfulSwingThreadCallback);
377 catch (InterruptedException e) {
378 LOG.error(e);
380 catch (InvocationTargetException e) {
381 LOG.error(e);
387 * Override in subclasses
389 protected void prepareTestRun() {
393 public final void run() {
394 if (ApplicationManager.getApplication().isUnitTestMode()) {
395 testRun();
397 else {
398 doRun();
402 private void testRun() {
403 PsiDocumentManager.getInstance(myProject).commitAllDocuments();
404 prepareTestRun();
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;
434 prepareSuccessful();
435 return true;
438 @NotNull
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;