IDEA-160898 Inspection to convert several commonly-used lambdas to method references

This commit is contained in:
Tagir Valeev
2016-09-08 12:25:37 +07:00
parent f0c23baa29
commit 8015562c2d
25 changed files with 232 additions and 47 deletions
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2012 JetBrains s.r.o.
* 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.
@@ -83,9 +83,10 @@ public class AnonymousCanBeMethodReferenceInspection extends BaseJavaBatchLocalI
if (AnonymousCanBeLambdaInspection.canBeConvertedToLambda(aClass, true, reportNotAnnotatedInterfaces, Collections.emptySet())) {
final PsiMethod method = aClass.getMethods()[0];
final PsiCodeBlock body = method.getBody();
final PsiCallExpression callExpression =
final PsiExpression methodRefCandidate =
LambdaCanBeMethodReferenceInspection.canBeMethodReferenceProblem(body, method.getParameterList().getParameters(), aClass.getBaseClassType(), aClass.getParent());
if (callExpression != null) {
if (methodRefCandidate instanceof PsiCallExpression) {
final PsiCallExpression callExpression = (PsiCallExpression)methodRefCandidate;
final PsiMethod resolveMethod = callExpression.resolveMethod();
if (resolveMethod != method &&
!AnonymousCanBeLambdaInspection.functionalInterfaceMethodReferenced(resolveMethod, aClass, callExpression)) {
@@ -17,15 +17,18 @@ package com.intellij.codeInspection;
import com.intellij.codeInsight.FileModificationService;
import com.intellij.codeInsight.daemon.GroupNames;
import com.intellij.codeInspection.ui.MultipleCheckboxOptionsPanel;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Conditions;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.impl.source.resolve.graphInference.FunctionalInterfaceParameterizationUtil;
import com.intellij.psi.infos.MethodCandidateInfo;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.*;
import com.intellij.refactoring.util.RefactoringChangeUtil;
import com.intellij.util.ArrayUtil;
@@ -35,6 +38,7 @@ import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.Collection;
import java.util.Map;
@@ -42,8 +46,30 @@ import java.util.Map;
* User: anna
*/
public class LambdaCanBeMethodReferenceInspection extends BaseJavaBatchLocalInspectionTool {
private static final String SHORT_NAME = "Convert2MethodRef";
public static final Logger LOG = Logger.getInstance("#" + LambdaCanBeMethodReferenceInspection.class.getName());
public boolean REPLACE_INSTANCEOF;
public boolean REPLACE_CAST;
public boolean REPLACE_NULL_CHECK = true;
@Nullable
@Override
public JComponent createOptionsPanel() {
final MultipleCheckboxOptionsPanel panel = new MultipleCheckboxOptionsPanel(this);
panel.addCheckbox("Replace instanceof", "REPLACE_INSTANCEOF");
panel.addCheckbox("Replace cast", "REPLACE_CAST");
panel.addCheckbox("Replace null-check", "REPLACE_NULL_CHECK");
return panel;
}
@Nullable
static LambdaCanBeMethodReferenceInspection getInstance(@NotNull PsiElement element) {
final InspectionProfile inspectionProfile = InspectionProjectProfileManager.getInstance(element.getProject()).getCurrentProfile();
return (LambdaCanBeMethodReferenceInspection)inspectionProfile.getUnwrappedTool(SHORT_NAME, element);
}
@Nls
@NotNull
@Override
@@ -80,9 +106,9 @@ public class LambdaCanBeMethodReferenceInspection extends BaseJavaBatchLocalInsp
final PsiElement body = expression.getBody();
final PsiType functionalInterfaceType = expression.getFunctionalInterfaceType();
if (functionalInterfaceType != null) {
final PsiCallExpression callExpression = canBeMethodReferenceProblem(body, expression.getParameterList().getParameters(), functionalInterfaceType);
if (callExpression != null) {
holder.registerProblem(callExpression,
final PsiExpression candidate = canBeMethodReferenceProblem(body, expression.getParameterList().getParameters(), functionalInterfaceType);
if (candidate != null) {
holder.registerProblem(candidate,
"Can be replaced with method reference",
ProblemHighlightType.GENERIC_ERROR_OR_WARNING, new ReplaceWithMethodRefFix());
}
@@ -97,34 +123,36 @@ public class LambdaCanBeMethodReferenceInspection extends BaseJavaBatchLocalInsp
final PsiVariable[] parameters,
final PsiType functionalInterfaceType,
@Nullable PsiElement context) {
final PsiCallExpression toConvertCall = canBeMethodReferenceProblem(body, parameters, functionalInterfaceType, context);
return createMethodReferenceText(toConvertCall, functionalInterfaceType, parameters);
final PsiExpression candidate = canBeMethodReferenceProblem(body, parameters, functionalInterfaceType, context);
return createMethodReferenceText(candidate, functionalInterfaceType, parameters);
}
@Nullable
public static PsiCallExpression canBeMethodReferenceProblem(@Nullable final PsiElement body,
public static PsiExpression canBeMethodReferenceProblem(@Nullable final PsiElement body,
final PsiVariable[] parameters,
final PsiType functionalInterfaceType) {
return canBeMethodReferenceProblem(body, parameters, functionalInterfaceType, null);
}
@Nullable
public static PsiCallExpression canBeMethodReferenceProblem(@Nullable final PsiElement body,
final PsiVariable[] parameters,
PsiType functionalInterfaceType,
@Nullable PsiElement context) {
final PsiCallExpression callExpression = extractMethodCallFromBlock(body);
if (callExpression instanceof PsiNewExpression) {
final PsiNewExpression newExpression = (PsiNewExpression)callExpression;
public static PsiExpression canBeMethodReferenceProblem(@Nullable final PsiElement body,
final PsiVariable[] parameters,
PsiType functionalInterfaceType,
@Nullable PsiElement context) {
final PsiExpression methodRefCandidate = extractMethodReferenceCandidateExpression(body);
if (methodRefCandidate instanceof PsiNewExpression) {
final PsiNewExpression newExpression = (PsiNewExpression)methodRefCandidate;
if (newExpression.getAnonymousClass() != null || newExpression.getArrayInitializer() != null) {
return null;
}
}
final String methodReferenceText = createMethodReferenceText(callExpression, functionalInterfaceType, parameters);
final String methodReferenceText = createMethodReferenceText(methodRefCandidate, functionalInterfaceType, parameters);
if (methodReferenceText != null) {
LOG.assertTrue(callExpression != null);
LOG.assertTrue(methodRefCandidate != null);
if (!(methodRefCandidate instanceof PsiCallExpression)) return methodRefCandidate;
PsiCallExpression callExpression = (PsiCallExpression)methodRefCandidate;
final PsiMethod method = callExpression.resolveMethod();
if (method != null) {
if (!isSimpleCall(parameters, callExpression, method)) {
@@ -244,16 +272,51 @@ public class LambdaCanBeMethodReferenceInspection extends BaseJavaBatchLocalInsp
return expression instanceof PsiReferenceExpression && ((PsiReferenceExpression)expression).resolve() == parameter;
}
public static PsiCallExpression extractMethodCallFromBlock(PsiElement body) {
static boolean isNull(PsiElement element) {
return element instanceof PsiLiteralExpression && ((PsiLiteralExpression)element).getValue() == null;
}
public static PsiExpression extractMethodReferenceCandidateExpression(PsiElement body) {
final PsiExpression expression = LambdaUtil.extractSingleExpressionFromBody(body);
if (expression == null) {
return null;
}
if (expression instanceof PsiNewExpression) {
if (checkQualifier(((PsiNewExpression)expression).getQualifier())) {
return (PsiCallExpression)expression;
return expression;
}
}
if (expression instanceof PsiMethodCallExpression) {
else if (expression instanceof PsiMethodCallExpression) {
if (checkQualifier(((PsiMethodCallExpression)expression).getMethodExpression().getQualifier())) {
return (PsiCallExpression)expression;
return expression;
}
}
LambdaCanBeMethodReferenceInspection instance = getInstance(expression);
if(instance != null) {
if (expression instanceof PsiInstanceOfExpression && instance.REPLACE_INSTANCEOF) {
return expression;
}
else if (expression instanceof PsiBinaryExpression && instance.REPLACE_NULL_CHECK) {
IElementType tokenType = ((PsiBinaryExpression)expression).getOperationTokenType();
if (JavaTokenType.EQEQ.equals(tokenType) || JavaTokenType.NE.equals(tokenType)) {
if (isNull(((PsiBinaryExpression)expression).getLOperand()) ||
isNull(((PsiBinaryExpression)expression).getROperand())) {
return expression;
}
}
}
else if (expression instanceof PsiTypeCastExpression && instance.REPLACE_CAST) {
PsiTypeElement typeElement = ((PsiTypeCastExpression)expression).getCastType();
if (typeElement != null) {
PsiJavaCodeReferenceElement refs = typeElement.getInnermostComponentReferenceElement();
if (refs != null && refs.getParameterList() != null && refs.getParameterList().getTypeParameterElements().length != 0) {
return null;
}
PsiType type = typeElement.getType();
if (type instanceof PsiPrimitiveType)
return null;
return expression;
}
}
}
return null;
@@ -313,6 +376,12 @@ public class LambdaCanBeMethodReferenceInspection extends BaseJavaBatchLocalInsp
TypeConversionUtil.areTypesConvertible(nonReceiverCandidateParams[0].getType(), receiverType);
}
private static boolean isSoleParameter(@NotNull PsiVariable[] parameters, @Nullable PsiExpression expression) {
return parameters.length == 1 &&
expression instanceof PsiReferenceExpression &&
parameters[0] == ((PsiReferenceExpression)expression).resolve();
}
@Nullable
public static String createMethodReferenceText(final PsiElement element,
final PsiType functionalInterfaceType,
@@ -338,6 +407,40 @@ public class LambdaCanBeMethodReferenceInspection extends BaseJavaBatchLocalInsp
return qualifierByNew + ((PsiNewExpression)element).getTypeArgumentList().getText() + "::new";
}
}
else if (element instanceof PsiInstanceOfExpression) {
if(isSoleParameter(parameters, ((PsiInstanceOfExpression)element).getOperand())) {
PsiTypeElement type = ((PsiInstanceOfExpression)element).getCheckType();
if(type != null) {
return type.getText() + ".class::isInstance";
}
}
}
else if (element instanceof PsiBinaryExpression) {
PsiBinaryExpression nullCheck = (PsiBinaryExpression)element;
PsiExpression operand;
if (isNull(nullCheck.getROperand())) {
operand = nullCheck.getLOperand();
} else if(isNull(nullCheck.getLOperand())) {
operand = nullCheck.getROperand();
} else return null;
if(isSoleParameter(parameters, operand)) {
IElementType tokenType = nullCheck.getOperationTokenType();
if(JavaTokenType.EQEQ.equals(tokenType)) {
return "java.util.Objects::isNull";
} else if(JavaTokenType.NE.equals(tokenType)) {
return "java.util.Objects::nonNull";
}
}
}
else if (element instanceof PsiTypeCastExpression) {
PsiTypeCastExpression castExpression = (PsiTypeCastExpression)element;
if(isSoleParameter(parameters, castExpression.getOperand())) {
PsiTypeElement type = castExpression.getCastType();
if (type != null) {
return type.getText() + ".class::cast";
}
}
}
return null;
}
@@ -398,14 +398,14 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
private static boolean isTrivial(PsiStatement body, PsiParameter parameter) {
//method reference
final PsiCallExpression callExpression = LambdaCanBeMethodReferenceInspection
final PsiExpression candidate = LambdaCanBeMethodReferenceInspection
.canBeMethodReferenceProblem(body instanceof PsiBlockStatement ? ((PsiBlockStatement)body).getCodeBlock() : body,
new PsiParameter[]{parameter},
createDefaultConsumerType(parameter.getProject(), parameter));
if (callExpression == null) {
if (!(candidate instanceof PsiCallExpression)) {
return true;
}
final PsiMethod method = callExpression.resolveMethod();
final PsiMethod method = ((PsiCallExpression)candidate).resolveMethod();
return method != null && isThrowsCompatible(method);
}
@@ -598,8 +598,8 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
}
private static String createForEachFunctionalExpressionText(Project project, PsiElement block, PsiVariable variable) {
final PsiCallExpression callExpression = LambdaCanBeMethodReferenceInspection.extractMethodCallFromBlock(block);
if (callExpression != null) {
final PsiExpression methodRefCandidate = LambdaCanBeMethodReferenceInspection.extractMethodReferenceCandidateExpression(block);
if (methodRefCandidate != null) {
final PsiClassType functionalType = createDefaultConsumerType(project, variable);
final PsiVariable[] parameters = {variable};
String methodReferenceText =
@@ -0,0 +1,8 @@
// "Replace lambda with method reference" "true"
import java.util.function.Function;
class Bar extends Random {
public void test(Object obj) {
Function<Object, String> fn = String.class::cast;
}
}
@@ -0,0 +1,8 @@
// "Replace lambda with method reference" "true"
import java.util.function.Predicate;
class Bar extends Random {
public void test(Object obj) {
Predicate<Object> pred = String.class::isInstance;
}
}
@@ -0,0 +1,9 @@
// "Replace lambda with method reference" "true"
import java.util.Objects;
import java.util.function.Predicate;
class Bar extends Random {
public void test(Object obj) {
Predicate<Object> pred = Objects::nonNull;
}
}
@@ -0,0 +1,8 @@
// "Replace lambda with method reference" "true"
import java.util.function.Function;
class Bar extends Random {
public void test(Object obj) {
Function<Object, String> fn = s -> (String)<caret>s;
}
}
@@ -0,0 +1,8 @@
// "Replace lambda with method reference" "false"
import java.util.function.Function;
class Bar extends Random {
public void test(Object obj) {
Function<Object, List<String>> fn = s -> (List<String>)<caret>s;
}
}
@@ -0,0 +1,8 @@
// "Replace lambda with method reference" "false"
import java.util.function.ToIntFunction;
class Bar extends Random {
public void test(Object obj) {
ToIntFunction<Object> fn = s -> (int)<caret>s;
}
}
@@ -0,0 +1,8 @@
// "Replace lambda with method reference" "true"
import java.util.function.Predicate;
class Bar extends Random {
public void test(Object obj) {
Predicate<Object> pred = s -> s instanceof <caret>String;
}
}
@@ -0,0 +1,8 @@
// "Replace lambda with method reference" "true"
import java.util.function.Predicate;
class Bar extends Random {
public void test(Object obj) {
Predicate<Object> pred = s -> s != <caret>null;
}
}
@@ -0,0 +1,8 @@
// "Replace lambda with method reference" "false"
import java.util.function.Predicate;
class Bar extends Random {
public void test(Object obj) {
Predicate<Object> pred = s -> obj != <caret>null;
}
}
@@ -1,11 +1,12 @@
// "Replace with forEach" "true"
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
class Sample {
List<String> foo = new ArrayList<>();
String foo(){
((List<String>) foo).stream().filter(s -> s == null).forEach(System.out::println);
((List<String>) foo).stream().filter(Objects::isNull).forEach(System.out::println);
return null;
}
}
@@ -10,6 +10,6 @@ public class Collect {
}
void collectNames(List<Person> persons){
List<String> names = persons.stream().filter(person -> person != null).map(Person::getName).collect(Collectors.toList());
List<String> names = persons.stream().filter(Objects::nonNull).map(Person::getName).collect(Collectors.toList());
}
}
@@ -1,10 +1,11 @@
// "Replace with count()" "true"
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Set;
public class Main {
public void test(List<Set<String>> nested) {
int count = (int) nested.stream().filter(element -> element != null).flatMap(Collection::stream).filter(str -> str.startsWith("xyz")).count();
int count = (int) nested.stream().filter(Objects::nonNull).flatMap(Collection::stream).filter(str -> str.startsWith("xyz")).count();
}
}
@@ -1,12 +1,9 @@
// "Replace with collect" "true"
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.*;
import java.util.stream.Collectors;
public class Main {
public void test(List<Set<String>> nested) {
List<String> result = nested.stream().filter(element -> element != null).flatMap(Collection::stream).filter(str -> str.startsWith("xyz")).map(String::trim).collect(Collectors.toList());
List<String> result = nested.stream().filter(Objects::nonNull).flatMap(Collection::stream).filter(str -> str.startsWith("xyz")).map(String::trim).collect(Collectors.toList());
}
}
@@ -1,10 +1,11 @@
// "Replace with forEach" "true"
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
class Sample {
List<String> foo = new ArrayList<>();
{
foo.stream().filter(s -> s != null).forEach(System.out::println);
foo.stream().filter(Objects::nonNull).forEach(System.out::println);
}
}
@@ -2,11 +2,12 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
public class Main {
public List<String> test(String[][] arr) {
List<String> result = Arrays.stream(arr).filter(subArr -> subArr != null).flatMap(Arrays::stream).collect(Collectors.toList());
List<String> result = Arrays.stream(arr).filter(Objects::nonNull).flatMap(Arrays::stream).collect(Collectors.toList());
return result;
}
}
@@ -2,11 +2,12 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
public class Main {
public List<Integer> test(int[][] arr) {
List<Integer> result = new ArrayList<>();
Arrays.stream(arr).filter(subArr -> subArr != null).forEach(subArr -> {
Arrays.stream(arr).filter(Objects::nonNull).forEach(subArr -> {
for (int str : subArr) {
result.add(str);
}
@@ -1,11 +1,12 @@
// "Replace with forEach" "true"
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
class Sample {
List<String> foo = new ArrayList<>();
String foo(){
foo.stream().filter(s -> s == null).forEach(s -> {
foo.stream().filter(Objects::isNull).forEach(s -> {
int i = 0;
});
return null;
@@ -1,10 +1,11 @@
// "Replace with forEach" "true"
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
class Sample {
List<String> foo = new ArrayList<>();
{
foo.stream().filter(s -> s != null).forEach(System.out::println);
foo.stream().filter(Objects::nonNull).forEach(System.out::println);
}
}
@@ -1,10 +1,11 @@
// "Replace with forEach" "true"
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
class Sample {
List<String> foo = new ArrayList<>();
{
foo.stream().filter(s -> s != null).filter(s -> s.startsWith("a")).forEach(System.out::println);
foo.stream().filter(Objects::nonNull).filter(s -> s.startsWith("a")).forEach(System.out::println);
}
}
@@ -1,11 +1,12 @@
// "Replace with sum()" "true"
import java.util.Arrays;
import java.util.Objects;
public class Main {
public double test(String[][] array) {
double d = 10;
d += Arrays.stream(array).filter(arr -> arr != null).flatMap(Arrays::stream).filter(a -> a.startsWith("xyz")).mapToDouble(a -> 1.0 / a.length()).sum();
d += Arrays.stream(array).filter(Objects::nonNull).flatMap(Arrays::stream).filter(a -> a.startsWith("xyz")).mapToDouble(a -> 1.0 / a.length()).sum();
return d;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2012 JetBrains s.r.o.
* 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.
@@ -24,9 +24,9 @@ public class Lambda2MethodReferenceInspectionTest extends LightQuickFixParameter
@NotNull
@Override
protected LocalInspectionTool[] configureLocalInspectionTools() {
return new LocalInspectionTool[]{
new LambdaCanBeMethodReferenceInspection(),
};
LambdaCanBeMethodReferenceInspection inspection = new LambdaCanBeMethodReferenceInspection();
inspection.REPLACE_CAST = inspection.REPLACE_INSTANCEOF = inspection.REPLACE_NULL_CHECK = true;
return new LocalInspectionTool[]{inspection};
}
public void test() throws Exception { doAllTests(); }
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
* 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.
@@ -15,6 +15,7 @@
*/
package com.intellij.codeInsight.daemon.quickFix;
import com.intellij.codeInspection.LambdaCanBeMethodReferenceInspection;
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.codeInspection.StreamApiMigrationInspection;
import org.jetbrains.annotations.NotNull;
@@ -26,6 +27,7 @@ public class StreamApiMigrationInspectionTest extends LightQuickFixParameterized
protected LocalInspectionTool[] configureLocalInspectionTools() {
return new LocalInspectionTool[]{
new StreamApiMigrationInspection(),
new LambdaCanBeMethodReferenceInspection()
};
}