ComponentWithBrowseButton - optional remove listener on hide
[fedora-idea.git] / java / java-impl / src / com / intellij / refactoring / introduceparameterobject / IntroduceParameterObjectProcessor.java
blob9bfa80a91654b2e7957b5aae3ae48b90d838b7a4
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.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;
56 import java.util.Set;
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,
78 String packageName,
79 PsiMethod method,
80 ParameterTablePanel.VariableData[] parameters, boolean keepMethodAsDelegate, final boolean useExistingClass,
81 final boolean createInnerClass,
82 String newVisibility,
83 boolean generateAccessors) {
84 super(method.getProject());
85 this.method = method;
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;
106 break;
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);
128 @Override
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 =
179 body != null
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);
217 else {
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();
257 try {
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);
266 } else {
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) {
282 logger.info(e);
284 return null;
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) {
329 super();
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)) {
341 return;
343 final PsiParameter parameter = (PsiParameter)referent;
344 if (paramsToMerge.contains(parameter)) {
345 parameterUsages.add(expression);
349 public Set<PsiReferenceExpression> getParameterUsages() {
350 return parameterUsages;
354 @Nullable
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;
372 break;
375 if (compatibleConstructor == null) {
376 return 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);
385 if (field == null) {
386 return null;
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()) {
408 return false;
410 for (int i = 0; i < constructorParams.length; i++) {
411 if (!TypeConversionUtil.isAssignable(constructorParams[i].getType(), params.get(i).parameter.type)) {
412 return false;
415 return true;
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) {
429 this.field = 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() {
441 return field;
444 @Nullable
445 public static ParameterChunk getChunkByParameter(PsiParameter param, List<ParameterChunk> params) {
446 for (ParameterChunk chunk : params) {
447 if (chunk.parameter.variable.equals(param)) {
448 return chunk;
451 return null;
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) {
468 this.param = 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)) {
476 return;
478 if (!(rhs instanceof PsiReferenceExpression)) {
479 return;
481 final PsiElement referent = ((PsiReference)rhs).resolve();
482 if (referent == null || !referent.equals(param)) {
483 return;
485 final PsiElement assigned = ((PsiReference)lhs).resolve();
486 if (assigned == null || !(assigned instanceof PsiField)) {
487 return;
489 fieldAssigned = (PsiField)assigned;
492 public PsiField getFieldAssigned() {
493 return fieldAssigned;