mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
intention to extract lambda body to method and convert to method reference: avoid overhead caused by extract method processor
This commit is contained in:
+32
-33
@@ -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;
|
||||
}
|
||||
|
||||
+89
-129
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+11
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
+14
@@ -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);
|
||||
}
|
||||
}
|
||||
+9
@@ -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);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -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;
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -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;
|
||||
}
|
||||
}
|
||||
+16
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user