| /* |
| * Copyright 2000-2014 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.intellij.refactoring.util.duplicates; |
| |
| import com.intellij.analysis.AnalysisScope; |
| import com.intellij.analysis.AnalysisUIOptions; |
| import com.intellij.analysis.BaseAnalysisActionDialog; |
| import com.intellij.history.LocalHistory; |
| import com.intellij.history.LocalHistoryAction; |
| import com.intellij.openapi.actionSystem.DataContext; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.application.ApplicationNamesInfo; |
| import com.intellij.openapi.application.ModalityState; |
| import com.intellij.openapi.command.CommandProcessor; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.editor.Editor; |
| import com.intellij.openapi.module.Module; |
| import com.intellij.openapi.module.ModuleUtilCore; |
| import com.intellij.openapi.progress.ProgressIndicator; |
| import com.intellij.openapi.progress.ProgressManager; |
| import com.intellij.openapi.progress.Task; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.project.ProjectUtil; |
| import com.intellij.openapi.ui.Messages; |
| import com.intellij.openapi.util.Computable; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.openapi.wm.WindowManager; |
| import com.intellij.psi.*; |
| import com.intellij.psi.impl.source.PostprocessReformattingAspect; |
| import com.intellij.psi.search.LocalSearchScope; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.refactoring.HelpID; |
| import com.intellij.refactoring.RefactoringActionHandler; |
| import com.intellij.refactoring.RefactoringBundle; |
| import com.intellij.refactoring.extractMethod.InputVariables; |
| import com.intellij.refactoring.util.CommonRefactoringUtil; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.*; |
| |
| /** |
| * @author dsl |
| */ |
| public class MethodDuplicatesHandler implements RefactoringActionHandler { |
| public static final String REFACTORING_NAME = RefactoringBundle.message("replace.method.code.duplicates.title"); |
| private static final Logger LOG = Logger.getInstance("#" + MethodDuplicatesHandler.class.getName()); |
| |
| @Override |
| public void invoke(@NotNull final Project project, final Editor editor, PsiFile file, DataContext dataContext) { |
| final int offset = editor.getCaretModel().getOffset(); |
| final PsiElement element = file.findElementAt(offset); |
| final PsiMember member = PsiTreeUtil.getParentOfType(element, PsiMember.class); |
| final String cannotRefactorMessage = getCannotRefactorMessage(member); |
| if (cannotRefactorMessage != null) { |
| String message = RefactoringBundle.getCannotRefactorMessage(cannotRefactorMessage); |
| showErrorMessage(message, project, editor); |
| return; |
| } |
| |
| final AnalysisScope scope = new AnalysisScope(file); |
| final Module module = ModuleUtilCore.findModuleForPsiElement(file); |
| final BaseAnalysisActionDialog dlg = new BaseAnalysisActionDialog(RefactoringBundle.message("replace.method.duplicates.scope.chooser.title", REFACTORING_NAME), |
| RefactoringBundle.message("replace.method.duplicates.scope.chooser.message"), |
| project, scope, module != null ? module.getName() : null, false, |
| AnalysisUIOptions.getInstance(project), element); |
| dlg.show(); |
| if (dlg.isOK()) { |
| ProgressManager.getInstance().run(new Task.Backgroundable(project, "Locate duplicates", true) { |
| @Override |
| public void run(@NotNull ProgressIndicator indicator) { |
| indicator.setIndeterminate(true); |
| invokeOnScope(project, member, dlg.getScope(AnalysisUIOptions.getInstance(project), scope, project, module)); |
| } |
| }); |
| } |
| } |
| |
| @Nullable |
| private static String getCannotRefactorMessage(PsiMember member) { |
| if (member == null) { |
| return RefactoringBundle.message("locate.caret.inside.a.method"); |
| } |
| if (member instanceof PsiMethod) { |
| if (((PsiMethod)member).isConstructor()) { |
| return RefactoringBundle.message("replace.with.method.call.does.not.work.for.constructors"); |
| } |
| final PsiCodeBlock body = ((PsiMethod)member).getBody(); |
| if (body == null) { |
| return RefactoringBundle.message("method.does.not.have.a.body", member.getName()); |
| } |
| final PsiStatement[] statements = body.getStatements(); |
| if (statements.length == 0) { |
| return RefactoringBundle.message("method.has.an.empty.body", member.getName()); |
| } |
| } else if (member instanceof PsiField) { |
| final PsiField field = (PsiField)member; |
| if (!field.hasInitializer()) { |
| return "Field " + member.getName() + " doesn't have initializer"; |
| } |
| final PsiClass containingClass = field.getContainingClass(); |
| if (!field.hasModifierProperty(PsiModifier.FINAL) || !field.hasModifierProperty(PsiModifier.STATIC) || |
| containingClass == null || containingClass.getQualifiedName() == null) { |
| return "Replace Duplicates works with constants only"; |
| } |
| } else { |
| return "Caret should be inside method or constant"; |
| } |
| return null; |
| } |
| |
| public static void invokeOnScope(final Project project, final PsiMember member, final AnalysisScope scope) { |
| invokeOnScope(project, Collections.singleton(member), scope, false); |
| } |
| |
| public static void invokeOnScope(final Project project, final Set<PsiMember> members, final AnalysisScope scope, boolean silent) { |
| final Map<PsiMember, List<Match>> duplicates = new HashMap<PsiMember, List<Match>>(); |
| final int fileCount = scope.getFileCount(); |
| final ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator(); |
| if (progressIndicator != null) { |
| progressIndicator.setIndeterminate(false); |
| } |
| |
| final Map<PsiMember, Set<Module>> memberWithModulesMap = new HashMap<PsiMember, Set<Module>>(); |
| for (final PsiMember member : members) { |
| final Module module = ApplicationManager.getApplication().runReadAction(new Computable<Module>() { |
| @Override |
| public Module compute() { |
| return ModuleUtilCore.findModuleForPsiElement(member); |
| } |
| }); |
| if (module != null) { |
| final HashSet<Module> dependencies = new HashSet<Module>(); |
| ApplicationManager.getApplication().runReadAction(new Runnable() { |
| public void run() { |
| ModuleUtilCore.collectModulesDependsOn(module, dependencies); |
| } |
| }); |
| memberWithModulesMap.put(member, dependencies); |
| } |
| } |
| |
| scope.accept(new PsiRecursiveElementVisitor() { |
| private int myFileCount = 0; |
| @Override public void visitFile(final PsiFile file) { |
| if (progressIndicator != null){ |
| if (progressIndicator.isCanceled()) return; |
| progressIndicator.setFraction(((double)myFileCount++)/fileCount); |
| final VirtualFile virtualFile = file.getVirtualFile(); |
| if (virtualFile != null) { |
| progressIndicator.setText2(ProjectUtil.calcRelativeToProjectPath(virtualFile, project)); |
| } |
| } |
| final Module targetModule = ModuleUtilCore.findModuleForPsiElement(file); |
| if (targetModule == null) return; |
| for (Map.Entry<PsiMember, Set<Module>> entry : memberWithModulesMap.entrySet()) { |
| final Set<Module> dependencies = entry.getValue(); |
| if (dependencies == null || !dependencies.contains(targetModule)) continue; |
| |
| final PsiMember method = entry.getKey(); |
| final List<Match> matchList = hasDuplicates(file, method); |
| for (Iterator<Match> iterator = matchList.iterator(); iterator.hasNext(); ) { |
| Match match = iterator.next(); |
| final PsiElement matchStart = match.getMatchStart(); |
| final PsiElement matchEnd = match.getMatchEnd(); |
| for (PsiMember psiMember : members) { |
| if (PsiTreeUtil.isAncestor(psiMember, matchStart, false) || |
| PsiTreeUtil.isAncestor(psiMember, matchEnd, false)) { |
| iterator.remove(); |
| break; |
| } |
| } |
| } |
| if (!matchList.isEmpty()) { |
| List<Match> matches = duplicates.get(method); |
| if (matches == null) { |
| matches = new ArrayList<Match>(); |
| duplicates.put(method, matches); |
| } |
| matches.addAll(matchList); |
| } |
| } |
| } |
| }); |
| replaceDuplicate(project, duplicates, members); |
| if (!silent) { |
| final Runnable nothingFoundRunnable = new Runnable() { |
| @Override |
| public void run() { |
| if (duplicates.isEmpty()) { |
| final String message = RefactoringBundle.message("idea.has.not.found.any.code.that.can.be.replaced.with.method.call", |
| ApplicationNamesInfo.getInstance().getProductName()); |
| Messages.showInfoMessage(project, message, REFACTORING_NAME); |
| } |
| } |
| }; |
| if (ApplicationManager.getApplication().isUnitTestMode()) { |
| nothingFoundRunnable.run(); |
| } else { |
| ApplicationManager.getApplication().invokeLater(nothingFoundRunnable, ModalityState.NON_MODAL); |
| } |
| } |
| } |
| |
| private static void replaceDuplicate(final Project project, final Map<PsiMember, List<Match>> duplicates, final Set<PsiMember> methods) { |
| final ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator(); |
| if (progressIndicator != null && progressIndicator.isCanceled()) return; |
| |
| final Runnable replaceRunnable = new Runnable() { |
| @Override |
| public void run() { |
| LocalHistoryAction a = LocalHistory.getInstance().startAction(REFACTORING_NAME); |
| try { |
| for (final PsiMember member : methods) { |
| final List<Match> matches = duplicates.get(member); |
| if (matches == null) continue; |
| final int duplicatesNo = matches.size(); |
| WindowManager.getInstance().getStatusBar(project).setInfo(getStatusMessage(duplicatesNo)); |
| CommandProcessor.getInstance().executeCommand(project, new Runnable() { |
| @Override |
| public void run() { |
| PostprocessReformattingAspect.getInstance(project).postponeFormattingInside(new Runnable() { |
| @Override |
| public void run() { |
| final MatchProvider matchProvider = |
| member instanceof PsiMethod ? new MethodDuplicatesMatchProvider((PsiMethod)member, matches) |
| : new ConstantMatchProvider(member, project, matches); |
| DuplicatesImpl.invoke(project, matchProvider); |
| } |
| }); |
| } |
| }, REFACTORING_NAME, REFACTORING_NAME); |
| |
| WindowManager.getInstance().getStatusBar(project).setInfo(""); |
| } |
| } |
| finally { |
| a.finish(); |
| } |
| } |
| }; |
| ApplicationManager.getApplication().invokeLater(replaceRunnable, ModalityState.NON_MODAL); |
| } |
| |
| public static List<Match> hasDuplicates(final PsiFile file, final PsiMember member) { |
| PsiElement[] pattern; |
| ReturnValue matchedReturnValue = null; |
| if (member instanceof PsiMethod) { |
| final PsiCodeBlock body = ((PsiMethod)member).getBody(); |
| LOG.assertTrue(body != null); |
| final PsiStatement[] statements = body.getStatements(); |
| pattern = statements; |
| matchedReturnValue = null; |
| if (statements.length != 1 || !(statements[0] instanceof PsiReturnStatement)) { |
| final PsiStatement lastStatement = statements.length > 0 ? statements[statements.length - 1] : null; |
| if (lastStatement instanceof PsiReturnStatement) { |
| final PsiExpression returnValue = ((PsiReturnStatement)lastStatement).getReturnValue(); |
| if (returnValue instanceof PsiReferenceExpression) { |
| final PsiElement resolved = ((PsiReferenceExpression)returnValue).resolve(); |
| if (resolved instanceof PsiVariable) { |
| pattern = new PsiElement[statements.length - 1]; |
| System.arraycopy(statements, 0, pattern, 0, statements.length - 1); |
| matchedReturnValue = new VariableReturnValue((PsiVariable)resolved); |
| } |
| } |
| } |
| } else { |
| final PsiExpression returnValue = ((PsiReturnStatement)statements[0]).getReturnValue(); |
| if (returnValue != null) { |
| pattern = new PsiElement[]{returnValue}; |
| } |
| } |
| } else { |
| pattern = new PsiElement[]{((PsiField)member).getInitializer()}; |
| } |
| if (pattern.length == 0) { |
| return Collections.emptyList(); |
| } |
| final List<? extends PsiVariable> inputVariables = |
| member instanceof PsiMethod ? Arrays.asList(((PsiMethod)member).getParameterList().getParameters()) : new ArrayList<PsiVariable>(); |
| final DuplicatesFinder duplicatesFinder = |
| new DuplicatesFinder(pattern, |
| new InputVariables(inputVariables, member.getProject(), new LocalSearchScope(pattern), false), |
| matchedReturnValue, |
| new ArrayList<PsiVariable>()); |
| |
| return duplicatesFinder.findDuplicates(file); |
| } |
| |
| static String getStatusMessage(final int duplicatesNo) { |
| return RefactoringBundle.message("method.duplicates.found.message", duplicatesNo); |
| } |
| |
| private static void showErrorMessage(String message, Project project, Editor editor) { |
| CommonRefactoringUtil.showErrorHint(project, editor, message, REFACTORING_NAME, HelpID.METHOD_DUPLICATES); |
| } |
| |
| @Override |
| public void invoke(@NotNull Project project, @NotNull PsiElement[] elements, DataContext dataContext) { |
| throw new UnsupportedOperationException(); |
| } |
| } |