diff --git a/java/java-impl/src/com/intellij/refactoring/extractMethod/ExtractMethodProcessor.java b/java/java-impl/src/com/intellij/refactoring/extractMethod/ExtractMethodProcessor.java index 483056038191..302bd6c5f96b 100644 --- a/java/java-impl/src/com/intellij/refactoring/extractMethod/ExtractMethodProcessor.java +++ b/java/java-impl/src/com/intellij/refactoring/extractMethod/ExtractMethodProcessor.java @@ -1616,39 +1616,7 @@ public class ExtractMethodProcessor implements MatchProvider { throws PrepareFailedException { myStatic = shouldBeStatic(); final Set 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 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; } diff --git a/plugins/IntentionPowerPak/src/com/siyeh/ipp/functional/ExtractToMethodReferenceIntention.java b/plugins/IntentionPowerPak/src/com/siyeh/ipp/functional/ExtractToMethodReferenceIntention.java index d81abab94969..065bfd5899af 100644 --- a/plugins/IntentionPowerPak/src/com/siyeh/ipp/functional/ExtractToMethodReferenceIntention.java +++ b/plugins/IntentionPowerPak/src/com/siyeh/ipp/functional/ExtractToMethodReferenceIntention.java @@ -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 suggestedNames = new ArrayList<>(); - suggestedNames.add(method.getName()); - processor.substituteElementToRename(method, editor, new Pass() { - @Override - public void pass(PsiElement substitutedElement) { - final MemberInplaceRenamer renamer = new MemberInplaceRenamer(method, substitutedElement, editor); - final LinkedHashSet 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 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 suggestedNames = new ArrayList<>(); + suggestedNames.add(method.getName()); + processor.substituteElementToRename(method, editor, new Pass() { + @Override + public void pass(PsiElement substitutedElement) { + final MemberInplaceRenamer renamer = new MemberInplaceRenamer(method, substitutedElement, editor); + final LinkedHashSet 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; + }); } } diff --git a/plugins/IntentionPowerPak/test/com/siyeh/ipp/functional/extractToMethodReference/FieldsUsedInsideLambda.java b/plugins/IntentionPowerPak/test/com/siyeh/ipp/functional/extractToMethodReference/FieldsUsedInsideLambda.java new file mode 100644 index 000000000000..4a22446ca772 --- /dev/null +++ b/plugins/IntentionPowerPak/test/com/siyeh/ipp/functional/extractToMethodReference/FieldsUsedInsideLambda.java @@ -0,0 +1,11 @@ +import java.util.List; + +class MyClass { + + + String myStr; + void f(List l){ + l.stream().filter(s -> s.startsWith(myStr)); + } + +} \ No newline at end of file diff --git a/plugins/IntentionPowerPak/test/com/siyeh/ipp/functional/extractToMethodReference/FieldsUsedInsideLambda_after.java b/plugins/IntentionPowerPak/test/com/siyeh/ipp/functional/extractToMethodReference/FieldsUsedInsideLambda_after.java new file mode 100644 index 000000000000..550a31da1efd --- /dev/null +++ b/plugins/IntentionPowerPak/test/com/siyeh/ipp/functional/extractToMethodReference/FieldsUsedInsideLambda_after.java @@ -0,0 +1,14 @@ +import java.util.List; + +class MyClass { + + + String myStr; + void f(List l){ + l.stream().filter(this::test); + } + + private boolean test(String s) { + return s.startsWith(myStr); + } +} \ No newline at end of file diff --git a/plugins/IntentionPowerPak/test/com/siyeh/ipp/functional/extractToMethodReference/NonDenotableParameterTypes.java b/plugins/IntentionPowerPak/test/com/siyeh/ipp/functional/extractToMethodReference/NonDenotableParameterTypes.java new file mode 100644 index 000000000000..98bc62415f04 --- /dev/null +++ b/plugins/IntentionPowerPak/test/com/siyeh/ipp/functional/extractToMethodReference/NonDenotableParameterTypes.java @@ -0,0 +1,9 @@ +import java.util.List; + +class MyClass { + + + void f(List l) { + l.stream().filter(s -> s.substring(1).length() > 0); + } +} \ No newline at end of file diff --git a/plugins/IntentionPowerPak/test/com/siyeh/ipp/functional/extractToMethodReference/UnableToCreateStatic_after.java b/plugins/IntentionPowerPak/test/com/siyeh/ipp/functional/extractToMethodReference/UnableToCreateStatic_after.java index 4f5accb0fa9a..11a1dec92912 100644 --- a/plugins/IntentionPowerPak/test/com/siyeh/ipp/functional/extractToMethodReference/UnableToCreateStatic_after.java +++ b/plugins/IntentionPowerPak/test/com/siyeh/ipp/functional/extractToMethodReference/UnableToCreateStatic_after.java @@ -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; } } \ No newline at end of file diff --git a/plugins/IntentionPowerPak/test/com/siyeh/ipp/functional/extractToMethodReference/UnusedParameterToConfirmSignature_after.java b/plugins/IntentionPowerPak/test/com/siyeh/ipp/functional/extractToMethodReference/UnusedParameterToConfirmSignature_after.java index 216cd22ca70b..936f7b97e356 100644 --- a/plugins/IntentionPowerPak/test/com/siyeh/ipp/functional/extractToMethodReference/UnusedParameterToConfirmSignature_after.java +++ b/plugins/IntentionPowerPak/test/com/siyeh/ipp/functional/extractToMethodReference/UnusedParameterToConfirmSignature_after.java @@ -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; - } } \ No newline at end of file diff --git a/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/functional/ExtractToMethodReferenceTest.java b/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/functional/ExtractToMethodReferenceTest.java index 0fc88338c1f4..5be4b04af3bf 100644 --- a/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/functional/ExtractToMethodReferenceTest.java +++ b/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/functional/ExtractToMethodReferenceTest.java @@ -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; + } }