[duplicates] enable duplicates analysis in PyCharm/WebStorm/PhpStorm/RubyMine
[idea/community.git] / java / java-impl / src / com / intellij / codeInsight / daemon / impl / quickfix / CreateTypeParameterFromUsageFix.java
1 // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.codeInsight.daemon.impl.quickfix;
3
4 import com.intellij.codeInsight.FileModificationService;
5 import com.intellij.codeInsight.daemon.QuickFixBundle;
6 import com.intellij.codeInsight.intention.impl.BaseIntentionAction;
7 import com.intellij.ide.highlighter.JavaFileType;
8 import com.intellij.openapi.application.Application;
9 import com.intellij.openapi.application.ApplicationManager;
10 import com.intellij.openapi.command.WriteCommandAction;
11 import com.intellij.openapi.editor.Editor;
12 import com.intellij.openapi.project.Project;
13 import com.intellij.openapi.util.Pass;
14 import com.intellij.psi.*;
15 import com.intellij.psi.util.PsiUtil;
16 import com.intellij.refactoring.IntroduceTargetChooser;
17 import com.intellij.util.IncorrectOperationException;
18 import com.intellij.util.SmartList;
19 import org.jetbrains.annotations.Nls;
20 import org.jetbrains.annotations.NotNull;
21 import org.jetbrains.annotations.Nullable;
22
23 import java.util.List;
24
25 import static com.intellij.util.ObjectUtils.tryCast;
26
27 public class CreateTypeParameterFromUsageFix extends BaseIntentionAction {
28   private final SmartPsiElementPointer<PsiJavaCodeReferenceElement> myRef;
29
30   public CreateTypeParameterFromUsageFix(PsiJavaCodeReferenceElement refElement) {
31     myRef = SmartPointerManager.getInstance(refElement.getProject()).createSmartPsiElementPointer(refElement);
32   }
33
34   @Nullable
35   private PsiJavaCodeReferenceElement getElement() {
36     return myRef.getElement();
37   }
38
39   @Nls(capitalization = Nls.Capitalization.Sentence)
40   @NotNull
41   @Override
42   public String getFamilyName() {
43     return QuickFixBundle.message("create.type.parameter.from.usage.family");
44   }
45
46   @Override
47   public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
48     PsiJavaCodeReferenceElement element = getElement();
49     if (element == null) return false;
50     Context context = Context.from(element, true);
51     boolean available = context != null;
52     if (available) {
53       setText(QuickFixBundle.message("create.type.parameter.from.usage.text", context.typeName));
54     }
55     return available;
56   }
57
58   @Override
59   public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
60     PsiJavaCodeReferenceElement element = getElement();
61     if (element == null) return;
62     Context context = Context.from(element, false);
63     if (context == null) return;
64     List<PsiNameIdentifierOwner> placesToAdd = context.myPlacesToAdd;
65
66     Application application = ApplicationManager.getApplication();
67     if (placesToAdd.size() == 1 || application.isUnitTestMode() || editor == null) {
68       PsiElement first = placesToAdd.get(0);
69       createTypeParameter(first, context.typeName);
70     }
71     else {
72       IntroduceTargetChooser.showChooser(
73         editor,
74         placesToAdd,
75         new Pass<PsiNameIdentifierOwner>() {
76           @Override
77           public void pass(PsiNameIdentifierOwner owner) {
78             createTypeParameter(owner, context.typeName);
79           }
80         },
81         PsiNamedElement::getName,
82         QuickFixBundle.message("create.type.parameter.from.usage.chooser.title")
83       );
84     }
85   }
86
87
88   private static void createTypeParameter(@NotNull PsiElement methodOrClass, @NotNull String name) {
89     Project project = methodOrClass.getProject();
90     if (!FileModificationService.getInstance().preparePsiElementsForWrite(methodOrClass)) return;
91     WriteCommandAction.runWriteCommandAction(project, () -> {
92       PsiTypeParameterListOwner typeParameterListOwner = tryCast(methodOrClass, PsiTypeParameterListOwner.class);
93       if (typeParameterListOwner == null) {
94         throw new IllegalStateException("Only methods and classes allowed here, but was: " + methodOrClass.getClass());
95       }
96       PsiTypeParameterList typeParameterList = typeParameterListOwner.getTypeParameterList();
97       final String typeParameterListText;
98       if (typeParameterList == null) {
99         typeParameterListText = "<" + name + ">";
100       }
101       else {
102         String existingTypeParameterText = typeParameterList.getText();
103         if (typeParameterList.getTypeParameters().length == 0) {
104           typeParameterListText = "<" + name + ">";
105         }
106         else {
107           String prefix = existingTypeParameterText.substring(0, existingTypeParameterText.length() - 1);
108           typeParameterListText = prefix + ", " + name + ">";
109         }
110       }
111       PsiTypeParameterList newTypeParameterList = createTypeParameterList(typeParameterListText, project);
112       replaceOrAddTypeParameterList(methodOrClass, typeParameterList, newTypeParameterList);
113     });
114   }
115
116   private static void replaceOrAddTypeParameterList(@NotNull PsiElement methodOrClass,
117                                                     @Nullable PsiTypeParameterList typeParameterList,
118                                                     @NotNull PsiTypeParameterList newTypeParameterList) {
119     if (methodOrClass instanceof PsiMethod) {
120       PsiMethod method = (PsiMethod)methodOrClass;
121       if (typeParameterList == null) {
122         PsiTypeElement returnTypeElement = method.getReturnTypeElement();
123         if (returnTypeElement == null) return;
124         method.addBefore(newTypeParameterList, returnTypeElement);
125       }
126       else {
127         typeParameterList.replace(newTypeParameterList);
128       }
129     }
130     else {
131       PsiClass aClass = (PsiClass)methodOrClass;
132       if (typeParameterList == null) {
133         PsiIdentifier nameIdentifier = aClass.getNameIdentifier();
134         if (nameIdentifier == null) return;
135         aClass.addAfter(newTypeParameterList, nameIdentifier);
136       }
137       else {
138         typeParameterList.replace(newTypeParameterList);
139       }
140     }
141   }
142
143   private static PsiTypeParameterList createTypeParameterList(@NotNull String text, Project project) {
144     PsiJavaFile javaFile = (PsiJavaFile)PsiFileFactory.getInstance(project)
145                                                       .createFileFromText("_DUMMY_", JavaFileType.INSTANCE,
146                                                                           "class __DUMMY__ " + text + " {}");
147     PsiClass[] classes = javaFile.getClasses();
148     return classes[0].getTypeParameterList();
149   }
150
151   private static class Context {
152     @NotNull final List<PsiNameIdentifierOwner> myPlacesToAdd;
153     @NotNull final String typeName;
154
155     Context(@NotNull List<PsiNameIdentifierOwner> add, @NotNull String name) {
156       myPlacesToAdd = add;
157       typeName = name;
158     }
159
160     @Nullable
161     static Context from(@NotNull PsiJavaCodeReferenceElement element, boolean findFirstOnly) {
162       if (!PsiUtil.isLanguageLevel5OrHigher(element)) return null;
163       if (element.isQualified()) return null;
164       PsiElement parent = element.getParent();
165       if (parent instanceof PsiMethodCallExpression ||
166           parent instanceof PsiJavaCodeReferenceElement ||
167           parent instanceof PsiNewExpression ||
168           (parent instanceof PsiTypeElement && parent.getParent() instanceof PsiClassObjectAccessExpression) ||
169           element instanceof PsiReferenceExpression) {
170         return null;
171       }
172       List<PsiNameIdentifierOwner> candidates = collectParentClassesAndMethodsUntilStatic(element, findFirstOnly);
173       if (candidates.isEmpty()) return null;
174       String name = element.getReferenceName();
175       if (name == null) return null;
176       return new Context(candidates, name);
177     }
178   }
179
180
181   static List<PsiNameIdentifierOwner> collectParentClassesAndMethodsUntilStatic(PsiElement element, boolean findFirstOnly) {
182     element = element.getParent();
183     List<PsiNameIdentifierOwner> parents = new SmartList<>();
184     while (element != null) {
185       if (element instanceof PsiField && ((PsiField)element).hasModifierProperty(PsiModifier.STATIC)) {
186         break;
187       }
188       if (element instanceof PsiClass && ((PsiClass)element).isEnum()) break;
189       if (element instanceof PsiMethod || isValidClass(element)) {
190         if (((PsiMember)element).getName() != null) {
191           parents.add((PsiNameIdentifierOwner)element);
192           if (findFirstOnly) {
193             return parents;
194           }
195         }
196         if (((PsiModifierListOwner)element).hasModifierProperty(PsiModifier.STATIC)) break;
197       }
198       element = element.getParent();
199     }
200     return parents;
201   }
202
203   private static boolean isValidClass(PsiElement element) {
204     return element instanceof PsiClass && !(element instanceof PsiTypeParameter);
205   }
206 }