update copyright
[fedora-idea.git] / java / java-impl / src / com / intellij / refactoring / introduceparameterobject / IntroduceParameterObjectProcessor.java
blob49fb019f46d593124d1bd85b4a114e3d9393f68c
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.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;
54 import java.util.*;
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,
74 String packageName,
75 PsiMethod method,
76 ParameterTablePanel.VariableData[] parameters,
77 List<String> getterNames,
78 boolean keepMethodAsDelegate, final boolean useExistingClass, final boolean createInnerClass) {
79 super(method.getProject());
80 this.method = method;
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)) {
96 paramsToMerge[p] = i;
97 break;
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);
119 @Override
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) {
128 conflicts
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);
144 @Override
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);
185 break;
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);
201 else {
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)) {
214 return newParamName;
216 int count = 1;
217 while (true) {
218 final String testParamName = settings.PARAMETER_NAME_PREFIX + baseParamName + count + settings.PARAMETER_NAME_SUFFIX;
219 if (!isParamNameUsed(testParamName, testMethod)) {
220 return testParamName;
222 count++;
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)) {
232 return true;
236 final PsiCodeBlock body = testMethod.getBody();
237 if (body == null) {
238 return false;
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) {
247 if (i == j) {
248 return true;
251 return false;
254 protected void performRefactoring(UsageInfo[] usageInfos) {
255 if (buildClass()) {
256 super.performRefactoring(usageInfos);
260 private boolean buildClass() {
261 if (existingClass != null) {
262 return true;
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();
275 try {
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);
284 } else {
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);
296 } else {
297 return false;
301 catch (IncorrectOperationException e) {
302 logger.info(e);
303 return false;
305 return true;
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) {
319 super();
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)) {
331 return;
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) {
349 super();
350 this.paramName = paramName;
353 public void visitElement(PsiElement element) {
354 if (nameUsed) {
355 return;
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)) {
364 nameUsed = true;
368 public void visitReferenceExpression(PsiReferenceExpression expression) {
369 super.visitReferenceExpression(expression);
370 if (expression.getQualifier() == null) {
371 return;
373 final String referenceName = expression.getReferenceName();
374 if (paramName.equals(referenceName)) {
375 nameUsed = true;
379 public boolean isNameUsed() {
380 return nameUsed;
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");
389 return true;
392 final PsiMethod[] constructors = aClass.getConstructors();
393 PsiMethod compatibleConstructor = null;
394 for (PsiMethod constructor : constructors) {
395 if (constructorIsCompatible(constructor, params)) {
396 compatibleConstructor = constructor;
397 break;
400 if (compatibleConstructor == null) {
401 return false;
403 final PsiParameterList parameterList = compatibleConstructor.getParameterList();
404 final PsiParameter[] constructorParams = parameterList.getParameters();
405 for (PsiParameter param : constructorParams) {
406 final PsiField field = findFieldAssigned(param, compatibleConstructor);
407 if (field == null) {
408 return false;
410 final PsiMethod getter = PropertyUtils.findGetterForField(field);
411 if (getter == null) {
412 return false;
414 getterNames.add(getter.getName());
416 //TODO: this fails if there are any setters required
417 return true;
420 private static boolean constructorIsCompatible(PsiMethod constructor, List<ParameterTablePanel.VariableData> params) {
421 if (!constructor.hasModifierProperty(PsiModifier.PUBLIC)) {
422 return false;
424 final PsiParameterList parameterList = constructor.getParameterList();
425 final PsiParameter[] constructorParams = parameterList.getParameters();
426 if (constructorParams.length != params.size()) {
427 return false;
429 for (int i = 0; i < constructorParams.length; i++) {
430 if (!TypeConversionUtil.isAssignable(constructorParams[i].getType(), params.get(i).type)) {
431 return false;
434 return true;
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) {
450 this.param = 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)) {
458 return;
460 if (!(rhs instanceof PsiReferenceExpression)) {
461 return;
463 final PsiElement referent = ((PsiReference)rhs).resolve();
464 if (referent == null || !referent.equals(param)) {
465 return;
467 final PsiElement assigned = ((PsiReference)lhs).resolve();
468 if (assigned == null || !(assigned instanceof PsiField)) {
469 return;
471 fieldAssigned = (PsiField)assigned;
474 public PsiField getFieldAssigned() {
475 return fieldAssigned;