IDEA-161007 Support method chains and comparingInt/Long/Double

This commit is contained in:
Tagir Valeev
2016-09-13 16:03:36 +07:00
parent 2ede0c7c9a
commit e2c7ca2fff
8 changed files with 235 additions and 34 deletions
@@ -15,20 +15,25 @@
*/
package com.intellij.codeInspection;
import com.intellij.codeInsight.FileModificationService;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.util.PsiTreeUtil;
import com.siyeh.ig.psiutils.MethodUtils;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.Objects;
public class ComparatorCombinatorsInspection extends BaseJavaBatchLocalInspectionTool {
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new JavaElementVisitor() {
@SuppressWarnings("DialogTitleCapitalization")
@Override
public void visitLambdaExpression(PsiLambdaExpression lambda) {
super.visitLambdaExpression(lambda);
@@ -40,19 +45,29 @@ public class ComparatorCombinatorsInspection extends BaseJavaBatchLocalInspectio
if(MethodUtils.isCompareToCall(methodCall)) {
PsiExpression left = methodCall.getMethodExpression().getQualifierExpression();
PsiExpression right = methodCall.getArgumentList().getExpressions()[0];
if(left instanceof PsiMethodCallExpression && right instanceof PsiMethodCallExpression) {
PsiMethodCallExpression leftCall = (PsiMethodCallExpression)left;
PsiMethodCallExpression rightCall = (PsiMethodCallExpression)right;
if(leftCall.getArgumentList().getExpressions().length == 0 &&
rightCall.getArgumentList().getExpressions().length == 0) {
PsiMethod leftMethod = leftCall.resolveMethod();
PsiMethod rightMethod = rightCall.resolveMethod();
if(leftMethod != null && rightMethod != null && leftMethod == rightMethod) {
if (areLambdaParameters(lambda, leftCall.getMethodExpression().getQualifierExpression(),
rightCall.getMethodExpression().getQualifierExpression())) {
//noinspection DialogTitleCapitalization
holder.registerProblem(lambda, "Can be replaced with Comparator.comparing", new ReplaceWithComparatorFix());
}
if(areEquivalent(lambda.getParameterList().getParameters(), left, right)) {
holder.registerProblem(lambda, "Can be replaced with Comparator.comparing", new ReplaceWithComparatorFix("comparing"));
}
} else {
PsiMethod method = methodCall.resolveMethod();
if(method != null && method.getName().equals("compare")) {
PsiExpression[] args = methodCall.getArgumentList().getExpressions();
PsiClass compareClass = method.getContainingClass();
if(args.length == 2 && compareClass != null) {
String replacementMethodName;
if(CommonClassNames.JAVA_LANG_DOUBLE.equals(compareClass.getQualifiedName())) {
replacementMethodName = "comparingDouble";
}
else if(CommonClassNames.JAVA_LANG_INTEGER.equals(compareClass.getQualifiedName())) {
replacementMethodName = "comparingInt";
}
else if(CommonClassNames.JAVA_LANG_LONG.equals(compareClass.getQualifiedName())) {
replacementMethodName = "comparingLong";
}
else return;
if(areEquivalent(lambda.getParameterList().getParameters(), args[0], args[1])) {
holder.registerProblem(lambda, "Can be replaced with Comparator." + replacementMethodName,
new ReplaceWithComparatorFix(replacementMethodName));
}
}
}
@@ -63,15 +78,49 @@ public class ComparatorCombinatorsInspection extends BaseJavaBatchLocalInspectio
};
}
private static boolean areLambdaParameters(PsiLambdaExpression lambda, PsiExpression left, PsiExpression right) {
PsiParameter[] parameters = lambda.getParameterList().getParameters();
return left instanceof PsiReferenceExpression &&
right instanceof PsiReferenceExpression &&
((PsiReferenceExpression)left).resolve() == parameters[0] &&
((PsiReferenceExpression)right).resolve() == parameters[1];
private static boolean areEquivalent(PsiParameter[] parameters, PsiExpression left, PsiExpression right) {
if(left == null && right == null) return true;
if(left instanceof PsiMethodCallExpression && right instanceof PsiMethodCallExpression) {
PsiMethodCallExpression leftCall = (PsiMethodCallExpression)left;
PsiMethodCallExpression rightCall = (PsiMethodCallExpression)right;
PsiMethod leftMethod = leftCall.resolveMethod();
PsiMethod rightMethod = rightCall.resolveMethod();
if(leftMethod == null || leftMethod != rightMethod) return false;
PsiExpression[] leftArgs = leftCall.getArgumentList().getExpressions();
PsiExpression[] rightArgs = rightCall.getArgumentList().getExpressions();
return leftArgs.length == rightArgs.length &&
areEquivalent(parameters, leftCall.getMethodExpression(), rightCall.getMethodExpression()) &&
!StreamEx.zip(leftArgs, rightArgs, (leftArg, rightArg) -> areEquivalent(parameters, leftArg, rightArg)).has(Boolean.FALSE);
}
if(left instanceof PsiReferenceExpression && right instanceof PsiReferenceExpression) {
PsiReferenceExpression leftRef = (PsiReferenceExpression)left;
PsiReferenceExpression rightRef = (PsiReferenceExpression)right;
PsiElement leftElement = leftRef.resolve();
PsiElement rightElement = rightRef.resolve();
if(leftElement instanceof PsiParameter) {
return leftElement == parameters[0] && rightElement == parameters[1];
}
if(leftElement != rightElement) return false;
PsiType[] leftTypes = leftRef.getTypeParameters();
PsiType[] rightTypes = leftRef.getTypeParameters();
return leftTypes.length == rightTypes.length && !StreamEx.zip(leftTypes, rightTypes, Objects::equals).has(Boolean.FALSE)
&& areEquivalent(parameters, leftRef.getQualifierExpression(), rightRef.getQualifierExpression());
}
if(left instanceof PsiTypeCastExpression && right instanceof PsiTypeCastExpression) {
PsiTypeCastExpression leftCast = (PsiTypeCastExpression)left;
PsiTypeCastExpression rightCast = (PsiTypeCastExpression)right;
return leftCast.getCastType() == rightCast.getCastType() &&
areEquivalent(parameters, leftCast.getOperand(), rightCast.getOperand());
}
return false;
}
static class ReplaceWithComparatorFix implements LocalQuickFix {
private final String myMethodName;
public ReplaceWithComparatorFix(String methodName) {
myMethodName = methodName;
}
@Nls
@NotNull
@@ -84,7 +133,7 @@ public class ComparatorCombinatorsInspection extends BaseJavaBatchLocalInspectio
@NotNull
@Override
public String getFamilyName() {
return "Replace with Comparator.comparing";
return "Replace with Comparator." + myMethodName;
}
@Override
@@ -95,22 +144,79 @@ public class ComparatorCombinatorsInspection extends BaseJavaBatchLocalInspectio
PsiElement body = lambda.getBody();
if (!(body instanceof PsiMethodCallExpression)) return;
PsiMethodCallExpression methodCall = (PsiMethodCallExpression)body;
if (!MethodUtils.isCompareToCall(methodCall)) return;
PsiExpression qualifier = methodCall.getMethodExpression().getQualifierExpression();
if (!(qualifier instanceof PsiMethodCallExpression)) return;
PsiMethodCallExpression call = (PsiMethodCallExpression)qualifier;
if (call.getArgumentList().getExpressions().length != 0) return;
PsiMethod method = call.resolveMethod();
if (method == null) return;
PsiClass methodClass = method.getContainingClass();
if (methodClass == null) return;
if (!FileModificationService.getInstance().preparePsiElementForWrite(element)) return;
PsiExpression keyExtractor = null;
String methodName = null;
if (MethodUtils.isCompareToCall(methodCall)) {
methodName = "comparing";
keyExtractor = methodCall.getMethodExpression().getQualifierExpression();
} else {
PsiMethod method = methodCall.resolveMethod();
if(method != null && method.getName().equals("compare")) {
PsiClass containingClass = method.getContainingClass();
if(containingClass != null) {
String className = containingClass.getQualifiedName();
if(className != null) {
PsiExpression[] args = methodCall.getArgumentList().getExpressions();
if(args.length != 2) return;
keyExtractor = args[0];
switch (className) {
case CommonClassNames.JAVA_LANG_LONG:
methodName = "comparingLong";
break;
case CommonClassNames.JAVA_LANG_INTEGER:
methodName = "comparingInt";
break;
case CommonClassNames.JAVA_LANG_DOUBLE:
methodName = "comparingDouble";
break;
default:
return;
}
}
}
}
}
if(methodName == null || keyExtractor == null) return;
PsiParameter parameter = lambda.getParameterList().getParameters()[0];
String parameterName = parameter.getName();
PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
PsiExpression replacement =
factory.createExpressionFromText("java.util.Comparator.comparing(" + methodClass.getQualifiedName() + "::" + method.getName() + ")",
element);
PsiExpression replacement = factory
.createExpressionFromText("java.util.Comparator." + methodName + "(" + parameterName + " -> " + keyExtractor.getText() + ")",
element);
PsiElement result = lambda.replace(replacement);
normalizeLambda(((PsiMethodCallExpression)result).getArgumentList().getExpressions()[0], factory);
CodeStyleManager.getInstance(project).reformat(JavaCodeStyleManager.getInstance(project).shortenClassReferences(result));
}
private static void normalizeLambda(PsiExpression expression, PsiElementFactory factory) {
if(!(expression instanceof PsiLambdaExpression)) return;
PsiLambdaExpression lambda = (PsiLambdaExpression)expression;
PsiParameter[] parameters = lambda.getParameterList().getParameters();
PsiElement body = lambda.getBody();
if(body == null) return;
String methodReference =
LambdaCanBeMethodReferenceInspection.convertToMethodReference(body, parameters, lambda.getFunctionalInterfaceType(), lambda);
if(methodReference != null) {
lambda.replace(factory.createExpressionFromText(methodReference, lambda));
} else {
PsiParameter parameter = parameters[0];
String name = parameter.getName();
String nameCandidate = null;
if(name != null) {
if(name.length() > 1 && name.endsWith("1")) {
nameCandidate = name.substring(0, name.length()-1);
}
}
if(nameCandidate != null) {
String newName = JavaCodeStyleManager.getInstance(expression.getProject()).suggestUniqueVariableName(nameCandidate, lambda, true);
if(newName.equals(nameCandidate)) {
Collection<PsiReferenceExpression> references = PsiTreeUtil.collectElementsOfType(body, PsiReferenceExpression.class);
StreamEx.of(references).filter(ref -> ref.resolve() == parameter).map(PsiJavaCodeReferenceElement::getReferenceNameElement)
.nonNull().forEach(nameElement -> nameElement.replace(factory.createIdentifier(newName)));
parameter.setName(newName);
}
}
}
}
}
}
@@ -0,0 +1,13 @@
// "Replace with Comparator.comparing" "true"
import java.util.*;
public class Main {
void sort(List<Person> persons) {
persons.sort(Comparator.comparing(p -> p.getName().toLowerCase(Locale.ENGLISH)));
}
interface Person {
String getName();
}
}
@@ -0,0 +1,13 @@
// "Replace with Comparator.comparingDouble" "true"
import java.util.*;
public class Main {
void sort(List<Person> persons) {
persons.sort(Comparator.comparingDouble(p -> p.getName().length()));
}
interface Person {
String getName();
}
}
@@ -0,0 +1,14 @@
// "Replace with Comparator.comparing" "true"
import java.util.*;
public class Main {
void sort(List<Person> persons) {
String p;
persons.sort(Comparator.comparing(p1 -> p1.name));
}
class Person {
String name;
}
}
@@ -0,0 +1,14 @@
// "Replace with Comparator.comparing" "true"
import java.util.*;
public class Main {
void sort(List<Person> persons) {
persons.sort((p1, p2) -> p1.getName()<caret>.toLowerCase(Locale.ENGLISH)
.compareTo(p2.getName().toLowerCase(Locale.ENGLISH)));
}
interface Person {
String getName();
}
}
@@ -0,0 +1,14 @@
// "Replace with Comparator.comparing" "false"
import java.util.*;
public class Main {
void sort(List<Person> persons) {
persons.sort((p1, p2) -> p1.getName()<caret>.toLowerCase(Locale.ENGLISH)
.compareTo(p2.getName().toLowerCase(Locale.CANADA)));
}
interface Person {
String getName();
}
}
@@ -0,0 +1,13 @@
// "Replace with Comparator.comparingDouble" "true"
import java.util.*;
public class Main {
void sort(List<Person> persons) {
persons.sort((p1, p2) -> Double.com<caret>pare(p1.getName().length(), p2.getName().length()));
}
interface Person {
String getName();
}
}
@@ -0,0 +1,14 @@
// "Replace with Comparator.comparing" "true"
import java.util.*;
public class Main {
void sort(List<Person> persons) {
String p;
persons.sort((p1, p2) -> p1.name.compar<caret>eTo(p2.name));
}
class Person {
String name;
}
}