| /* |
| * Copyright 2000-2014 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.wrapreturnvalue; |
| |
| import com.intellij.ide.highlighter.JavaFileType; |
| import com.intellij.ide.util.PackageUtil; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.module.Module; |
| import com.intellij.openapi.module.ModuleUtilCore; |
| 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.search.GlobalSearchScope; |
| import com.intellij.psi.search.searches.OverridingMethodsSearch; |
| import com.intellij.psi.search.searches.ReferencesSearch; |
| import com.intellij.psi.util.PropertyUtil; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.psi.util.TypeConversionUtil; |
| import com.intellij.refactoring.MoveDestination; |
| import com.intellij.refactoring.RefactorJBundle; |
| import com.intellij.refactoring.psi.TypeParametersVisitor; |
| import com.intellij.refactoring.util.FixableUsageInfo; |
| import com.intellij.refactoring.util.FixableUsagesRefactoringProcessor; |
| import com.intellij.refactoring.wrapreturnvalue.usageInfo.ChangeReturnType; |
| import com.intellij.refactoring.wrapreturnvalue.usageInfo.ReturnWrappedValue; |
| import com.intellij.refactoring.wrapreturnvalue.usageInfo.UnwrapCall; |
| import com.intellij.refactoring.wrapreturnvalue.usageInfo.WrapReturnValue; |
| import com.intellij.usageView.UsageInfo; |
| import com.intellij.usageView.UsageViewDescriptor; |
| import com.intellij.util.Function; |
| import com.intellij.util.IncorrectOperationException; |
| import com.intellij.util.containers.MultiMap; |
| import org.jetbrains.annotations.NotNull; |
| |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| public class WrapReturnValueProcessor extends FixableUsagesRefactoringProcessor { |
| private static final Logger LOG = Logger.getInstance("com.siyeh.rpp.wrapreturnvalue.WrapReturnValueProcessor"); |
| |
| private MoveDestination myMoveDestination; |
| private final PsiMethod myMethod; |
| private final String myClassName; |
| private final String myPackageName; |
| private final boolean myCreateInnerClass; |
| private final PsiField myDelegateField; |
| private final String myQualifiedName; |
| private final boolean myUseExistingClass; |
| private final List<PsiTypeParameter> myTypeParameters; |
| private final String myUnwrapMethodName; |
| |
| public WrapReturnValueProcessor(String className, |
| String packageName, |
| MoveDestination moveDestination, |
| PsiMethod method, |
| boolean useExistingClass, |
| final boolean createInnerClass, |
| PsiField delegateField) { |
| super(method.getProject()); |
| myMoveDestination = moveDestination; |
| myMethod = method; |
| myClassName = className; |
| myPackageName = packageName; |
| myCreateInnerClass = createInnerClass; |
| myDelegateField = delegateField; |
| myQualifiedName = StringUtil.getQualifiedName(packageName, className); |
| myUseExistingClass = useExistingClass; |
| |
| final Set<PsiTypeParameter> typeParamSet = new HashSet<PsiTypeParameter>(); |
| final TypeParametersVisitor visitor = new TypeParametersVisitor(typeParamSet); |
| final PsiTypeElement returnTypeElement = method.getReturnTypeElement(); |
| assert returnTypeElement != null; |
| returnTypeElement.accept(visitor); |
| myTypeParameters = new ArrayList<PsiTypeParameter>(typeParamSet); |
| if (useExistingClass) { |
| myUnwrapMethodName = calculateUnwrapMethodName(); |
| } |
| else { |
| myUnwrapMethodName = "getValue"; |
| } |
| } |
| |
| private String calculateUnwrapMethodName() { |
| final PsiClass existingClass = JavaPsiFacade.getInstance(myProject).findClass(myQualifiedName, GlobalSearchScope.allScope(myProject)); |
| if (existingClass != null) { |
| if (TypeConversionUtil.isPrimitiveWrapper(myQualifiedName)) { |
| final PsiPrimitiveType unboxedType = |
| PsiPrimitiveType.getUnboxedType(JavaPsiFacade.getInstance(myProject).getElementFactory().createType(existingClass)); |
| assert unboxedType != null; |
| return unboxedType.getCanonicalText() + "Value()"; |
| } |
| |
| final PsiMethod getter = PropertyUtil.findGetterForField(myDelegateField); |
| return getter != null ? getter.getName() : ""; |
| } |
| return ""; |
| } |
| |
| @NotNull |
| @Override |
| protected UsageViewDescriptor createUsageViewDescriptor(UsageInfo[] usageInfos) { |
| return new WrapReturnValueUsageViewDescriptor(myMethod, usageInfos); |
| } |
| |
| @Override |
| public void findUsages(@NotNull List<FixableUsageInfo> usages) { |
| findUsagesForMethod(myMethod, usages); |
| for (PsiMethod overridingMethod : OverridingMethodsSearch.search(myMethod)) { |
| findUsagesForMethod(overridingMethod, usages); |
| } |
| } |
| |
| private void findUsagesForMethod(PsiMethod psiMethod, List<FixableUsageInfo> usages) { |
| for (PsiReference reference : ReferencesSearch.search(psiMethod, psiMethod.getUseScope())) { |
| final PsiElement referenceElement = reference.getElement(); |
| final PsiElement parent = referenceElement.getParent(); |
| if (parent instanceof PsiCallExpression) { |
| usages.add(new UnwrapCall((PsiCallExpression)parent, myUnwrapMethodName)); |
| } |
| } |
| final String returnType = calculateReturnTypeString(); |
| usages.add(new ChangeReturnType(psiMethod, returnType)); |
| psiMethod.accept(new ReturnSearchVisitor(usages, returnType, psiMethod)); |
| } |
| |
| private String calculateReturnTypeString() { |
| final String qualifiedName = StringUtil.getQualifiedName(myPackageName, myClassName); |
| final StringBuilder returnTypeBuffer = new StringBuilder(qualifiedName); |
| if (!myTypeParameters.isEmpty()) { |
| returnTypeBuffer.append('<'); |
| returnTypeBuffer.append(StringUtil.join(myTypeParameters, new Function<PsiTypeParameter, String>() { |
| @Override |
| public String fun(final PsiTypeParameter typeParameter) { |
| final String paramName = typeParameter.getName(); |
| LOG.assertTrue(paramName != null); |
| return paramName; |
| } |
| }, ",")); |
| returnTypeBuffer.append('>'); |
| } |
| return returnTypeBuffer.toString(); |
| } |
| |
| @Override |
| protected boolean preprocessUsages(final Ref<UsageInfo[]> refUsages) { |
| MultiMap<PsiElement, String> conflicts = new MultiMap<PsiElement, String>(); |
| final PsiClass existingClass = JavaPsiFacade.getInstance(myProject).findClass(myQualifiedName, GlobalSearchScope.allScope(myProject)); |
| if (myUseExistingClass) { |
| if (existingClass == null) { |
| conflicts.putValue(null, RefactorJBundle.message("could.not.find.selected.wrapping.class")); |
| } |
| else { |
| boolean foundConstructor = false; |
| final Set<PsiType> returnTypes = new HashSet<PsiType>(); |
| returnTypes.add(myMethod.getReturnType()); |
| final PsiCodeBlock methodBody = myMethod.getBody(); |
| if (methodBody != null) { |
| methodBody.accept(new JavaRecursiveElementWalkingVisitor() { |
| @Override |
| public void visitReturnStatement(final PsiReturnStatement statement) { |
| super.visitReturnStatement(statement); |
| if (PsiTreeUtil.getParentOfType(statement, PsiMethod.class) != myMethod) return; |
| final PsiExpression returnValue = statement.getReturnValue(); |
| if (returnValue != null) { |
| returnTypes.add(returnValue.getType()); |
| } |
| } |
| }); |
| } |
| |
| final PsiMethod[] constructors = existingClass.getConstructors(); |
| constr: for (PsiMethod constructor : constructors) { |
| final PsiParameter[] parameters = constructor.getParameterList().getParameters(); |
| if (parameters.length == 1) { |
| final PsiParameter parameter = parameters[0]; |
| final PsiType parameterType = parameter.getType(); |
| for (PsiType returnType : returnTypes) { |
| if (!TypeConversionUtil.isAssignable(parameterType, returnType)) { |
| continue constr; |
| } |
| } |
| final PsiCodeBlock body = constructor.getBody(); |
| LOG.assertTrue(body != null); |
| final boolean[] found = new boolean[1]; |
| body.accept(new JavaRecursiveElementWalkingVisitor() { |
| @Override |
| public void visitAssignmentExpression(final PsiAssignmentExpression expression) { |
| super.visitAssignmentExpression(expression); |
| final PsiExpression lExpression = expression.getLExpression(); |
| if (lExpression instanceof PsiReferenceExpression && ((PsiReferenceExpression)lExpression).resolve() == myDelegateField) { |
| final PsiExpression rExpression = expression.getRExpression(); |
| if (rExpression instanceof PsiReferenceExpression && ((PsiReferenceExpression)rExpression).resolve() == parameter) { |
| found[0] = true; |
| } |
| } |
| } |
| }); |
| if (found[0]) { |
| foundConstructor = true; |
| break; |
| } |
| } |
| } |
| if (!foundConstructor) { |
| conflicts.putValue(existingClass, "Existing class does not have appropriate constructor"); |
| } |
| } |
| if (myUnwrapMethodName.length() == 0) { |
| conflicts.putValue(existingClass, |
| "Existing class does not have getter for selected field"); |
| } |
| } |
| else { |
| if (existingClass != null) { |
| conflicts.putValue(existingClass, RefactorJBundle.message("there.already.exists.a.class.with.the.selected.name")); |
| } |
| if (myMoveDestination != null && !myMoveDestination.isTargetAccessible(myProject, myMethod.getContainingFile().getVirtualFile())) { |
| conflicts.putValue(myMethod, "Created class won't be accessible in the call place"); |
| } |
| } |
| return showConflicts(conflicts, refUsages.get()); |
| } |
| |
| @Override |
| protected void performRefactoring(UsageInfo[] usageInfos) { |
| if (!myUseExistingClass && !buildClass()) return; |
| super.performRefactoring(usageInfos); |
| } |
| |
| private boolean buildClass() { |
| final PsiManager manager = myMethod.getManager(); |
| final Project project = myMethod.getProject(); |
| final ReturnValueBeanBuilder beanClassBuilder = new ReturnValueBeanBuilder(); |
| beanClassBuilder.setProject(project); |
| beanClassBuilder.setTypeArguments(myTypeParameters); |
| beanClassBuilder.setClassName(myClassName); |
| beanClassBuilder.setPackageName(myPackageName); |
| beanClassBuilder.setStatic(myCreateInnerClass && myMethod.hasModifierProperty(PsiModifier.STATIC)); |
| final PsiType returnType = myMethod.getReturnType(); |
| beanClassBuilder.setValueType(returnType); |
| |
| final String classString; |
| try { |
| classString = beanClassBuilder.buildBeanClass(); |
| } |
| catch (IOException e) { |
| LOG.error(e); |
| return false; |
| } |
| |
| try { |
| final PsiFileFactory factory = PsiFileFactory.getInstance(project); |
| final PsiJavaFile psiFile = (PsiJavaFile)factory.createFileFromText(myClassName + ".java", JavaFileType.INSTANCE, classString); |
| final CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(manager.getProject()); |
| if (myCreateInnerClass) { |
| final PsiClass containingClass = myMethod.getContainingClass(); |
| final PsiElement innerClass = containingClass.add(psiFile.getClasses()[0]); |
| JavaCodeStyleManager.getInstance(project).shortenClassReferences(innerClass); |
| } |
| else { |
| final PsiFile containingFile = myMethod.getContainingFile(); |
| |
| final PsiDirectory containingDirectory = containingFile.getContainingDirectory(); |
| final PsiDirectory directory; |
| if (myMoveDestination != null) { |
| directory = myMoveDestination.getTargetDirectory(containingDirectory); |
| } |
| else { |
| final Module module = ModuleUtilCore.findModuleForPsiElement(containingFile); |
| directory = PackageUtil.findOrCreateDirectoryForPackage(module, myPackageName, containingDirectory, true, true); |
| } |
| |
| if (directory != null) { |
| final PsiElement shortenedFile = JavaCodeStyleManager.getInstance(project).shortenClassReferences(psiFile); |
| final PsiElement reformattedFile = codeStyleManager.reformat(shortenedFile); |
| directory.add(reformattedFile); |
| } |
| else { |
| return false; |
| } |
| } |
| } |
| catch (IncorrectOperationException e) { |
| LOG.info(e); |
| return false; |
| } |
| return true; |
| } |
| |
| protected String getCommandName() { |
| final PsiClass containingClass = myMethod.getContainingClass(); |
| return RefactorJBundle.message("wrapped.return.command.name", myClassName, containingClass.getName(), '.', myMethod.getName()); |
| } |
| |
| |
| private class ReturnSearchVisitor extends JavaRecursiveElementWalkingVisitor { |
| private final List<FixableUsageInfo> usages; |
| private final String type; |
| private final PsiMethod myMethod; |
| |
| ReturnSearchVisitor(List<FixableUsageInfo> usages, String type, final PsiMethod psiMethod) { |
| super(); |
| this.usages = usages; |
| this.type = type; |
| myMethod = psiMethod; |
| } |
| |
| @Override |
| public void visitLambdaExpression(PsiLambdaExpression expression) { |
| } |
| |
| public void visitReturnStatement(PsiReturnStatement statement) { |
| super.visitReturnStatement(statement); |
| |
| if (PsiTreeUtil.getParentOfType(statement, PsiMethod.class) != myMethod) return; |
| |
| final PsiExpression returnValue = statement.getReturnValue(); |
| if (myUseExistingClass && returnValue instanceof PsiMethodCallExpression) { |
| final PsiMethodCallExpression callExpression = (PsiMethodCallExpression)returnValue; |
| if (callExpression.getArgumentList().getExpressions().length == 0) { |
| final PsiReferenceExpression callMethodExpression = callExpression.getMethodExpression(); |
| final String methodName = callMethodExpression.getReferenceName(); |
| if (Comparing.strEqual(myUnwrapMethodName, methodName)) { |
| final PsiExpression qualifier = callMethodExpression.getQualifierExpression(); |
| if (qualifier != null) { |
| final PsiType qualifierType = qualifier.getType(); |
| if (qualifierType != null && qualifierType.getCanonicalText().equals(myQualifiedName)) { |
| usages.add(new ReturnWrappedValue(statement)); |
| return; |
| } |
| } |
| } |
| } |
| } |
| usages.add(new WrapReturnValue(statement, type)); |
| } |
| } |
| } |