| /* |
| * Copyright 2000-2012 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.intellij.refactoring.replaceConstructorWithBuilder; |
| |
| import com.intellij.ide.highlighter.JavaFileType; |
| import com.intellij.ide.util.PackageUtil; |
| import com.intellij.openapi.module.Module; |
| import com.intellij.openapi.module.ModuleUtil; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.Comparing; |
| import com.intellij.openapi.util.Ref; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.psi.*; |
| import com.intellij.psi.codeStyle.CodeStyleManager; |
| import com.intellij.psi.codeStyle.JavaCodeStyleManager; |
| import com.intellij.psi.codeStyle.VariableKind; |
| import com.intellij.psi.search.GlobalSearchScope; |
| import com.intellij.psi.search.searches.ReferencesSearch; |
| import com.intellij.psi.util.PropertyUtil; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.psi.util.PsiUtil; |
| import com.intellij.psi.util.TypeConversionUtil; |
| import com.intellij.refactoring.MoveDestination; |
| import com.intellij.refactoring.replaceConstructorWithBuilder.usageInfo.ReplaceConstructorWithSettersChainInfo; |
| import com.intellij.refactoring.util.FixableUsageInfo; |
| import com.intellij.refactoring.util.FixableUsagesRefactoringProcessor; |
| import com.intellij.refactoring.util.RefactoringUtil; |
| import com.intellij.usageView.UsageInfo; |
| import com.intellij.usageView.UsageViewDescriptor; |
| import com.intellij.util.IncorrectOperationException; |
| import com.intellij.util.VisibilityUtil; |
| import com.intellij.util.containers.MultiMap; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * @author anna |
| * @since 04-Sep-2008 |
| */ |
| public class ReplaceConstructorWithBuilderProcessor extends FixableUsagesRefactoringProcessor { |
| public static final String REFACTORING_NAME = "Replace Constructor with Builder"; |
| private final PsiMethod[] myConstructors; |
| private final Map<String, ParameterData> myParametersMap; |
| private final String myClassName; |
| private final String myPackageName; |
| private final boolean myCreateNewBuilderClass; |
| private final PsiElementFactory myElementFactory; |
| private MoveDestination myMoveDestination; |
| |
| |
| public ReplaceConstructorWithBuilderProcessor(Project project, |
| PsiMethod[] constructors, |
| Map<String, ParameterData> parametersMap, |
| String className, |
| String packageName, |
| MoveDestination moveDestination, boolean createNewBuilderClass) { |
| super(project); |
| myMoveDestination = moveDestination; |
| myElementFactory = JavaPsiFacade.getInstance(myProject).getElementFactory(); |
| myConstructors = constructors; |
| myParametersMap = parametersMap; |
| |
| myClassName = className; |
| myPackageName = packageName; |
| myCreateNewBuilderClass = createNewBuilderClass; |
| } |
| |
| @NotNull |
| protected UsageViewDescriptor createUsageViewDescriptor(final UsageInfo[] usages) { |
| return new ReplaceConstructorWithBuilderViewDescriptor(); |
| } |
| |
| protected void findUsages(@NotNull final List<FixableUsageInfo> usages) { |
| final String builderQualifiedName = StringUtil.getQualifiedName(myPackageName, myClassName); |
| final PsiClass builderClass = |
| JavaPsiFacade.getInstance(myProject).findClass(builderQualifiedName, GlobalSearchScope.projectScope(myProject)); |
| |
| for (PsiMethod constructor : myConstructors) { |
| for (PsiReference reference : ReferencesSearch.search(constructor)) { |
| final PsiElement element = reference.getElement(); |
| final PsiNewExpression newExpression = PsiTreeUtil.getParentOfType(element, PsiNewExpression.class); |
| if (newExpression != null && !PsiTreeUtil.isAncestor(builderClass, element, false)) { |
| usages.add(new ReplaceConstructorWithSettersChainInfo(newExpression, StringUtil.getQualifiedName(myPackageName, myClassName), myParametersMap)); |
| } |
| } |
| } |
| } |
| |
| @Nullable |
| private PsiClass createBuilderClass() { |
| final PsiClass psiClass = myConstructors[0].getContainingClass(); |
| assert psiClass != null; |
| final PsiTypeParameterList typeParameterList = psiClass.getTypeParameterList(); |
| final String text = "public class " + myClassName + (typeParameterList != null ? typeParameterList.getText() : "") + "{}"; |
| final PsiFileFactory factory = PsiFileFactory.getInstance(myProject); |
| final PsiJavaFile newFile = (PsiJavaFile)factory.createFileFromText(myClassName + ".java", JavaFileType.INSTANCE, text); |
| |
| final PsiFile containingFile = myConstructors[0].getContainingFile(); |
| final PsiDirectory containingDirectory = containingFile.getContainingDirectory(); |
| final PsiDirectory directory; |
| if (myMoveDestination != null) { |
| directory = myMoveDestination.getTargetDirectory(containingDirectory); |
| } else { |
| final Module module = ModuleUtil.findModuleForPsiElement(containingFile); |
| assert module != null; |
| directory = PackageUtil.findOrCreateDirectoryForPackage(module, myPackageName, containingDirectory, true, true); |
| } |
| |
| if (directory != null) { |
| |
| final CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(PsiManager.getInstance(myProject).getProject()); |
| final PsiJavaFile reformattedFile = (PsiJavaFile)codeStyleManager.reformat(JavaCodeStyleManager.getInstance(newFile.getProject()).shortenClassReferences(newFile)); |
| |
| if (directory.findFile(reformattedFile.getName()) != null) return reformattedFile.getClasses()[0]; |
| return ((PsiJavaFile)directory.add(reformattedFile)).getClasses()[0]; |
| } |
| return null; |
| } |
| |
| @Override |
| protected void performRefactoring(UsageInfo[] usageInfos) { |
| |
| final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(myProject); |
| final PsiClass builderClass = myCreateNewBuilderClass |
| ? createBuilderClass() |
| : psiFacade.findClass(StringUtil.getQualifiedName(myPackageName, myClassName), |
| GlobalSearchScope.projectScope(myProject)); |
| if (builderClass == null) return; |
| |
| for (String propertyName : myParametersMap.keySet()) { |
| final ParameterData parameterData = myParametersMap.get(propertyName); |
| final PsiField field = createField(builderClass, parameterData); |
| createSetter(builderClass, parameterData, field); |
| } |
| |
| super.performRefactoring(usageInfos); |
| |
| final PsiMethod method = createMethodSignature(createMethodName()); |
| if (builderClass.findMethodBySignature(method, false) == null) { |
| builderClass.add(method); |
| } |
| |
| //fix visibilities |
| final PsiMethod constructor = getWorkingConstructor(); |
| VisibilityUtil.escalateVisibility(constructor, builderClass); |
| PsiClass containingClass = constructor.getContainingClass(); |
| while (containingClass != null) { |
| VisibilityUtil.escalateVisibility(containingClass, builderClass); |
| containingClass = containingClass.getContainingClass(); |
| } |
| } |
| |
| private void createSetter(PsiClass builderClass, ParameterData parameterData, PsiField field) { |
| PsiMethod setter = null; |
| for (PsiMethod method : builderClass.getMethods()) { |
| if (Comparing.strEqual(method.getName(), parameterData.getSetterName()) && method.getParameterList().getParametersCount() == 1 |
| && TypeConversionUtil.isAssignable(method.getParameterList().getParameters()[0].getType(), parameterData.getType())) { |
| setter = method; |
| fixSetterReturnType(builderClass, field, setter); |
| break; |
| } |
| } |
| if (setter == null) { |
| setter = PropertyUtil.generateSetterPrototype(field, builderClass, true); |
| final PsiIdentifier nameIdentifier = setter.getNameIdentifier(); |
| assert nameIdentifier != null; |
| nameIdentifier.replace(myElementFactory.createIdentifier(parameterData.getSetterName())); |
| setter.getParameterList().getParameters()[0].getTypeElement().replace(myElementFactory.createTypeElement(parameterData.getType())); //setter varargs |
| builderClass.add(setter); |
| } |
| } |
| |
| private PsiField createField(PsiClass builderClass, ParameterData parameterData) { |
| PsiField field = builderClass.findFieldByName(parameterData.getFieldName(), false); |
| |
| if (field == null) { |
| PsiType type = parameterData.getType(); |
| if (type instanceof PsiEllipsisType) { |
| type = ((PsiEllipsisType)type).toArrayType(); |
| } |
| field = myElementFactory.createField(parameterData.getFieldName(), type); |
| field = (PsiField)builderClass.add(field); |
| } |
| |
| final String defaultValue = parameterData.getDefaultValue(); |
| if (defaultValue != null) { |
| final PsiExpression initializer = field.getInitializer(); |
| if (initializer == null) { |
| try { |
| field.setInitializer(myElementFactory.createExpressionFromText(defaultValue, field)); |
| } |
| catch (IncorrectOperationException e) { |
| //skip invalid default value |
| } |
| } |
| } |
| return field; |
| } |
| |
| private void fixSetterReturnType(PsiClass builderClass, PsiField field, PsiMethod method) { |
| if (PsiUtil.resolveClassInType(method.getReturnType()) != builderClass) { |
| final PsiCodeBlock body = method.getBody(); |
| final PsiCodeBlock generatedBody = PropertyUtil.generateSetterPrototype(field, builderClass, true).getBody(); |
| assert body != null; |
| assert generatedBody != null; |
| body.replace(generatedBody); |
| final PsiTypeElement typeElement = method.getReturnTypeElement(); |
| assert typeElement != null; |
| typeElement.replace(myElementFactory.createTypeElement(myElementFactory.createType(builderClass))); |
| } |
| } |
| |
| private PsiMethod createMethodSignature(String createMethodName) { |
| JavaCodeStyleManager styleManager = JavaCodeStyleManager.getInstance(myProject); |
| final StringBuffer buf = new StringBuffer(); |
| final PsiMethod constructor = getWorkingConstructor(); |
| for (PsiParameter parameter : constructor.getParameterList().getParameters()) { |
| final String pureParamName = styleManager.variableNameToPropertyName(parameter.getName(), VariableKind.PARAMETER); |
| if (buf.length() > 0) buf.append(", "); |
| buf.append(myParametersMap.get(pureParamName).getFieldName()); |
| } |
| return myElementFactory.createMethodFromText("public " + |
| constructor.getName() + |
| " " + |
| createMethodName + |
| "(){\n return new " + |
| constructor.getName() + |
| "(" + |
| buf.toString() + |
| ")" + |
| ";\n}", constructor); |
| } |
| |
| private PsiMethod getWorkingConstructor() { |
| PsiMethod constructor = getMostCommonConstructor(); |
| if (constructor == null){ |
| constructor = myConstructors[0]; |
| if (constructor.getParameterList().getParametersCount() == 0) { |
| constructor = myConstructors[1]; |
| } |
| } |
| return constructor; |
| } |
| |
| @Nullable |
| private PsiMethod getMostCommonConstructor() { |
| if (myConstructors.length == 1) return myConstructors[0]; |
| PsiMethod commonConstructor = null; |
| for (PsiMethod constructor : myConstructors) { |
| final PsiMethod chainedConstructor = RefactoringUtil.getChainedConstructor(constructor); |
| if (chainedConstructor == null) { |
| if (commonConstructor != null) { |
| if (!isChained(commonConstructor, constructor)) { |
| return null; |
| } |
| } |
| commonConstructor = constructor; |
| } else { |
| if (commonConstructor == null) { |
| commonConstructor = chainedConstructor; |
| } else { |
| if (!isChained(commonConstructor, chainedConstructor)) { |
| return null; |
| } |
| } |
| } |
| } |
| return commonConstructor; |
| } |
| |
| private static boolean isChained(PsiMethod first, PsiMethod last) { |
| if (first == null) return false; |
| if (first == last) return true; |
| return isChained(RefactoringUtil.getChainedConstructor(first), last); |
| } |
| |
| private String createMethodName() { |
| return "create" + StringUtil.capitalize(myConstructors[0].getName()); |
| } |
| |
| |
| @Override |
| protected boolean preprocessUsages(Ref<UsageInfo[]> refUsages) { |
| final MultiMap<PsiElement, String> conflicts = new MultiMap<PsiElement, String>(); |
| final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(myProject); |
| final PsiClass builderClass = |
| psiFacade.findClass(StringUtil.getQualifiedName(myPackageName, myClassName), GlobalSearchScope.projectScope(myProject)); |
| if (builderClass == null) { |
| if (!myCreateNewBuilderClass) { |
| conflicts.putValue(null, "Selected class was not found."); |
| } |
| } else if (myCreateNewBuilderClass){ |
| conflicts.putValue(builderClass, "Class with chosen name already exist."); |
| } |
| |
| if (myMoveDestination != null && myCreateNewBuilderClass) { |
| myMoveDestination.analyzeModuleConflicts(Collections.<PsiElement>emptyList(), conflicts, refUsages.get()); |
| } |
| |
| final PsiMethod commonConstructor = getMostCommonConstructor(); |
| if (commonConstructor == null) { |
| conflicts.putValue(null, "Found constructors are not reducible to simple chain"); |
| } |
| |
| return showConflicts(conflicts, refUsages.get()); |
| } |
| |
| protected String getCommandName() { |
| return REFACTORING_NAME; |
| } |
| } |