blob: fe4208deada702fe086a608232539f6160c1e342 [file] [log] [blame]
/*
* Copyright 2000-2009 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.codeInsight.generation;
import com.intellij.codeInsight.CodeInsightBundle;
import com.intellij.codeInsight.CodeInsightUtilBase;
import com.intellij.ide.util.MemberChooser;
import com.intellij.lang.LanguageCodeInsightActionHandler;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ScrollType;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.javadoc.PsiDocComment;
import com.intellij.psi.scope.processor.VariablesProcessor;
import com.intellij.psi.scope.util.PsiScopesUtil;
import com.intellij.psi.util.*;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.HashMap;
import com.intellij.util.containers.HashSet;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* @author mike
*/
public class GenerateDelegateHandler implements LanguageCodeInsightActionHandler {
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.generation.GenerateDelegateHandler");
private boolean myToCopyJavaDoc = false;
@Override
public boolean isValidFor(Editor editor, PsiFile file) {
if (!(file instanceof PsiJavaFile)) return false;
return OverrideImplementUtil.getContextClass(editor.getProject(), editor, file, false) != null && isApplicable(file, editor);
}
@Override
public void invoke(@NotNull final Project project, @NotNull final Editor editor, @NotNull final PsiFile file) {
if (!CodeInsightUtilBase.prepareEditorForWrite(editor)) return;
if (!FileDocumentManager.getInstance().requestWriting(editor.getDocument(), project)) {
return;
}
PsiDocumentManager.getInstance(project).commitAllDocuments();
final PsiElementClassMember target = chooseTarget(file, editor, project);
if (target == null) return;
final PsiMethodMember[] candidates = chooseMethods(target, file, editor, project);
if (candidates == null || candidates.length == 0) return;
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
try {
int offset = editor.getCaretModel().getOffset();
List<PsiGenerationInfo<PsiMethod>> prototypes = new ArrayList<PsiGenerationInfo<PsiMethod>>(candidates.length);
for (PsiMethodMember candidate : candidates) {
prototypes.add(generateDelegatePrototype(candidate, target.getElement()));
}
List<PsiGenerationInfo<PsiMethod>> results = GenerateMembersUtil.insertMembersAtOffset(file, offset, prototypes);
if (!results.isEmpty()) {
PsiMethod firstMethod = results.get(0).getPsiMember();
final PsiCodeBlock block = firstMethod.getBody();
assert block != null;
final PsiElement first = block.getFirstBodyElement();
assert first != null;
editor.getCaretModel().moveToOffset(first.getTextRange().getStartOffset());
editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
editor.getSelectionModel().removeSelection();
}
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
}
});
}
@Override
public boolean startInWriteAction() {
return false;
}
private PsiGenerationInfo<PsiMethod> generateDelegatePrototype(PsiMethodMember methodCandidate, PsiElement target) throws IncorrectOperationException {
PsiMethod method = GenerateMembersUtil.substituteGenericMethod(methodCandidate.getElement(), methodCandidate.getSubstitutor());
clearMethod(method);
clearModifiers(method);
@NonNls StringBuffer call = new StringBuffer();
PsiModifierList modifierList = null;
if (method.getReturnType() != PsiType.VOID) {
call.append("return ");
}
boolean isMethodStatic = methodCandidate.getElement().hasModifierProperty(PsiModifier.STATIC);
if (target instanceof PsiField) {
PsiField field = (PsiField)target;
modifierList = field.getModifierList();
if (isMethodStatic) {
call.append(methodCandidate.getContainingClass().getQualifiedName());
} else {
final String name = field.getName();
final PsiParameter[] parameters = method.getParameterList().getParameters();
for (PsiParameter parameter : parameters) {
if (name.equals(parameter.getName())) {
call.append("this.");
break;
}
}
call.append(name);
}
call.append(".");
}
else if (target instanceof PsiMethod) {
PsiMethod m = (PsiMethod)target;
modifierList = m.getModifierList();
if (isMethodStatic) {
call.append(methodCandidate.getContainingClass().getQualifiedName()).append(".");
}
else {
call.append(m.getName());
call.append("().");
}
}
call.append(method.getName());
call.append("(");
final PsiParameter[] parameters = method.getParameterList().getParameters();
for (int j = 0; j < parameters.length; j++) {
PsiParameter parameter = parameters[j];
if (j > 0) call.append(",");
call.append(parameter.getName());
}
call.append(");");
final PsiManager psiManager = method.getManager();
PsiStatement stmt = JavaPsiFacade.getInstance(psiManager.getProject()).getElementFactory().createStatementFromText(call.toString(), method);
stmt = (PsiStatement)CodeStyleManager.getInstance(psiManager.getProject()).reformat(stmt);
method.getBody().add(stmt);
for (PsiAnnotation annotation : methodCandidate.getElement().getModifierList().getAnnotations()) {
if (SuppressWarnings.class.getName().equals(annotation.getQualifiedName())) continue;
method.getModifierList().add(annotation.copy());
}
if (isMethodStatic || modifierList != null && modifierList.hasModifierProperty(PsiModifier.STATIC)) {
PsiUtil.setModifierProperty(method, PsiModifier.STATIC, true);
}
PsiUtil.setModifierProperty(method, PsiModifier.PUBLIC, true);
final PsiClass targetClass = ((PsiMember)target).getContainingClass();
LOG.assertTrue(targetClass != null);
PsiMethod overridden = targetClass.findMethodBySignature(method, true);
if (overridden != null && overridden.getContainingClass() != targetClass) {
OverrideImplementUtil.annotateOnOverrideImplement(method, targetClass, overridden);
}
return new PsiGenerationInfo<PsiMethod>(method);
}
private void clearMethod(PsiMethod method) throws IncorrectOperationException {
LOG.assertTrue(!method.isPhysical());
PsiCodeBlock codeBlock = JavaPsiFacade.getInstance(method.getProject()).getElementFactory().createCodeBlock();
if (method.getBody() != null) {
method.getBody().replace(codeBlock);
}
else {
method.add(codeBlock);
}
if (!myToCopyJavaDoc) {
final PsiDocComment docComment = method.getDocComment();
if (docComment != null) {
docComment.delete();
}
}
}
private static void clearModifiers(PsiMethod method) throws IncorrectOperationException {
final PsiElement[] children = method.getModifierList().getChildren();
for (PsiElement child : children) {
if (child instanceof PsiKeyword) child.delete();
}
}
@Nullable
private PsiMethodMember[] chooseMethods(PsiElementClassMember targetMember, PsiFile file, Editor editor, Project project) {
PsiClassType.ClassResolveResult resolveResult = null;
final PsiDocCommentOwner target = targetMember.getElement();
if (target instanceof PsiField) {
resolveResult = PsiUtil.resolveGenericsClassInType(targetMember.getSubstitutor().substitute(((PsiField)target).getType()));
}
else if (target instanceof PsiMethod) {
resolveResult = PsiUtil.resolveGenericsClassInType(targetMember.getSubstitutor().substitute(((PsiMethod)target).getReturnType()));
}
if (resolveResult == null || resolveResult.getElement() == null) return null;
PsiClass targetClass = resolveResult.getElement();
PsiSubstitutor substitutor = resolveResult.getSubstitutor();
int offset = editor.getCaretModel().getOffset();
PsiElement element = file.findElementAt(offset);
if (element == null) return null;
PsiClass aClass = PsiTreeUtil.getParentOfType(element, PsiClass.class);
if (aClass == null) return null;
List<PsiMethodMember> methodInstances = new ArrayList<PsiMethodMember>();
final PsiMethod[] allMethods;
if (targetClass instanceof PsiTypeParameter) {
LinkedHashSet<PsiMethod> meths = new LinkedHashSet<PsiMethod>();
for (PsiClass superClass : targetClass.getSupers()) {
meths.addAll(Arrays.asList(superClass.getAllMethods()));
}
allMethods = meths.toArray(new PsiMethod[meths.size()]);
}
else {
allMethods = targetClass.getAllMethods();
}
final Set<MethodSignature> signatures = new HashSet<MethodSignature>();
final Set<MethodSignature> existingSignatures = new HashSet<MethodSignature>(aClass.getVisibleSignatures());
final Set<PsiMethodMember> selection = new HashSet<PsiMethodMember>();
Map<PsiClass, PsiSubstitutor> superSubstitutors = new HashMap<PsiClass, PsiSubstitutor>();
final PsiClass containingClass = targetMember.getContainingClass();
JavaPsiFacade facade = JavaPsiFacade.getInstance(target.getProject());
for (PsiMethod method : allMethods) {
final PsiClass superClass = method.getContainingClass();
if (CommonClassNames.JAVA_LANG_OBJECT.equals(superClass.getQualifiedName())) continue;
if (method.isConstructor()) continue;
//do not suggest to override final method
if (method.hasModifierProperty(PsiModifier.FINAL)) {
PsiMethod overridden = containingClass.findMethodBySignature(method, true);
if (overridden != null && overridden.getContainingClass() != containingClass) {
continue;
}
}
PsiSubstitutor superSubstitutor = superSubstitutors.get(superClass);
if (superSubstitutor == null) {
superSubstitutor = TypeConversionUtil.getSuperClassSubstitutor(superClass, targetClass, substitutor);
superSubstitutors.put(superClass, superSubstitutor);
}
PsiSubstitutor methodSubstitutor = OverrideImplementExploreUtil.correctSubstitutor(method, superSubstitutor);
MethodSignature signature = method.getSignature(methodSubstitutor);
if (!signatures.contains(signature)) {
signatures.add(signature);
if (facade.getResolveHelper().isAccessible(method, target, aClass)) {
final PsiMethodMember methodMember = new PsiMethodMember(method, methodSubstitutor);
methodInstances.add(methodMember);
if (!existingSignatures.contains(signature)) {
selection.add(methodMember);
}
}
}
}
PsiMethodMember[] result;
if (!ApplicationManager.getApplication().isUnitTestMode()) {
MemberChooser<PsiElementClassMember> chooser = new MemberChooser<PsiElementClassMember>(methodInstances.toArray(new PsiMethodMember[methodInstances.size()]), false, true, project);
chooser.setTitle(CodeInsightBundle.message("generate.delegate.method.chooser.title"));
chooser.setCopyJavadocVisible(true);
if (!selection.isEmpty()) {
chooser.selectElements(selection.toArray(new ClassMember[selection.size()]));
}
chooser.show();
if (chooser.getExitCode() != DialogWrapper.OK_EXIT_CODE) return null;
myToCopyJavaDoc = chooser.isCopyJavadoc();
final List<PsiElementClassMember> list = chooser.getSelectedElements();
result = list.toArray(new PsiMethodMember[list.size()]);
}
else {
result = methodInstances.isEmpty() ? new PsiMethodMember[0] : new PsiMethodMember[] {methodInstances.get(0)};
}
return result;
}
public void setToCopyJavaDoc(boolean toCopyJavaDoc) {
myToCopyJavaDoc = toCopyJavaDoc;
}
public static boolean isApplicable(PsiFile file, Editor editor) {
ClassMember[] targetElements = getTargetElements(file, editor);
return targetElements != null && targetElements.length > 0;
}
@Nullable
private static PsiElementClassMember chooseTarget(PsiFile file, Editor editor, Project project) {
final PsiElementClassMember[] targetElements = getTargetElements(file, editor);
if (targetElements == null || targetElements.length == 0) return null;
if (!ApplicationManager.getApplication().isUnitTestMode()) {
MemberChooser<PsiElementClassMember> chooser = new MemberChooser<PsiElementClassMember>(targetElements, false, false, project);
chooser.setTitle(CodeInsightBundle.message("generate.delegate.target.chooser.title"));
chooser.setCopyJavadocVisible(false);
chooser.show();
if (chooser.getExitCode() != DialogWrapper.OK_EXIT_CODE) return null;
final List<PsiElementClassMember> selectedElements = chooser.getSelectedElements();
if (selectedElements != null && selectedElements.size() > 0) return selectedElements.get(0);
}
else {
return targetElements[0];
}
return null;
}
@Nullable
private static PsiElementClassMember[] getTargetElements(PsiFile file, Editor editor) {
int offset = editor.getCaretModel().getOffset();
PsiElement element = file.findElementAt(offset);
if (element == null) return null;
PsiClass aClass = PsiTreeUtil.getParentOfType(element, PsiClass.class);
if (aClass == null) return null;
List<PsiElementClassMember> result = new ArrayList<PsiElementClassMember>();
while (aClass != null) {
collectTargetsInClass(element, aClass, result);
if (aClass.hasModifierProperty(PsiModifier.STATIC)) break;
aClass = PsiTreeUtil.getParentOfType(aClass, PsiClass.class, true);
}
return result.toArray(new PsiElementClassMember[result.size()]);
}
private static void collectTargetsInClass(PsiElement element, final PsiClass aClass, List<PsiElementClassMember> result) {
final PsiField[] fields = aClass.getAllFields();
PsiResolveHelper helper = JavaPsiFacade.getInstance(aClass.getProject()).getResolveHelper();
for (PsiField field : fields) {
final PsiType type = field.getType();
if (helper.isAccessible(field, aClass, aClass) && type instanceof PsiClassType && !PsiTreeUtil.isAncestor(field, element, false)) {
final PsiClass containingClass = field.getContainingClass();
if (containingClass != null) {
result.add(new PsiFieldMember(field, TypeConversionUtil.getSuperClassSubstitutor(containingClass, aClass, PsiSubstitutor.EMPTY)));
}
}
}
final PsiMethod[] methods = aClass.getAllMethods();
for (PsiMethod method : methods) {
final PsiClass containingClass = method.getContainingClass();
if (containingClass == null || CommonClassNames.JAVA_LANG_OBJECT.equals(containingClass.getQualifiedName())) continue;
final PsiType returnType = method.getReturnType();
if (returnType != null && PropertyUtil.isSimplePropertyGetter(method) && helper.isAccessible(method, aClass, aClass) &&
returnType instanceof PsiClassType && !PsiTreeUtil.isAncestor(method, element, false)) {
result.add(new PsiMethodMember(method, TypeConversionUtil.getSuperClassSubstitutor( containingClass, aClass,PsiSubstitutor.EMPTY)));
}
}
if (aClass instanceof PsiAnonymousClass) {
VariablesProcessor proc = new VariablesProcessor(false) {
@Override
protected boolean check(PsiVariable var, ResolveState state) {
return var.hasModifierProperty(PsiModifier.FINAL) && var instanceof PsiLocalVariable || var instanceof PsiParameter;
}
};
PsiElement scope = aClass;
while (scope != null) {
if (scope instanceof PsiFile || scope instanceof PsiMethod || scope instanceof PsiClassInitializer) break;
scope = scope.getParent();
}
if (scope != null) {
PsiScopesUtil.treeWalkUp(proc, aClass, scope);
for (int i = 0; i < proc.size(); i++) {
final PsiVariable psiVariable = proc.getResult(i);
final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(aClass.getProject());
final PsiType type = psiVariable.getType();
result.add(new PsiFieldMember(elementFactory.createField(psiVariable.getName(), type instanceof PsiEllipsisType ? ((PsiEllipsisType)type).toArrayType() : type)) {
@Override
protected PsiClass getContainingClass() {
return aClass;
}
});
}
}
}
}
}