mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-02-05 08:06:56 +07:00
385 lines
14 KiB
Java
385 lines
14 KiB
Java
package com.intellij.refactoring;
|
|
|
|
import com.intellij.find.findUsages.PsiElement2UsageTargetAdapter;
|
|
import com.intellij.openapi.application.ApplicationManager;
|
|
import com.intellij.openapi.application.ModalityState;
|
|
import com.intellij.openapi.command.CommandProcessor;
|
|
import com.intellij.openapi.diagnostic.Logger;
|
|
import com.intellij.openapi.localVcs.impl.LvcsIntegration;
|
|
import com.intellij.openapi.project.Project;
|
|
import com.intellij.openapi.util.EmptyRunnable;
|
|
import com.intellij.openapi.util.Factory;
|
|
import com.intellij.openapi.util.SystemInfo;
|
|
import com.intellij.openapi.wm.WindowManager;
|
|
import com.intellij.psi.PsiDocumentManager;
|
|
import com.intellij.psi.PsiElement;
|
|
import com.intellij.refactoring.listeners.RefactoringListenerManager;
|
|
import com.intellij.refactoring.listeners.impl.RefactoringListenerManagerImpl;
|
|
import com.intellij.refactoring.listeners.impl.RefactoringTransaction;
|
|
import com.intellij.refactoring.ui.ConflictsDialog;
|
|
import com.intellij.refactoring.util.RefactoringChangeMarker;
|
|
import com.intellij.refactoring.util.RefactoringUtil;
|
|
import com.intellij.usageView.FindUsagesCommand;
|
|
import com.intellij.usageView.UsageInfo;
|
|
import com.intellij.usageView.UsageViewDescriptor;
|
|
import com.intellij.usageView.UsageViewUtil;
|
|
import com.intellij.usages.*;
|
|
import com.intellij.util.Processor;
|
|
|
|
import javax.swing.*;
|
|
import java.lang.reflect.InvocationTargetException;
|
|
import java.util.*;
|
|
|
|
public abstract class BaseRefactoringProcessor {
|
|
private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.BaseRefactoringProcessor");
|
|
public static final Runnable EMPTY_CALLBACK = EmptyRunnable.getInstance();
|
|
protected final Project myProject;
|
|
|
|
private Object myMarkerId;
|
|
private RefactoringTransaction myTransaction;
|
|
private boolean myIsPreviewUsages;
|
|
protected Runnable myPrepareSuccessfulSwingThreadCallback = EMPTY_CALLBACK;
|
|
|
|
|
|
public BaseRefactoringProcessor(Project project) {
|
|
this(project, null);
|
|
}
|
|
|
|
public BaseRefactoringProcessor(Project project, Runnable prepareSuccessfulCallback) {
|
|
myProject = project;
|
|
myPrepareSuccessfulSwingThreadCallback = prepareSuccessfulCallback;
|
|
}
|
|
|
|
protected abstract UsageViewDescriptor createUsageViewDescriptor(UsageInfo[] usages, FindUsagesCommand refreshCommand);
|
|
|
|
/**
|
|
* Is called inside atomic action.
|
|
*/
|
|
protected abstract UsageInfo[] findUsages();
|
|
|
|
/**
|
|
* is called when usage search is re-run.
|
|
* @param elements - refreshed elements that are returned by UsageViewDescriptor.getElements()
|
|
*/
|
|
protected abstract void refreshElements(PsiElement[] elements);
|
|
|
|
/**
|
|
* Is called inside atomic action.
|
|
*/
|
|
protected boolean preprocessUsages(UsageInfo[][] usages) {
|
|
prepareSuccessful();
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Is called inside atomic action.
|
|
*/
|
|
protected boolean isPreviewUsages(UsageInfo[] usages) {
|
|
if (UsageViewUtil.hasReadOnlyUsages(usages)) {
|
|
WindowManager.getInstance().getStatusBar(myProject).setInfo("Occurrences found in read-only files");
|
|
return true;
|
|
}
|
|
return myIsPreviewUsages;
|
|
}
|
|
|
|
boolean isPreviewUsages() {
|
|
return myIsPreviewUsages;
|
|
}
|
|
|
|
|
|
public void setPreviewUsages(boolean isPreviewUsages) {
|
|
myIsPreviewUsages = isPreviewUsages;
|
|
}
|
|
|
|
public void setPrepareSuccessfulSwingThreadCallback(Runnable prepareSuccessfulSwingThreadCallback) {
|
|
myPrepareSuccessfulSwingThreadCallback = prepareSuccessfulSwingThreadCallback;
|
|
}
|
|
|
|
protected RefactoringTransaction getTransaction() {
|
|
return myTransaction;
|
|
}
|
|
|
|
/**
|
|
* Is called in a command and inside atomic action.
|
|
*/
|
|
protected abstract void performRefactoring(UsageInfo[] usages);
|
|
|
|
/**
|
|
* If this method returns <code>true</code>, it means that element to rename is variable, and in the findUsages tree it
|
|
* will be shown with read-access or write-access icons.
|
|
* @return true if element to rename is variable(local, field, or parameter).
|
|
*/
|
|
protected boolean isVariable() {
|
|
return false;
|
|
}
|
|
|
|
protected abstract String getCommandName();
|
|
|
|
public void run(Object markerId) {
|
|
myMarkerId = markerId;
|
|
|
|
final UsageInfo[][] usages = new UsageInfo[1][];
|
|
|
|
final Runnable findUsagesRunnable = new Runnable() {
|
|
public void run() {
|
|
ApplicationManager.getApplication().runReadAction(new Runnable() {
|
|
public void run() {
|
|
usages[0] = findUsages();
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
if (!ApplicationManager.getApplication().runProcessWithProgressSynchronously(findUsagesRunnable, "Looking For Usages...", true, myProject)) return;
|
|
|
|
LOG.assertTrue(usages[0] != null);
|
|
if (!preprocessUsages(usages)) return;
|
|
boolean toPreview = isPreviewUsages(usages[0]);
|
|
if (toPreview) {
|
|
FindUsagesCommand findUsagesCommand = new FindUsagesCommand() {
|
|
public UsageInfo[] execute(PsiElement[] elementsToSearch) {
|
|
refreshElements(elementsToSearch);
|
|
findUsagesRunnable.run();
|
|
return usages[0];
|
|
}
|
|
};
|
|
UsageViewDescriptor descriptor = createUsageViewDescriptor(usages[0], findUsagesCommand);
|
|
showUsageView(descriptor, isVariable(), isVariable());
|
|
} else {
|
|
final UsageInfo[] usages1 = usages[0];
|
|
execute(usages1);
|
|
}
|
|
}
|
|
|
|
void execute(final UsageInfo[] usages) {
|
|
CommandProcessor.getInstance().executeCommand(
|
|
myProject, new Runnable() {
|
|
public void run() {
|
|
ApplicationManager.getApplication().runWriteAction(new Runnable() {
|
|
public void run() {
|
|
doRefactoring(usages, null);
|
|
}
|
|
});
|
|
}
|
|
},
|
|
getCommandName(),
|
|
null
|
|
);
|
|
}
|
|
|
|
private UsageViewPresentation createPresentation(UsageViewDescriptor descriptor) {
|
|
UsageViewPresentation presentation = new UsageViewPresentation();
|
|
presentation.setTabText("Refactoring preview");
|
|
presentation.setTargetsNodeText(descriptor.getProcessedElementsHeader());
|
|
presentation.setShowReadOnlyStatusAsRed(true);
|
|
presentation.setShowCancelButton(true);
|
|
presentation.setCodeUsagesString(descriptor.getCodeReferencesText(1, 1)); //TODO
|
|
presentation.setNonCodeUsagesString(descriptor.getCommentReferencesText(1, 1)); //TODO
|
|
return presentation;
|
|
}
|
|
|
|
private void showUsageView(final UsageViewDescriptor viewDescriptor, boolean showReadAccessIcon, boolean showWriteAccessIcon) {
|
|
UsageViewManager viewManager = myProject.getComponent(UsageViewManager.class);
|
|
|
|
final PsiElement[] initialElements = viewDescriptor.getElements();
|
|
final UsageTarget[] targets = PsiElement2UsageTargetAdapter.convert(initialElements);
|
|
|
|
Factory<UsageSearcher> searcherFactory = new Factory<UsageSearcher>() {
|
|
boolean myRequireRefresh = false;
|
|
|
|
public UsageSearcher create() {
|
|
UsageSearcher usageSearcher = new UsageSearcher() {
|
|
public void generate(Processor<Usage> processor) {
|
|
final PsiElement[] currentElements;
|
|
if (myRequireRefresh) {
|
|
List<PsiElement> elements = new ArrayList<PsiElement>();
|
|
for (int i = 0; i < targets.length; i++) {
|
|
UsageTarget target = targets[i];
|
|
if (target.isValid()) {
|
|
elements.add(((PsiElement2UsageTargetAdapter)target).getElement());
|
|
}
|
|
}
|
|
currentElements = elements.toArray(new PsiElement[elements.size()]);
|
|
viewDescriptor.refresh(currentElements);
|
|
}
|
|
else {
|
|
currentElements = initialElements;
|
|
myRequireRefresh = true;
|
|
}
|
|
|
|
UsageInfo[] usageInfos = viewDescriptor.getUsages();
|
|
final Usage[] usages = UsageInfoToUsageConverter.convert(new UsageInfoToUsageConverter.TargetElementsDescriptor(currentElements), usageInfos);
|
|
|
|
for (int i = 0; i < usages.length; i++) {
|
|
Usage usage = usages[i];
|
|
if (!processor.process(usage)) return;
|
|
}
|
|
|
|
if (usages.length > 0) {
|
|
ApplicationManager.getApplication().invokeLater(new Runnable() { // some people call processors in write action
|
|
public void run() {
|
|
RefactoringUtil.showInfoDialog(getInfo(), myProject);
|
|
}
|
|
}, ModalityState.NON_MMODAL);
|
|
}
|
|
}
|
|
};
|
|
|
|
return usageSearcher;
|
|
}
|
|
};
|
|
|
|
final com.intellij.usages.UsageView usageView = viewManager.searchAndShowUsages(targets, searcherFactory, true, false, createPresentation(viewDescriptor));
|
|
|
|
final Runnable refactoringRunnable = new Runnable() {
|
|
public void run() {
|
|
final Set<UsageInfo> excludedUsageInfos = getExcludedUsages(usageView);
|
|
doRefactoring(viewDescriptor.getUsages(), excludedUsageInfos);
|
|
}
|
|
};
|
|
|
|
String canNotMakeString = "Cannot perform the refactoring operation.\n" +
|
|
"There were changes in code after the usages have been found.\n" +
|
|
"Please, perform the usage search again.";
|
|
|
|
usageView.addPerformOperationAction(refactoringRunnable, getCommandName(), canNotMakeString, "Do Refactor", SystemInfo.isMac ? 0 : 'D');
|
|
}
|
|
|
|
private Set<UsageInfo> getExcludedUsages(final UsageView usageView) {
|
|
Set<Usage> usages = usageView.getExcludedUsages();
|
|
|
|
Set<UsageInfo> excludedUsageInfos = new HashSet<UsageInfo>();
|
|
for (Iterator<Usage> i = usages.iterator(); i.hasNext();) {
|
|
Usage usage = i.next();
|
|
if (usage instanceof UsageInfo2UsageAdapter) {
|
|
excludedUsageInfos.add(((UsageInfo2UsageAdapter)usage).getUsageInfo());
|
|
}
|
|
}
|
|
return excludedUsageInfos;
|
|
}
|
|
|
|
private static String getInfo() {
|
|
return "Press the \"Do Refactor\" button at the bottom of the search results panel\n" +
|
|
"to complete the refactoring operation.";
|
|
}
|
|
|
|
private void doRefactoring(UsageInfo[] usages, Set<UsageInfo> excludedUsages) {
|
|
if (usages != null) {
|
|
ArrayList<UsageInfo> array = new ArrayList<UsageInfo>();
|
|
for (int i = 0; i < usages.length; i++) {
|
|
UsageInfo usage = usages[i];
|
|
if (excludedUsages != null && excludedUsages.contains(usage)) {
|
|
continue;
|
|
}
|
|
//if (usage.isExcluded()) continue;
|
|
final PsiElement element = usage.getElement();
|
|
if (element == null) continue;
|
|
if (!element.isWritable()) continue;
|
|
array.add(usage);
|
|
}
|
|
usages = array.toArray(new UsageInfo[array.size()]);
|
|
}
|
|
|
|
com.intellij.openapi.localVcs.LvcsAction action = LvcsIntegration.checkinFilesBeforeRefactoring(myProject, getCommandName());
|
|
|
|
try {
|
|
final UsageInfo[] _usages = usages;
|
|
ApplicationManager.getApplication().runWriteAction(new RefactoringChangeMarker() {
|
|
public Object getId() {
|
|
return myMarkerId;
|
|
}
|
|
|
|
public void run() {
|
|
PsiDocumentManager.getInstance(myProject).commitAllDocuments();
|
|
RefactoringListenerManagerImpl listenerManager =
|
|
(RefactoringListenerManagerImpl) RefactoringListenerManager.getInstance(myProject);
|
|
myTransaction = listenerManager.startTransaction();
|
|
performRefactoring(_usages);
|
|
myTransaction.commit();
|
|
performPsiSpoilingRefactoring();
|
|
}
|
|
});
|
|
} finally {
|
|
LvcsIntegration.checkinFilesAfterRefactoring(myProject, action);
|
|
}
|
|
|
|
if (usages != null) {
|
|
int count = usages.length;
|
|
if (count > 0) {
|
|
WindowManager.getInstance().getStatusBar(myProject).setInfo(count + " occurrence" + (count > 1 ? "s" : "") + " changed");
|
|
} else {
|
|
if (!isPreviewUsages(usages)) {
|
|
WindowManager.getInstance().getStatusBar(myProject).setInfo("No occurrences found");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Refactorings that spoil PSI (write something directly to documents etc.) should
|
|
* do that in this method.<br>
|
|
* This method is called immediately after
|
|
* <code>{@link #performRefactoring(com.intellij.usageView.UsageInfo[])}</code>.
|
|
*/
|
|
protected void performPsiSpoilingRefactoring() {
|
|
|
|
}
|
|
|
|
protected void prepareSuccessful() {
|
|
if (myPrepareSuccessfulSwingThreadCallback != null) {
|
|
// make sure that dialog is closed in swing thread
|
|
if (!ApplicationManager.getApplication().isDispatchThread()) {
|
|
try {
|
|
SwingUtilities.invokeAndWait(myPrepareSuccessfulSwingThreadCallback);
|
|
} catch (InterruptedException e) {
|
|
LOG.error(e);
|
|
} catch (InvocationTargetException e) {
|
|
LOG.error(e);
|
|
}
|
|
} else {
|
|
myPrepareSuccessfulSwingThreadCallback.run();
|
|
}
|
|
// ToolWindowManager.getInstance(myProject).invokeLater(myPrepareSuccessfulSwingThreadCallback);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Override in subclasses
|
|
*/
|
|
protected void prepareTestRun() {
|
|
|
|
}
|
|
|
|
public final void testRun() {
|
|
PsiDocumentManager.getInstance(myProject).commitAllDocuments();
|
|
prepareTestRun();
|
|
UsageInfo[] usages = findUsages();
|
|
UsageInfo[][] u = new UsageInfo[][]{usages};
|
|
preprocessUsages(u);
|
|
RefactoringListenerManagerImpl listenerManager =
|
|
(RefactoringListenerManagerImpl) RefactoringListenerManager.getInstance(myProject);
|
|
myTransaction = listenerManager.startTransaction();
|
|
performRefactoring(u[0]);
|
|
myTransaction.commit();
|
|
performPsiSpoilingRefactoring();
|
|
}
|
|
|
|
public boolean showConflicts(final ArrayList<String> conflicts, UsageInfo[][] usages) {
|
|
boolean result;
|
|
if (conflicts.size() > 0 && myPrepareSuccessfulSwingThreadCallback != null) {
|
|
final ConflictsDialog conflictsDialog = new ConflictsDialog(conflicts.toArray(new String[conflicts.size()]), myProject);
|
|
conflictsDialog.show();
|
|
result = conflictsDialog.isOK();
|
|
}
|
|
else {
|
|
result = true;
|
|
}
|
|
if (result) {
|
|
prepareSuccessful();
|
|
return true;
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
} |