cleanup
[fedora-idea.git] / platform / lang-impl / src / com / intellij / refactoring / BaseRefactoringProcessor.java
blob0e914e3aadffe79abd2af8bf464b2349dcfcb307
1 /*
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;
63 import java.util.*;
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) {
76 this(project, null);
79 protected BaseRefactoringProcessor(Project project, @Nullable Runnable prepareSuccessfulCallback) {
80 myProject = project;
81 myPrepareSuccessfulSwingThreadCallback = prepareSuccessfulCallback;
84 protected abstract UsageViewDescriptor createUsageViewDescriptor(UsageInfo[] usages);
86 /**
87 * Is called inside atomic action.
89 @NotNull
90 protected abstract UsageInfo[] findUsages();
92 /**
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);
99 /**
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) {
106 prepareSuccessful();
107 return true;
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() {
149 public void run() {
150 ApplicationManager.getApplication().runReadAction(new Runnable() {
151 public void run() {
152 try {
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)) {
170 return;
173 if (!refErrorLanguage.isNull()) {
174 Messages.showErrorDialog(myProject, RefactoringBundle.message("unsupported.refs.found", refErrorLanguage.get().getDisplayName()), RefactoringBundle.message("error.title"));
175 return;
177 if (!dumbModeOccured.isNull()) {
178 DumbService.getInstance(myProject).showDumbModeNotification("Usage search is not available until indices are ready");
179 return;
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"));
184 return;
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);
194 if (!isPreview) {
195 isPreview = !ensureElementsWritable(usages, descriptor) || UsageViewUtil.hasReadOnlyUsages(usages);
196 if (isPreview) {
197 WindowManager.getInstance().getStatusBar(myProject).setInfo(RefactoringBundle.message("readonly.occurences.found"));
200 if (isPreview) {
201 previewRefactoring(usages);
203 else {
204 execute(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() {
217 public void run() {
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() {
226 public void run() {
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() {
261 public void run() {
262 ApplicationManager.getApplication().runWriteAction(new Runnable() {
263 public void run() {
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()) {
297 nonCodeUsageCount++;
298 nonCodeFiles.add(elementUsage.getElement().getContainingFile());
300 else {
301 codeUsageCount++;
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()));
311 return presentation;
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() {
326 public void run() {
327 ApplicationManager.getApplication().runWriteAction(new Runnable() {
328 public void run() {
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);
353 return usageInfos;
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()) {
363 iterator.remove();
367 LocalHistoryAction action = LocalHistory.startAction(myProject, getCommandName());
369 final UsageInfo[] writableUsageInfos = usageInfoSet.toArray(new UsageInfo[usageInfoSet.size()]);
370 try {
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();
386 finally {
387 action.finish();
390 int count = writableUsageInfos.length;
391 if (count > 0) {
392 WindowManager.getInstance().getStatusBar(myProject).setInfo(RefactoringBundle.message("statusBar.refactoring.result", count));
394 else {
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
414 try {
415 GuiUtils.runOrInvokeAndWait(myPrepareSuccessfulSwingThreadCallback);
417 catch (InterruptedException e) {
418 LOG.error(e);
420 catch (InvocationTargetException e) {
421 LOG.error(e);
427 * Override in subclasses
429 protected void prepareTestRun() {
433 public final void run() {
434 if (ApplicationManager.getApplication().isUnitTestMode()) {
435 testRun();
437 else {
438 doRun();
442 private void testRun() {
443 PsiDocumentManager.getInstance(myProject).commitAllDocuments();
444 prepareTestRun();
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() {
481 return myTestIgnore;
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("<[^>]+>", ""));
489 return result;
492 @Override
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();
508 return false;
512 prepareSuccessful();
513 return true;
516 @NotNull
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;