intention to extract lambda body to method and convert to method reference: avoid overhead caused by extract method processor

This commit is contained in:
Anna.Kozlova
2017-04-07 12:35:25 +02:00
parent b68a4734b9
commit af675743e3
8 changed files with 177 additions and 168 deletions
@@ -1616,39 +1616,7 @@ public class ExtractMethodProcessor implements MatchProvider {
throws PrepareFailedException {
myStatic = shouldBeStatic();
final Set<PsiField> fields = new LinkedHashSet<>();
if (!PsiUtil.isLocalOrAnonymousClass(myTargetClass) && (myTargetClass.getContainingClass() == null || myTargetClass.hasModifierProperty(PsiModifier.STATIC))) {
boolean canBeStatic = true;
if (myTargetClass.isInterface()) {
final PsiMethod containingMethod = PsiTreeUtil.getParentOfType(myCodeFragmentMember, PsiMethod.class, false);
canBeStatic = containingMethod == null || containingMethod.hasModifierProperty(PsiModifier.STATIC);
}
if (canBeStatic) {
ElementNeedsThis needsThis = new ElementNeedsThis(myTargetClass) {
@Override
protected void visitClassMemberReferenceElement(PsiMember classMember, PsiJavaCodeReferenceElement classMemberReference) {
if (classMember instanceof PsiField && !classMember.hasModifierProperty(PsiModifier.STATIC)) {
final PsiExpression expression = PsiTreeUtil.getParentOfType(classMemberReference, PsiExpression.class, false);
if (expression == null || !PsiUtil.isAccessedForWriting(expression)) {
fields.add((PsiField)classMember);
return;
}
}
super.visitClassMemberReferenceElement(classMember, classMemberReference);
}
};
for (int i = 0; i < myElements.length && !needsThis.usesMembers(); i++) {
PsiElement element = myElements[i];
element.accept(needsThis);
}
myCanBeStatic = !needsThis.usesMembers();
}
else {
myCanBeStatic = false;
}
}
else {
myCanBeStatic = false;
}
myCanBeStatic = canBeStatic(myTargetClass, myCodeFragmentMember, myElements, fields);
myInputVariables = new InputVariables(inputVariables, myProject, new LocalSearchScope(myElements), isFoldingApplicable());
myInputVariables.setUsedInstanceFields(fields);
@@ -1665,6 +1633,37 @@ public class ExtractMethodProcessor implements MatchProvider {
return true;
}
public static boolean canBeStatic(final PsiClass targetClass, final PsiElement place, final PsiElement[] elements, Set<PsiField> usedFields) {
if (!PsiUtil.isLocalOrAnonymousClass(targetClass) && (targetClass.getContainingClass() == null || targetClass.hasModifierProperty(PsiModifier.STATIC))) {
boolean canBeStatic = true;
if (targetClass.isInterface()) {
final PsiMethod containingMethod = PsiTreeUtil.getParentOfType(place, PsiMethod.class, false);
canBeStatic = containingMethod == null || containingMethod.hasModifierProperty(PsiModifier.STATIC);
}
if (canBeStatic) {
ElementNeedsThis needsThis = new ElementNeedsThis(targetClass) {
@Override
protected void visitClassMemberReferenceElement(PsiMember classMember, PsiJavaCodeReferenceElement classMemberReference) {
if (classMember instanceof PsiField && !classMember.hasModifierProperty(PsiModifier.STATIC)) {
final PsiExpression expression = PsiTreeUtil.getParentOfType(classMemberReference, PsiExpression.class, false);
if (expression == null || !PsiUtil.isAccessedForWriting(expression)) {
usedFields.add((PsiField)classMember);
return;
}
}
super.visitClassMemberReferenceElement(classMember, classMemberReference);
}
};
for (int i = 0; i < elements.length && !needsThis.usesMembers(); i++) {
PsiElement element = elements[i];
element.accept(needsThis);
}
return !needsThis.usesMembers();
}
}
return false;
}
protected boolean isFoldingApplicable() {
return true;
}
@@ -18,33 +18,35 @@ package com.siyeh.ipp.functional;
import com.intellij.codeInsight.CodeInsightUtilCore;
import com.intellij.codeInsight.intention.BaseElementAtCaretIntentionAction;
import com.intellij.codeInspection.LambdaCanBeMethodReferenceInspection;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pass;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.extractMethod.AbstractExtractDialog;
import com.intellij.refactoring.extractMethod.ControlFlowWrapper;
import com.intellij.refactoring.extractMethod.ExtractMethodProcessor;
import com.intellij.refactoring.extractMethod.PrepareFailedException;
import com.intellij.refactoring.rename.RenamePsiElementProcessor;
import com.intellij.refactoring.rename.inplace.MemberInplaceRenamer;
import com.intellij.refactoring.util.VariableData;
import com.intellij.refactoring.util.LambdaRefactoringUtil;
import com.intellij.refactoring.util.RefactoringUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.ObjectUtils;
import com.intellij.util.text.UniqueNameGenerator;
import com.siyeh.IntentionPowerPackBundle;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
public class ExtractToMethodReferenceIntention extends BaseElementAtCaretIntentionAction {
private static final Logger LOG = Logger.getInstance(ExtractToMethodReferenceIntention.class);
@NotNull
@Override
public String getText() {
@@ -63,8 +65,20 @@ public class ExtractToMethodReferenceIntention extends BaseElementAtCaretIntenti
if (lambdaExpression != null) {
PsiElement body = lambdaExpression.getBody();
if (body == null) return false;
//is a valid lambda
PsiType functionalInterfaceType = lambdaExpression.getFunctionalInterfaceType();
if (functionalInterfaceType == null) return false;
if (functionalInterfaceType == null ||
LambdaUtil.getFunctionalInterfaceReturnType(functionalInterfaceType) == null ||
!LambdaUtil.isLambdaFullyInferred(lambdaExpression, functionalInterfaceType)) {
return false;
}
//can types be specified
if (LambdaRefactoringUtil.createLambdaParameterListWithFormalTypes(functionalInterfaceType, lambdaExpression, false) == null) {
return false;
}
PsiExpression asMethodReference = LambdaCanBeMethodReferenceInspection
.canBeMethodReferenceProblem(body, lambdaExpression.getParameterList().getParameters(), functionalInterfaceType, null);
if (asMethodReference != null) return false;
@@ -84,139 +98,85 @@ public class ExtractToMethodReferenceIntention extends BaseElementAtCaretIntenti
@Override
public void invoke(@NotNull Project project, Editor editor, @NotNull PsiElement element) throws IncorrectOperationException {
final PsiLambdaExpression lambdaExpression = PsiTreeUtil.getParentOfType(element, PsiLambdaExpression.class, false);
PsiLambdaExpression lambdaExpression = PsiTreeUtil.getParentOfType(element, PsiLambdaExpression.class, false);
if (lambdaExpression != null) {
PsiElement body = lambdaExpression.getBody();
if (body != null) {
PsiElement[] elements = body instanceof PsiCodeBlock ? ((PsiCodeBlock)body).getStatements() : new PsiElement[] {body};
PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(lambdaExpression.getFunctionalInterfaceType());
String initialMethodName = interfaceMethod != null ? interfaceMethod.getName() : "name";
ExtractMethodProcessor methodProcessor = new ExtractMethodProcessor(project, editor, elements, null, getFamilyName(), null, null) {
@Override
public boolean showDialog() {
apply(new MyExtractMethodDialog(myTargetClass, lambdaExpression, myCanBeStatic, initialMethodName));
return true;
}
};
try {
methodProcessor.prepare();
}
catch (PrepareFailedException e) {
return;
}
methodProcessor.showDialog();
WriteAction.run(() -> {
methodProcessor.doExtract();
PsiExpression expression = LambdaCanBeMethodReferenceInspection.replaceLambdaWithMethodReference(lambdaExpression);
if (expression instanceof PsiMethodReferenceExpression) {
PsiMethod method = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(methodProcessor.getExtractedMethod());
PsiElement refElement = ((PsiMethodReferenceExpression)expression).getReferenceNameElement();
PsiIdentifier nameIdentifier = method.getNameIdentifier();
if (nameIdentifier == null) return;
//try to navigate to reference name
editor.getCaretModel().moveToOffset(ObjectUtils.notNull(refElement, nameIdentifier).getTextOffset());
final RenamePsiElementProcessor processor = RenamePsiElementProcessor.forElement(method);
if (!processor.isInplaceRenameSupported()) {
return;
}
List<String> suggestedNames = new ArrayList<>();
suggestedNames.add(method.getName());
processor.substituteElementToRename(method, editor, new Pass<PsiElement>() {
@Override
public void pass(PsiElement substitutedElement) {
final MemberInplaceRenamer renamer = new MemberInplaceRenamer(method, substitutedElement, editor);
final LinkedHashSet<String> nameSuggestions = new LinkedHashSet<>(suggestedNames);
renamer.performInplaceRefactoring(nameSuggestions);
}
});
}
});
if (body instanceof PsiExpression) {
lambdaExpression = (PsiLambdaExpression)RefactoringUtil.expandExpressionLambdaToCodeBlock(body);
body = lambdaExpression.getBody();
}
}
}
@Nullable
@Override
public PsiElement getElementToMakeWritable(@NotNull PsiFile currentFile) {
return currentFile;
}
PsiClass targetClass = PsiTreeUtil.getParentOfType(lambdaExpression, PsiClass.class);
if (targetClass == null) return;
LOG.assertTrue(body instanceof PsiCodeBlock);
PsiElement[] elements = ((PsiCodeBlock)body).getStatements();
@Override
public boolean startInWriteAction() {
return false;
}
private static class MyExtractMethodDialog implements AbstractExtractDialog {
private final String myTargetMethodName;
private final boolean myCanBeStatic;
private final VariableData[] myVariableData;
public MyExtractMethodDialog(@NotNull PsiClass targetClass,
PsiLambdaExpression lambdaExpression,
boolean canBeStatic,
String initialMethodName) {
myVariableData = Arrays.stream(lambdaExpression.getParameterList().getParameters())
.map(parameter -> {
VariableData data = new VariableData(parameter);
data.passAsParameter = true;
data.name = parameter.getName();
return data;
})
.toArray(VariableData[]::new);
myCanBeStatic = canBeStatic;
HashSet<PsiField> usedFields = new HashSet<>();
boolean canBeStatic = ExtractMethodProcessor.canBeStatic(targetClass, lambdaExpression, elements, usedFields) && usedFields.isEmpty();
PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(targetClass.getProject());
String parameters = "(" + StringUtil.join(myVariableData, data -> data.type.getCanonicalText() + " " + data.name, ", ") + "){}";
myTargetMethodName = UniqueNameGenerator.generateUniqueName(initialMethodName,
methodName -> {
String methodText = "private void " + methodName + parameters;
PsiMethod patternMethod = elementFactory.createMethodFromText(methodText, lambdaExpression);
return targetClass.findMethodBySignature(patternMethod, true) == null;
});
PsiType functionalInterfaceType = lambdaExpression.getFunctionalInterfaceType();
String parameters = LambdaRefactoringUtil.createLambdaParameterListWithFormalTypes(functionalInterfaceType, lambdaExpression, false) + "{}";
String targetMethodName = getUniqueMethodName(targetClass, elementFactory, functionalInterfaceType, parameters);
PsiType returnType = LambdaUtil.getFunctionalInterfaceReturnType(lambdaExpression);
LOG.assertTrue(returnType != null);
PsiMethod container = PsiTreeUtil.getParentOfType(lambdaExpression, PsiMethod.class);
PsiTypeParameterList typeParamsList =
container != null ? RefactoringUtil.createTypeParameterListWithUsedTypeParameters(container.getTypeParameterList(), elements) : null;
PsiMethod emptyMethod = elementFactory.createMethodFromText("private " + (canBeStatic ? "static " : "") +
(typeParamsList != null ? typeParamsList.getText() + " " : "") +
returnType.getCanonicalText() + " " +
targetMethodName + parameters, targetClass);
PsiCodeBlock targetMethodBody = emptyMethod.getBody();
LOG.assertTrue(targetMethodBody != null);
targetMethodBody.addRange(elements[0], elements[elements.length - 1]);
PsiMethod method = (PsiMethod)CodeStyleManager.getInstance(project).reformat(JavaCodeStyleManager.getInstance(project).shortenClassReferences(targetClass.add(emptyMethod)));
PsiMethodReferenceExpression methodReference =
(PsiMethodReferenceExpression)elementFactory.createExpressionFromText((canBeStatic ? targetClass.getName() : "this") + "::" + targetMethodName, lambdaExpression);
methodReference = (PsiMethodReferenceExpression)lambdaExpression.replace(methodReference);
startInplaceRename(editor, method, methodReference);
}
}
@Override
public String getChosenMethodName() {
return myTargetMethodName;
private static void startInplaceRename(Editor editor, PsiMethod method, PsiMethodReferenceExpression methodReference) {
PsiIdentifier nameIdentifier = method.getNameIdentifier();
if (nameIdentifier == null) return;
nameIdentifier = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(nameIdentifier);
//try to navigate to reference name
editor.getCaretModel().moveToOffset(ObjectUtils.notNull(methodReference.getReferenceNameElement(), nameIdentifier).getTextOffset());
final RenamePsiElementProcessor processor = RenamePsiElementProcessor.forElement(method);
if (!processor.isInplaceRenameSupported()) {
return;
}
List<String> suggestedNames = new ArrayList<>();
suggestedNames.add(method.getName());
processor.substituteElementToRename(method, editor, new Pass<PsiElement>() {
@Override
public void pass(PsiElement substitutedElement) {
final MemberInplaceRenamer renamer = new MemberInplaceRenamer(method, substitutedElement, editor);
final LinkedHashSet<String> nameSuggestions = new LinkedHashSet<>(suggestedNames);
renamer.performInplaceRefactoring(nameSuggestions);
}
});
}
@Override
public VariableData[] getChosenParameters() {
return myVariableData;
}
@NotNull
@Override
public String getVisibility() {
return PsiModifier.PRIVATE;
}
@Override
public boolean isMakeStatic() {
return myCanBeStatic;
}
@Override
public boolean isChainedConstructor() {
return false;
}
@Override
public PsiType getReturnType() {
return null;
}
@Override
public void show() {}
@Override
public boolean isOK() {
return true;
}
private static String getUniqueMethodName(PsiClass targetClass,
PsiElementFactory elementFactory,
PsiType functionalInterfaceType,
String parameters) {
PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(functionalInterfaceType);
String initialMethodName = interfaceMethod != null ? interfaceMethod.getName() : "name";
return UniqueNameGenerator.generateUniqueName(initialMethodName,
methodName -> {
String methodText = "private void " + methodName + parameters;
PsiMethod patternMethod = elementFactory.createMethodFromText(methodText, targetClass);
return targetClass.findMethodBySignature(patternMethod, true) == null;
});
}
}
@@ -0,0 +1,11 @@
import java.util.List;
class MyClass {
String myStr;
void f(List<String> l){
l.stream().filter(s -> s.startsWith(my<caret>Str));
}
}
@@ -0,0 +1,14 @@
import java.util.List;
class MyClass {
String myStr;
void f(List<String> l){
l.stream().filter(this::test);
}
private boolean test(String s) {
return s.startsWith(myStr);
}
}
@@ -0,0 +1,9 @@
import java.util.List;
class MyClass {
void f(List<? extends String> l) {
l.stream().filter(s -> s.substring(1).le<caret>ngth() > 0);
}
}
@@ -3,10 +3,10 @@ interface I {
}
class B {
class X {
I i = this::m;
private void m(int i1) {
System.out.println("");
}
I i = this::m;
}
}
@@ -2,11 +2,11 @@ interface I {
void m(int i);
}
class B {
{
I i = B::m;
}
private static void m(int i1) {
System.out.println("");
}
{
I i = B::m;
}
}
@@ -15,8 +15,10 @@
*/
package com.siyeh.ipp.functional;
import com.intellij.testFramework.LightProjectDescriptor;
import com.siyeh.IntentionPowerPackBundle;
import com.siyeh.ipp.IPPTestCase;
import org.jetbrains.annotations.NotNull;
public class ExtractToMethodReferenceTest extends IPPTestCase {
@@ -40,10 +42,18 @@ public class ExtractToMethodReferenceTest extends IPPTestCase {
doTest();
}
public void testFieldsUsedInsideLambda() throws Exception {
doTest();
}
public void testConvertableToMethodReference() throws Exception {
assertIntentionNotAvailable();
}
public void testNonDenotableParameterTypes() throws Exception {
assertIntentionNotAvailable();
}
public void testUsedLocalVariables() throws Exception {
assertIntentionNotAvailable();
}
@@ -57,5 +67,11 @@ public class ExtractToMethodReferenceTest extends IPPTestCase {
protected String getRelativePath() {
return "functional/extractToMethodReference";
}
@NotNull
@Override
protected LightProjectDescriptor getProjectDescriptor() {
return JAVA_8;
}
}