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
.introduceparameterobject
;
18 import com
.intellij
.ide
.util
.PackageUtil
;
19 import com
.intellij
.openapi
.application
.ApplicationManager
;
20 import com
.intellij
.openapi
.diagnostic
.Logger
;
21 import com
.intellij
.openapi
.module
.Module
;
22 import com
.intellij
.openapi
.module
.ModuleUtil
;
23 import com
.intellij
.openapi
.project
.Project
;
24 import com
.intellij
.openapi
.util
.Ref
;
25 import com
.intellij
.openapi
.util
.text
.StringUtil
;
26 import com
.intellij
.psi
.*;
27 import com
.intellij
.psi
.codeStyle
.CodeStyleManager
;
28 import com
.intellij
.psi
.codeStyle
.CodeStyleSettings
;
29 import com
.intellij
.psi
.codeStyle
.CodeStyleSettingsManager
;
30 import com
.intellij
.psi
.codeStyle
.JavaCodeStyleManager
;
31 import com
.intellij
.psi
.search
.GlobalSearchScope
;
32 import com
.intellij
.psi
.search
.searches
.OverridingMethodsSearch
;
33 import com
.intellij
.psi
.util
.PropertyUtil
;
34 import com
.intellij
.psi
.util
.PsiUtil
;
35 import com
.intellij
.psi
.util
.TypeConversionUtil
;
36 import com
.intellij
.refactoring
.RefactorJBundle
;
37 import com
.intellij
.refactoring
.introduceparameterobject
.usageInfo
.MergeMethodArguments
;
38 import com
.intellij
.refactoring
.introduceparameterobject
.usageInfo
.ReplaceParameterAssignmentWithCall
;
39 import com
.intellij
.refactoring
.introduceparameterobject
.usageInfo
.ReplaceParameterIncrementDecrement
;
40 import com
.intellij
.refactoring
.introduceparameterobject
.usageInfo
.ReplaceParameterReferenceWithCall
;
41 import com
.intellij
.refactoring
.psi
.PropertyUtils
;
42 import com
.intellij
.refactoring
.psi
.TypeParametersVisitor
;
43 import com
.intellij
.refactoring
.util
.FixableUsageInfo
;
44 import com
.intellij
.refactoring
.util
.FixableUsagesRefactoringProcessor
;
45 import com
.intellij
.refactoring
.util
.ParameterTablePanel
;
46 import com
.intellij
.refactoring
.util
.RefactoringUtil
;
47 import com
.intellij
.usageView
.UsageInfo
;
48 import com
.intellij
.usageView
.UsageViewDescriptor
;
49 import com
.intellij
.util
.IncorrectOperationException
;
50 import com
.intellij
.util
.containers
.MultiMap
;
51 import org
.jetbrains
.annotations
.NonNls
;
52 import org
.jetbrains
.annotations
.NotNull
;
56 public class IntroduceParameterObjectProcessor
extends FixableUsagesRefactoringProcessor
{
57 private static final Logger logger
= Logger
.getInstance("com.siyeh.rpp.introduceparameterobject.IntroduceParameterObjectProcessor");
59 private final PsiMethod method
;
60 private final String className
;
61 private final String packageName
;
62 private final List
<String
> getterNames
;
63 private final boolean keepMethodAsDelegate
;
64 private final boolean myUseExistingClass
;
65 private final boolean myCreateInnerClass
;
66 private final List
<ParameterTablePanel
.VariableData
> parameters
;
67 private final int[] paramsToMerge
;
68 private final List
<PsiTypeParameter
> typeParams
;
69 private final Set
<PsiParameter
> paramsNeedingSetters
= new HashSet
<PsiParameter
>();
70 private final PsiClass existingClass
;
71 private boolean myExistingClassCompatible
= true;
73 public IntroduceParameterObjectProcessor(String className
,
76 ParameterTablePanel
.VariableData
[] parameters
,
77 List
<String
> getterNames
,
78 boolean keepMethodAsDelegate
, final boolean useExistingClass
, final boolean createInnerClass
) {
79 super(method
.getProject());
81 this.className
= className
;
82 this.packageName
= packageName
;
83 this.getterNames
= getterNames
;
84 this.keepMethodAsDelegate
= keepMethodAsDelegate
;
85 myUseExistingClass
= useExistingClass
;
86 myCreateInnerClass
= createInnerClass
;
87 this.parameters
= new ArrayList
<ParameterTablePanel
.VariableData
>(Arrays
.asList(parameters
));
88 final PsiParameterList parameterList
= method
.getParameterList();
89 final PsiParameter
[] methodParams
= parameterList
.getParameters();
90 paramsToMerge
= new int[parameters
.length
];
91 for (int p
= 0; p
< parameters
.length
; p
++) {
92 ParameterTablePanel
.VariableData parameter
= parameters
[p
];
93 for (int i
= 0; i
< methodParams
.length
; i
++) {
94 final PsiParameter methodParam
= methodParams
[i
];
95 if (parameter
.variable
.equals(methodParam
)) {
101 final Set
<PsiTypeParameter
> typeParamSet
= new HashSet
<PsiTypeParameter
>();
102 final JavaRecursiveElementWalkingVisitor visitor
= new TypeParametersVisitor(typeParamSet
);
103 for (ParameterTablePanel
.VariableData parameter
: parameters
) {
104 parameter
.variable
.accept(visitor
);
106 typeParams
= new ArrayList
<PsiTypeParameter
>(typeParamSet
);
108 final String qualifiedName
= StringUtil
.getQualifiedName(packageName
, className
);
109 final GlobalSearchScope scope
= GlobalSearchScope
.allScope(myProject
);
110 existingClass
= JavaPsiFacade
.getInstance(myProject
).findClass(qualifiedName
, scope
);
114 protected UsageViewDescriptor
createUsageViewDescriptor(UsageInfo
[] usageInfos
) {
115 return new IntroduceParameterObjectUsageViewDescriptor(method
);
120 protected boolean preprocessUsages(final Ref
<UsageInfo
[]> refUsages
) {
121 MultiMap
<PsiElement
, String
> conflicts
= new MultiMap
<PsiElement
, String
>();
122 if (myUseExistingClass
) {
123 if (existingClass
== null) {
124 conflicts
.putValue(null, RefactorJBundle
.message("cannot.perform.the.refactoring") + "Could not find the selected class");
126 final String incompatibilityMessage
= "Selected class is not compatible with chosen parameters";
127 if (!myExistingClassCompatible
) {
129 .putValue(existingClass
, RefactorJBundle
.message("cannot.perform.the.refactoring") + incompatibilityMessage
);
132 if (!paramsNeedingSetters
.isEmpty()) {
133 conflicts
.putValue(existingClass
, RefactorJBundle
.message("cannot.perform.the.refactoring") + incompatibilityMessage
);
136 else if (existingClass
!= null) {
137 conflicts
.putValue(existingClass
,
138 RefactorJBundle
.message("cannot.perform.the.refactoring") +
139 RefactorJBundle
.message("there.already.exists.a.class.with.the.chosen.name"));
141 return showConflicts(conflicts
);
145 protected boolean showConflicts(final MultiMap
<PsiElement
, String
> conflicts
) {
146 if (!conflicts
.isEmpty() && ApplicationManager
.getApplication().isUnitTestMode()) {
147 throw new RuntimeException(StringUtil
.join(conflicts
.values(), "\n"));
149 return super.showConflicts(conflicts
);
152 public void findUsages(@NotNull List
<FixableUsageInfo
> usages
) {
153 if (myUseExistingClass
&& existingClass
!= null) {
154 myExistingClassCompatible
= existingClassIsCompatible(existingClass
, parameters
, getterNames
);
155 if (!myExistingClassCompatible
) return;
157 findUsagesForMethod(method
, usages
);
159 final PsiMethod
[] overridingMethods
= OverridingMethodsSearch
.search(method
, method
.getUseScope(), true).toArray(PsiMethod
.EMPTY_ARRAY
);
160 for (PsiMethod siblingMethod
: overridingMethods
) {
161 findUsagesForMethod(siblingMethod
, usages
);
165 private void findUsagesForMethod(PsiMethod overridingMethod
, List
<FixableUsageInfo
> usages
) {
166 final String fixedParamName
= calculateNewParamNameForMethod(overridingMethod
);
168 usages
.add(new MergeMethodArguments(overridingMethod
, className
, packageName
, fixedParamName
, paramsToMerge
, typeParams
, keepMethodAsDelegate
, myCreateInnerClass ? method
.getContainingClass() : null));
170 final ParamUsageVisitor visitor
= new ParamUsageVisitor(overridingMethod
, paramsToMerge
);
171 overridingMethod
.accept(visitor
);
172 final Set
<PsiReferenceExpression
> values
= visitor
.getParameterUsages();
173 for (PsiReferenceExpression paramUsage
: values
) {
174 final PsiParameter parameter
= (PsiParameter
)paramUsage
.resolve();
175 assert parameter
!= null;
176 final PsiMethod containingMethod
= (PsiMethod
)parameter
.getDeclarationScope();
177 final int index
= containingMethod
.getParameterList().getParameterIndex(parameter
);
178 final PsiParameter replacedParameter
= method
.getParameterList().getParameters()[index
];
179 @NonNls String getter
= null;
180 if (getterNames
!= null) {
181 for (int i
= 0; i
< parameters
.size(); i
++) {
182 ParameterTablePanel
.VariableData data
= parameters
.get(i
);
183 if (data
.variable
.equals(parameter
)) {
184 getter
= getterNames
.get(i
);
189 if (getter
== null) {
190 getter
= PropertyUtil
.suggestGetterName(replacedParameter
.getName(), replacedParameter
.getType());
192 @NonNls final String setter
= PropertyUtil
.suggestSetterName(replacedParameter
.getName());
193 if (RefactoringUtil
.isPlusPlusOrMinusMinus(paramUsage
.getParent())) {
194 usages
.add(new ReplaceParameterIncrementDecrement(paramUsage
, fixedParamName
, setter
, getter
));
195 paramsNeedingSetters
.add(replacedParameter
);
197 else if (RefactoringUtil
.isAssignmentLHS(paramUsage
)) {
198 usages
.add(new ReplaceParameterAssignmentWithCall(paramUsage
, fixedParamName
, setter
, getter
));
199 paramsNeedingSetters
.add(replacedParameter
);
202 usages
.add(new ReplaceParameterReferenceWithCall(paramUsage
, fixedParamName
, getter
));
207 private String
calculateNewParamNameForMethod(PsiMethod testMethod
) {
208 final Project project
= testMethod
.getProject();
209 final CodeStyleSettingsManager settingsManager
= CodeStyleSettingsManager
.getInstance(project
);
210 final CodeStyleSettings settings
= settingsManager
.getCurrentSettings();
211 final String baseParamName
= settings
.PARAMETER_NAME_PREFIX
.length() == 0 ? StringUtil
.decapitalize(className
) : className
;
212 final String newParamName
= settings
.PARAMETER_NAME_PREFIX
+ baseParamName
+ settings
.PARAMETER_NAME_SUFFIX
;
213 if (!isParamNameUsed(newParamName
, testMethod
)) {
218 final String testParamName
= settings
.PARAMETER_NAME_PREFIX
+ baseParamName
+ count
+ settings
.PARAMETER_NAME_SUFFIX
;
219 if (!isParamNameUsed(testParamName
, testMethod
)) {
220 return testParamName
;
226 private boolean isParamNameUsed(String paramName
, PsiMethod testMethod
) {
227 final PsiParameterList testParamList
= testMethod
.getParameterList();
228 final PsiParameter
[] testParameters
= testParamList
.getParameters();
229 for (int i
= 0; i
< testParameters
.length
; i
++) {
230 if (!isParamToMerge(i
)) {
231 if (testParameters
[i
].getName().equals(paramName
)) {
236 final PsiCodeBlock body
= testMethod
.getBody();
240 final NameUsageVisitor visitor
= new NameUsageVisitor(paramName
);
241 body
.accept(visitor
);
242 return visitor
.isNameUsed();
245 private boolean isParamToMerge(int i
) {
246 for (int j
: paramsToMerge
) {
254 protected void performRefactoring(UsageInfo
[] usageInfos
) {
256 super.performRefactoring(usageInfos
);
260 private boolean buildClass() {
261 if (existingClass
!= null) {
264 final ParameterObjectBuilder beanClassBuilder
= new ParameterObjectBuilder();
265 beanClassBuilder
.setProject(myProject
);
266 beanClassBuilder
.setTypeArguments(typeParams
);
267 beanClassBuilder
.setClassName(className
);
268 beanClassBuilder
.setPackageName(packageName
);
269 for (ParameterTablePanel
.VariableData parameter
: parameters
) {
270 final boolean setterRequired
= paramsNeedingSetters
.contains(parameter
.variable
);
271 beanClassBuilder
.addField((PsiParameter
)parameter
.variable
, parameter
.name
, parameter
.type
, setterRequired
);
273 final String classString
= beanClassBuilder
.buildBeanClass();
276 final PsiJavaFile newFile
= (PsiJavaFile
)PsiFileFactory
.getInstance(method
.getProject()).createFileFromText(className
+ ".java", classString
);
277 if (myCreateInnerClass
) {
278 final PsiClass containingClass
= method
.getContainingClass();
279 final PsiClass
[] classes
= newFile
.getClasses();
280 assert classes
.length
> 0 : classString
;
281 final PsiClass innerClass
= (PsiClass
)containingClass
.add(classes
[0]);
282 PsiUtil
.setModifierProperty(innerClass
, PsiModifier
.STATIC
, true);
283 JavaCodeStyleManager
.getInstance(newFile
.getProject()).shortenClassReferences(innerClass
);
285 final PsiFile containingFile
= method
.getContainingFile();
286 final PsiDirectory containingDirectory
= containingFile
.getContainingDirectory();
287 final Module module
= ModuleUtil
.findModuleForPsiElement(containingFile
);
288 final PsiDirectory directory
= PackageUtil
.findOrCreateDirectoryForPackage(module
, packageName
, containingDirectory
, true);
290 if (directory
!= null) {
292 final CodeStyleManager codeStyleManager
= method
.getManager().getCodeStyleManager();
293 final PsiElement shortenedFile
= JavaCodeStyleManager
.getInstance(newFile
.getProject()).shortenClassReferences(newFile
);
294 final PsiElement reformattedFile
= codeStyleManager
.reformat(shortenedFile
);
295 directory
.add(reformattedFile
);
301 catch (IncorrectOperationException e
) {
308 protected String
getCommandName() {
309 final PsiClass containingClass
= method
.getContainingClass();
310 return RefactorJBundle
.message("introduced.parameter.class.command.name", className
, containingClass
.getName(), method
.getName());
314 private static class ParamUsageVisitor
extends JavaRecursiveElementWalkingVisitor
{
315 private final Set
<PsiParameter
> paramsToMerge
= new HashSet
<PsiParameter
>();
316 private final Set
<PsiReferenceExpression
> parameterUsages
= new HashSet
<PsiReferenceExpression
>(4);
318 ParamUsageVisitor(PsiMethod method
, int[] paramIndicesToMerge
) {
320 final PsiParameterList paramList
= method
.getParameterList();
321 final PsiParameter
[] parameters
= paramList
.getParameters();
322 for (int i
: paramIndicesToMerge
) {
323 paramsToMerge
.add(parameters
[i
]);
327 public void visitReferenceExpression(PsiReferenceExpression expression
) {
328 super.visitReferenceExpression(expression
);
329 final PsiElement referent
= expression
.resolve();
330 if (!(referent
instanceof PsiParameter
)) {
333 final PsiParameter parameter
= (PsiParameter
)referent
;
334 if (paramsToMerge
.contains(parameter
)) {
335 parameterUsages
.add(expression
);
339 public Set
<PsiReferenceExpression
> getParameterUsages() {
340 return parameterUsages
;
344 private static class NameUsageVisitor
extends JavaRecursiveElementWalkingVisitor
{
345 private boolean nameUsed
= false;
346 private final String paramName
;
348 NameUsageVisitor(String paramName
) {
350 this.paramName
= paramName
;
353 public void visitElement(PsiElement element
) {
357 super.visitElement(element
);
360 public void visitVariable(PsiVariable variable
) {
361 super.visitVariable(variable
);
362 final String variableName
= variable
.getName();
363 if (paramName
.equals(variableName
)) {
368 public void visitReferenceExpression(PsiReferenceExpression expression
) {
369 super.visitReferenceExpression(expression
);
370 if (expression
.getQualifier() == null) {
373 final String referenceName
= expression
.getReferenceName();
374 if (paramName
.equals(referenceName
)) {
379 public boolean isNameUsed() {
384 private static boolean existingClassIsCompatible(PsiClass aClass
, List
<ParameterTablePanel
.VariableData
> params
, @NonNls List
<String
> getterNames
) {
385 if (params
.size() == 1) {
386 final PsiType paramType
= params
.get(0).type
;
387 if (TypeConversionUtil
.isPrimitiveWrapper(aClass
.getQualifiedName())) {
388 getterNames
.add(paramType
.getCanonicalText() + "Value");
392 final PsiMethod
[] constructors
= aClass
.getConstructors();
393 PsiMethod compatibleConstructor
= null;
394 for (PsiMethod constructor
: constructors
) {
395 if (constructorIsCompatible(constructor
, params
)) {
396 compatibleConstructor
= constructor
;
400 if (compatibleConstructor
== null) {
403 final PsiParameterList parameterList
= compatibleConstructor
.getParameterList();
404 final PsiParameter
[] constructorParams
= parameterList
.getParameters();
405 for (PsiParameter param
: constructorParams
) {
406 final PsiField field
= findFieldAssigned(param
, compatibleConstructor
);
410 final PsiMethod getter
= PropertyUtils
.findGetterForField(field
);
411 if (getter
== null) {
414 getterNames
.add(getter
.getName());
416 //TODO: this fails if there are any setters required
420 private static boolean constructorIsCompatible(PsiMethod constructor
, List
<ParameterTablePanel
.VariableData
> params
) {
421 if (!constructor
.hasModifierProperty(PsiModifier
.PUBLIC
)) {
424 final PsiParameterList parameterList
= constructor
.getParameterList();
425 final PsiParameter
[] constructorParams
= parameterList
.getParameters();
426 if (constructorParams
.length
!= params
.size()) {
429 for (int i
= 0; i
< constructorParams
.length
; i
++) {
430 if (!TypeConversionUtil
.isAssignable(constructorParams
[i
].getType(), params
.get(i
).type
)) {
437 private static PsiField
findFieldAssigned(PsiParameter param
, PsiMethod constructor
) {
438 final ParamAssignmentFinder visitor
= new ParamAssignmentFinder(param
);
439 constructor
.accept(visitor
);
440 return visitor
.getFieldAssigned();
443 private static class ParamAssignmentFinder
extends JavaRecursiveElementWalkingVisitor
{
445 private final PsiParameter param
;
447 private PsiField fieldAssigned
= null;
449 ParamAssignmentFinder(PsiParameter param
) {
453 public void visitAssignmentExpression(PsiAssignmentExpression assignment
) {
454 super.visitAssignmentExpression(assignment
);
455 final PsiExpression lhs
= assignment
.getLExpression();
456 final PsiExpression rhs
= assignment
.getRExpression();
457 if (!(lhs
instanceof PsiReferenceExpression
)) {
460 if (!(rhs
instanceof PsiReferenceExpression
)) {
463 final PsiElement referent
= ((PsiReference
)rhs
).resolve();
464 if (referent
== null || !referent
.equals(param
)) {
467 final PsiElement assigned
= ((PsiReference
)lhs
).resolve();
468 if (assigned
== null || !(assigned
instanceof PsiField
)) {
471 fieldAssigned
= (PsiField
)assigned
;
474 public PsiField
getFieldAssigned() {
475 return fieldAssigned
;