2 * Copyright 2000-2009 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.intellij.codeInsight.generation;
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;
40 import java.util.ArrayList;
41 import java.util.List;
48 public class GenerateDelegateHandler implements LanguageCodeInsightActionHandler {
49 private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.generation.GenerateDelegateHandler");
50 private boolean myToCopyJavaDoc = false;
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);
58 public void invoke(@NotNull final Project project, @NotNull final Editor editor, @NotNull final PsiFile file) {
59 if (!FileDocumentManager.getInstance().requestWriting(editor.getDocument(), project)) {
62 PsiDocumentManager.getInstance(project).commitAllDocuments();
64 final PsiElement target = chooseTarget(file, editor, project);
65 if (target == null) return;
67 final PsiMethodMember[] candidates = chooseMethods(target, file, editor, project);
68 if (candidates == null || candidates.length == 0) return;
71 ApplicationManager.getApplication().runWriteAction(new Runnable() {
74 int offset = editor.getCaretModel().getOffset();
76 List<PsiGenerationInfo<PsiMethod>> prototypes = new ArrayList<PsiGenerationInfo<PsiMethod>>(candidates.length);
77 for (PsiMethodMember candidate : candidates) {
78 prototypes.add(generateDelegatePrototype(candidate, target));
81 List<PsiGenerationInfo<PsiMethod>> results = GenerateMembersUtil.insertMembersAtOffset(file, offset, prototypes);
83 if (!results.isEmpty()) {
84 PsiMethod firstMethod = results.get(0).getPsiMember();
85 final PsiCodeBlock block = firstMethod.getBody();
87 final PsiElement first = block.getFirstBodyElement();
89 editor.getCaretModel().moveToOffset(first.getTextRange().getStartOffset());
90 editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
91 editor.getSelectionModel().removeSelection();
94 catch (IncorrectOperationException e) {
101 public boolean startInWriteAction() {
105 private PsiGenerationInfo<PsiMethod> generateDelegatePrototype(PsiMethodMember methodCandidate, PsiElement target) throws IncorrectOperationException {
106 PsiMethod method = GenerateMembersUtil.substituteGenericMethod(methodCandidate.getElement(), methodCandidate.getSubstitutor());
109 clearModifiers(method);
111 @NonNls StringBuffer call = new StringBuffer();
113 PsiModifierList modifierList = null;
115 if (method.getReturnType() != PsiType.VOID) {
116 call.append("return ");
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());
126 final String name = field.getName();
128 final PsiParameter[] parameters = method.getParameterList().getParameters();
129 for (PsiParameter parameter : parameters) {
130 if (name.equals(parameter.getName())) {
131 call.append("this.");
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(".");
147 call.append(m.getName());
152 call.append(method.getName());
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());
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);
167 if (isMethodStatic || modifierList != null && modifierList.hasModifierProperty(PsiModifier.STATIC)) {
168 PsiUtil.setModifierProperty(method, PsiModifier.STATIC, true);
171 PsiUtil.setModifierProperty(method, PsiModifier.PUBLIC, true);
173 final Project project = method.getProject();
174 for (PsiAnnotation annotation : methodCandidate.getElement().getModifierList().getAnnotations()) {
175 OverrideImplementUtil.annotate(method, annotation.getQualifiedName());
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);
185 return new PsiGenerationInfo<PsiMethod>(method);
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);
195 method.add(codeBlock);
198 if (!myToCopyJavaDoc) {
199 final PsiDocComment docComment = method.getDocComment();
200 if (docComment != null) {
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();
214 private PsiMethodMember[] chooseMethods(PsiElement target, PsiFile file, Editor editor, Project project) {
215 PsiClassType.ClassResolveResult resolveResult = null;
217 if (target instanceof PsiField) {
218 resolveResult = PsiUtil.resolveGenericsClassInType(((PsiField)target).getType());
220 else if (target instanceof PsiMethod) {
221 resolveResult = PsiUtil.resolveGenericsClassInType(((PsiMethod)target).getReturnType());
224 if (resolveResult == null || resolveResult.getElement() == null) return null;
225 PsiClass targetClass = resolveResult.getElement();
226 PsiSubstitutor substitutor = resolveResult.getSubstitutor();
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;
234 List<PsiMethodMember> methodInstances = new ArrayList<PsiMethodMember>();
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);
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);
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()]));
275 if (chooser.getExitCode() != MemberChooser.OK_EXIT_CODE) return null;
277 myToCopyJavaDoc = chooser.isCopyJavadoc();
278 final List<PsiElementClassMember> list = chooser.getSelectedElements();
279 result = list.toArray(new PsiMethodMember[list.size()]);
282 result = methodInstances.isEmpty() ? new PsiMethodMember[0] : new PsiMethodMember[] {methodInstances.get(0)};
288 public void setToCopyJavaDoc(boolean toCopyJavaDoc) {
289 myToCopyJavaDoc = toCopyJavaDoc;
292 public static boolean isApplicable(PsiFile file, Editor editor) {
293 ClassMember[] targetElements = getTargetElements(file, editor);
294 return targetElements != null && targetElements.length > 0;
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);
308 if (chooser.getExitCode() != MemberChooser.OK_EXIT_CODE) return null;
310 final List<PsiElementClassMember> selectedElements = chooser.getSelectedElements();
312 if (selectedElements != null && selectedElements.size() > 0) target = selectedElements.get(0).getElement();
315 target = targetElements[0].getElement();
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;
328 List<PsiElementClassMember> result = new ArrayList<PsiElementClassMember>();
330 while (aClass != null) {
331 collectTargetsInClass(element, aClass, result);
332 if (aClass.hasModifierProperty(PsiModifier.STATIC)) break;
333 aClass = PsiTreeUtil.getParentOfType(aClass, PsiClass.class, true);
336 return result.toArray(new PsiElementClassMember[result.size()]);
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));
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));
359 if (aClass instanceof PsiAnonymousClass) {
360 VariablesProcessor proc = new VariablesProcessor(false) {
362 protected boolean check(PsiVariable var, ResolveState state) {
363 return var.hasModifierProperty(PsiModifier.FINAL) && var instanceof PsiLocalVariable || var instanceof PsiParameter;
366 PsiElement scope = aClass;
367 while (scope != null) {
368 if (scope instanceof PsiFile || scope instanceof PsiMethod || scope instanceof PsiClassInitializer) break;
369 scope = scope.getParent();
372 PsiScopesUtil.treeWalkUp(proc, aClass, scope);
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)) {
380 protected PsiClass getContainingClass() {