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
.diagnostic
.Logger
;
20 import com
.intellij
.openapi
.module
.Module
;
21 import com
.intellij
.openapi
.module
.ModuleUtil
;
22 import com
.intellij
.openapi
.util
.Ref
;
23 import com
.intellij
.openapi
.util
.text
.StringUtil
;
24 import com
.intellij
.psi
.*;
25 import com
.intellij
.psi
.codeStyle
.CodeStyleManager
;
26 import com
.intellij
.psi
.codeStyle
.JavaCodeStyleManager
;
27 import com
.intellij
.psi
.codeStyle
.VariableKind
;
28 import com
.intellij
.psi
.javadoc
.PsiDocComment
;
29 import com
.intellij
.psi
.javadoc
.PsiDocTag
;
30 import com
.intellij
.psi
.search
.GlobalSearchScope
;
31 import com
.intellij
.psi
.search
.searches
.OverridingMethodsSearch
;
32 import com
.intellij
.psi
.search
.searches
.ReferencesSearch
;
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
.*;
38 import com
.intellij
.refactoring
.psi
.PropertyUtils
;
39 import com
.intellij
.refactoring
.psi
.TypeParametersVisitor
;
40 import com
.intellij
.refactoring
.util
.FixableUsageInfo
;
41 import com
.intellij
.refactoring
.util
.FixableUsagesRefactoringProcessor
;
42 import com
.intellij
.refactoring
.util
.ParameterTablePanel
;
43 import com
.intellij
.refactoring
.util
.RefactoringUtil
;
44 import com
.intellij
.usageView
.UsageInfo
;
45 import com
.intellij
.usageView
.UsageViewDescriptor
;
46 import com
.intellij
.util
.IncorrectOperationException
;
47 import com
.intellij
.util
.VisibilityUtil
;
48 import com
.intellij
.util
.containers
.MultiMap
;
49 import org
.jetbrains
.annotations
.NonNls
;
50 import org
.jetbrains
.annotations
.NotNull
;
51 import org
.jetbrains
.annotations
.Nullable
;
53 import java
.util
.ArrayList
;
54 import java
.util
.HashSet
;
55 import java
.util
.List
;
58 public class IntroduceParameterObjectProcessor
extends FixableUsagesRefactoringProcessor
{
59 private static final Logger logger
= Logger
.getInstance("com.siyeh.rpp.introduceparameterobject.IntroduceParameterObjectProcessor");
61 private final PsiMethod method
;
62 private final String className
;
63 private final String packageName
;
64 private final boolean keepMethodAsDelegate
;
65 private final boolean myUseExistingClass
;
66 private final boolean myCreateInnerClass
;
67 private final String myNewVisibility
;
68 private final boolean myGenerateAccessors
;
69 private final List
<ParameterChunk
> parameters
;
70 private final int[] paramsToMerge
;
71 private final List
<PsiTypeParameter
> typeParams
;
72 private final Set
<PsiParameter
> paramsNeedingSetters
= new HashSet
<PsiParameter
>();
73 private final Set
<PsiParameter
> paramsNeedingGetters
= new HashSet
<PsiParameter
>();
74 private final PsiClass existingClass
;
75 private PsiMethod myExistingClassCompatibleConstructor
;
77 public IntroduceParameterObjectProcessor(String className
,
80 ParameterTablePanel
.VariableData
[] parameters
, boolean keepMethodAsDelegate
, final boolean useExistingClass
,
81 final boolean createInnerClass
,
83 boolean generateAccessors
) {
84 super(method
.getProject());
86 this.className
= className
;
87 this.packageName
= packageName
;
88 this.keepMethodAsDelegate
= keepMethodAsDelegate
;
89 myUseExistingClass
= useExistingClass
;
90 myCreateInnerClass
= createInnerClass
;
91 myNewVisibility
= newVisibility
;
92 myGenerateAccessors
= generateAccessors
;
93 this.parameters
= new ArrayList
<ParameterChunk
>();
94 for (ParameterTablePanel
.VariableData parameter
: parameters
) {
95 this.parameters
.add(new ParameterChunk(parameter
));
97 final PsiParameterList parameterList
= method
.getParameterList();
98 final PsiParameter
[] methodParams
= parameterList
.getParameters();
99 paramsToMerge
= new int[parameters
.length
];
100 for (int p
= 0; p
< parameters
.length
; p
++) {
101 ParameterTablePanel
.VariableData parameter
= parameters
[p
];
102 for (int i
= 0; i
< methodParams
.length
; i
++) {
103 final PsiParameter methodParam
= methodParams
[i
];
104 if (parameter
.variable
.equals(methodParam
)) {
105 paramsToMerge
[p
] = i
;
110 final Set
<PsiTypeParameter
> typeParamSet
= new HashSet
<PsiTypeParameter
>();
111 final JavaRecursiveElementWalkingVisitor visitor
= new TypeParametersVisitor(typeParamSet
);
112 for (ParameterTablePanel
.VariableData parameter
: parameters
) {
113 parameter
.variable
.accept(visitor
);
115 typeParams
= new ArrayList
<PsiTypeParameter
>(typeParamSet
);
117 final String qualifiedName
= StringUtil
.getQualifiedName(packageName
, className
);
118 final GlobalSearchScope scope
= GlobalSearchScope
.allScope(myProject
);
119 existingClass
= JavaPsiFacade
.getInstance(myProject
).findClass(qualifiedName
, scope
);
123 protected UsageViewDescriptor
createUsageViewDescriptor(UsageInfo
[] usageInfos
) {
124 return new IntroduceParameterObjectUsageViewDescriptor(method
);
129 protected boolean preprocessUsages(final Ref
<UsageInfo
[]> refUsages
) {
130 MultiMap
<PsiElement
, String
> conflicts
= new MultiMap
<PsiElement
, String
>();
131 if (myUseExistingClass
) {
132 if (existingClass
== null) {
133 conflicts
.putValue(null, RefactorJBundle
.message("cannot.perform.the.refactoring") + "Could not find the selected class");
135 if (myExistingClassCompatibleConstructor
== null) {
136 conflicts
.putValue(existingClass
, RefactorJBundle
.message("cannot.perform.the.refactoring") + "Selected class has no compatible constructors");
139 else if (existingClass
!= null) {
140 conflicts
.putValue(existingClass
,
141 RefactorJBundle
.message("cannot.perform.the.refactoring") +
142 RefactorJBundle
.message("there.already.exists.a.class.with.the.chosen.name"));
144 for (UsageInfo usageInfo
: refUsages
.get()) {
145 if (usageInfo
instanceof FixableUsageInfo
) {
146 final String conflictMessage
= ((FixableUsageInfo
)usageInfo
).getConflictMessage();
147 if (conflictMessage
!= null) {
148 conflicts
.putValue(usageInfo
.getElement(), conflictMessage
);
152 return showConflicts(conflicts
);
155 public void findUsages(@NotNull List
<FixableUsageInfo
> usages
) {
156 if (myUseExistingClass
&& existingClass
!= null) {
157 myExistingClassCompatibleConstructor
= existingClassIsCompatible(existingClass
, parameters
);
159 findUsagesForMethod(method
, usages
);
161 if (myUseExistingClass
&& existingClass
!= null && !(paramsNeedingGetters
.isEmpty() && paramsNeedingSetters
.isEmpty())) {
162 usages
.add(new AppendAccessorsUsageInfo(existingClass
, myGenerateAccessors
, paramsNeedingGetters
, paramsNeedingSetters
, parameters
));
165 final PsiMethod
[] overridingMethods
= OverridingMethodsSearch
.search(method
, method
.getUseScope(), true).toArray(PsiMethod
.EMPTY_ARRAY
);
166 for (PsiMethod siblingMethod
: overridingMethods
) {
167 findUsagesForMethod(siblingMethod
, usages
);
170 if (myNewVisibility
!= null) {
171 usages
.add(new BeanClassVisibilityUsageInfo(existingClass
, usages
.toArray(new UsageInfo
[usages
.size()]), myNewVisibility
, myExistingClassCompatibleConstructor
));
175 private void findUsagesForMethod(PsiMethod overridingMethod
, List
<FixableUsageInfo
> usages
) {
176 final PsiCodeBlock body
= overridingMethod
.getBody();
177 final String baseParameterName
= StringUtil
.decapitalize(className
);
178 final String fixedParamName
=
180 ? JavaCodeStyleManager
.getInstance(myProject
).suggestUniqueVariableName(baseParameterName
, body
.getLBrace(), true)
181 : JavaCodeStyleManager
.getInstance(myProject
).propertyNameToVariableName(baseParameterName
, VariableKind
.PARAMETER
);
183 usages
.add(new MergeMethodArguments(overridingMethod
, className
, packageName
, fixedParamName
, paramsToMerge
, typeParams
, keepMethodAsDelegate
, myCreateInnerClass ? method
.getContainingClass() : null));
185 final ParamUsageVisitor visitor
= new ParamUsageVisitor(overridingMethod
, paramsToMerge
);
186 overridingMethod
.accept(visitor
);
187 final Set
<PsiReferenceExpression
> values
= visitor
.getParameterUsages();
188 for (PsiReferenceExpression paramUsage
: values
) {
189 final PsiParameter parameter
= (PsiParameter
)paramUsage
.resolve();
190 assert parameter
!= null;
191 final PsiMethod containingMethod
= (PsiMethod
)parameter
.getDeclarationScope();
192 final int index
= containingMethod
.getParameterList().getParameterIndex(parameter
);
193 final PsiParameter replacedParameter
= method
.getParameterList().getParameters()[index
];
194 final ParameterChunk parameterChunk
= ParameterChunk
.getChunkByParameter(parameter
, parameters
);
196 @NonNls String getter
= parameterChunk
!= null ? parameterChunk
.getter
: null;
197 if (getter
== null) {
198 getter
= PropertyUtil
.suggestGetterName(replacedParameter
.getName(), replacedParameter
.getType());
199 paramsNeedingGetters
.add(replacedParameter
);
201 @NonNls String setter
= parameterChunk
!= null ? parameterChunk
.setter
: null;
202 if (setter
== null) {
203 setter
= PropertyUtil
.suggestSetterName(replacedParameter
.getName());
205 if (RefactoringUtil
.isPlusPlusOrMinusMinus(paramUsage
.getParent())) {
206 usages
.add(new ReplaceParameterIncrementDecrement(paramUsage
, fixedParamName
, setter
, getter
));
207 if (parameterChunk
== null || parameterChunk
.setter
== null) {
208 paramsNeedingSetters
.add(replacedParameter
);
211 else if (RefactoringUtil
.isAssignmentLHS(paramUsage
)) {
212 usages
.add(new ReplaceParameterAssignmentWithCall(paramUsage
, fixedParamName
, setter
, getter
));
213 if (parameterChunk
== null || parameterChunk
.setter
== null) {
214 paramsNeedingSetters
.add(replacedParameter
);
218 usages
.add(new ReplaceParameterReferenceWithCall(paramUsage
, fixedParamName
, getter
));
223 protected void performRefactoring(UsageInfo
[] usageInfos
) {
224 final PsiClass psiClass
= buildClass();
225 if (psiClass
!= null) {
226 fixJavadocForConstructor(psiClass
);
227 super.performRefactoring(usageInfos
);
228 if (!myUseExistingClass
) {
229 for (PsiReference reference
: ReferencesSearch
.search(method
)) {
230 final PsiElement place
= reference
.getElement();
231 VisibilityUtil
.escalateVisibility(psiClass
, place
);
232 for (PsiMethod constructor
: psiClass
.getConstructors()) {
233 VisibilityUtil
.escalateVisibility(constructor
, place
);
240 private PsiClass
buildClass() {
241 if (existingClass
!= null) {
242 return existingClass
;
244 final ParameterObjectBuilder beanClassBuilder
= new ParameterObjectBuilder();
245 beanClassBuilder
.setVisibility(myCreateInnerClass ? PsiModifier
.PRIVATE
: PsiModifier
.PUBLIC
);
246 beanClassBuilder
.setProject(myProject
);
247 beanClassBuilder
.setTypeArguments(typeParams
);
248 beanClassBuilder
.setClassName(className
);
249 beanClassBuilder
.setPackageName(packageName
);
250 for (ParameterChunk parameterChunk
: parameters
) {
251 final ParameterTablePanel
.VariableData parameter
= parameterChunk
.parameter
;
252 final boolean setterRequired
= paramsNeedingSetters
.contains(parameter
.variable
);
253 beanClassBuilder
.addField((PsiParameter
)parameter
.variable
, parameter
.name
, parameter
.type
, setterRequired
);
255 final String classString
= beanClassBuilder
.buildBeanClass();
258 final PsiJavaFile newFile
= (PsiJavaFile
)PsiFileFactory
.getInstance(method
.getProject()).createFileFromText(className
+ ".java", classString
);
259 if (myCreateInnerClass
) {
260 final PsiClass containingClass
= method
.getContainingClass();
261 final PsiClass
[] classes
= newFile
.getClasses();
262 assert classes
.length
> 0 : classString
;
263 final PsiClass innerClass
= (PsiClass
)containingClass
.add(classes
[0]);
264 PsiUtil
.setModifierProperty(innerClass
, PsiModifier
.STATIC
, true);
265 return (PsiClass
)JavaCodeStyleManager
.getInstance(newFile
.getProject()).shortenClassReferences(innerClass
);
267 final PsiFile containingFile
= method
.getContainingFile();
268 final PsiDirectory containingDirectory
= containingFile
.getContainingDirectory();
269 final Module module
= ModuleUtil
.findModuleForPsiElement(containingFile
);
270 final PsiDirectory directory
= PackageUtil
.findOrCreateDirectoryForPackage(module
, packageName
, containingDirectory
, true);
272 if (directory
!= null) {
274 final CodeStyleManager codeStyleManager
= method
.getManager().getCodeStyleManager();
275 final PsiElement shortenedFile
= JavaCodeStyleManager
.getInstance(newFile
.getProject()).shortenClassReferences(newFile
);
276 final PsiElement reformattedFile
= codeStyleManager
.reformat(shortenedFile
);
277 return ((PsiJavaFile
)directory
.add(reformattedFile
)).getClasses()[0];
281 catch (IncorrectOperationException e
) {
287 private void fixJavadocForConstructor(PsiClass psiClass
) {
288 final PsiDocComment docComment
= method
.getDocComment();
289 if (docComment
!= null) {
290 final List
<PsiDocTag
> mergedTags
= new ArrayList
<PsiDocTag
>();
291 final PsiDocTag
[] paramTags
= docComment
.findTagsByName("param");
292 for (PsiDocTag paramTag
: paramTags
) {
293 final PsiElement
[] dataElements
= paramTag
.getDataElements();
294 if (dataElements
.length
> 0) {
295 mergedTags
.add((PsiDocTag
)paramTag
.copy());
299 PsiMethod compatibleParamObjectConstructor
= null;
300 if (myExistingClassCompatibleConstructor
!= null && myExistingClassCompatibleConstructor
.getDocComment() == null) {
301 compatibleParamObjectConstructor
= myExistingClassCompatibleConstructor
;
302 } else if (!myUseExistingClass
){
303 compatibleParamObjectConstructor
= psiClass
.getConstructors()[0];
306 if (compatibleParamObjectConstructor
!= null) {
307 PsiDocComment psiDocComment
=
308 JavaPsiFacade
.getElementFactory(myProject
).createDocCommentFromText("/**\n*/", compatibleParamObjectConstructor
);
309 psiDocComment
= (PsiDocComment
)compatibleParamObjectConstructor
.addBefore(psiDocComment
, compatibleParamObjectConstructor
.getFirstChild());
311 for (PsiDocTag tag
: mergedTags
) {
312 psiDocComment
.add(tag
);
318 protected String
getCommandName() {
319 final PsiClass containingClass
= method
.getContainingClass();
320 return RefactorJBundle
.message("introduced.parameter.class.command.name", className
, containingClass
.getName(), method
.getName());
324 private static class ParamUsageVisitor
extends JavaRecursiveElementVisitor
{
325 private final Set
<PsiParameter
> paramsToMerge
= new HashSet
<PsiParameter
>();
326 private final Set
<PsiReferenceExpression
> parameterUsages
= new HashSet
<PsiReferenceExpression
>(4);
328 ParamUsageVisitor(PsiMethod method
, int[] paramIndicesToMerge
) {
330 final PsiParameterList paramList
= method
.getParameterList();
331 final PsiParameter
[] parameters
= paramList
.getParameters();
332 for (int i
: paramIndicesToMerge
) {
333 paramsToMerge
.add(parameters
[i
]);
337 public void visitReferenceExpression(PsiReferenceExpression expression
) {
338 super.visitReferenceExpression(expression
);
339 final PsiElement referent
= expression
.resolve();
340 if (!(referent
instanceof PsiParameter
)) {
343 final PsiParameter parameter
= (PsiParameter
)referent
;
344 if (paramsToMerge
.contains(parameter
)) {
345 parameterUsages
.add(expression
);
349 public Set
<PsiReferenceExpression
> getParameterUsages() {
350 return parameterUsages
;
355 private static PsiMethod
existingClassIsCompatible(PsiClass aClass
, List
<ParameterChunk
> params
) {
356 if (params
.size() == 1) {
357 final ParameterChunk parameterChunk
= params
.get(0);
358 final PsiType paramType
= parameterChunk
.parameter
.type
;
359 if (TypeConversionUtil
.isPrimitiveWrapper(aClass
.getQualifiedName())) {
360 parameterChunk
.setField(aClass
.findFieldByName("value", false));
361 parameterChunk
.setGetter(paramType
.getCanonicalText() + "Value");
362 for (PsiMethod constructor
: aClass
.getConstructors()) {
363 if (constructorIsCompatible(constructor
, params
)) return constructor
;
367 final PsiMethod
[] constructors
= aClass
.getConstructors();
368 PsiMethod compatibleConstructor
= null;
369 for (PsiMethod constructor
: constructors
) {
370 if (constructorIsCompatible(constructor
, params
)) {
371 compatibleConstructor
= constructor
;
375 if (compatibleConstructor
== null) {
378 final PsiParameterList parameterList
= compatibleConstructor
.getParameterList();
379 final PsiParameter
[] constructorParams
= parameterList
.getParameters();
380 for (int i
= 0; i
< constructorParams
.length
; i
++) {
381 final PsiParameter param
= constructorParams
[i
];
382 final ParameterChunk parameterChunk
= params
.get(i
);
384 final PsiField field
= findFieldAssigned(param
, compatibleConstructor
);
389 parameterChunk
.setField(field
);
391 final PsiMethod getterForField
= PropertyUtils
.findGetterForField(field
);
392 if (getterForField
!= null) {
393 parameterChunk
.setGetter(getterForField
.getName());
396 final PsiMethod setterForField
= PropertyUtils
.findSetterForField(field
);
397 if (setterForField
!= null) {
398 parameterChunk
.setSetter(setterForField
.getName());
401 return compatibleConstructor
;
404 private static boolean constructorIsCompatible(PsiMethod constructor
, List
<ParameterChunk
> params
) {
405 final PsiParameterList parameterList
= constructor
.getParameterList();
406 final PsiParameter
[] constructorParams
= parameterList
.getParameters();
407 if (constructorParams
.length
!= params
.size()) {
410 for (int i
= 0; i
< constructorParams
.length
; i
++) {
411 if (!TypeConversionUtil
.isAssignable(constructorParams
[i
].getType(), params
.get(i
).parameter
.type
)) {
418 public static class ParameterChunk
{
419 private ParameterTablePanel
.VariableData parameter
;
420 private PsiField field
;
421 private String getter
;
422 private String setter
;
424 public ParameterChunk(ParameterTablePanel
.VariableData parameter
) {
425 this.parameter
= parameter
;
428 public void setField(PsiField field
) {
432 public void setGetter(String getter
) {
433 this.getter
= getter
;
436 public void setSetter(String setter
) {
437 this.setter
= setter
;
440 public PsiField
getField() {
445 public static ParameterChunk
getChunkByParameter(PsiParameter param
, List
<ParameterChunk
> params
) {
446 for (ParameterChunk chunk
: params
) {
447 if (chunk
.parameter
.variable
.equals(param
)) {
455 private static PsiField
findFieldAssigned(PsiParameter param
, PsiMethod constructor
) {
456 final ParamAssignmentFinder visitor
= new ParamAssignmentFinder(param
);
457 constructor
.accept(visitor
);
458 return visitor
.getFieldAssigned();
461 private static class ParamAssignmentFinder
extends JavaRecursiveElementWalkingVisitor
{
463 private final PsiParameter param
;
465 private PsiField fieldAssigned
= null;
467 ParamAssignmentFinder(PsiParameter param
) {
471 public void visitAssignmentExpression(PsiAssignmentExpression assignment
) {
472 super.visitAssignmentExpression(assignment
);
473 final PsiExpression lhs
= assignment
.getLExpression();
474 final PsiExpression rhs
= assignment
.getRExpression();
475 if (!(lhs
instanceof PsiReferenceExpression
)) {
478 if (!(rhs
instanceof PsiReferenceExpression
)) {
481 final PsiElement referent
= ((PsiReference
)rhs
).resolve();
482 if (referent
== null || !referent
.equals(param
)) {
485 final PsiElement assigned
= ((PsiReference
)lhs
).resolve();
486 if (assigned
== null || !(assigned
instanceof PsiField
)) {
489 fieldAssigned
= (PsiField
)assigned
;
492 public PsiField
getFieldAssigned() {
493 return fieldAssigned
;