Cleanup: NotNull/Nullable
[idea/community.git] / java / java-impl / src / com / intellij / refactoring / wrapreturnvalue / WrapReturnValueProcessor.java
1 /*
2  * Copyright 2000-2017 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.refactoring.wrapreturnvalue;
17
18 import com.intellij.ide.highlighter.JavaFileType;
19 import com.intellij.ide.util.PackageUtil;
20 import com.intellij.openapi.diagnostic.Logger;
21 import com.intellij.openapi.module.Module;
22 import com.intellij.openapi.module.ModuleUtilCore;
23 import com.intellij.openapi.project.Project;
24 import com.intellij.openapi.util.Comparing;
25 import com.intellij.openapi.util.Ref;
26 import com.intellij.openapi.util.text.StringUtil;
27 import com.intellij.psi.*;
28 import com.intellij.psi.codeStyle.CodeStyleManager;
29 import com.intellij.psi.codeStyle.JavaCodeStyleManager;
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.PropertyUtilBase;
34 import com.intellij.psi.util.PsiUtil;
35 import com.intellij.psi.util.TypeConversionUtil;
36 import com.intellij.refactoring.MoveDestination;
37 import com.intellij.refactoring.RefactorJBundle;
38 import com.intellij.refactoring.psi.TypeParametersVisitor;
39 import com.intellij.refactoring.util.FixableUsageInfo;
40 import com.intellij.refactoring.util.FixableUsagesRefactoringProcessor;
41 import com.intellij.refactoring.wrapreturnvalue.usageInfo.ChangeReturnType;
42 import com.intellij.refactoring.wrapreturnvalue.usageInfo.ReturnWrappedValue;
43 import com.intellij.refactoring.wrapreturnvalue.usageInfo.UnwrapCall;
44 import com.intellij.refactoring.wrapreturnvalue.usageInfo.WrapReturnValue;
45 import com.intellij.usageView.UsageInfo;
46 import com.intellij.usageView.UsageViewDescriptor;
47 import com.intellij.util.IncorrectOperationException;
48 import com.intellij.util.containers.MultiMap;
49 import org.jetbrains.annotations.NotNull;
50
51 import java.io.IOException;
52 import java.util.ArrayList;
53 import java.util.HashSet;
54 import java.util.List;
55 import java.util.Set;
56
57 public class WrapReturnValueProcessor extends FixableUsagesRefactoringProcessor {
58   private static final Logger LOG = Logger.getInstance("com.siyeh.rpp.wrapreturnvalue.WrapReturnValueProcessor");
59
60   private final MoveDestination myMoveDestination;
61   private final PsiMethod myMethod;
62   @NotNull
63   private final String myClassName;
64   private final String myPackageName;
65   private final boolean myCreateInnerClass;
66   private final PsiField myDelegateField;
67   private final String myQualifiedName;
68   private final boolean myUseExistingClass;
69   private final List<PsiTypeParameter> myTypeParameters;
70   private final String myUnwrapMethodName;
71
72   public WrapReturnValueProcessor(@NotNull String className,
73                                   String packageName,
74                                   MoveDestination moveDestination,
75                                   PsiMethod method,
76                                   boolean useExistingClass,
77                                   final boolean createInnerClass,
78                                   PsiField delegateField) {
79     super(method.getProject());
80     myMoveDestination = moveDestination;
81     myMethod = method;
82     myClassName = className;
83     myPackageName = packageName;
84     myCreateInnerClass = createInnerClass;
85     myDelegateField = delegateField;
86     myQualifiedName = StringUtil.getQualifiedName(packageName, className);
87     myUseExistingClass = useExistingClass;
88
89     final Set<PsiTypeParameter> typeParamSet = new HashSet<>();
90     final TypeParametersVisitor visitor = new TypeParametersVisitor(typeParamSet);
91     final PsiTypeElement returnTypeElement = method.getReturnTypeElement();
92     assert returnTypeElement != null;
93     returnTypeElement.accept(visitor);
94     myTypeParameters = new ArrayList<>(typeParamSet);
95     if (useExistingClass) {
96       myUnwrapMethodName = calculateUnwrapMethodName();
97     }
98     else {
99       myUnwrapMethodName = "getValue";
100     }
101   }
102
103   private String calculateUnwrapMethodName() {
104     final PsiClass existingClass = JavaPsiFacade.getInstance(myProject).findClass(myQualifiedName, GlobalSearchScope.allScope(myProject));
105     if (existingClass != null) {
106       if (TypeConversionUtil.isPrimitiveWrapper(myQualifiedName)) {
107         final PsiPrimitiveType unboxedType =
108           PsiPrimitiveType.getUnboxedType(JavaPsiFacade.getElementFactory(myProject).createType(existingClass));
109         assert unboxedType != null;
110         return unboxedType.getCanonicalText() + "Value()";
111       }
112
113       final PsiMethod getter = PropertyUtilBase.findGetterForField(myDelegateField);
114       return getter != null ? getter.getName() : "";
115     }
116     return "";
117   }
118
119   @NotNull
120   @Override
121   protected UsageViewDescriptor createUsageViewDescriptor(@NotNull UsageInfo[] usageInfos) {
122     return new WrapReturnValueUsageViewDescriptor(myMethod, usageInfos);
123   }
124
125   @Override
126   public void findUsages(@NotNull List<FixableUsageInfo> usages) {
127     findUsagesForMethod(myMethod, usages);
128     for (PsiMethod overridingMethod : OverridingMethodsSearch.search(myMethod)) {
129       findUsagesForMethod(overridingMethod, usages);
130     }
131   }
132
133   private void findUsagesForMethod(PsiMethod psiMethod, List<FixableUsageInfo> usages) {
134     for (PsiReference reference : ReferencesSearch.search(psiMethod, psiMethod.getUseScope())) {
135       final PsiElement referenceElement = reference.getElement();
136       final PsiElement parent = referenceElement.getParent();
137       if (parent instanceof PsiCallExpression) {
138         usages.add(new UnwrapCall((PsiCallExpression)parent, myUnwrapMethodName));
139       }
140       else if (referenceElement instanceof PsiMethodReferenceExpression) {
141         usages.add(new UnwrapCall((PsiMethodReferenceExpression)referenceElement, myUnwrapMethodName));
142       }
143     }
144     final String returnType = calculateReturnTypeString();
145     usages.add(new ChangeReturnType(psiMethod, returnType));
146     psiMethod.accept(new ReturnSearchVisitor(usages, returnType));
147   }
148
149   private String calculateReturnTypeString() {
150     final String qualifiedName = StringUtil.getQualifiedName(myPackageName, myClassName);
151     final StringBuilder returnTypeBuffer = new StringBuilder(qualifiedName);
152     if (!myTypeParameters.isEmpty()) {
153       returnTypeBuffer.append('<');
154       returnTypeBuffer.append(StringUtil.join(myTypeParameters, typeParameter -> {
155         final String paramName = typeParameter.getName();
156         LOG.assertTrue(paramName != null);
157         return paramName;
158       }, ","));
159       returnTypeBuffer.append('>');
160     }
161     else if (myDelegateField != null) {
162       final PsiType type = myDelegateField.getType();
163       final PsiType returnType = myMethod.getReturnType();
164       final PsiClass containingClass = myDelegateField.getContainingClass();
165       final PsiType inferredType = getInferredType(type, returnType, containingClass, myMethod);
166       if (inferredType != null) {
167         returnTypeBuffer.append("<").append(inferredType.getCanonicalText()).append(">");
168       }
169     }
170     return returnTypeBuffer.toString();
171   }
172
173   protected static PsiType getInferredType(PsiType type, PsiType returnType, PsiClass containingClass, PsiMethod method) {
174     if (containingClass != null && containingClass.getTypeParameters().length == 1) {
175       final PsiSubstitutor substitutor = PsiResolveHelper.SERVICE.getInstance(method.getProject())
176         .inferTypeArguments(containingClass.getTypeParameters(), new PsiType[]{type}, new PsiType[]{returnType}, PsiUtil.getLanguageLevel(
177           method));
178       final PsiTypeParameter typeParameter = containingClass.getTypeParameters()[0];
179       final PsiType substituted = substitutor.substitute(typeParameter);
180       if (substituted != null && !typeParameter.equals(PsiUtil.resolveClassInClassTypeOnly(substituted))) {
181         return substituted;
182       }
183     }
184     return null;
185   }
186
187   @Override
188   protected boolean preprocessUsages(@NotNull final Ref<UsageInfo[]> refUsages) {
189     MultiMap<PsiElement, String> conflicts = new MultiMap<>();
190     PsiClass existingClass = JavaPsiFacade.getInstance(myProject).findClass(myQualifiedName, GlobalSearchScope.allScope(myProject));
191     if (myUseExistingClass) {
192       if (existingClass == null) {
193         conflicts.putValue(null, RefactorJBundle.message("could.not.find.selected.wrapping.class"));
194       }
195       else {
196         PsiElement navigationElement = existingClass.getNavigationElement();
197         if (navigationElement instanceof PsiClass) {
198           existingClass = (PsiClass)navigationElement;
199         }
200         boolean foundConstructor = false;
201         final Set<PsiType> returnTypes = new HashSet<>();
202         returnTypes.add(myMethod.getReturnType());
203         final PsiCodeBlock methodBody = myMethod.getBody();
204         if (methodBody != null) {
205           methodBody.accept(new JavaRecursiveElementWalkingVisitor() {
206             @Override
207             public void visitReturnStatement(final PsiReturnStatement statement) {
208               super.visitReturnStatement(statement);
209               final PsiExpression returnValue = statement.getReturnValue();
210               if (returnValue != null) {
211                 returnTypes.add(returnValue.getType());
212               }
213             }
214
215             @Override
216             public void visitClass(PsiClass aClass) {}
217             @Override
218             public void visitLambdaExpression(PsiLambdaExpression expression) {}
219           });
220         }
221
222         final PsiMethod[] constructors = existingClass.getConstructors();
223         constr: for (PsiMethod constructor : constructors) {
224           final PsiParameter[] parameters = constructor.getParameterList().getParameters();
225           if (parameters.length == 1) {
226             final PsiParameter parameter = parameters[0];
227             final PsiType parameterType = parameter.getType();
228             for (PsiType returnType : returnTypes) {
229               if (getInferredType(parameterType, returnType, existingClass, myMethod) == null && !TypeConversionUtil.isAssignable(parameterType, returnType)) {
230                 continue constr;
231               }
232             }
233             if (!PsiUtil.isAccessible(constructor, myMethod, null)) {
234               continue constr;
235             }
236             final PsiCodeBlock body = constructor.getBody();
237             if (body == null) continue constr;
238             final boolean[] found = new boolean[1];
239             body.accept(new JavaRecursiveElementWalkingVisitor() {
240               @Override
241               public void visitAssignmentExpression(final PsiAssignmentExpression expression) {
242                 super.visitAssignmentExpression(expression);
243                 final PsiExpression lExpression = expression.getLExpression();
244                 if (lExpression instanceof PsiReferenceExpression && myDelegateField.isEquivalentTo(((PsiReferenceExpression)lExpression).resolve())) {
245                   found[0] = true;
246                 }
247               }
248             });
249             if (found[0]) {
250               foundConstructor = true;
251               break;
252             }
253           }
254         }
255         if (!foundConstructor) {
256           conflicts.putValue(existingClass, "Existing class does not have appropriate constructor");
257         }
258       }
259       if (myUnwrapMethodName.length() == 0) {
260         conflicts.putValue(existingClass,
261                       "Existing class does not have getter for selected field");
262       }
263     }
264     else {
265       if (existingClass != null) {
266         conflicts.putValue(existingClass, RefactorJBundle.message("there.already.exists.a.class.with.the.selected.name"));
267       }
268       if (myMoveDestination != null && !myMoveDestination.isTargetAccessible(myProject, myMethod.getContainingFile().getVirtualFile())) {
269         conflicts.putValue(myMethod, "Created class won't be accessible in the call place");
270       }
271     }
272     return showConflicts(conflicts, refUsages.get());
273   }
274
275   @Override
276   protected void performRefactoring(@NotNull UsageInfo[] usageInfos) {
277     if (!myUseExistingClass && !buildClass()) return;
278     super.performRefactoring(usageInfos);
279   }
280
281   private boolean buildClass() {
282     final PsiManager manager = myMethod.getManager();
283     final Project project = myMethod.getProject();
284     final ReturnValueBeanBuilder beanClassBuilder = new ReturnValueBeanBuilder();
285     beanClassBuilder.setProject(project);
286     beanClassBuilder.setFile(myMethod.getContainingFile());
287     beanClassBuilder.setTypeArguments(myTypeParameters);
288     beanClassBuilder.setClassName(myClassName);
289     beanClassBuilder.setPackageName(myPackageName);
290     beanClassBuilder.setStatic(myCreateInnerClass && myMethod.hasModifierProperty(PsiModifier.STATIC));
291     final PsiType returnType = myMethod.getReturnType();
292     beanClassBuilder.setValueType(returnType);
293
294     final String classString;
295     try {
296       classString = beanClassBuilder.buildBeanClass();
297     }
298     catch (IOException e) {
299       LOG.error(e);
300       return false;
301     }
302
303     try {
304       final PsiFileFactory factory = PsiFileFactory.getInstance(project);
305       final PsiJavaFile psiFile = (PsiJavaFile)factory.createFileFromText(myClassName + ".java", JavaFileType.INSTANCE, classString);
306       final CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(manager.getProject());
307       if (myCreateInnerClass) {
308         final PsiClass containingClass = myMethod.getContainingClass();
309         final PsiElement innerClass = containingClass.add(psiFile.getClasses()[0]);
310         JavaCodeStyleManager.getInstance(project).shortenClassReferences(innerClass);
311       }
312       else {
313         final PsiFile containingFile = myMethod.getContainingFile();
314
315         final PsiDirectory containingDirectory = containingFile.getContainingDirectory();
316         final PsiDirectory directory;
317         if (myMoveDestination != null) {
318           directory = myMoveDestination.getTargetDirectory(containingDirectory);
319         }
320         else {
321           final Module module = ModuleUtilCore.findModuleForPsiElement(containingFile);
322           directory = PackageUtil.findOrCreateDirectoryForPackage(module, myPackageName, containingDirectory, true, true);
323         }
324
325         if (directory != null) {
326           final PsiElement shortenedFile = JavaCodeStyleManager.getInstance(project).shortenClassReferences(psiFile);
327           final PsiElement reformattedFile = codeStyleManager.reformat(shortenedFile);
328           directory.add(reformattedFile);
329         }
330         else {
331           return false;
332         }
333       }
334     }
335     catch (IncorrectOperationException e) {
336       LOG.info(e);
337       return false;
338     }
339     return true;
340   }
341
342   @Override
343   @NotNull
344   protected String getCommandName() {
345     final PsiClass containingClass = myMethod.getContainingClass();
346     return RefactorJBundle.message("wrapped.return.command.name", myClassName, containingClass.getName(), '.', myMethod.getName());
347   }
348
349
350   private class ReturnSearchVisitor extends JavaRecursiveElementWalkingVisitor {
351     private final List<? super FixableUsageInfo> usages;
352     private final String type;
353
354     ReturnSearchVisitor(List<? super FixableUsageInfo> usages, String type) {
355       super();
356       this.usages = usages;
357       this.type = type;
358     }
359
360     @Override
361     public void visitClass(PsiClass aClass) {}
362     @Override
363     public void visitLambdaExpression(PsiLambdaExpression expression) {}
364
365     @Override
366     public void visitReturnStatement(PsiReturnStatement statement) {
367       super.visitReturnStatement(statement);
368
369       final PsiExpression returnValue = statement.getReturnValue();
370       if (myUseExistingClass && returnValue instanceof PsiMethodCallExpression) {
371         final PsiMethodCallExpression callExpression = (PsiMethodCallExpression)returnValue;
372         if (callExpression.getArgumentList().isEmpty()) {
373           final PsiReferenceExpression callMethodExpression = callExpression.getMethodExpression();
374           final String methodName = callMethodExpression.getReferenceName();
375           if (Comparing.strEqual(myUnwrapMethodName, methodName)) {
376             final PsiExpression qualifier = callMethodExpression.getQualifierExpression();
377             if (qualifier != null) {
378               final PsiType qualifierType = qualifier.getType();
379               if (qualifierType != null && qualifierType.getCanonicalText().equals(myQualifiedName)) {
380                 usages.add(new ReturnWrappedValue(statement));
381                 return;
382               }
383             }
384           }
385         }
386       }
387       usages.add(new WrapReturnValue(statement, type));
388     }
389   }
390 }