From d8ea1bc2feade3b52fb68cec5437c4c2d7e315c8 Mon Sep 17 00:00:00 2001 From: Anna Kozlova Date: Mon, 11 Jan 2016 18:49:26 +0100 Subject: [PATCH] inline method to method reference: convert to lambda (IDEA-148481) --- .../inline/InlineMethodHandler.java | 5 - .../inline/InlineMethodProcessor.java | 68 ++++- .../util/LambdaRefactoringUtil.java | 263 ++++++++++++++++++ .../src/com/intellij/psi/LambdaUtil.java | 3 +- .../InlineIntoConstructorRef.java | 15 + .../InlineIntoConstructorRef.java.after | 15 + .../inlineMethod/InlineIntoMethodRef.java | 11 + .../InlineIntoMethodRef.java.after | 11 + .../SideEffectsInMethodRefQualifier.java | 13 + .../refactoring/inline/InlineMethodTest.java | 12 + .../guava/FluentIterableConversionUtil.java | 5 +- .../ReplaceMethodRefWithLambdaIntention.java | 238 +--------------- 12 files changed, 406 insertions(+), 253 deletions(-) create mode 100644 java/java-impl/src/com/intellij/refactoring/util/LambdaRefactoringUtil.java create mode 100644 java/java-tests/testData/refactoring/inlineMethod/InlineIntoConstructorRef.java create mode 100644 java/java-tests/testData/refactoring/inlineMethod/InlineIntoConstructorRef.java.after create mode 100644 java/java-tests/testData/refactoring/inlineMethod/InlineIntoMethodRef.java create mode 100644 java/java-tests/testData/refactoring/inlineMethod/InlineIntoMethodRef.java.after create mode 100644 java/java-tests/testData/refactoring/inlineMethod/SideEffectsInMethodRefQualifier.java diff --git a/java/java-impl/src/com/intellij/refactoring/inline/InlineMethodHandler.java b/java/java-impl/src/com/intellij/refactoring/inline/InlineMethodHandler.java index 4aeec6d791a5..a086d22b565e 100644 --- a/java/java-impl/src/com/intellij/refactoring/inline/InlineMethodHandler.java +++ b/java/java-impl/src/com/intellij/refactoring/inline/InlineMethodHandler.java @@ -83,11 +83,6 @@ class InlineMethodHandler extends JavaInlineActionHandler { return; } - if (reference instanceof PsiMethodReferenceExpression) { - CommonRefactoringUtil.showErrorHint(project, editor, REFACTORING_NAME + " cannot be applied to method references", REFACTORING_NAME, HelpID.INLINE_METHOD); - return; - } - if (reference != null) { final String errorMessage = InlineMethodProcessor.checkCalledInSuperOrThisExpr(methodBody, reference.getElement()); if (errorMessage != null) { diff --git a/java/java-impl/src/com/intellij/refactoring/inline/InlineMethodProcessor.java b/java/java-impl/src/com/intellij/refactoring/inline/InlineMethodProcessor.java index b2c82e29a2bb..873ff1427fce 100644 --- a/java/java-impl/src/com/intellij/refactoring/inline/InlineMethodProcessor.java +++ b/java/java-impl/src/com/intellij/refactoring/inline/InlineMethodProcessor.java @@ -57,6 +57,7 @@ import com.intellij.util.Function; import com.intellij.util.IncorrectOperationException; import com.intellij.util.containers.HashMap; import com.intellij.util.containers.MultiMap; +import com.siyeh.ig.psiutils.SideEffectChecker; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -193,7 +194,14 @@ public class InlineMethodProcessor extends BaseRefactoringProcessor { conflicts.putValue(element, "Inlined method is used in javadoc"); } if (element instanceof PsiMethodReferenceExpression) { - conflicts.putValue(element, "Inlined method is used in method reference"); + final PsiExpression qualifierExpression = ((PsiMethodReferenceExpression)element).getQualifierExpression(); + if (qualifierExpression != null) { + final List sideEffects = new ArrayList(); + SideEffectChecker.checkSideEffects(qualifierExpression, sideEffects); + if (!sideEffects.isEmpty()) { + conflicts.putValue(element, "Inlined method is used in method reference with side effects in qualifier"); + } + } } final String errorMessage = checkCalledInSuperOrThisExpr(myMethod.getBody(), element); @@ -412,14 +420,24 @@ public class InlineMethodProcessor extends BaseRefactoringProcessor { try { if (myInlineThisOnly) { if (myMethod.isConstructor() && InlineMethodHandler.isChainingConstructor(myMethod)) { - PsiCall constructorCall = RefactoringUtil.getEnclosingConstructorCall(myReference); - if (constructorCall != null) { - inlineConstructorCall(constructorCall); + if (myReference instanceof PsiMethodReferenceExpression) { + inlineMethodReference((PsiMethodReferenceExpression)myReference); + } + else { + PsiCall constructorCall = RefactoringUtil.getEnclosingConstructorCall(myReference); + if (constructorCall != null) { + inlineConstructorCall(constructorCall); + } } } else { myReference = addBracesWhenNeeded(new PsiReferenceExpression[]{(PsiReferenceExpression)myReference})[0]; - inlineMethodCall((PsiReferenceExpression)myReference); + if (myReference instanceof PsiMethodReferenceExpression) { + inlineMethodReference((PsiMethodReferenceExpression)myReference); + } + else { + inlineMethodCall((PsiReferenceExpression)myReference); + } } } else { @@ -427,7 +445,10 @@ public class InlineMethodProcessor extends BaseRefactoringProcessor { if (myMethod.isConstructor()) { for (UsageInfo usage : usages) { PsiElement element = usage.getElement(); - if (element instanceof PsiJavaCodeReferenceElement) { + if (element instanceof PsiMethodReferenceExpression) { + inlineMethodReference((PsiMethodReferenceExpression)element); + } + else if (element instanceof PsiJavaCodeReferenceElement) { PsiCall constructorCall = RefactoringUtil.getEnclosingConstructorCall((PsiJavaCodeReferenceElement)element); if (constructorCall != null) { inlineConstructorCall(constructorCall); @@ -449,7 +470,8 @@ public class InlineMethodProcessor extends BaseRefactoringProcessor { final PsiElement element = usage.getElement(); if (element instanceof PsiReferenceExpression) { refExprList.add((PsiReferenceExpression)element); - } else if (element instanceof PsiImportStaticReferenceElement) { + } + else if (element instanceof PsiImportStaticReferenceElement) { final JavaResolveResult[] resolveResults = ((PsiImportStaticReferenceElement)element).multiResolve(false); if (resolveResults.length < 2) { //no overloads available: ensure broken import are deleted and @@ -464,8 +486,12 @@ public class InlineMethodProcessor extends BaseRefactoringProcessor { PsiReferenceExpression[] refs = refExprList.toArray(new PsiReferenceExpression[refExprList.size()]); refs = addBracesWhenNeeded(refs); for (PsiReferenceExpression ref : refs) { - if (ref instanceof PsiMethodReferenceExpression) continue; - inlineMethodCall(ref); + if (ref instanceof PsiMethodReferenceExpression) { + inlineMethodReference((PsiMethodReferenceExpression)ref); + } + else { + inlineMethodCall(ref); + } } for (PsiElement psiElement : imports2Delete) { if (psiElement != null && psiElement.isValid()) { @@ -482,6 +508,25 @@ public class InlineMethodProcessor extends BaseRefactoringProcessor { } } + private void inlineMethodReference(PsiMethodReferenceExpression reference) { + final PsiLambdaExpression lambdaExpression = LambdaRefactoringUtil.convertMethodReferenceToLambda(reference, false, false); + final PsiExpression callExpression = LambdaUtil.extractSingleExpressionFromBody(lambdaExpression.getBody()); + if (callExpression instanceof PsiMethodCallExpression) { + inlineMethodCall(((PsiMethodCallExpression)callExpression).getMethodExpression()); + } + else if (callExpression instanceof PsiCall) { + inlineConstructorCall((PsiCall)callExpression); + } + else { + LOG.error("Unexpected expr: " + callExpression.getText()); + } + LambdaRefactoringUtil.simplifyToExpressionLambda(lambdaExpression); + + if (myInlineThisOnly) { + LambdaRefactoringUtil.removeSideEffectsFromLambdaBody(myEditor, lambdaExpression); + } + } + public static void inlineConstructorCall(PsiCall constructorCall) { final PsiMethod oldConstructor = constructorCall.resolveMethod(); LOG.assertTrue(oldConstructor != null); @@ -1234,12 +1279,17 @@ public class InlineMethodProcessor extends BaseRefactoringProcessor { myAddedClassInitializers = new HashMap(); for (PsiReferenceExpression ref : refs) { + if (ref instanceof PsiMethodReferenceExpression) continue; ref.putCopyableUserData(MARK_KEY, ""); } RefLoop: for (PsiReferenceExpression ref : refs) { if (!ref.isValid()) continue; + if (ref instanceof PsiMethodReferenceExpression) { + refsVector.add(ref); + continue; + } PsiElement parentStatement = RefactoringUtil.getParentStatement(ref, true); if (parentStatement != null) { diff --git a/java/java-impl/src/com/intellij/refactoring/util/LambdaRefactoringUtil.java b/java/java-impl/src/com/intellij/refactoring/util/LambdaRefactoringUtil.java new file mode 100644 index 000000000000..b3b218b41634 --- /dev/null +++ b/java/java-impl/src/com/intellij/refactoring/util/LambdaRefactoringUtil.java @@ -0,0 +1,263 @@ +/* + * Copyright 2000-2016 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.refactoring.util; + +import com.intellij.codeInspection.RedundantLambdaCodeBlockInspection; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.*; +import com.intellij.psi.codeStyle.JavaCodeStyleManager; +import com.intellij.psi.codeStyle.SuggestedNameInfo; +import com.intellij.psi.codeStyle.VariableKind; +import com.intellij.psi.util.MethodSignature; +import com.intellij.psi.util.PsiUtil; +import com.intellij.psi.util.RedundantCastUtil; +import com.intellij.refactoring.introduceField.ElementToWorkOn; +import com.intellij.refactoring.introduceVariable.IntroduceVariableHandler; +import com.intellij.util.Function; +import com.intellij.util.text.UniqueNameGenerator; +import com.siyeh.ig.psiutils.SideEffectChecker; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class LambdaRefactoringUtil { + private static final Logger LOG = Logger.getInstance("#" + LambdaRefactoringUtil.class.getName()); + + @NotNull + public static PsiLambdaExpression convertMethodReferenceToLambda(final PsiMethodReferenceExpression referenceExpression, + final boolean ignoreCast, + final boolean simplifyToExpressionLambda) { + final PsiElement resolve = referenceExpression.resolve(); + final PsiType functionalInterfaceType = referenceExpression.getFunctionalInterfaceType(); + final PsiClassType.ClassResolveResult functionalInterfaceResolveResult = PsiUtil.resolveGenericsClassInType(functionalInterfaceType); + final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(functionalInterfaceType); + LOG.assertTrue(interfaceMethod != null); + final PsiSubstitutor psiSubstitutor = LambdaUtil.getSubstitutor(interfaceMethod, functionalInterfaceResolveResult); + final MethodSignature signature = interfaceMethod.getSignature(psiSubstitutor); + final boolean isReceiver; + if (resolve instanceof PsiMethod){ + final PsiMethod method = (PsiMethod)resolve; + isReceiver = PsiMethodReferenceUtil.isResolvedBySecondSearch(referenceExpression, signature, + method.isVarArgs(), + method.hasModifierProperty(PsiModifier.STATIC), + method.getParameterList().getParametersCount()); + } + else { + isReceiver = false; + } + final PsiParameter[] psiParameters = resolve instanceof PsiMethod ? ((PsiMethod)resolve).getParameterList().getParameters() : null; + + final StringBuilder buf = new StringBuilder("("); + LOG.assertTrue(functionalInterfaceType != null); + buf.append(GenericsUtil.getVariableTypeByExpressionType(functionalInterfaceType).getCanonicalText()).append(")("); + final PsiParameterList parameterList = interfaceMethod.getParameterList(); + final PsiParameter[] parameters = parameterList.getParameters(); + + final Map map = new HashMap(); + final UniqueNameGenerator nameGenerator = new UniqueNameGenerator(); + final JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(referenceExpression.getProject()); + final String paramsString = StringUtil.join(parameters, new Function() { + @Override + public String fun(PsiParameter parameter) { + final int parameterIndex = parameterList.getParameterIndex(parameter); + String baseName; + if (isReceiver && parameterIndex == 0) { + final SuggestedNameInfo + nameInfo = codeStyleManager.suggestVariableName(VariableKind.PARAMETER, null, null, psiSubstitutor.substitute(parameter.getType())); + baseName = nameInfo.names.length > 0 ? nameInfo.names[0] : parameter.getName(); + } + else { + final String initialName; + if (psiParameters != null) { + final int idx = parameterIndex - (isReceiver ? 1 : 0); + initialName = psiParameters.length > 0 ? psiParameters[idx < psiParameters.length ? idx : psiParameters.length - 1].getName() + : parameter.getName(); + } + else { + initialName = parameter.getName(); + } + baseName = codeStyleManager.variableNameToPropertyName(initialName, VariableKind.PARAMETER); + } + + if (baseName != null) { + String parameterName = nameGenerator.generateUniqueName(codeStyleManager.suggestUniqueVariableName(baseName, referenceExpression, true)); + map.put(parameter, parameterName); + return parameterName; + } + return ""; + } + }, ", "); + buf.append(paramsString); + buf.append(") -> "); + + + final JavaResolveResult resolveResult = referenceExpression.advancedResolve(false); + final PsiElement resolveElement = resolveResult.getElement(); + final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(referenceExpression.getProject()); + if (resolveElement instanceof PsiMember) { + + buf.append("{"); + + if (!PsiType.VOID.equals(interfaceMethod.getReturnType())) { + buf.append("return "); + } + final PsiElement qualifier = referenceExpression.getQualifier(); + PsiClass containingClass = null; + if (resolveElement instanceof PsiMethod) { + containingClass = ((PsiMember)resolveElement).getContainingClass(); + LOG.assertTrue(containingClass != null); + } else if (resolveElement instanceof PsiClass) { + containingClass = (PsiClass)resolveElement; + } + + final boolean onArrayRef = + elementFactory.getArrayClass(PsiUtil.getLanguageLevel(referenceExpression)) == containingClass; + + final PsiElement referenceNameElement = referenceExpression.getReferenceNameElement(); + if (isReceiver){ + buf.append(map.get(parameters[0])).append("."); + } else { + if (!(referenceNameElement instanceof PsiKeyword)) { + if (qualifier instanceof PsiTypeElement) { + final PsiJavaCodeReferenceElement referenceElement = ((PsiTypeElement)qualifier).getInnermostComponentReferenceElement(); + LOG.assertTrue(referenceElement != null); + buf.append(referenceElement.getReferenceName()).append("."); + } + else if (qualifier != null && !(qualifier instanceof PsiThisExpression && ((PsiThisExpression)qualifier).getQualifier() == null)) { + buf.append(qualifier.getText()).append("."); + } + } + } + + //new or method name + buf.append(referenceExpression.getReferenceName()); + + if (referenceNameElement instanceof PsiKeyword) { + //class name + buf.append(" "); + if (onArrayRef) { + if (qualifier instanceof PsiTypeElement) { + final PsiType type = ((PsiTypeElement)qualifier).getType(); + int dim = type.getArrayDimensions(); + buf.append(type.getDeepComponentType().getCanonicalText()); + buf.append("["); + buf.append(map.get(parameters[0])); + buf.append("]"); + while (--dim > 0) { + buf.append("[]"); + } + } + } else { + buf.append(((PsiMember)resolveElement).getName()); + + final PsiSubstitutor substitutor = resolveResult.getSubstitutor(); + + LOG.assertTrue(containingClass != null); + if (containingClass.hasTypeParameters() && !PsiUtil.isRawSubstitutor(containingClass, substitutor)) { + buf.append("<").append(StringUtil.join(containingClass.getTypeParameters(), new Function() { + @Override + public String fun(PsiTypeParameter parameter) { + final PsiType psiType = substitutor.substitute(parameter); + LOG.assertTrue(psiType != null); + return psiType.getCanonicalText(); + } + }, ", ")).append(">"); + } + } + } + + if (!onArrayRef || isReceiver) { + //param list + buf.append("("); + boolean first = true; + for (int i = isReceiver ? 1 : 0; i < parameters.length; i++) { + PsiParameter parameter = parameters[i]; + if (!first) { + buf.append(", "); + } else { + first = false; + } + buf.append(map.get(parameter)); + } + buf.append(")"); + } + + buf.append(";}"); + } + + + final PsiTypeCastExpression typeCastExpression = (PsiTypeCastExpression)referenceExpression.replace(elementFactory.createExpressionFromText(buf.toString(), referenceExpression)); + PsiLambdaExpression lambdaExpression = (PsiLambdaExpression)typeCastExpression.getOperand(); + LOG.assertTrue(lambdaExpression != null, buf.toString()); + if (RedundantCastUtil.isCastRedundant(typeCastExpression) || ignoreCast) { + final PsiExpression operand = typeCastExpression.getOperand(); + LOG.assertTrue(operand != null); + lambdaExpression = (PsiLambdaExpression)typeCastExpression.replace(operand); + } + + if (simplifyToExpressionLambda) { + simplifyToExpressionLambda(lambdaExpression); + } + + return lambdaExpression; + } + + public static void simplifyToExpressionLambda(@NotNull final PsiLambdaExpression lambdaExpression) { + final PsiElement body = lambdaExpression.getBody(); + final PsiExpression singleExpression = RedundantLambdaCodeBlockInspection.isCodeBlockRedundant(lambdaExpression, body); + if (singleExpression != null) { + body.replace(singleExpression); + } + } + + /** + * Works for expression lambdas/one statement code block lambdas to ensures equivalent method ref -> lambda transformation. + */ + public static void removeSideEffectsFromLambdaBody(Editor editor, PsiLambdaExpression lambdaExpression) { + if (lambdaExpression != null && lambdaExpression.isValid()) { + final PsiElement body = lambdaExpression.getBody(); + PsiExpression methodCall = LambdaUtil.extractSingleExpressionFromBody(body); + PsiExpression qualifierExpression = null; + if (methodCall instanceof PsiMethodCallExpression) { + qualifierExpression = ((PsiMethodCallExpression)methodCall).getMethodExpression().getQualifierExpression(); + } + else if (methodCall instanceof PsiNewExpression) { + qualifierExpression = ((PsiNewExpression)methodCall).getQualifier(); + } + + if (qualifierExpression != null) { + final List sideEffects = new ArrayList(); + SideEffectChecker.checkSideEffects(qualifierExpression, sideEffects); + if (!sideEffects.isEmpty()) { + if (ApplicationManager.getApplication().isUnitTestMode() || + Messages.showYesNoDialog(lambdaExpression.getProject(), "There are possible side effects found in method reference qualifier." + + "\nIntroduce local variable?", "Side Effects Detected", Messages.getQuestionIcon()) == Messages.YES) { + //ensure introduced before lambda + qualifierExpression.putUserData(ElementToWorkOn.PARENT, lambdaExpression); + new IntroduceVariableHandler().invoke(qualifierExpression.getProject(), editor, qualifierExpression); + } + } + } + } + } +} diff --git a/java/java-psi-api/src/com/intellij/psi/LambdaUtil.java b/java/java-psi-api/src/com/intellij/psi/LambdaUtil.java index 7e0786281066..5872b4f76d96 100644 --- a/java/java-psi-api/src/com/intellij/psi/LambdaUtil.java +++ b/java/java-psi-api/src/com/intellij/psi/LambdaUtil.java @@ -520,7 +520,8 @@ public class LambdaUtil { } else if (statements[0] instanceof PsiExpressionStatement) { expression = ((PsiExpressionStatement)statements[0]).getExpression(); - } else if (statements[0] instanceof PsiBlockStatement) { + } + else if (statements[0] instanceof PsiBlockStatement) { return extractSingleExpressionFromBody(((PsiBlockStatement)statements[0]).getCodeBlock()); } } diff --git a/java/java-tests/testData/refactoring/inlineMethod/InlineIntoConstructorRef.java b/java/java-tests/testData/refactoring/inlineMethod/InlineIntoConstructorRef.java new file mode 100644 index 000000000000..c522b20a52f2 --- /dev/null +++ b/java/java-tests/testData/refactoring/inlineMethod/InlineIntoConstructorRef.java @@ -0,0 +1,15 @@ +import java.util.function.Supplier; + +public class Test { + + public Test() { + this(0); + } + + public Test(int i) {} + + + { + Supplier sup = Test::new; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/refactoring/inlineMethod/InlineIntoConstructorRef.java.after b/java/java-tests/testData/refactoring/inlineMethod/InlineIntoConstructorRef.java.after new file mode 100644 index 000000000000..34fbae24db7b --- /dev/null +++ b/java/java-tests/testData/refactoring/inlineMethod/InlineIntoConstructorRef.java.after @@ -0,0 +1,15 @@ +import java.util.function.Supplier; + +public class Test { + + public Test() { + this(0); + } + + public Test(int i) {} + + + { + Supplier sup = () -> new Test(0); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/refactoring/inlineMethod/InlineIntoMethodRef.java b/java/java-tests/testData/refactoring/inlineMethod/InlineIntoMethodRef.java new file mode 100644 index 000000000000..762b0f595b7b --- /dev/null +++ b/java/java-tests/testData/refactoring/inlineMethod/InlineIntoMethodRef.java @@ -0,0 +1,11 @@ +import java.util.function.Supplier; + +class Test { + { + Supplier sup = this::get; + } + + private String get() { + return null; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/refactoring/inlineMethod/InlineIntoMethodRef.java.after b/java/java-tests/testData/refactoring/inlineMethod/InlineIntoMethodRef.java.after new file mode 100644 index 000000000000..2a7e11c5fe08 --- /dev/null +++ b/java/java-tests/testData/refactoring/inlineMethod/InlineIntoMethodRef.java.after @@ -0,0 +1,11 @@ +import java.util.function.Supplier; + +class Test { + { + Supplier sup = () -> null; + } + + private String get() { + return null; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/refactoring/inlineMethod/SideEffectsInMethodRefQualifier.java b/java/java-tests/testData/refactoring/inlineMethod/SideEffectsInMethodRefQualifier.java new file mode 100644 index 000000000000..a138b39b60a1 --- /dev/null +++ b/java/java-tests/testData/refactoring/inlineMethod/SideEffectsInMethodRefQualifier.java @@ -0,0 +1,13 @@ + +import java.util.function.Supplier; + +public class Test { + + { + Supplier sup = new Test()::get; + } + + private String get() { + return null; + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/refactoring/inline/InlineMethodTest.java b/java/java-tests/testSrc/com/intellij/refactoring/inline/InlineMethodTest.java index 5b1e5a733c7b..ba6395fa993b 100644 --- a/java/java-tests/testSrc/com/intellij/refactoring/inline/InlineMethodTest.java +++ b/java/java-tests/testSrc/com/intellij/refactoring/inline/InlineMethodTest.java @@ -292,6 +292,18 @@ public class InlineMethodTest extends LightRefactoringTestCase { doTestInlineThisOnly(); } + public void testInlineIntoMethodRef() throws Exception { + doTestInlineThisOnly(); + } + + public void testInlineIntoConstructorRef() throws Exception { + doTestInlineThisOnly(); + } + + public void testSideEffectsInMethodRefQualifier() throws Exception { + doTestConflict("Inlined method is used in method reference with side effects in qualifier"); + } + private void doTestInlineThisOnly() { @NonNls String fileName = "/refactoring/inlineMethod/" + getTestName(false) + ".java"; configureByFile(fileName); diff --git a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/FluentIterableConversionUtil.java b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/FluentIterableConversionUtil.java index d986896c3b8d..3987df0e2f26 100644 --- a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/FluentIterableConversionUtil.java +++ b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/FluentIterableConversionUtil.java @@ -29,12 +29,11 @@ import com.intellij.psi.util.PsiUtil; import com.intellij.psi.util.TypeConversionUtil; import com.intellij.refactoring.typeMigration.TypeConversionDescriptor; import com.intellij.refactoring.typeMigration.TypeEvaluator; +import com.intellij.refactoring.util.LambdaRefactoringUtil; import com.intellij.util.IncorrectOperationException; import com.intellij.util.SmartList; import com.intellij.util.text.UniqueNameGenerator; import com.siyeh.ig.psiutils.ParenthesesUtils; -import com.siyeh.ipp.types.ReplaceMethodRefWithLambdaIntention; -import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -157,7 +156,7 @@ public class FluentIterableConversionUtil { } if (argument instanceof PsiMethodReferenceExpression) { - argument = ReplaceMethodRefWithLambdaIntention.convertMethodReferenceToLambda((PsiMethodReferenceExpression)argument, true); + argument = LambdaRefactoringUtil.convertMethodReferenceToLambda((PsiMethodReferenceExpression)argument, true, true); } if (argument instanceof PsiLambdaExpression) { List> iterableReturnValues = new SmartList>(); diff --git a/plugins/IntentionPowerPak/src/com/siyeh/ipp/types/ReplaceMethodRefWithLambdaIntention.java b/plugins/IntentionPowerPak/src/com/siyeh/ipp/types/ReplaceMethodRefWithLambdaIntention.java index ab167af90564..7a81831a0c9f 100644 --- a/plugins/IntentionPowerPak/src/com/siyeh/ipp/types/ReplaceMethodRefWithLambdaIntention.java +++ b/plugins/IntentionPowerPak/src/com/siyeh/ipp/types/ReplaceMethodRefWithLambdaIntention.java @@ -15,34 +15,17 @@ */ package com.siyeh.ipp.types; -import com.intellij.codeInspection.RedundantLambdaCodeBlockInspection; import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; -import com.intellij.psi.codeStyle.JavaCodeStyleManager; -import com.intellij.psi.codeStyle.SuggestedNameInfo; -import com.intellij.psi.codeStyle.VariableKind; -import com.intellij.psi.util.MethodSignature; import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.psi.util.PsiUtil; -import com.intellij.psi.util.RedundantCastUtil; -import com.intellij.refactoring.introduceField.ElementToWorkOn; -import com.intellij.refactoring.introduceVariable.IntroduceVariableHandler; -import com.intellij.util.Function; -import com.intellij.util.text.UniqueNameGenerator; -import com.siyeh.ig.psiutils.SideEffectChecker; +import com.intellij.refactoring.util.LambdaRefactoringUtil; import com.siyeh.ipp.base.Intention; import com.siyeh.ipp.base.PsiElementPredicate; import org.jetbrains.annotations.NotNull; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - public class ReplaceMethodRefWithLambdaIntention extends Intention { private static final Logger LOG = Logger.getInstance("#" + ReplaceMethodRefWithLambdaIntention.class.getName()); @@ -59,10 +42,10 @@ public class ReplaceMethodRefWithLambdaIntention extends Intention { protected void processIntention(final Editor editor, @NotNull PsiElement element) { final PsiMethodReferenceExpression referenceExpression = PsiTreeUtil.getParentOfType(element, PsiMethodReferenceExpression.class); LOG.assertTrue(referenceExpression != null); - final PsiLambdaExpression expr = convertMethodReferenceToLambda(referenceExpression, false); + final PsiLambdaExpression expr = LambdaRefactoringUtil.convertMethodReferenceToLambda(referenceExpression, false, true); final Runnable runnable = new Runnable() { public void run() { - introduceQualifierAsLocalVariable(editor, expr); + LambdaRefactoringUtil.removeSideEffectsFromLambdaBody(editor, expr); } }; final Application application = ApplicationManager.getApplication(); @@ -73,221 +56,6 @@ public class ReplaceMethodRefWithLambdaIntention extends Intention { } } - public static PsiLambdaExpression convertMethodReferenceToLambda(final PsiMethodReferenceExpression referenceExpression, final boolean ignoreCast) { - final PsiElement resolve = referenceExpression.resolve(); - final PsiType functionalInterfaceType = referenceExpression.getFunctionalInterfaceType(); - final PsiClassType.ClassResolveResult functionalInterfaceResolveResult = PsiUtil.resolveGenericsClassInType(functionalInterfaceType); - final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(functionalInterfaceType); - LOG.assertTrue(interfaceMethod != null); - final PsiSubstitutor psiSubstitutor = LambdaUtil.getSubstitutor(interfaceMethod, functionalInterfaceResolveResult); - final MethodSignature signature = interfaceMethod.getSignature(psiSubstitutor); - final boolean isReceiver; - if (resolve instanceof PsiMethod){ - final PsiMethod method = (PsiMethod)resolve; - isReceiver = PsiMethodReferenceUtil.isResolvedBySecondSearch(referenceExpression, signature, - method.isVarArgs(), - method.hasModifierProperty(PsiModifier.STATIC), - method.getParameterList().getParametersCount()); - } - else { - isReceiver = false; - } - final PsiParameter[] psiParameters = resolve instanceof PsiMethod ? ((PsiMethod)resolve).getParameterList().getParameters() : null; - - final StringBuilder buf = new StringBuilder("("); - LOG.assertTrue(functionalInterfaceType != null); - buf.append(GenericsUtil.getVariableTypeByExpressionType(functionalInterfaceType).getCanonicalText()).append(")("); - final PsiParameterList parameterList = interfaceMethod.getParameterList(); - final PsiParameter[] parameters = parameterList.getParameters(); - - final Map map = new HashMap(); - final UniqueNameGenerator nameGenerator = new UniqueNameGenerator(); - final JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(referenceExpression.getProject()); - final String paramsString = StringUtil.join(parameters, new Function() { - @Override - public String fun(PsiParameter parameter) { - final int parameterIndex = parameterList.getParameterIndex(parameter); - String baseName; - if (isReceiver && parameterIndex == 0) { - final SuggestedNameInfo - nameInfo = codeStyleManager.suggestVariableName(VariableKind.PARAMETER, null, null, psiSubstitutor.substitute(parameter.getType())); - baseName = nameInfo.names.length > 0 ? nameInfo.names[0] : parameter.getName(); - } - else { - final String initialName; - if (psiParameters != null) { - final int idx = parameterIndex - (isReceiver ? 1 : 0); - initialName = psiParameters.length > 0 ? psiParameters[idx < psiParameters.length ? idx : psiParameters.length - 1].getName() - : parameter.getName(); - } - else { - initialName = parameter.getName(); - } - baseName = codeStyleManager.variableNameToPropertyName(initialName, VariableKind.PARAMETER); - } - - if (baseName != null) { - String parameterName = nameGenerator.generateUniqueName(codeStyleManager.suggestUniqueVariableName(baseName, referenceExpression, true)); - map.put(parameter, parameterName); - return parameterName; - } - return ""; - } - }, ", "); - buf.append(paramsString); - buf.append(") -> "); - - - final JavaResolveResult resolveResult = referenceExpression.advancedResolve(false); - final PsiElement resolveElement = resolveResult.getElement(); - final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(referenceExpression.getProject()); - if (resolveElement instanceof PsiMember) { - - buf.append("{"); - - if (!PsiType.VOID.equals(interfaceMethod.getReturnType())) { - buf.append("return "); - } - final PsiElement qualifier = referenceExpression.getQualifier(); - PsiClass containingClass = null; - if (resolveElement instanceof PsiMethod) { - containingClass = ((PsiMember)resolveElement).getContainingClass(); - LOG.assertTrue(containingClass != null); - } else if (resolveElement instanceof PsiClass) { - containingClass = (PsiClass)resolveElement; - } - - final boolean onArrayRef = - elementFactory.getArrayClass(PsiUtil.getLanguageLevel(referenceExpression)) == containingClass; - - final PsiElement referenceNameElement = referenceExpression.getReferenceNameElement(); - if (isReceiver){ - buf.append(map.get(parameters[0])).append("."); - } else { - if (!(referenceNameElement instanceof PsiKeyword)) { - if (qualifier instanceof PsiTypeElement) { - final PsiJavaCodeReferenceElement referenceElement = ((PsiTypeElement)qualifier).getInnermostComponentReferenceElement(); - LOG.assertTrue(referenceElement != null); - buf.append(referenceElement.getReferenceName()).append("."); - } - else if (qualifier != null && !(qualifier instanceof PsiThisExpression && ((PsiThisExpression)qualifier).getQualifier() == null)) { - buf.append(qualifier.getText()).append("."); - } - } - } - - //new or method name - buf.append(referenceExpression.getReferenceName()); - - if (referenceNameElement instanceof PsiKeyword) { - //class name - buf.append(" "); - if (onArrayRef) { - if (qualifier instanceof PsiTypeElement) { - final PsiType type = ((PsiTypeElement)qualifier).getType(); - int dim = type.getArrayDimensions(); - buf.append(type.getDeepComponentType().getCanonicalText()); - buf.append("["); - buf.append(map.get(parameters[0])); - buf.append("]"); - while (--dim > 0) { - buf.append("[]"); - } - } - } else { - buf.append(((PsiMember)resolveElement).getName()); - - final PsiSubstitutor substitutor = resolveResult.getSubstitutor(); - - LOG.assertTrue(containingClass != null); - if (containingClass.hasTypeParameters() && !PsiUtil.isRawSubstitutor(containingClass, substitutor)) { - buf.append("<").append(StringUtil.join(containingClass.getTypeParameters(), new Function() { - @Override - public String fun(PsiTypeParameter parameter) { - final PsiType psiType = substitutor.substitute(parameter); - LOG.assertTrue(psiType != null); - return psiType.getCanonicalText(); - } - }, ", ")).append(">"); - } - } - } - - if (!onArrayRef || isReceiver) { - //param list - buf.append("("); - boolean first = true; - for (int i = isReceiver ? 1 : 0; i < parameters.length; i++) { - PsiParameter parameter = parameters[i]; - if (!first) { - buf.append(", "); - } else { - first = false; - } - buf.append(map.get(parameter)); - } - buf.append(")"); - } - - buf.append(";}"); - } - - - final PsiTypeCastExpression typeCastExpression = (PsiTypeCastExpression)referenceExpression.replace(elementFactory.createExpressionFromText(buf.toString(), referenceExpression)); - PsiLambdaExpression lambdaExpression = (PsiLambdaExpression)typeCastExpression.getOperand(); - LOG.assertTrue(lambdaExpression != null, buf.toString()); - if (RedundantCastUtil.isCastRedundant(typeCastExpression) || ignoreCast) { - final PsiExpression operand = typeCastExpression.getOperand(); - LOG.assertTrue(operand != null); - lambdaExpression = (PsiLambdaExpression)typeCastExpression.replace(operand); - } - - final PsiElement body = lambdaExpression.getBody(); - final PsiExpression singleExpression = RedundantLambdaCodeBlockInspection.isCodeBlockRedundant(lambdaExpression, body); - if (singleExpression != null) { - body.replace(singleExpression); - } - - return lambdaExpression; - } - - private static void introduceQualifierAsLocalVariable(Editor editor, PsiLambdaExpression lambdaExpression) { - if (lambdaExpression != null && lambdaExpression.isValid()) { - final PsiElement body = lambdaExpression.getBody(); - PsiExpression methodCall = null; - if (body instanceof PsiCodeBlock) { - final PsiStatement[] statements = ((PsiCodeBlock)body).getStatements(); - LOG.assertTrue(statements.length == 1); - final PsiStatement statement = statements[0]; - if (statement instanceof PsiReturnStatement) { - methodCall = ((PsiReturnStatement)statement).getReturnValue(); - } - else if (statement instanceof PsiExpressionStatement) { - methodCall = ((PsiExpressionStatement)statement).getExpression(); - } - } else { - methodCall = (PsiExpression)body; - } - PsiExpression qualifierExpression = null; - if (methodCall instanceof PsiMethodCallExpression) { - qualifierExpression = ((PsiMethodCallExpression)methodCall).getMethodExpression().getQualifierExpression(); - } - else if (methodCall instanceof PsiNewExpression) { - qualifierExpression = ((PsiNewExpression)methodCall).getQualifier(); - } - - if (qualifierExpression != null) { - final List sideEffects = new ArrayList(); - SideEffectChecker.checkSideEffects(qualifierExpression, sideEffects); - if (!sideEffects.isEmpty()) { - //ensure introduced before lambda - qualifierExpression.putUserData(ElementToWorkOn.PARENT, lambdaExpression); - new IntroduceVariableHandler().invoke(qualifierExpression.getProject(), editor, qualifierExpression); - } - } - } - } - private static class MethodRefPredicate implements PsiElementPredicate { @Override public boolean satisfiedBy(PsiElement element) {