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
;
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);
72 String message
= RefactoringBundle
.getCannotRefactorMessage(RefactoringBundle
.message("locate.caret.inside.a.method"));
73 showErrorMessage(message
, project
, editor
);
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();
82 String message
= RefactoringBundle
.getCannotRefactorMessage(RefactoringBundle
.message("method.does.not.have.a.body", method
.getName()));
83 showErrorMessage(message
, project
, editor
);
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
);
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
));
101 ProgressManager
.getInstance().runProcessWithProgressSynchronously(new Runnable() {
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() {
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();
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
);
139 final ProgressIndicator progressIndicator
= ProgressManager
.getInstance().getProgressIndicator();
140 if (progressIndicator
!= null && progressIndicator
.isCanceled()) return;
142 final Runnable replaceRunnable
= new Runnable() {
144 final int duplicatesNo
= duplicates
.size();
145 WindowManager
.getInstance().getStatusBar(project
).setInfo(getStatusMessage(duplicatesNo
));
146 CommandProcessor
.getInstance().executeCommand(project
, new Runnable() {
148 PostprocessReformattingAspect
.getInstance(project
).postponeFormattingInside(new Runnable() {
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();
163 ApplicationManager
.getApplication().invokeLater(replaceRunnable
, ModalityState
.NON_MODAL
);
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
);
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
) {
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
);
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);
254 qualifierExpression
.replace(match
.getInstanceExpression());
256 qualifierExpression
.replace(factory
.createReferenceExpression(containingClass
));
259 VisibilityUtil
.escalateVisibility(myMethod
, match
.getMatchStart());
260 final PsiCodeBlock body
= myMethod
.getBody();
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)) {
282 final PsiClass psiClass
= PsiTreeUtil
.getParentOfType(match
.getMatchStart(), PsiClass
.class);
283 if (psiClass
!= null) {
284 if (InheritanceUtil
.isInheritorOrSelf(psiClass
, myMethod
.getContainingClass(), true)) return false;
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;
299 public List
<Match
> getDuplicates() {
303 public boolean hasDuplicates() {
304 return myDuplicates
.isEmpty();
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");