EA-33373 - IOE: PsiElementFactoryImpl.createField
[idea/community.git] / java / java-impl / src / com / intellij / codeInsight / generation / GenerateDelegateHandler.java
1 /*
2  * Copyright 2000-2009 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.codeInsight.generation;
17
18 import com.intellij.codeInsight.CodeInsightBundle;
19 import com.intellij.ide.util.MemberChooser;
20 import com.intellij.lang.LanguageCodeInsightActionHandler;
21 import com.intellij.openapi.application.ApplicationManager;
22 import com.intellij.openapi.diagnostic.Logger;
23 import com.intellij.openapi.editor.Editor;
24 import com.intellij.openapi.editor.ScrollType;
25 import com.intellij.openapi.fileEditor.FileDocumentManager;
26 import com.intellij.openapi.project.Project;
27 import com.intellij.psi.*;
28 import com.intellij.psi.codeStyle.CodeStyleManager;
29 import com.intellij.psi.javadoc.PsiDocComment;
30 import com.intellij.psi.scope.processor.VariablesProcessor;
31 import com.intellij.psi.scope.util.PsiScopesUtil;
32 import com.intellij.psi.util.*;
33 import com.intellij.util.IncorrectOperationException;
34 import com.intellij.util.containers.HashMap;
35 import com.intellij.util.containers.HashSet;
36 import org.jetbrains.annotations.NonNls;
37 import org.jetbrains.annotations.NotNull;
38 import org.jetbrains.annotations.Nullable;
39
40 import java.util.ArrayList;
41 import java.util.List;
42 import java.util.Map;
43 import java.util.Set;
44
45 /**
46  * @author mike
47  */
48 public class GenerateDelegateHandler implements LanguageCodeInsightActionHandler {
49   private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.generation.GenerateDelegateHandler");
50   private boolean myToCopyJavaDoc = false;
51
52   @Override
53   public boolean isValidFor(Editor editor, PsiFile file) {
54     if (!(file instanceof PsiJavaFile)) return false;
55     return OverrideImplementUtil.getContextClass(editor.getProject(), editor, file, false) != null && isApplicable(file, editor);
56   }
57
58   public void invoke(@NotNull final Project project, @NotNull final Editor editor, @NotNull final PsiFile file) {
59     if (!FileDocumentManager.getInstance().requestWriting(editor.getDocument(), project)) {
60       return;
61     }
62     PsiDocumentManager.getInstance(project).commitAllDocuments();
63
64     final PsiElement target = chooseTarget(file, editor, project);
65     if (target == null) return;
66
67     final PsiMethodMember[] candidates = chooseMethods(target, file, editor, project);
68     if (candidates == null || candidates.length == 0) return;
69
70
71     ApplicationManager.getApplication().runWriteAction(new Runnable() {
72       public void run() {
73         try {
74           int offset = editor.getCaretModel().getOffset();
75
76           List<PsiGenerationInfo<PsiMethod>> prototypes = new ArrayList<PsiGenerationInfo<PsiMethod>>(candidates.length);
77           for (PsiMethodMember candidate : candidates) {
78             prototypes.add(generateDelegatePrototype(candidate, target));
79           }
80
81           List<PsiGenerationInfo<PsiMethod>> results = GenerateMembersUtil.insertMembersAtOffset(file, offset, prototypes);
82
83           if (!results.isEmpty()) {
84             PsiMethod firstMethod = results.get(0).getPsiMember();
85             final PsiCodeBlock block = firstMethod.getBody();
86             assert block != null;
87             final PsiElement first = block.getFirstBodyElement();
88             assert first != null;
89             editor.getCaretModel().moveToOffset(first.getTextRange().getStartOffset());
90             editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
91             editor.getSelectionModel().removeSelection();
92           }
93         }
94         catch (IncorrectOperationException e) {
95           LOG.error(e);
96         }
97       }
98     });
99   }
100
101   public boolean startInWriteAction() {
102     return false;
103   }
104
105   private PsiGenerationInfo<PsiMethod> generateDelegatePrototype(PsiMethodMember methodCandidate, PsiElement target) throws IncorrectOperationException {
106     PsiMethod method = GenerateMembersUtil.substituteGenericMethod(methodCandidate.getElement(), methodCandidate.getSubstitutor());
107     clearMethod(method);
108
109     clearModifiers(method);
110
111     @NonNls StringBuffer call = new StringBuffer();
112
113     PsiModifierList modifierList = null;
114
115     if (method.getReturnType() != PsiType.VOID) {
116       call.append("return ");
117     }
118
119     boolean isMethodStatic = methodCandidate.getElement().hasModifierProperty(PsiModifier.STATIC);
120     if (target instanceof PsiField) {
121       PsiField field = (PsiField)target;
122       modifierList = field.getModifierList();
123       if (isMethodStatic) {
124         call.append(methodCandidate.getContainingClass().getQualifiedName());
125       } else {
126         final String name = field.getName();
127
128         final PsiParameter[] parameters = method.getParameterList().getParameters();
129         for (PsiParameter parameter : parameters) {
130           if (name.equals(parameter.getName())) {
131             call.append("this.");
132             break;
133           }
134         }
135
136         call.append(name);
137       }
138       call.append(".");
139     }
140     else if (target instanceof PsiMethod) {
141       PsiMethod m = (PsiMethod)target;
142       modifierList = m.getModifierList();
143       if (isMethodStatic) {
144         call.append(methodCandidate.getContainingClass().getQualifiedName()).append(".");
145       }
146       else {
147         call.append(m.getName());
148         call.append("().");
149       }
150     }
151
152     call.append(method.getName());
153     call.append("(");
154     final PsiParameter[] parameters = method.getParameterList().getParameters();
155     for (int j = 0; j < parameters.length; j++) {
156       PsiParameter parameter = parameters[j];
157       if (j > 0) call.append(",");
158       call.append(parameter.getName());
159     }
160     call.append(");");
161
162     final PsiManager psiManager = method.getManager();
163     PsiStatement stmt = JavaPsiFacade.getInstance(psiManager.getProject()).getElementFactory().createStatementFromText(call.toString(), method);
164     stmt = (PsiStatement)CodeStyleManager.getInstance(psiManager.getProject()).reformat(stmt);
165     method.getBody().add(stmt);
166
167     if (isMethodStatic || modifierList != null && modifierList.hasModifierProperty(PsiModifier.STATIC)) {
168       PsiUtil.setModifierProperty(method, PsiModifier.STATIC, true);
169     }
170
171     PsiUtil.setModifierProperty(method, PsiModifier.PUBLIC, true);
172
173     final Project project = method.getProject();
174     for (PsiAnnotation annotation : methodCandidate.getElement().getModifierList().getAnnotations()) {
175       OverrideImplementUtil.annotate(method, annotation.getQualifiedName());
176     }
177
178     final PsiClass targetClass = ((PsiMember)target).getContainingClass();
179     LOG.assertTrue(targetClass != null);
180     PsiMethod overridden = targetClass.findMethodBySignature(method, true);
181     if (overridden != null && overridden.getContainingClass() != targetClass) {
182       OverrideImplementUtil.annotateOnOverrideImplement(method, targetClass, overridden);
183     }
184
185     return new PsiGenerationInfo<PsiMethod>(method);
186   }
187
188   private void clearMethod(PsiMethod method) throws IncorrectOperationException {
189     LOG.assertTrue(!method.isPhysical());
190     PsiCodeBlock codeBlock = JavaPsiFacade.getInstance(method.getProject()).getElementFactory().createCodeBlock();
191     if (method.getBody() != null) {
192       method.getBody().replace(codeBlock);
193     }
194     else {
195       method.add(codeBlock);
196     }
197
198     if (!myToCopyJavaDoc) {
199       final PsiDocComment docComment = method.getDocComment();
200       if (docComment != null) {
201         docComment.delete();
202       }
203     }
204   }
205
206   private static void clearModifiers(PsiMethod method) throws IncorrectOperationException {
207     final PsiElement[] children = method.getModifierList().getChildren();
208     for (PsiElement child : children) {
209       if (child instanceof PsiKeyword) child.delete();
210     }
211   }
212
213   @Nullable
214   private PsiMethodMember[] chooseMethods(PsiElement target, PsiFile file, Editor editor, Project project) {
215     PsiClassType.ClassResolveResult resolveResult = null;
216
217     if (target instanceof PsiField) {
218       resolveResult = PsiUtil.resolveGenericsClassInType(((PsiField)target).getType());
219     }
220     else if (target instanceof PsiMethod) {
221       resolveResult = PsiUtil.resolveGenericsClassInType(((PsiMethod)target).getReturnType());
222     }
223
224     if (resolveResult == null || resolveResult.getElement() == null) return null;
225     PsiClass targetClass = resolveResult.getElement();
226     PsiSubstitutor substitutor = resolveResult.getSubstitutor();
227
228     int offset = editor.getCaretModel().getOffset();
229     PsiElement element = file.findElementAt(offset);
230     if (element == null) return null;
231     PsiClass aClass = PsiTreeUtil.getParentOfType(element, PsiClass.class);
232     if (aClass == null) return null;
233
234     List<PsiMethodMember> methodInstances = new ArrayList<PsiMethodMember>();
235
236     final PsiMethod[] allMethods = targetClass.getAllMethods();
237     final Set<MethodSignature> signatures = new HashSet<MethodSignature>();
238     final Set<MethodSignature> existingSignatures = new HashSet<MethodSignature>(aClass.getVisibleSignatures());
239     final Set<PsiMethodMember> selection = new HashSet<PsiMethodMember>();
240     Map<PsiClass, PsiSubstitutor> superSubstitutors = new HashMap<PsiClass, PsiSubstitutor>();
241     JavaPsiFacade facade = JavaPsiFacade.getInstance(target.getProject());
242     for (PsiMethod method : allMethods) {
243       final PsiClass superClass = method.getContainingClass();
244       if (CommonClassNames.JAVA_LANG_OBJECT.equals(superClass.getQualifiedName())) continue;
245       if (method.isConstructor()) continue;
246       PsiSubstitutor superSubstitutor = superSubstitutors.get(superClass);
247       if (superSubstitutor == null) {
248         superSubstitutor = TypeConversionUtil.getSuperClassSubstitutor(superClass, targetClass, substitutor);
249         superSubstitutors.put(superClass, superSubstitutor);
250       }
251       PsiSubstitutor methodSubstitutor = GenerateMembersUtil.correctSubstitutor(method, superSubstitutor);
252       MethodSignature signature = method.getSignature(methodSubstitutor);
253       if (!signatures.contains(signature)) {
254         signatures.add(signature);
255         if (facade.getResolveHelper().isAccessible(method, target, aClass)) {
256           final PsiMethodMember methodMember = new PsiMethodMember(method, methodSubstitutor);
257           methodInstances.add(methodMember);
258           if (!existingSignatures.contains(signature)) {
259             selection.add(methodMember);
260           }
261         }
262       }
263     }
264
265     PsiMethodMember[] result;
266     if (!ApplicationManager.getApplication().isUnitTestMode()) {
267       MemberChooser<PsiElementClassMember> chooser = new MemberChooser<PsiElementClassMember>(methodInstances.toArray(new PsiMethodMember[methodInstances.size()]), false, true, project);
268       chooser.setTitle(CodeInsightBundle.message("generate.delegate.method.chooser.title"));
269       chooser.setCopyJavadocVisible(true);
270       if (!selection.isEmpty()) {
271         chooser.selectElements(selection.toArray(new ClassMember[selection.size()]));
272       }
273       chooser.show();
274
275       if (chooser.getExitCode() != MemberChooser.OK_EXIT_CODE) return null;
276
277       myToCopyJavaDoc = chooser.isCopyJavadoc();
278       final List<PsiElementClassMember> list = chooser.getSelectedElements();
279       result = list.toArray(new PsiMethodMember[list.size()]);
280     }
281     else {
282       result = methodInstances.isEmpty() ? new PsiMethodMember[0] : new PsiMethodMember[] {methodInstances.get(0)};
283     }
284
285     return result;
286   }
287
288   public void setToCopyJavaDoc(boolean toCopyJavaDoc) {
289     myToCopyJavaDoc = toCopyJavaDoc;
290   }
291
292   public static boolean isApplicable(PsiFile file, Editor editor) {
293     ClassMember[] targetElements = getTargetElements(file, editor);
294     return targetElements != null && targetElements.length > 0;
295   }
296
297   @Nullable
298   private static PsiElement chooseTarget(PsiFile file, Editor editor, Project project) {
299     PsiElement target = null;
300     final PsiElementClassMember[] targetElements = getTargetElements(file, editor);
301     if (targetElements == null || targetElements.length == 0) return null;
302     if (!ApplicationManager.getApplication().isUnitTestMode()) {
303       MemberChooser<PsiElementClassMember> chooser = new MemberChooser<PsiElementClassMember>(targetElements, false, false, project);
304       chooser.setTitle(CodeInsightBundle.message("generate.delegate.target.chooser.title"));
305       chooser.setCopyJavadocVisible(false);
306       chooser.show();
307
308       if (chooser.getExitCode() != MemberChooser.OK_EXIT_CODE) return null;
309
310       final List<PsiElementClassMember> selectedElements = chooser.getSelectedElements();
311
312       if (selectedElements != null && selectedElements.size() > 0) target = selectedElements.get(0).getElement();
313     }
314     else {
315       target = targetElements[0].getElement();
316     }
317     return target;
318   }
319
320   @Nullable
321   private static PsiElementClassMember[] getTargetElements(PsiFile file, Editor editor) {
322     int offset = editor.getCaretModel().getOffset();
323     PsiElement element = file.findElementAt(offset);
324     if (element == null) return null;
325     PsiClass aClass = PsiTreeUtil.getParentOfType(element, PsiClass.class);
326     if (aClass == null) return null;
327
328     List<PsiElementClassMember> result = new ArrayList<PsiElementClassMember>();
329
330     while (aClass != null) {
331       collectTargetsInClass(element, aClass, result);
332       if (aClass.hasModifierProperty(PsiModifier.STATIC)) break;
333       aClass = PsiTreeUtil.getParentOfType(aClass, PsiClass.class, true);
334     }
335
336     return result.toArray(new PsiElementClassMember[result.size()]);
337   }
338
339   private static void collectTargetsInClass(PsiElement element, final PsiClass aClass, List<PsiElementClassMember> result) {
340     final PsiField[] fields = aClass.getAllFields();
341     PsiResolveHelper helper = JavaPsiFacade.getInstance(aClass.getProject()).getResolveHelper();
342     for (PsiField field : fields) {
343       final PsiType type = field.getType();
344       if (helper.isAccessible(field, aClass, aClass) && type instanceof PsiClassType && !PsiTreeUtil.isAncestor(field, element, false)) {
345         result.add(new PsiFieldMember(field));
346       }
347     }
348
349     final PsiMethod[] methods = aClass.getAllMethods();
350     for (PsiMethod method : methods) {
351       if (CommonClassNames.JAVA_LANG_OBJECT.equals(method.getContainingClass().getQualifiedName())) continue;
352       final PsiType returnType = method.getReturnType();
353       if (returnType != null && PropertyUtil.isSimplePropertyGetter(method) && helper.isAccessible(method, aClass, aClass) &&
354           returnType instanceof PsiClassType && !PsiTreeUtil.isAncestor(method, element, false)) {
355         result.add(new PsiMethodMember(method));
356       }
357     }
358
359     if (aClass instanceof PsiAnonymousClass) {
360       VariablesProcessor proc = new VariablesProcessor(false) {
361         @Override
362         protected boolean check(PsiVariable var, ResolveState state) {
363           return var.hasModifierProperty(PsiModifier.FINAL) && var instanceof PsiLocalVariable || var instanceof PsiParameter;
364         }
365       };
366       PsiElement scope = aClass;
367       while (scope != null) {
368         if (scope instanceof PsiFile || scope instanceof PsiMethod || scope instanceof PsiClassInitializer) break;
369         scope = scope.getParent();
370       }
371       if (scope != null) {
372         PsiScopesUtil.treeWalkUp(proc, aClass, scope);
373
374         for (int i = 0; i < proc.size(); i++) {
375           final PsiVariable psiVariable = proc.getResult(i);
376           final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(aClass.getProject());
377           final PsiType type = psiVariable.getType();
378           result.add(new PsiFieldMember(elementFactory.createField(psiVariable.getName(), type instanceof PsiEllipsisType ? ((PsiEllipsisType)type).toArrayType() : type)) {
379             @Override
380             protected PsiClass getContainingClass() {
381               return aClass;
382             }
383           });
384         }
385       }
386     }
387   }
388 }