mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-10-06 10:07:41 +07:00
extract lambda -> method reference to code style settings
This commit is contained in:
-175
@@ -1,175 +0,0 @@
|
||||
/*
|
||||
* 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.codeInspection;
|
||||
|
||||
import com.intellij.codeInsight.FileModificationService;
|
||||
import com.intellij.codeInsight.daemon.GroupNames;
|
||||
import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.RedundantCastUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
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.Collections;
|
||||
|
||||
/**
|
||||
* User: anna
|
||||
*/
|
||||
public class AnonymousCanBeMethodReferenceInspection extends BaseJavaBatchLocalInspectionTool {
|
||||
private static final Logger LOG = Logger.getInstance("#" + AnonymousCanBeMethodReferenceInspection.class.getName());
|
||||
|
||||
public boolean reportNotAnnotatedInterfaces = true;
|
||||
|
||||
@Nls
|
||||
@NotNull
|
||||
@Override
|
||||
public String getGroupDisplayName() {
|
||||
return GroupNames.LANGUAGE_LEVEL_SPECIFIC_GROUP_NAME;
|
||||
}
|
||||
|
||||
@Nls
|
||||
@NotNull
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return "Anonymous type can be replaced with method reference";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabledByDefault() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getShortName() {
|
||||
return "Anonymous2MethodRef";
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public JComponent createOptionsPanel() {
|
||||
return new SingleCheckboxOptionsPanel("Report when interface is not annotated with @FunctionalInterface", this, "reportNotAnnotatedInterfaces");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
|
||||
return new JavaElementVisitor() {
|
||||
@Override
|
||||
public void visitAnonymousClass(PsiAnonymousClass aClass) {
|
||||
super.visitAnonymousClass(aClass);
|
||||
if (AnonymousCanBeLambdaInspection.canBeConvertedToLambda(aClass, true, reportNotAnnotatedInterfaces, Collections.emptySet())) {
|
||||
final PsiMethod method = aClass.getMethods()[0];
|
||||
final PsiCodeBlock body = method.getBody();
|
||||
LambdaCanBeMethodReferenceInspection methodReferenceInspection = new LambdaCanBeMethodReferenceInspection();
|
||||
PsiExpression lambdaBodyCandidate = methodReferenceInspection.extractMethodReferenceCandidateExpression(body, false);
|
||||
final PsiExpression methodRefCandidate =
|
||||
methodReferenceInspection.canBeMethodReferenceProblem(method.getParameterList().getParameters(), aClass.getBaseClassType(), aClass.getParent(), lambdaBodyCandidate);
|
||||
if (methodRefCandidate instanceof PsiCallExpression) {
|
||||
final PsiCallExpression callExpression = (PsiCallExpression)methodRefCandidate;
|
||||
final PsiMethod resolveMethod = callExpression.resolveMethod();
|
||||
if (resolveMethod != method &&
|
||||
!AnonymousCanBeLambdaInspection.functionalInterfaceMethodReferenced(resolveMethod, aClass, callExpression)) {
|
||||
final PsiElement parent = aClass.getParent();
|
||||
if (parent instanceof PsiNewExpression) {
|
||||
final PsiJavaCodeReferenceElement classReference = ((PsiNewExpression)parent).getClassOrAnonymousClassReference();
|
||||
if (classReference != null) {
|
||||
final PsiElement lBrace = aClass.getLBrace();
|
||||
LOG.assertTrue(lBrace != null);
|
||||
final TextRange rangeInElement = new TextRange(0, aClass.getStartOffsetInParent() + lBrace.getStartOffsetInParent());
|
||||
ProblemHighlightType highlightType = LambdaCanBeMethodReferenceInspection.checkQualifier(lambdaBodyCandidate) ? ProblemHighlightType.LIKE_UNUSED_SYMBOL
|
||||
: ProblemHighlightType.INFORMATION;
|
||||
holder.registerProblem(parent,
|
||||
"Anonymous #ref #loc can be replaced with method reference",
|
||||
highlightType, rangeInElement, new ReplaceWithMethodRefFix());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static class ReplaceWithMethodRefFix implements LocalQuickFix {
|
||||
@NotNull
|
||||
@Override
|
||||
public String getFamilyName() {
|
||||
return "Replace with method reference";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
|
||||
final PsiElement element = descriptor.getPsiElement();
|
||||
if (element instanceof PsiNewExpression) {
|
||||
if (!FileModificationService.getInstance().preparePsiElementForWrite(element)) return;
|
||||
final PsiAnonymousClass anonymousClass = ((PsiNewExpression)element).getAnonymousClass();
|
||||
if (anonymousClass == null) return;
|
||||
final PsiMethod[] methods = anonymousClass.getMethods();
|
||||
if (methods.length != 1) return;
|
||||
|
||||
final PsiParameter[] parameters = methods[0].getParameterList().getParameters();
|
||||
final PsiType functionalInterfaceType = anonymousClass.getBaseClassType();
|
||||
LambdaCanBeMethodReferenceInspection methodReferenceInspection = new LambdaCanBeMethodReferenceInspection();
|
||||
PsiExpression methodRefCandidate = methodReferenceInspection.extractMethodReferenceCandidateExpression(methods[0].getBody(), false);
|
||||
final PsiExpression candidate = methodReferenceInspection.canBeMethodReferenceProblem(parameters, functionalInterfaceType, anonymousClass.getParent(), methodRefCandidate);
|
||||
|
||||
final String methodRefText = LambdaCanBeMethodReferenceInspection.createMethodReferenceText(candidate, functionalInterfaceType, parameters);
|
||||
|
||||
replaceWithMethodReference(project, methodRefText, anonymousClass.getBaseClassType(), anonymousClass.getParent());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void replaceWithMethodReference(@NotNull Project project,
|
||||
String methodRefText,
|
||||
PsiType castType,
|
||||
PsiElement replacementTarget) {
|
||||
final Collection<PsiComment> comments = ContainerUtil.map(PsiTreeUtil.findChildrenOfType(replacementTarget, PsiComment.class),
|
||||
comment -> (PsiComment)comment.copy());
|
||||
|
||||
if (methodRefText != null) {
|
||||
final String canonicalText = castType.getCanonicalText();
|
||||
final PsiExpression psiExpression = JavaPsiFacade
|
||||
.getElementFactory(project).createExpressionFromText("(" + canonicalText + ")" + methodRefText, replacementTarget);
|
||||
|
||||
PsiElement castExpr = replacementTarget.replace(psiExpression);
|
||||
if (RedundantCastUtil.isCastRedundant((PsiTypeCastExpression)castExpr)) {
|
||||
final PsiExpression operand = ((PsiTypeCastExpression)castExpr).getOperand();
|
||||
LOG.assertTrue(operand != null);
|
||||
castExpr = castExpr.replace(operand);
|
||||
}
|
||||
|
||||
PsiElement anchor = PsiTreeUtil.getParentOfType(castExpr, PsiStatement.class);
|
||||
if (anchor == null) {
|
||||
anchor = castExpr;
|
||||
}
|
||||
for (PsiComment comment : comments) {
|
||||
anchor.getParent().addBefore(comment, anchor);
|
||||
}
|
||||
JavaCodeStyleManager.getInstance(project).shortenClassReferences(castExpr);
|
||||
}
|
||||
}
|
||||
}
|
||||
-269
@@ -1,269 +0,0 @@
|
||||
/*
|
||||
* 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.codeInspection;
|
||||
|
||||
import com.intellij.codeInsight.FileModificationService;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
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.codeStyle.SuggestedNameInfo;
|
||||
import com.intellij.psi.codeStyle.VariableKind;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiTypesUtil;
|
||||
import com.siyeh.ig.psiutils.EquivalenceChecker;
|
||||
import com.siyeh.ig.psiutils.MethodCallUtils;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public class ComparatorCombinatorsInspection extends BaseJavaBatchLocalInspectionTool {
|
||||
private static final Logger LOG = Logger.getInstance("#" + ComparatorCombinatorsInspection.class.getName());
|
||||
|
||||
@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);
|
||||
PsiType type = lambda.getFunctionalInterfaceType();
|
||||
PsiParameter[] parameters = lambda.getParameterList().getParameters();
|
||||
if (parameters.length != 2 ||
|
||||
!(type instanceof PsiClassType) ||
|
||||
!((PsiClassType)type).rawType().equalsToText(CommonClassNames.JAVA_UTIL_COMPARATOR)) {
|
||||
return;
|
||||
}
|
||||
PsiElement body = LambdaUtil.extractSingleExpressionFromBody(lambda.getBody());
|
||||
String methodName = null;
|
||||
if (body instanceof PsiMethodCallExpression) {
|
||||
PsiMethodCallExpression methodCall = (PsiMethodCallExpression)body;
|
||||
PsiExpression[] args = methodCall.getArgumentList().getExpressions();
|
||||
if (args.length == 1 && MethodCallUtils.isCompareToCall(methodCall)) {
|
||||
PsiExpression left = methodCall.getMethodExpression().getQualifierExpression();
|
||||
if (left == null) {
|
||||
return;
|
||||
}
|
||||
PsiExpression right = args[0];
|
||||
if (left instanceof PsiReferenceExpression && right instanceof PsiReferenceExpression) {
|
||||
PsiElement leftElement = ((PsiReferenceExpression)left).resolve();
|
||||
PsiElement rightElement = ((PsiReferenceExpression)right).resolve();
|
||||
if (leftElement == parameters[0] && rightElement == parameters[1]) {
|
||||
methodName = "naturalOrder";
|
||||
}
|
||||
else if (leftElement == parameters[1] && rightElement == parameters[0]) {
|
||||
methodName = "reverseOrder";
|
||||
}
|
||||
}
|
||||
if (methodName == null && areEquivalent(parameters, left, right)) {
|
||||
methodName = "comparing";
|
||||
}
|
||||
}
|
||||
else {
|
||||
PsiMethod method = methodCall.resolveMethod();
|
||||
if (args.length == 2 && method != null && method.getName().equals("compare")) {
|
||||
PsiClass compareClass = method.getContainingClass();
|
||||
if (compareClass != null) {
|
||||
methodName = getComparingMethodName(compareClass.getQualifiedName());
|
||||
if (!areEquivalent(parameters, args[0], args[1])) return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (body instanceof PsiBinaryExpression) {
|
||||
PsiBinaryExpression binOp = (PsiBinaryExpression)body;
|
||||
if (binOp.getOperationTokenType().equals(JavaTokenType.MINUS) &&
|
||||
areEquivalent(parameters, binOp.getLOperand(), binOp.getROperand())) {
|
||||
PsiType opType = binOp.getLOperand().getType();
|
||||
if(opType == null) return;
|
||||
methodName = getComparingMethodName(opType.getCanonicalText());
|
||||
}
|
||||
}
|
||||
if (methodName != null) {
|
||||
holder
|
||||
.registerProblem(lambda, "Can be replaced with Comparator." + methodName,
|
||||
ProblemHighlightType.LIKE_UNUSED_SYMBOL, new ReplaceWithComparatorFix(methodName));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Contract(value = "null -> null", pure = true)
|
||||
@Nullable
|
||||
private static String getComparingMethodName(String type) {
|
||||
if(type == null) return null;
|
||||
switch(PsiTypesUtil.unboxIfPossible(type)) {
|
||||
case "int":
|
||||
case "short":
|
||||
case "byte":
|
||||
case "char":
|
||||
return "comparingInt";
|
||||
case "long":
|
||||
return "comparingLong";
|
||||
case "double":
|
||||
return "comparingDouble";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Contract("_, null, _ -> false; _, !null, null -> false")
|
||||
private static boolean areEquivalent(@NotNull PsiParameter[] parameters, @Nullable PsiExpression left, @Nullable PsiExpression right) {
|
||||
if (left == null || right == null) return false;
|
||||
if (!PsiTreeUtil.processElements(left, e -> !(e instanceof PsiReferenceExpression) ||
|
||||
((PsiReferenceExpression)e).resolve() != parameters[1]) ||
|
||||
!PsiTreeUtil.processElements(right, e -> !(e instanceof PsiReferenceExpression) ||
|
||||
((PsiReferenceExpression)e).resolve() != parameters[0])) {
|
||||
return false;
|
||||
}
|
||||
PsiExpression copy = (PsiExpression)right.copy();
|
||||
PsiElement[] rightRefs = PsiTreeUtil.collectElements(copy, e -> e instanceof PsiReferenceExpression &&
|
||||
((PsiReferenceExpression)e).resolve() == parameters[1]);
|
||||
PsiElementFactory factory = JavaPsiFacade.getElementFactory(left.getProject());
|
||||
String paramName = parameters[0].getName();
|
||||
if (paramName == null) return false;
|
||||
for (PsiElement ref : rightRefs) {
|
||||
PsiElement nameElement = ((PsiReferenceExpression)ref).getReferenceNameElement();
|
||||
LOG.assertTrue(nameElement != null);
|
||||
nameElement.replace(factory.createIdentifier(paramName));
|
||||
}
|
||||
return EquivalenceChecker.getCanonicalPsiEquivalence().expressionsAreEquivalent(left, copy);
|
||||
}
|
||||
|
||||
static class ReplaceWithComparatorFix implements LocalQuickFix {
|
||||
private final String myMethodName;
|
||||
|
||||
public ReplaceWithComparatorFix(String methodName) {
|
||||
myMethodName = methodName;
|
||||
}
|
||||
|
||||
@Nls
|
||||
@NotNull
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Replace with Comparator." + myMethodName;
|
||||
}
|
||||
|
||||
@Nls
|
||||
@NotNull
|
||||
@Override
|
||||
public String getFamilyName() {
|
||||
return "Simplify comparator using Comparator static methods";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
|
||||
PsiElement element = descriptor.getStartElement();
|
||||
if (!(element instanceof PsiLambdaExpression)) return;
|
||||
PsiLambdaExpression lambda = (PsiLambdaExpression)element;
|
||||
PsiParameter[] parameters = lambda.getParameterList().getParameters();
|
||||
if (parameters.length != 2) return;
|
||||
PsiElement body = LambdaUtil.extractSingleExpressionFromBody(lambda.getBody());
|
||||
PsiExpression keyExtractor = null;
|
||||
String methodName = null;
|
||||
if (body instanceof PsiMethodCallExpression) {
|
||||
PsiMethodCallExpression methodCall = (PsiMethodCallExpression)body;
|
||||
if (MethodCallUtils.isCompareToCall(methodCall)) {
|
||||
methodName = "comparing";
|
||||
keyExtractor = methodCall.getMethodExpression().getQualifierExpression();
|
||||
if (keyExtractor instanceof PsiReferenceExpression) {
|
||||
PsiElement keyElement = ((PsiReferenceExpression)keyExtractor).resolve();
|
||||
if (keyElement == parameters[0]) {
|
||||
methodName = "naturalOrder";
|
||||
}
|
||||
else if (keyElement == parameters[1]) {
|
||||
methodName = "reverseOrder";
|
||||
}
|
||||
}
|
||||
}
|
||||
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];
|
||||
methodName = getComparingMethodName(className);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if(body instanceof PsiBinaryExpression) {
|
||||
PsiBinaryExpression binOp = (PsiBinaryExpression)body;
|
||||
if(!binOp.getOperationTokenType().equals(JavaTokenType.MINUS)) return;
|
||||
keyExtractor = binOp.getLOperand();
|
||||
PsiType type = keyExtractor.getType();
|
||||
if(type == null) return;
|
||||
methodName = getComparingMethodName(type.getCanonicalText());
|
||||
}
|
||||
if (methodName == null || keyExtractor == null) return;
|
||||
if (!FileModificationService.getInstance().preparePsiElementForWrite(element)) return;
|
||||
String parameterName = parameters[0].getName();
|
||||
PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
|
||||
PsiElement result;
|
||||
if (!methodName.startsWith("comparing")) {
|
||||
result = lambda.replace(factory.createExpressionFromText("java.util.Comparator." + methodName + "()", element));
|
||||
}
|
||||
else {
|
||||
String newLambda = parameterName + " -> " + keyExtractor.getText();
|
||||
PsiExpression replacement = factory.createExpressionFromText("java.util.Comparator." + methodName + "(" + newLambda + ")", element);
|
||||
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;
|
||||
if (LambdaCanBeMethodReferenceInspection.replaceLambdaWithMethodReference(lambda) == lambda) {
|
||||
PsiParameter parameter = parameters[0];
|
||||
String name = parameter.getName();
|
||||
SuggestedNameInfo nameCandidate = null;
|
||||
JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(expression.getProject());
|
||||
if (name != null) {
|
||||
if (name.length() > 1 && name.endsWith("1")) {
|
||||
nameCandidate = codeStyleManager.suggestVariableName(VariableKind.PARAMETER, name.substring(0, name.length() - 1),
|
||||
null, parameter.getType(), true);
|
||||
} else if (name.equals("first")) {
|
||||
nameCandidate =
|
||||
codeStyleManager.suggestVariableName(VariableKind.PARAMETER, null, null, parameter.getType(), true);
|
||||
}
|
||||
}
|
||||
if (nameCandidate != null) {
|
||||
String[] names = codeStyleManager.suggestUniqueVariableName(nameCandidate, lambda, true).names;
|
||||
if (names.length > 0) {
|
||||
String newName = names[0];
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-638
@@ -1,638 +0,0 @@
|
||||
/*
|
||||
* 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.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;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.siyeh.ig.psiutils.ExpressionUtils;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
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;
|
||||
|
||||
/**
|
||||
* User: anna
|
||||
*/
|
||||
public class LambdaCanBeMethodReferenceInspection extends BaseJavaBatchLocalInspectionTool {
|
||||
private 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 with MyClass.class::isInstance", "REPLACE_INSTANCEOF");
|
||||
panel.addCheckbox("Replace cast with MyClass.class::cast", "REPLACE_CAST");
|
||||
panel.addCheckbox("Replace null-check with Objects::nonNull or Objects::isNull", "REPLACE_NULL_CHECK");
|
||||
return panel;
|
||||
}
|
||||
|
||||
@Nls
|
||||
@NotNull
|
||||
@Override
|
||||
public String getGroupDisplayName() {
|
||||
return GroupNames.LANGUAGE_LEVEL_SPECIFIC_GROUP_NAME;
|
||||
}
|
||||
|
||||
@Nls
|
||||
@NotNull
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return "Lambda can be replaced with method reference";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabledByDefault() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getShortName() {
|
||||
return "Convert2MethodRef";
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
|
||||
return new JavaElementVisitor() {
|
||||
@Override
|
||||
public void visitLambdaExpression(PsiLambdaExpression expression) {
|
||||
super.visitLambdaExpression(expression);
|
||||
if (PsiUtil.getLanguageLevel(expression).isAtLeast(LanguageLevel.JDK_1_8)) {
|
||||
final PsiElement body = expression.getBody();
|
||||
final PsiType functionalInterfaceType = expression.getFunctionalInterfaceType();
|
||||
if (functionalInterfaceType != null) {
|
||||
PsiExpression methodRefCandidate = extractMethodReferenceCandidateExpression(body, false);
|
||||
final PsiExpression candidate =
|
||||
canBeMethodReferenceProblem(expression.getParameterList().getParameters(), functionalInterfaceType, null, methodRefCandidate);
|
||||
if (candidate != null) {
|
||||
ProblemHighlightType errorOrWarning = checkQualifier(methodRefCandidate) ? ProblemHighlightType.GENERIC_ERROR_OR_WARNING
|
||||
: ProblemHighlightType.INFORMATION;
|
||||
holder.registerProblem(InspectionProjectProfileManager.isInformationLevel(getShortName(), expression) ? expression : candidate,
|
||||
"Can be replaced with method reference",
|
||||
errorOrWarning, new ReplaceWithMethodRefFix());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public PsiExpression canBeMethodReferenceProblem(@Nullable final PsiElement body,
|
||||
final PsiVariable[] parameters,
|
||||
PsiType functionalInterfaceType,
|
||||
@Nullable PsiElement context) {
|
||||
PsiExpression methodRefCandidate = extractMethodReferenceCandidateExpression(body, true);
|
||||
return canBeMethodReferenceProblem(parameters, functionalInterfaceType, context, methodRefCandidate);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public PsiExpression canBeMethodReferenceProblem(final PsiVariable[] parameters,
|
||||
PsiType functionalInterfaceType,
|
||||
@Nullable PsiElement context,
|
||||
final PsiExpression methodRefCandidate) {
|
||||
if (methodRefCandidate instanceof PsiNewExpression) {
|
||||
final PsiNewExpression newExpression = (PsiNewExpression)methodRefCandidate;
|
||||
if (newExpression.getAnonymousClass() != null || newExpression.getArrayInitializer() != null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
final String methodReferenceText = createMethodReferenceText(methodRefCandidate, functionalInterfaceType, parameters);
|
||||
if (methodReferenceText != 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)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOG.assertTrue(callExpression instanceof PsiNewExpression);
|
||||
if (((PsiNewExpression)callExpression).getQualifier() != null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final PsiExpression[] dims = ((PsiNewExpression)callExpression).getArrayDimensions();
|
||||
if (dims.length == 1 && parameters.length == 1){
|
||||
if (!resolvesToParameter(dims[0], parameters[0])) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else if (dims.length > 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (callExpression.getTypeArguments().length > 0) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(callExpression.getProject());
|
||||
PsiMethodReferenceExpression methodReferenceExpression;
|
||||
try {
|
||||
methodReferenceExpression = (PsiMethodReferenceExpression)elementFactory.createExpressionFromText(methodReferenceText, context != null ? context : callExpression);
|
||||
}
|
||||
catch (IncorrectOperationException e) {
|
||||
LOG.error(callExpression.getText(), e);
|
||||
return null;
|
||||
}
|
||||
final Map<PsiElement, PsiType> map = LambdaUtil.getFunctionalTypeMap();
|
||||
try {
|
||||
map.put(methodReferenceExpression, functionalInterfaceType);
|
||||
final JavaResolveResult result = methodReferenceExpression.advancedResolve(false);
|
||||
final PsiElement element = result.getElement();
|
||||
if (element != null && result.isAccessible() &&
|
||||
!(result instanceof MethodCandidateInfo && !((MethodCandidateInfo)result).isApplicable())) {
|
||||
if (!(element instanceof PsiMethod)) {
|
||||
return callExpression;
|
||||
}
|
||||
|
||||
return method != null && MethodSignatureUtil.areSignaturesEqual((PsiMethod)element, method) ? callExpression : null;
|
||||
}
|
||||
}
|
||||
finally {
|
||||
map.remove(methodReferenceExpression);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean isSimpleCall(final PsiVariable[] parameters, PsiCallExpression callExpression, PsiMethod psiMethod) {
|
||||
final PsiExpressionList argumentList = callExpression.getArgumentList();
|
||||
if (argumentList == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int calledParametersCount = psiMethod.getParameterList().getParametersCount();
|
||||
final PsiExpression[] expressions = argumentList.getExpressions();
|
||||
|
||||
final PsiExpression qualifier;
|
||||
if (callExpression instanceof PsiMethodCallExpression) {
|
||||
qualifier = ((PsiMethodCallExpression)callExpression).getMethodExpression().getQualifierExpression();
|
||||
}
|
||||
else if (callExpression instanceof PsiNewExpression) {
|
||||
qualifier = ((PsiNewExpression)callExpression).getQualifier();
|
||||
}
|
||||
else {
|
||||
qualifier = null;
|
||||
}
|
||||
|
||||
if (expressions.length == 0 && parameters.length == 0) {
|
||||
return !(callExpression instanceof PsiNewExpression && qualifier != null);
|
||||
}
|
||||
|
||||
final int offset = parameters.length - calledParametersCount;
|
||||
if (expressions.length > calledParametersCount || offset < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < expressions.length; i++) {
|
||||
if (!resolvesToParameter(expressions[i], parameters[i + offset])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (offset == 0) {
|
||||
if (qualifier != null) {
|
||||
final boolean[] parameterUsed = new boolean[] {false};
|
||||
qualifier.accept(new JavaRecursiveElementWalkingVisitor() {
|
||||
@Override
|
||||
public void visitElement(PsiElement element) {
|
||||
if (parameterUsed[0]) return;
|
||||
super.visitElement(element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitReferenceExpression(PsiReferenceExpression expression) {
|
||||
super.visitReferenceExpression(expression);
|
||||
parameterUsed[0] |= ArrayUtil.find(parameters, expression.resolve()) >= 0;
|
||||
}
|
||||
});
|
||||
return !parameterUsed[0];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return resolvesToParameter(qualifier, parameters[0]);
|
||||
}
|
||||
|
||||
@Contract("null, _ -> false")
|
||||
private static boolean resolvesToParameter(PsiExpression expression, PsiVariable parameter) {
|
||||
return expression instanceof PsiReferenceExpression && ((PsiReferenceExpression)expression).resolve() == parameter;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public PsiExpression extractMethodReferenceCandidateExpression(PsiElement body, boolean checkSideEffectPureQualifier) {
|
||||
final PsiExpression expression = LambdaUtil.extractSingleExpressionFromBody(body);
|
||||
if (expression == null) {
|
||||
return null;
|
||||
}
|
||||
if (expression instanceof PsiNewExpression) {
|
||||
if (!checkSideEffectPureQualifier || checkQualifier(((PsiNewExpression)expression).getQualifier())) {
|
||||
return expression;
|
||||
}
|
||||
}
|
||||
else if (expression instanceof PsiMethodCallExpression) {
|
||||
if (!checkSideEffectPureQualifier || checkQualifier(((PsiMethodCallExpression)expression).getMethodExpression().getQualifier())) {
|
||||
return expression;
|
||||
}
|
||||
}
|
||||
if (expression instanceof PsiInstanceOfExpression && REPLACE_INSTANCEOF) {
|
||||
return expression;
|
||||
}
|
||||
else if (expression instanceof PsiBinaryExpression && REPLACE_NULL_CHECK) {
|
||||
IElementType tokenType = ((PsiBinaryExpression)expression).getOperationTokenType();
|
||||
if (JavaTokenType.EQEQ.equals(tokenType) || JavaTokenType.NE.equals(tokenType)) {
|
||||
if (ExpressionUtils.isNullLiteral(((PsiBinaryExpression)expression).getLOperand()) ||
|
||||
ExpressionUtils.isNullLiteral(((PsiBinaryExpression)expression).getROperand())) {
|
||||
return expression;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (expression instanceof PsiTypeCastExpression && 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 || PsiUtil.resolveClassInType(type) instanceof PsiTypeParameter) return null;
|
||||
return expression;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void replaceAllLambdasWithMethodReferences(PsiElement root) {
|
||||
Collection<PsiLambdaExpression> lambdas = PsiTreeUtil.findChildrenOfType(root, PsiLambdaExpression.class);
|
||||
if(!lambdas.isEmpty()) {
|
||||
for(PsiLambdaExpression lambda : lambdas) {
|
||||
replaceLambdaWithMethodReference(lambda);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static PsiExpression replaceLambdaWithMethodReference(@NotNull PsiLambdaExpression lambda) {
|
||||
PsiElement body = LambdaUtil.extractSingleExpressionFromBody(lambda.getBody());
|
||||
final PsiExpression candidate = new LambdaCanBeMethodReferenceInspection()
|
||||
.canBeMethodReferenceProblem(body, lambda.getParameterList().getParameters(), lambda.getFunctionalInterfaceType(), lambda);
|
||||
return tryConvertToMethodReference(lambda, candidate);
|
||||
}
|
||||
|
||||
public static boolean checkQualifier(PsiElement qualifier) {
|
||||
if (qualifier == null) {
|
||||
return true;
|
||||
}
|
||||
final Condition<PsiElement> callExpressionCondition = Conditions.instanceOf(PsiCallExpression.class);
|
||||
final Condition<PsiElement> nonFinalFieldRefCondition = expression -> {
|
||||
if (expression instanceof PsiReferenceExpression) {
|
||||
PsiElement element = ((PsiReferenceExpression)expression).resolve();
|
||||
if (element instanceof PsiField && !((PsiField)element).hasModifierProperty(PsiModifier.FINAL)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
return SyntaxTraverser
|
||||
.psiTraverser()
|
||||
.withRoot(qualifier)
|
||||
.filter(Conditions.or(callExpressionCondition, nonFinalFieldRefCondition)).toList().isEmpty();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PsiMethod getNonAmbiguousReceiver(PsiVariable[] parameters, @NotNull PsiMethod psiMethod) {
|
||||
String methodName = psiMethod.getName();
|
||||
PsiClass containingClass = psiMethod.getContainingClass();
|
||||
if (containingClass == null) return null;
|
||||
|
||||
final PsiMethod[] psiMethods = containingClass.findMethodsByName(methodName, false);
|
||||
if (psiMethods.length == 1) return psiMethod;
|
||||
|
||||
final PsiType receiverType = parameters[0].getType();
|
||||
for (PsiMethod method : psiMethods) {
|
||||
if (isPairedNoReceiver(parameters, receiverType, method)) {
|
||||
final PsiMethod[] deepestSuperMethods = psiMethod.findDeepestSuperMethods();
|
||||
if (deepestSuperMethods.length > 0) {
|
||||
for (PsiMethod superMethod : deepestSuperMethods) {
|
||||
PsiMethod validSuperMethod = getNonAmbiguousReceiver(parameters, superMethod);
|
||||
if (validSuperMethod != null) return validSuperMethod;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return psiMethod;
|
||||
}
|
||||
|
||||
private static boolean isPairedNoReceiver(PsiVariable[] parameters,
|
||||
PsiType receiverType,
|
||||
PsiMethod method) {
|
||||
final PsiParameter[] nonReceiverCandidateParams = method.getParameterList().getParameters();
|
||||
return nonReceiverCandidateParams.length == parameters.length &&
|
||||
method.hasModifierProperty(PsiModifier.STATIC) &&
|
||||
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
|
||||
static String createMethodReferenceText(final PsiElement element,
|
||||
final PsiType functionalInterfaceType,
|
||||
final PsiVariable[] parameters) {
|
||||
if (element instanceof PsiMethodCallExpression) {
|
||||
final PsiMethodCallExpression methodCall = (PsiMethodCallExpression)element;
|
||||
|
||||
JavaResolveResult result = methodCall.resolveMethodGenerics();
|
||||
final PsiMethod psiMethod = (PsiMethod)result.getElement();
|
||||
if (psiMethod == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final PsiReferenceExpression methodExpression = methodCall.getMethodExpression();
|
||||
final String qualifierByMethodCall = getQualifierTextByMethodCall(methodCall, functionalInterfaceType, parameters, psiMethod, result.getSubstitutor());
|
||||
if (qualifierByMethodCall != null) {
|
||||
return qualifierByMethodCall + "::" + ((PsiMethodCallExpression)element).getTypeArgumentList().getText() + methodExpression.getReferenceName();
|
||||
}
|
||||
}
|
||||
else if (element instanceof PsiNewExpression) {
|
||||
final String qualifierByNew = getQualifierTextByNewExpression((PsiNewExpression)element);
|
||||
if (qualifierByNew != null) {
|
||||
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 (ExpressionUtils.isNullLiteral(nullCheck.getROperand())) {
|
||||
operand = nullCheck.getLOperand();
|
||||
} else if(ExpressionUtils.isNullLiteral(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;
|
||||
}
|
||||
|
||||
private static String getQualifierTextByNewExpression(PsiNewExpression element) {
|
||||
final PsiType newExprType = element.getType();
|
||||
if (newExprType == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
PsiClass containingClass = null;
|
||||
final PsiJavaCodeReferenceElement classReference = element.getClassOrAnonymousClassReference();
|
||||
if (classReference != null) {
|
||||
final JavaResolveResult resolve = classReference.advancedResolve(false);
|
||||
final PsiElement resolveElement = resolve.getElement();
|
||||
if (resolveElement instanceof PsiClass) {
|
||||
containingClass = (PsiClass)resolveElement;
|
||||
}
|
||||
}
|
||||
|
||||
String classOrPrimitiveName = null;
|
||||
if (containingClass != null) {
|
||||
classOrPrimitiveName = getClassReferenceName(containingClass);
|
||||
}
|
||||
else if (newExprType instanceof PsiArrayType){
|
||||
final PsiType deepComponentType = newExprType.getDeepComponentType();
|
||||
if (deepComponentType instanceof PsiPrimitiveType) {
|
||||
classOrPrimitiveName = deepComponentType.getCanonicalText();
|
||||
}
|
||||
}
|
||||
|
||||
if (classOrPrimitiveName == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int dim = newExprType.getArrayDimensions();
|
||||
while (dim-- > 0) {
|
||||
classOrPrimitiveName += "[]";
|
||||
}
|
||||
return classOrPrimitiveName;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String getQualifierTextByMethodCall(final PsiMethodCallExpression methodCall,
|
||||
final PsiType functionalInterfaceType,
|
||||
final PsiVariable[] parameters,
|
||||
final PsiMethod psiMethod,
|
||||
final PsiSubstitutor substitutor) {
|
||||
|
||||
final PsiExpression qualifierExpression = methodCall.getMethodExpression().getQualifierExpression();
|
||||
|
||||
final PsiClass containingClass = psiMethod.getContainingClass();
|
||||
LOG.assertTrue(containingClass != null);
|
||||
|
||||
if (qualifierExpression != null) {
|
||||
boolean isReceiverType = false;
|
||||
if (qualifierExpression instanceof PsiReferenceExpression && ArrayUtil.find(parameters, ((PsiReferenceExpression)qualifierExpression).resolve()) > -1) {
|
||||
isReceiverType = PsiMethodReferenceUtil.isReceiverType(PsiMethodReferenceUtil.getFirstParameterType(functionalInterfaceType, qualifierExpression), containingClass, substitutor);
|
||||
}
|
||||
return isReceiverType ? composeReceiverQualifierText(parameters, psiMethod, containingClass, qualifierExpression)
|
||||
: qualifierExpression.getText();
|
||||
}
|
||||
else {
|
||||
if (psiMethod.hasModifierProperty(PsiModifier.STATIC)) {
|
||||
return getClassReferenceName(containingClass);
|
||||
}
|
||||
else {
|
||||
PsiClass parentContainingClass = PsiTreeUtil.getParentOfType(methodCall, PsiClass.class);
|
||||
if (parentContainingClass instanceof PsiAnonymousClass) {
|
||||
parentContainingClass = PsiTreeUtil.getParentOfType(parentContainingClass, PsiClass.class, true);
|
||||
}
|
||||
PsiClass treeContainingClass = parentContainingClass;
|
||||
while (treeContainingClass != null && !InheritanceUtil.isInheritorOrSelf(treeContainingClass, containingClass, true)) {
|
||||
treeContainingClass = PsiTreeUtil.getParentOfType(treeContainingClass, PsiClass.class, true);
|
||||
}
|
||||
if (treeContainingClass != null && containingClass != parentContainingClass && treeContainingClass != parentContainingClass) {
|
||||
final String treeContainingClassName = treeContainingClass.getName();
|
||||
if (treeContainingClassName == null) {
|
||||
return null;
|
||||
}
|
||||
return treeContainingClassName + ".this";
|
||||
}
|
||||
else {
|
||||
return "this";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String composeReceiverQualifierText(PsiVariable[] parameters,
|
||||
PsiMethod psiMethod,
|
||||
PsiClass containingClass,
|
||||
@NotNull PsiExpression qualifierExpression) {
|
||||
if (psiMethod.hasModifierProperty(PsiModifier.STATIC)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final PsiMethod nonAmbiguousMethod = getNonAmbiguousReceiver(parameters, psiMethod);
|
||||
if (nonAmbiguousMethod == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final PsiClass nonAmbiguousContainingClass = nonAmbiguousMethod.getContainingClass();
|
||||
if (!containingClass.equals(nonAmbiguousContainingClass)) {
|
||||
return getClassReferenceName(nonAmbiguousContainingClass);
|
||||
}
|
||||
|
||||
if (containingClass.isPhysical() &&
|
||||
qualifierExpression instanceof PsiReferenceExpression &&
|
||||
!PsiTypesUtil.isGetClass(psiMethod) &&
|
||||
ArrayUtil.find(parameters, ((PsiReferenceExpression)qualifierExpression).resolve()) > -1) {
|
||||
return getClassReferenceName(containingClass);
|
||||
}
|
||||
|
||||
final PsiType qualifierExpressionType = qualifierExpression.getType();
|
||||
if (qualifierExpressionType != null && !FunctionalInterfaceParameterizationUtil.isWildcardParameterized(qualifierExpressionType)) {
|
||||
try {
|
||||
final String canonicalText = qualifierExpressionType.getCanonicalText();
|
||||
JavaPsiFacade.getElementFactory(containingClass.getProject()).createExpressionFromText(canonicalText + "::foo", qualifierExpression);
|
||||
return canonicalText;
|
||||
}
|
||||
catch (IncorrectOperationException ignore){}
|
||||
}
|
||||
return getClassReferenceName(containingClass);
|
||||
}
|
||||
|
||||
private static String getClassReferenceName(PsiClass containingClass) {
|
||||
final String qualifiedName = containingClass.getQualifiedName();
|
||||
if (qualifiedName != null) {
|
||||
return qualifiedName;
|
||||
}
|
||||
else {
|
||||
return containingClass.getName();
|
||||
}
|
||||
}
|
||||
|
||||
private static class ReplaceWithMethodRefFix implements LocalQuickFix {
|
||||
@NotNull
|
||||
@Override
|
||||
public String getFamilyName() {
|
||||
return "Replace lambda with method reference";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
|
||||
PsiElement element = descriptor.getPsiElement();
|
||||
if (!FileModificationService.getInstance().preparePsiElementForWrite(element)) return;
|
||||
if (element instanceof PsiLambdaExpression) {
|
||||
element = LambdaUtil.extractSingleExpressionFromBody(((PsiLambdaExpression)element).getBody());
|
||||
}
|
||||
final PsiLambdaExpression lambdaExpression = PsiTreeUtil.getParentOfType(element, PsiLambdaExpression.class);
|
||||
if (lambdaExpression == null) return;
|
||||
tryConvertToMethodReference(lambdaExpression, element);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
static PsiExpression tryConvertToMethodReference(@NotNull PsiLambdaExpression lambda, PsiElement body) {
|
||||
Project project = lambda.getProject();
|
||||
PsiType functionalInterfaceType = lambda.getFunctionalInterfaceType();
|
||||
if (functionalInterfaceType == null || !functionalInterfaceType.isValid()) return lambda;
|
||||
final PsiType denotableFunctionalInterfaceType = RefactoringChangeUtil.getTypeByExpression(lambda);
|
||||
if (denotableFunctionalInterfaceType == null) return lambda;
|
||||
|
||||
Collection<PsiComment> comments = ContainerUtil.map(PsiTreeUtil.findChildrenOfType(lambda, PsiComment.class),
|
||||
(comment) -> (PsiComment)comment.copy());
|
||||
|
||||
final String methodRefText = createMethodReferenceText(body, functionalInterfaceType, lambda.getParameterList().getParameters());
|
||||
|
||||
if (methodRefText != null) {
|
||||
final PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
|
||||
final PsiExpression psiExpression = factory.createExpressionFromText(methodRefText, lambda);
|
||||
final SmartTypePointer typePointer = SmartTypePointerManager.getInstance(project).createSmartTypePointer(denotableFunctionalInterfaceType);
|
||||
PsiExpression replace = (PsiExpression)lambda.replace(psiExpression);
|
||||
final PsiType functionalTypeAfterReplacement = GenericsUtil.getVariableTypeByExpressionType(((PsiMethodReferenceExpression)replace).getFunctionalInterfaceType());
|
||||
functionalInterfaceType = typePointer.getType();
|
||||
if (functionalInterfaceType != null && (functionalTypeAfterReplacement == null ||
|
||||
!functionalTypeAfterReplacement.equals(functionalInterfaceType))) { //ambiguity
|
||||
final PsiTypeCastExpression cast = (PsiTypeCastExpression)factory.createExpressionFromText("(A)a", replace);
|
||||
PsiTypeElement castType = cast.getCastType();
|
||||
LOG.assertTrue(castType != null);
|
||||
castType.replace(factory.createTypeElement(functionalInterfaceType));
|
||||
PsiExpression castOperand = cast.getOperand();
|
||||
LOG.assertTrue(castOperand != null);
|
||||
castOperand.replace(replace);
|
||||
replace = (PsiExpression)replace.replace(cast);
|
||||
}
|
||||
|
||||
AnonymousCanBeLambdaInspection.restoreComments(comments, replace);
|
||||
JavaCodeStyleManager.getInstance(project).shortenClassReferences(replace);
|
||||
return replace;
|
||||
}
|
||||
return lambda;
|
||||
}
|
||||
}
|
||||
-278
@@ -1,278 +0,0 @@
|
||||
/*
|
||||
* 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.codeInspection.java18api;
|
||||
|
||||
import com.intellij.codeInsight.FileModificationService;
|
||||
import com.intellij.codeInsight.daemon.QuickFixBundle;
|
||||
import com.intellij.codeInspection.*;
|
||||
import com.intellij.codeInspection.util.LambdaGenerationUtil;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.CodeStyleManager;
|
||||
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
|
||||
import com.intellij.psi.codeStyle.SuggestedNameInfo;
|
||||
import com.intellij.psi.codeStyle.VariableKind;
|
||||
import com.intellij.psi.controlFlow.DefUseUtil;
|
||||
import com.intellij.psi.search.searches.ReferencesSearch;
|
||||
import com.intellij.psi.tree.IElementType;
|
||||
import com.intellij.psi.util.InheritanceUtil;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.siyeh.ig.psiutils.CommentTracker;
|
||||
import com.siyeh.ig.psiutils.ControlFlowUtils;
|
||||
import one.util.streamex.MoreCollectors;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* @author Tagir Valeev
|
||||
*/
|
||||
public class Java8CollectionRemoveIfInspection extends BaseJavaBatchLocalInspectionTool {
|
||||
@NotNull
|
||||
@Override
|
||||
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
|
||||
if (!PsiUtil.isLanguageLevel8OrHigher(holder.getFile())) {
|
||||
return PsiElementVisitor.EMPTY_VISITOR;
|
||||
}
|
||||
return new JavaElementVisitor() {
|
||||
void handleIteratorLoop(PsiLoopStatement statement, PsiJavaToken endToken, IteratorDeclaration declaration) {
|
||||
if (endToken == null) return;
|
||||
PsiStatement body = statement.getBody();
|
||||
if(!(body instanceof PsiBlockStatement)) return;
|
||||
PsiStatement[] statements = ((PsiBlockStatement)body).getCodeBlock().getStatements();
|
||||
if (statements.length == 2 && statements[1] instanceof PsiIfStatement) {
|
||||
PsiVariable element = declaration.getNextElementVariable(statements[0]);
|
||||
if (element == null) return;
|
||||
PsiIfStatement ifStatement = (PsiIfStatement)statements[1];
|
||||
if(checkAndExtractCondition(declaration, ifStatement) == null) return;
|
||||
registerProblem(statement, endToken);
|
||||
}
|
||||
else if (statements.length == 1 && statements[0] instanceof PsiIfStatement){
|
||||
PsiIfStatement ifStatement = (PsiIfStatement)statements[0];
|
||||
PsiExpression condition = checkAndExtractCondition(declaration, ifStatement);
|
||||
if (condition == null) return;
|
||||
PsiElement ref = declaration.findOnlyIteratorRef(condition);
|
||||
if (ref != null && declaration.isIteratorMethodCall(ref.getParent().getParent(), "next") && isAlwaysExecuted(condition, ref)) {
|
||||
registerProblem(statement, endToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isAlwaysExecuted(PsiExpression condition, PsiElement ref) {
|
||||
while(ref != condition) {
|
||||
PsiElement parent = ref.getParent();
|
||||
if(parent instanceof PsiPolyadicExpression) {
|
||||
PsiPolyadicExpression polyadicExpression = (PsiPolyadicExpression)parent;
|
||||
IElementType type = polyadicExpression.getOperationTokenType();
|
||||
if ((type.equals(JavaTokenType.ANDAND) || type.equals(JavaTokenType.OROR)) && polyadicExpression.getOperands()[0] != ref) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if(parent instanceof PsiConditionalExpression && ((PsiConditionalExpression)parent).getCondition() != ref) {
|
||||
return false;
|
||||
}
|
||||
ref = parent;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void registerProblem(PsiLoopStatement statement, PsiJavaToken endToken) {
|
||||
//noinspection DialogTitleCapitalization
|
||||
holder.registerProblem(statement, new TextRange(0, endToken.getTextOffset() - statement.getTextOffset() + 1),
|
||||
QuickFixBundle.message("java.8.collection.removeif.inspection.description"),
|
||||
new ReplaceWithRemoveIfQuickFix());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private PsiExpression checkAndExtractCondition(IteratorDeclaration declaration,
|
||||
PsiIfStatement ifStatement) {
|
||||
PsiExpression condition = ifStatement.getCondition();
|
||||
if (condition == null || ifStatement.getElseBranch() != null) return null;
|
||||
PsiStatement thenStatement = ControlFlowUtils.stripBraces(ifStatement.getThenBranch());
|
||||
if (!(thenStatement instanceof PsiExpressionStatement)) return null;
|
||||
if (!declaration.isIteratorMethodCall(((PsiExpressionStatement)thenStatement).getExpression(), "remove")) return null;
|
||||
if (!LambdaGenerationUtil.canBeUncheckedLambda(condition)) return null;
|
||||
return condition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitForStatement(PsiForStatement statement) {
|
||||
super.visitForStatement(statement);
|
||||
PsiStatement initialization = statement.getInitialization();
|
||||
IteratorDeclaration declaration = IteratorDeclaration.extract(initialization);
|
||||
if(declaration == null) return;
|
||||
if(statement.getUpdate() != null) return;
|
||||
if(!declaration.isHasNextCall(statement.getCondition())) return;
|
||||
handleIteratorLoop(statement, statement.getRParenth(), declaration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitWhileStatement(PsiWhileStatement statement) {
|
||||
super.visitWhileStatement(statement);
|
||||
PsiElement previous = PsiTreeUtil.skipSiblingsBackward(statement, PsiComment.class, PsiWhiteSpace.class);
|
||||
if(!(previous instanceof PsiDeclarationStatement)) return;
|
||||
IteratorDeclaration declaration = IteratorDeclaration.extract((PsiStatement)previous);
|
||||
if(declaration == null || !declaration.isHasNextCall(statement.getCondition())) return;
|
||||
if(!ReferencesSearch.search(declaration.myIterator, declaration.myIterator.getUseScope()).forEach(ref -> {
|
||||
return PsiTreeUtil.isAncestor(statement, ref.getElement(), true);
|
||||
})) return;
|
||||
handleIteratorLoop(statement, statement.getRParenth(), declaration);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static class ReplaceWithRemoveIfQuickFix implements LocalQuickFix {
|
||||
@Nls
|
||||
@NotNull
|
||||
@Override
|
||||
public String getFamilyName() {
|
||||
return QuickFixBundle.message("java.8.collection.removeif.inspection.fix.name");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
|
||||
PsiElement element = descriptor.getStartElement();
|
||||
if(!(element instanceof PsiLoopStatement)) return;
|
||||
PsiLoopStatement loop = (PsiLoopStatement)element;
|
||||
IteratorDeclaration declaration;
|
||||
PsiElement previous = null;
|
||||
if(loop instanceof PsiForStatement) {
|
||||
declaration = IteratorDeclaration.extract(((PsiForStatement)loop).getInitialization());
|
||||
} else if(loop instanceof PsiWhileStatement) {
|
||||
previous = PsiTreeUtil.skipSiblingsBackward(loop, PsiComment.class, PsiWhiteSpace.class);
|
||||
if(!(previous instanceof PsiDeclarationStatement)) return;
|
||||
declaration = IteratorDeclaration.extract((PsiStatement)previous);
|
||||
} else return;
|
||||
if(declaration == null) return;
|
||||
PsiStatement body = loop.getBody();
|
||||
if(!(body instanceof PsiBlockStatement)) return;
|
||||
PsiStatement[] statements = ((PsiBlockStatement)body).getCodeBlock().getStatements();
|
||||
PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
|
||||
String replacement = null;
|
||||
if (!FileModificationService.getInstance().preparePsiElementForWrite(element)) return;
|
||||
CommentTracker ct = new CommentTracker();
|
||||
if (statements.length == 2 && statements[1] instanceof PsiIfStatement) {
|
||||
PsiVariable variable = declaration.getNextElementVariable(statements[0]);
|
||||
if (variable == null) return;
|
||||
PsiExpression condition = ((PsiIfStatement)statements[1]).getCondition();
|
||||
if (condition == null) return;
|
||||
replacement = generateRemoveIf(declaration, ct, condition, variable.getName());
|
||||
}
|
||||
else if (statements.length == 1 && statements[0] instanceof PsiIfStatement){
|
||||
PsiExpression condition = ((PsiIfStatement)statements[0]).getCondition();
|
||||
if (condition == null) return;
|
||||
PsiElement ref = declaration.findOnlyIteratorRef(condition);
|
||||
if(ref != null) {
|
||||
PsiElement call = ref.getParent().getParent();
|
||||
if(!declaration.isIteratorMethodCall(call, "next")) return;
|
||||
PsiType type = ((PsiExpression)call).getType();
|
||||
JavaCodeStyleManager javaCodeStyleManager = JavaCodeStyleManager.getInstance(project);
|
||||
SuggestedNameInfo info = javaCodeStyleManager.suggestVariableName(VariableKind.PARAMETER, null, null, type);
|
||||
if(info.names.length == 0) {
|
||||
info = javaCodeStyleManager.suggestVariableName(VariableKind.PARAMETER, "value", null, type);
|
||||
}
|
||||
String paramName = javaCodeStyleManager.suggestUniqueVariableName(info, condition, true).names[0];
|
||||
ct.replace(call, factory.createIdentifier(paramName));
|
||||
replacement = generateRemoveIf(declaration, ct, condition, paramName);
|
||||
}
|
||||
}
|
||||
if (replacement == null) return;
|
||||
if (previous != null) ct.delete(previous);
|
||||
PsiElement result = ct.replaceAndRestoreComments(loop, factory.createStatementFromText(replacement, loop));
|
||||
LambdaCanBeMethodReferenceInspection.replaceAllLambdasWithMethodReferences(result);
|
||||
CodeStyleManager.getInstance(project).reformat(result);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String generateRemoveIf(IteratorDeclaration declaration, CommentTracker ct,
|
||||
PsiExpression condition, String paramName) {
|
||||
return (declaration.myCollection == null ? "" : ct.text(declaration.myCollection) + ".") +
|
||||
"removeIf(" + paramName + "->" + ct.text(condition) + ");";
|
||||
}
|
||||
}
|
||||
|
||||
private static class IteratorDeclaration {
|
||||
private final @NotNull PsiLocalVariable myIterator;
|
||||
private final @Nullable PsiExpression myCollection;
|
||||
|
||||
private IteratorDeclaration(@NotNull PsiLocalVariable iterator, @Nullable PsiExpression collection) {
|
||||
myIterator = iterator;
|
||||
myCollection = collection;
|
||||
}
|
||||
|
||||
public boolean isHasNextCall(PsiExpression condition) {
|
||||
return isIteratorMethodCall(condition, "hasNext");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public PsiElement findOnlyIteratorRef(PsiExpression parent) {
|
||||
PsiElement element = PsiUtil.getVariableCodeBlock(myIterator, null);
|
||||
PsiCodeBlock block =
|
||||
element instanceof PsiCodeBlock ? (PsiCodeBlock)element : PsiTreeUtil.getParentOfType(element, PsiCodeBlock.class);
|
||||
if(block == null) return null;
|
||||
return StreamEx.of(DefUseUtil.getRefs(block, myIterator, myIterator.getInitializer()))
|
||||
.filter(e -> PsiTreeUtil.isAncestor(parent, e, false))
|
||||
.collect(MoreCollectors.onlyOne()).orElse(null);
|
||||
}
|
||||
|
||||
boolean isIteratorMethodCall(PsiElement candidate, String method) {
|
||||
if(!(candidate instanceof PsiMethodCallExpression)) return false;
|
||||
PsiMethodCallExpression call = (PsiMethodCallExpression)candidate;
|
||||
if(call.getArgumentList().getExpressions().length != 0) return false;
|
||||
PsiReferenceExpression expression = call.getMethodExpression();
|
||||
if(!method.equals(expression.getReferenceName())) return false;
|
||||
PsiExpression qualifier = expression.getQualifierExpression();
|
||||
if(!(qualifier instanceof PsiReferenceExpression)) return false;
|
||||
return ((PsiReferenceExpression)qualifier).isReferenceTo(myIterator);
|
||||
}
|
||||
|
||||
public PsiVariable getNextElementVariable(PsiStatement statement) {
|
||||
if(!(statement instanceof PsiDeclarationStatement)) return null;
|
||||
PsiDeclarationStatement declaration = (PsiDeclarationStatement)statement;
|
||||
if(declaration.getDeclaredElements().length != 1) return null;
|
||||
PsiElement element = declaration.getDeclaredElements()[0];
|
||||
if(!(element instanceof PsiLocalVariable)) return null;
|
||||
PsiLocalVariable var = (PsiLocalVariable)element;
|
||||
if(!isIteratorMethodCall(var.getInitializer(), "next")) return null;
|
||||
return var;
|
||||
}
|
||||
|
||||
@Contract("null -> null")
|
||||
static IteratorDeclaration extract(PsiStatement statement) {
|
||||
if(!(statement instanceof PsiDeclarationStatement)) return null;
|
||||
PsiDeclarationStatement declaration = (PsiDeclarationStatement)statement;
|
||||
if(declaration.getDeclaredElements().length != 1) return null;
|
||||
PsiElement element = declaration.getDeclaredElements()[0];
|
||||
if(!(element instanceof PsiLocalVariable)) return null;
|
||||
PsiLocalVariable variable = (PsiLocalVariable)element;
|
||||
PsiExpression initializer = variable.getInitializer();
|
||||
if(!(initializer instanceof PsiMethodCallExpression)) return null;
|
||||
PsiMethodCallExpression call = (PsiMethodCallExpression)initializer;
|
||||
if(call.getArgumentList().getExpressions().length != 0) return null;
|
||||
PsiReferenceExpression methodExpression = call.getMethodExpression();
|
||||
if(!"iterator".equals(methodExpression.getReferenceName())) return null;
|
||||
PsiMethod method = call.resolveMethod();
|
||||
if(method == null || !InheritanceUtil.isInheritor(method.getContainingClass(), CommonClassNames.JAVA_UTIL_COLLECTION)) return null;
|
||||
PsiType type = variable.getType();
|
||||
if(!(type instanceof PsiClassType) || !((PsiClassType)type).rawType().equalsToText(CommonClassNames.JAVA_UTIL_ITERATOR)) return null;
|
||||
return new IteratorDeclaration(variable, methodExpression.getQualifierExpression());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user