update copyright
[fedora-idea.git] / java / java-impl / src / com / intellij / refactoring / util / duplicates / MethodDuplicatesHandler.java
blobdadb1ec30ad772c3c399ea7dde680d2c88146102
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.
16 package com.intellij.refactoring.util.duplicates;
18 import com.intellij.analysis.AnalysisScope;
19 import com.intellij.analysis.AnalysisUIOptions;
20 import com.intellij.analysis.BaseAnalysisActionDialog;
21 import com.intellij.history.LocalHistory;
22 import com.intellij.history.LocalHistoryAction;
23 import com.intellij.openapi.actionSystem.DataContext;
24 import com.intellij.openapi.application.ApplicationManager;
25 import com.intellij.openapi.application.ApplicationNamesInfo;
26 import com.intellij.openapi.application.ModalityState;
27 import com.intellij.openapi.command.CommandProcessor;
28 import com.intellij.openapi.diagnostic.Logger;
29 import com.intellij.openapi.editor.Editor;
30 import com.intellij.openapi.module.Module;
31 import com.intellij.openapi.module.ModuleUtil;
32 import com.intellij.openapi.progress.ProgressIndicator;
33 import com.intellij.openapi.progress.ProgressManager;
34 import com.intellij.openapi.project.Project;
35 import com.intellij.openapi.ui.Messages;
36 import com.intellij.openapi.wm.WindowManager;
37 import com.intellij.psi.*;
38 import com.intellij.psi.codeStyle.CodeStyleManager;
39 import com.intellij.psi.impl.source.PostprocessReformattingAspect;
40 import com.intellij.psi.search.LocalSearchScope;
41 import com.intellij.psi.util.InheritanceUtil;
42 import com.intellij.psi.util.PsiTreeUtil;
43 import com.intellij.psi.util.PsiTypesUtil;
44 import com.intellij.psi.util.PsiUtil;
45 import com.intellij.refactoring.HelpID;
46 import com.intellij.refactoring.RefactoringActionHandler;
47 import com.intellij.refactoring.RefactoringBundle;
48 import com.intellij.refactoring.extractMethod.InputVariables;
49 import com.intellij.refactoring.util.CommonRefactoringUtil;
50 import com.intellij.refactoring.util.RefactoringUtil;
51 import com.intellij.util.VisibilityUtil;
52 import com.intellij.util.IncorrectOperationException;
53 import org.jetbrains.annotations.NonNls;
54 import org.jetbrains.annotations.NotNull;
56 import java.util.ArrayList;
57 import java.util.Arrays;
58 import java.util.List;
60 /**
61 * @author dsl
63 public class MethodDuplicatesHandler implements RefactoringActionHandler {
64 public static final String REFACTORING_NAME = RefactoringBundle.message("replace.method.code.duplicates.title");
65 private static final Logger LOG = Logger.getInstance("#" + MethodDuplicatesHandler.class.getName());
67 public void invoke(@NotNull final Project project, final Editor editor, PsiFile file, DataContext dataContext) {
68 final int offset = editor.getCaretModel().getOffset();
69 final PsiElement element = file.findElementAt(offset);
70 final PsiMethod method = PsiTreeUtil.getParentOfType(element, PsiMethod.class);
71 if (method == null) {
72 String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("locate.caret.inside.a.method"));
73 showErrorMessage(message, project, editor);
74 return;
76 if (method.isConstructor()) {
77 String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("replace.with.method.call.does.not.work.for.constructors"));
78 showErrorMessage(message, project, editor);
80 final PsiCodeBlock body = method.getBody();
81 if (body == null) {
82 String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("method.does.not.have.a.body", method.getName()));
83 showErrorMessage(message, project, editor);
84 return;
86 final PsiStatement[] statements = body.getStatements();
87 if (statements.length == 0) {
88 String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("method.has.an.empty.body", method.getName()));
90 showErrorMessage(message, project, editor);
91 return;
93 final AnalysisScope scope = new AnalysisScope(file);
94 final Module module = ModuleUtil.findModuleForPsiElement(file);
95 final BaseAnalysisActionDialog dlg = new BaseAnalysisActionDialog(RefactoringBundle.message("replace.method.duplicates.scope.chooser.title", REFACTORING_NAME),
96 RefactoringBundle.message("replace.method.duplicates.scope.chooser.message"),
97 project, scope, module != null ? module.getName() : null, false,
98 AnalysisUIOptions.getInstance(project));
99 dlg.show();
100 if (dlg.isOK()) {
101 ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() {
102 public void run() {
103 ProgressManager.getInstance().getProgressIndicator().setIndeterminate(true);
104 invokeOnScope(project, method, dlg.getScope(AnalysisUIOptions.getInstance(project), scope, project, module));
106 }, "Locate method duplicates", true, project) ;
110 public static void invokeOnScope(final Project project, final PsiMethod method, final AnalysisScope scope) {
111 final List<Match> duplicates = new ArrayList<Match>();
112 scope.accept(new PsiRecursiveElementVisitor() {
113 @Override public void visitFile(final PsiFile file) {
114 final ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator();
115 if (progressIndicator != null && progressIndicator.isCanceled()) return;
116 duplicates.addAll(hasDuplicates(file, method));
119 replaceDuplicate(project, duplicates, method);
120 final Runnable nothingFoundRunnable = new Runnable() {
121 public void run() {
122 if (duplicates.isEmpty()) {
123 final String message = RefactoringBundle.message("idea.has.not.found.any.code.that.can.be.replaced.with.method.call",
124 ApplicationNamesInfo.getInstance().getProductName());
125 Messages.showInfoMessage(project, message, REFACTORING_NAME);
129 if (ApplicationManager.getApplication().isUnitTestMode()) {
130 nothingFoundRunnable.run();
131 } else {
132 ApplicationManager.getApplication().invokeLater(nothingFoundRunnable, ModalityState.NON_MODAL);
136 private static void replaceDuplicate(final Project project, final List<Match> duplicates, final PsiMethod method) {
137 LocalHistoryAction a = LocalHistory.startAction(project, REFACTORING_NAME);
138 try {
139 final ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator();
140 if (progressIndicator != null && progressIndicator.isCanceled()) return;
142 final Runnable replaceRunnable = new Runnable() {
143 public void run() {
144 final int duplicatesNo = duplicates.size();
145 WindowManager.getInstance().getStatusBar(project).setInfo(getStatusMessage(duplicatesNo));
146 CommandProcessor.getInstance().executeCommand(project, new Runnable() {
147 public void run() {
148 PostprocessReformattingAspect.getInstance(project).postponeFormattingInside(new Runnable() {
149 public void run() {
150 DuplicatesImpl.invoke(project, new MethodDuplicatesMatchProvider(method, duplicates));
154 }, REFACTORING_NAME, REFACTORING_NAME);
156 WindowManager.getInstance().getStatusBar(project).setInfo("");
159 if (ApplicationManager.getApplication().isUnitTestMode()) {
160 replaceRunnable.run();
162 else {
163 ApplicationManager.getApplication().invokeLater(replaceRunnable, ModalityState.NON_MODAL);
166 finally {
167 a.finish();
171 public static List<Match> hasDuplicates(final PsiFile file, final PsiMethod method) {
172 final PsiCodeBlock body = method.getBody();
173 LOG.assertTrue(body != null);
174 final PsiStatement[] statements = body.getStatements();
175 PsiElement[] pattern = statements;
176 ReturnValue matchedReturnValue = null;
177 if (statements.length != 1 || !(statements[0] instanceof PsiReturnStatement)) {
178 final PsiStatement lastStatement = statements[statements.length - 1];
179 if (lastStatement instanceof PsiReturnStatement) {
180 final PsiExpression returnValue = ((PsiReturnStatement)lastStatement).getReturnValue();
181 if (returnValue instanceof PsiReferenceExpression) {
182 final PsiElement resolved = ((PsiReferenceExpression)returnValue).resolve();
183 if (resolved instanceof PsiVariable) {
184 pattern = new PsiElement[statements.length - 1];
185 System.arraycopy(statements, 0, pattern, 0, statements.length - 1);
186 matchedReturnValue = new VariableReturnValue((PsiVariable)resolved);
190 } else {
191 final PsiExpression returnValue = ((PsiReturnStatement)statements[0]).getReturnValue();
192 if (returnValue != null) {
193 pattern = new PsiElement[]{returnValue};
196 final DuplicatesFinder duplicatesFinder =
197 new DuplicatesFinder(pattern,
198 new InputVariables(Arrays.asList(method.getParameterList().getParameters()), method.getProject(), new LocalSearchScope(pattern), false), matchedReturnValue,
199 new ArrayList<PsiVariable>());
201 return duplicatesFinder.findDuplicates(file);
204 static String getStatusMessage(final int duplicatesNo) {
205 return RefactoringBundle.message("method.duplicates.found.message", duplicatesNo);
208 private static void showErrorMessage(String message, Project project, Editor editor) {
209 CommonRefactoringUtil.showErrorHint(project, editor, message, REFACTORING_NAME, HelpID.METHOD_DUPLICATES);
212 public void invoke(@NotNull Project project, @NotNull PsiElement[] elements, DataContext dataContext) {
213 throw new UnsupportedOperationException();
216 private static class MethodDuplicatesMatchProvider implements MatchProvider {
217 private final PsiMethod myMethod;
218 private final List<Match> myDuplicates;
220 private MethodDuplicatesMatchProvider(PsiMethod method, List<Match> duplicates) {
221 myMethod = method;
222 myDuplicates = duplicates;
225 public PsiElement processMatch(Match match) throws IncorrectOperationException {
226 match.changeSignature(myMethod);
227 final PsiClass containingClass = myMethod.getContainingClass();
228 if (isEssentialStaticContextAbsent(match)) {
229 PsiUtil.setModifierProperty(myMethod, PsiModifier.STATIC, true);
232 final PsiElementFactory factory = JavaPsiFacade.getInstance(myMethod.getProject()).getElementFactory();
233 final boolean needQualifier = match.getInstanceExpression() != null;
234 final boolean needStaticQualifier = isExternal(match);
235 @NonNls final String text = needQualifier || needStaticQualifier ? "q." + myMethod.getName() + "()": myMethod.getName() + "()";
236 PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)factory.createExpressionFromText(text, null);
237 methodCallExpression = (PsiMethodCallExpression)CodeStyleManager.getInstance(myMethod.getManager()).reformat(methodCallExpression);
238 final PsiParameter[] parameters = myMethod.getParameterList().getParameters();
239 for (final PsiParameter parameter : parameters) {
240 final List<PsiElement> parameterValue = match.getParameterValues(parameter);
241 if (parameterValue != null) {
242 for (PsiElement val : parameterValue) {
243 methodCallExpression.getArgumentList().add(val);
246 else {
247 methodCallExpression.getArgumentList().add(factory.createExpressionFromText(PsiTypesUtil.getDefaultValueOfType(parameter.getType()), parameter));
250 if (needQualifier || needStaticQualifier) {
251 final PsiExpression qualifierExpression = methodCallExpression.getMethodExpression().getQualifierExpression();
252 LOG.assertTrue(qualifierExpression != null);
253 if (needQualifier) {
254 qualifierExpression.replace(match.getInstanceExpression());
255 } else {
256 qualifierExpression.replace(factory.createReferenceExpression(containingClass));
259 VisibilityUtil.escalateVisibility(myMethod, match.getMatchStart());
260 final PsiCodeBlock body = myMethod.getBody();
261 assert body != null;
262 final PsiStatement[] statements = body.getStatements();
263 if (statements[statements.length - 1] instanceof PsiReturnStatement) {
264 final PsiExpression value = ((PsiReturnStatement)statements[statements.length - 1]).getReturnValue();
265 if (value instanceof PsiReferenceExpression) {
266 final PsiElement var = ((PsiReferenceExpression)value).resolve();
267 if (var instanceof PsiVariable) {
268 match.replace(myMethod, methodCallExpression, (PsiVariable)var);
269 return methodCallExpression;
273 return match.replace(myMethod, methodCallExpression, null);
278 private boolean isExternal(final Match match) {
279 if (PsiTreeUtil.isAncestor(myMethod.getContainingClass(), match.getMatchStart(), false)) {
280 return false;
282 final PsiClass psiClass = PsiTreeUtil.getParentOfType(match.getMatchStart(), PsiClass.class);
283 if (psiClass != null) {
284 if (InheritanceUtil.isInheritorOrSelf(psiClass, myMethod.getContainingClass(), true)) return false;
286 return true;
289 private boolean isEssentialStaticContextAbsent(final Match match) {
290 if (!myMethod.hasModifierProperty(PsiModifier.STATIC)) {
291 final PsiExpression instanceExpression = match.getInstanceExpression();
292 if (instanceExpression != null) return false;
293 if (isExternal(match)) return true;
294 if (PsiTreeUtil.isAncestor(myMethod.getContainingClass(), match.getMatchStart(), false) && RefactoringUtil.isInStaticContext(match.getMatchStart(), myMethod.getContainingClass())) return true;
296 return false;
299 public List<Match> getDuplicates() {
300 return myDuplicates;
303 public boolean hasDuplicates() {
304 return myDuplicates.isEmpty();
307 @NotNull
308 public String getConfirmDuplicatePrompt(final Match match) {
309 final PsiElement matchStart = match.getMatchStart();
310 @Modifier String visibility = VisibilityUtil.getPossibleVisibility(myMethod, matchStart);
311 final boolean shouldBeStatic = isEssentialStaticContextAbsent(match);
312 final String signature = match.getChangedSignature(myMethod, myMethod.hasModifierProperty(PsiModifier.STATIC) || shouldBeStatic, visibility);
313 if (signature != null) {
314 return RefactoringBundle.message("replace.this.code.fragment.and.change.signature", signature);
316 final boolean needToEscalateVisibility = !PsiUtil.isAccessible(myMethod, matchStart, null);
317 if (needToEscalateVisibility) {
318 final String visibilityPresentation = VisibilityUtil.toPresentableText(visibility);
319 return shouldBeStatic
320 ? RefactoringBundle.message("replace.this.code.fragment.and.make.method.static.visible", visibilityPresentation)
321 : RefactoringBundle.message("replace.this.code.fragment.and.make.method.visible", visibilityPresentation);
323 if (shouldBeStatic) {
324 return RefactoringBundle.message("replace.this.code.fragment.and.make.method.static");
326 return RefactoringBundle.message("replace.this.code.fragment");