Java8CollectionsApiInspection & Java8ReplaceMapGetInspection merged into Java8MapApiInspection; support method references in computeIfAbsent

fix for IDEA-163932 Map.get suggested replacement with Map.getOrDefault is misleading
This commit is contained in:
Tagir Valeev
2016-11-29 13:35:30 +07:00
parent 6ced0a3630
commit 5bcd2410da
60 changed files with 751 additions and 796 deletions
@@ -1,296 +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.daemon.QuickFixBundle;
import com.intellij.codeInspection.BaseJavaBatchLocalInspectionTool;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.codeInspection.ui.MultipleCheckboxOptionsPanel;
import com.intellij.codeInspection.util.LambdaGenerationUtil;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtil;
import com.siyeh.ig.psiutils.EquivalenceChecker;
import com.siyeh.ig.psiutils.ExpressionUtils;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
/**
* @author Dmitry Batkovich
*/
public class Java8CollectionsApiInspection extends BaseJavaBatchLocalInspectionTool {
private static final Logger LOG = Logger.getInstance(Java8CollectionsApiInspection.class);
public boolean myReportContainsCondition;
public boolean mySuggestPutIfAbsentForComplexExpression;
@Nullable
@Override
public JComponent createOptionsPanel() {
MultipleCheckboxOptionsPanel panel = new MultipleCheckboxOptionsPanel(this);
panel.addCheckbox("Report when \'containsKey\' is used in condition (may change semantics)", "myReportContainsCondition");
panel.addCheckbox("Suggest to replace with \'putIfAbsent\' if value is complex expression (may change semantics)", "mySuggestPutIfAbsentForComplexExpression");
return panel;
}
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
if (!PsiUtil.isLanguageLevel8OrHigher(holder.getFile())) {
return PsiElementVisitor.EMPTY_VISITOR;
}
return new JavaElementVisitor() {
@Override
public void visitConditionalExpression(PsiConditionalExpression expression) {
final ConditionInfo conditionInfo = extractConditionInfo(expression.getCondition());
if (conditionInfo == null) return;
final PsiExpression thenExpression = expression.getThenExpression();
final PsiExpression elseExpression = expression.getElseExpression();
if (thenExpression == null || elseExpression == null) return;
analyzeCorrespondenceOfPutAndGet(conditionInfo.isInverted() ? thenExpression : elseExpression,
conditionInfo.isInverted() ? elseExpression : thenExpression,
conditionInfo.getQualifier(), conditionInfo.getContainsKey(),
holder, expression);
}
@Override
public void visitIfStatement(PsiIfStatement statement) {
final PsiExpression condition = statement.getCondition();
final ConditionInfo conditionInfo = extractConditionInfo(condition);
if (conditionInfo == null) return;
PsiStatement maybeGetBranch = conditionInfo.isInverted() ? statement.getElseBranch() : statement.getThenBranch();
if (maybeGetBranch instanceof PsiBlockStatement) {
final PsiStatement[] getBranchStatements = ((PsiBlockStatement)maybeGetBranch).getCodeBlock().getStatements();
if (getBranchStatements.length > 1) return;
maybeGetBranch = getBranchStatements.length == 0 ? null : getBranchStatements[0];
}
final PsiStatement branch = conditionInfo.isInverted() ? statement.getThenBranch() : statement.getElseBranch();
final PsiStatement maybePutStatement;
if (branch instanceof PsiBlockStatement) {
final PsiStatement[] statements = ((PsiBlockStatement)branch).getCodeBlock().getStatements();
if (statements.length != 1) return;
maybePutStatement = statements[statements.length - 1];
}
else {
maybePutStatement = branch;
}
if (maybePutStatement != null) {
analyzeCorrespondenceOfPutAndGet(maybePutStatement, maybeGetBranch, conditionInfo.getQualifier(), conditionInfo.getContainsKey(),
holder, statement);
}
}
};
}
@Nullable
private ConditionInfo extractConditionInfo(PsiExpression condition) {
final ConditionInfo info = extractConditionInfoIfGet(condition);
if (info != null) {
return info;
}
return !myReportContainsCondition ? null : extractConditionInfoIfContains(condition);
}
@Nullable
static PsiExpression getValueComparedWithNull(PsiBinaryExpression binOp) {
if(!binOp.getOperationTokenType().equals(JavaTokenType.EQEQ) &&
!binOp.getOperationTokenType().equals(JavaTokenType.NE)) return null;
PsiExpression left = binOp.getLOperand();
PsiExpression right = binOp.getROperand();
if(ExpressionUtils.isNullLiteral(right)) return left;
if(ExpressionUtils.isNullLiteral(left)) return right;
return null;
}
@Nullable
private static ConditionInfo extractConditionInfoIfGet(PsiExpression condition) {
if(!(condition instanceof PsiBinaryExpression)) return null;
PsiBinaryExpression binOp = (PsiBinaryExpression)condition;
PsiExpression operand = getValueComparedWithNull(binOp);
if (!(operand instanceof PsiMethodCallExpression)) return null;
final PsiMethodCallExpression maybeGetCall = (PsiMethodCallExpression)operand;
if (!isJavaUtilMapMethodWithName(maybeGetCall, "get")) return null;
final PsiExpression[] arguments = maybeGetCall.getArgumentList().getExpressions();
if (arguments.length != 1) return null;
PsiExpression getQualifier = maybeGetCall.getMethodExpression().getQualifierExpression();
PsiExpression keyExpression = arguments[0];
return new ConditionInfo(getQualifier, keyExpression, binOp.getOperationTokenType().equals(JavaTokenType.EQEQ));
}
@Nullable
private static ConditionInfo extractConditionInfoIfContains(PsiExpression condition) {
boolean inverted = false;
final PsiMethodCallExpression conditionMethodCall;
if (condition instanceof PsiPrefixExpression) {
final PsiPrefixExpression prefixExpression = (PsiPrefixExpression)condition;
if (JavaTokenType.EXCL.equals(prefixExpression.getOperationSign().getTokenType()) &&
prefixExpression.getOperand() instanceof PsiMethodCallExpression) {
conditionMethodCall = (PsiMethodCallExpression)prefixExpression.getOperand();
inverted = true;
}
else {
return null;
}
}
else if (condition instanceof PsiMethodCallExpression) {
conditionMethodCall = (PsiMethodCallExpression)condition;
}
else {
return null;
}
if (!isJavaUtilMapMethodWithName(conditionMethodCall, "containsKey")) {
return null;
}
final PsiExpression containsQualifier = conditionMethodCall.getMethodExpression().getQualifierExpression();
if (containsQualifier == null) {
return null;
}
final PsiExpression[] expressions = conditionMethodCall.getArgumentList().getExpressions();
if (expressions.length != 1) {
return null;
}
PsiExpression containsKey = expressions[0];
return new ConditionInfo(containsQualifier, containsKey, inverted);
}
private void analyzeCorrespondenceOfPutAndGet(@NotNull PsiElement adjustedElseBranch,
@Nullable PsiElement adjustedThenBranch,
@Nullable PsiExpression containsQualifier,
@Nullable PsiExpression containsKey,
@NotNull ProblemsHolder holder,
@NotNull PsiElement context) {
final PsiElement maybePutMethodCall;
final PsiElement maybeGetMethodCall;
if (adjustedThenBranch == null) {
maybeGetMethodCall = null;
if (adjustedElseBranch instanceof PsiExpressionStatement) {
final PsiExpression expression = ((PsiExpressionStatement)adjustedElseBranch).getExpression();
if (expression instanceof PsiMethodCallExpression && isJavaUtilMapMethodWithName((PsiMethodCallExpression)expression, "put")) {
maybePutMethodCall = expression;
}
else {
maybePutMethodCall = null;
}
}
else {
maybePutMethodCall = null;
}
}
else {
if (adjustedElseBranch instanceof PsiStatement && adjustedThenBranch instanceof PsiStatement) {
final EquivalenceChecker.Decision decision = EquivalenceChecker.getCanonicalPsiEquivalence().statementsAreEquivalentDecision((PsiStatement)adjustedElseBranch,
(PsiStatement)adjustedThenBranch);
maybePutMethodCall = decision.getLeftDiff();
maybeGetMethodCall = decision.getRightDiff();
}
else {
maybePutMethodCall = adjustedElseBranch;
maybeGetMethodCall = adjustedThenBranch;
}
}
if (maybePutMethodCall instanceof PsiMethodCallExpression &&
(maybeGetMethodCall == null || maybeGetMethodCall instanceof PsiMethodCallExpression)) {
final PsiMethodCallExpression putMethodCall = (PsiMethodCallExpression)maybePutMethodCall;
final PsiMethodCallExpression getMethodCall = (PsiMethodCallExpression)maybeGetMethodCall;
final PsiExpression putQualifier = putMethodCall.getMethodExpression().getQualifierExpression();
final PsiExpression getQualifier = getMethodCall == null ? null : getMethodCall.getMethodExpression().getQualifierExpression();
if ((getMethodCall == null || EquivalenceChecker.getCanonicalPsiEquivalence().expressionsAreEquivalent(putQualifier, getQualifier)) &&
EquivalenceChecker.getCanonicalPsiEquivalence().expressionsAreEquivalent(putQualifier, containsQualifier) &&
isJavaUtilMapMethodWithName(putMethodCall, "put") &&
(getMethodCall == null || isJavaUtilMapMethodWithName(getMethodCall, "get"))) {
PsiExpression getArgument;
if (getMethodCall != null) {
final PsiExpression[] arguments = getMethodCall.getArgumentList().getExpressions();
if (arguments.length != 1) {
return;
}
getArgument = arguments[0];
}
else {
getArgument = null;
}
final PsiExpression[] putArguments = putMethodCall.getArgumentList().getExpressions();
if (putArguments.length != 2) {
return;
}
PsiExpression putKeyArgument = putArguments[0];
PsiExpression putValueArgument = putArguments[1];
if (EquivalenceChecker.getCanonicalPsiEquivalence().expressionsAreEquivalent(containsKey, putKeyArgument) &&
(getArgument == null || EquivalenceChecker.getCanonicalPsiEquivalence().expressionsAreEquivalent(getArgument, putKeyArgument))) {
LocalQuickFix fix = null;
if (ExpressionUtils.isSimpleExpression(putValueArgument)) {
fix = new ReplaceConditionalMapPutFix(putMethodCall, false);
}
else if ((maybePutMethodCall.getParent() instanceof PsiExpressionStatement) && // only if result of put is not used
LambdaGenerationUtil.canBeUncheckedLambda(putValueArgument)) {
fix = new ReplaceConditionalMapPutFix(putMethodCall, true);
}
else if (mySuggestPutIfAbsentForComplexExpression) {
fix = new ReplaceConditionalMapPutFix(putMethodCall, false);
}
if(fix != null) {
holder.registerProblem(context, QuickFixBundle.message("java.8.collections.api.inspection.description"), fix);
}
}
}
}
}
static boolean isJavaUtilMapMethodWithName(@NotNull PsiMethodCallExpression methodCallExpression, @NotNull String expectedName) {
if (!expectedName.equals(methodCallExpression.getMethodExpression().getReferenceName())) {
return false;
}
final PsiMethod method = methodCallExpression.resolveMethod();
if (method == null) return false;
PsiMethod[] superMethods = method.findDeepestSuperMethods();
if (superMethods.length == 0) {
superMethods = new PsiMethod[]{method};
}
return StreamEx.of(superMethods).map(PsiMember::getContainingClass).nonNull().map(PsiClass::getQualifiedName)
.has(CommonClassNames.JAVA_UTIL_MAP);
}
private static class ConditionInfo {
private final PsiExpression myQualifier;
private final PsiExpression myContainsKey;
private final boolean myInverted;
private ConditionInfo(PsiExpression qualifier, PsiExpression containsKey, boolean inverted) {
myQualifier = qualifier;
myContainsKey = containsKey;
myInverted = inverted;
}
public PsiExpression getQualifier() {
return myQualifier;
}
public PsiExpression getContainsKey() {
return myContainsKey;
}
public boolean isInverted() {
return myInverted;
}
}
}
@@ -1,277 +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.daemon.QuickFixBundle;
import com.intellij.codeInspection.BaseJavaBatchLocalInspectionTool;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.codeInspection.ui.MultipleCheckboxOptionsPanel;
import com.intellij.codeInspection.util.LambdaGenerationUtil;
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.intellij.psi.util.PsiUtil;
import com.siyeh.ig.psiutils.CommentTracker;
import com.siyeh.ig.psiutils.ControlFlowUtils;
import com.siyeh.ig.psiutils.EquivalenceChecker;
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.*;
/**
* @author Tagir Valeev
*/
public class Java8ReplaceMapGetInspection extends BaseJavaBatchLocalInspectionTool {
public boolean mySuggestMapGetOrDefault = true;
public boolean mySuggestMapComputeIfAbsent = true;
public boolean mySuggestMapPutIfAbsent = true;
@Nullable
@Override
public JComponent createOptionsPanel() {
MultipleCheckboxOptionsPanel panel = new MultipleCheckboxOptionsPanel(this);
panel.addCheckbox("Suggest conversion to Map.computeIfAbsent", "mySuggestMapComputeIfAbsent");
panel.addCheckbox("Suggest conversion to Map.getOrDefault", "mySuggestMapGetOrDefault");
panel.addCheckbox("Suggest conversion to Map.putIfAbsent", "mySuggestMapPutIfAbsent");
return panel;
}
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
if (!PsiUtil.isLanguageLevel8OrHigher(holder.getFile())) {
return PsiElementVisitor.EMPTY_VISITOR;
}
return new JavaElementVisitor() {
@Override
public void visitIfStatement(PsiIfStatement statement) {
if(statement.getElseBranch() != null) return;
PsiExpression condition = statement.getCondition();
PsiReferenceExpression value = getReferenceComparedWithNull(condition);
if (value == null) return;
PsiElement previous = PsiTreeUtil.skipSiblingsBackward(statement, PsiWhiteSpace.class, PsiComment.class);
PsiMethodCallExpression getCall = tryExtractMapGetCall(value, previous);
if(getCall == null) return;
PsiExpression[] getArguments = getCall.getArgumentList().getExpressions();
if(getArguments.length != 1) return;
PsiStatement thenBranch = ControlFlowUtils.stripBraces(statement.getThenBranch());
PsiAssignmentExpression assignment = ExpressionUtils.getAssignment(thenBranch);
EquivalenceChecker equivalence = EquivalenceChecker.getCanonicalPsiEquivalence();
if(assignment != null) {
/*
value = map.get(key);
if(value == null) {
value = ...
}
*/
if (!mySuggestMapGetOrDefault) return;
if (ExpressionUtils.isSimpleExpression(assignment.getRExpression()) &&
equivalence.expressionsAreEquivalent(assignment.getLExpression(), value) &&
!equivalence.expressionsAreEquivalent(assignment.getRExpression(), value)) {
holder.registerProblem(condition, QuickFixBundle.message("java.8.replace.map.get.inspection.description"),
new ReplaceGetNullCheck("getOrDefault"));
}
} else {
/*
value = map.get(key);
if(value == null) {
value = ...
map.put(key, value);
}
*/
PsiExpression key = getArguments[0];
PsiExpression mapExpression = getCall.getMethodExpression().getQualifierExpression();
PsiExpression lambdaCandidate = extractLambdaCandidate(thenBranch, mapExpression, key, value);
if (lambdaCandidate != null && mySuggestMapComputeIfAbsent) {
holder.registerProblem(condition, QuickFixBundle.message("java.8.replace.map.get.inspection.description"),
new ReplaceGetNullCheck("computeIfAbsent"));
}
if (lambdaCandidate == null && mySuggestMapPutIfAbsent) {
PsiExpression expression = extractPutValue(thenBranch, mapExpression, key);
if(ExpressionUtils.isSimpleExpression(expression) && !equivalence.expressionsAreEquivalent(expression, value)) {
holder.registerProblem(condition, QuickFixBundle.message("java.8.replace.map.get.inspection.description"),
new ReplaceGetNullCheck("putIfAbsent"));
}
}
}
}
};
}
@Nullable
static PsiExpression extractLambdaCandidate(PsiStatement statement, PsiExpression mapExpression,
PsiExpression keyExpression, PsiReferenceExpression valueExpression) {
EquivalenceChecker equivalence = EquivalenceChecker.getCanonicalPsiEquivalence();
PsiAssignmentExpression assignment;
PsiExpression putValue = extractPutValue(statement, mapExpression, keyExpression);
if(putValue != null) {
// like map.put(key, val = new ArrayList<>());
assignment = ExpressionUtils.getAssignment(putValue);
}
else {
if (!(statement instanceof PsiBlockStatement)) return null;
// like val = new ArrayList<>(); map.put(key, val);
PsiStatement[] statements = ((PsiBlockStatement)statement).getCodeBlock().getStatements();
if (statements.length != 2) return null;
putValue = extractPutValue(statements[1], mapExpression, keyExpression);
if (!equivalence.expressionsAreEquivalent(valueExpression, putValue)) return null;
assignment = ExpressionUtils.getAssignment(statements[0]);
}
if (assignment == null) return null;
PsiExpression lambdaCandidate = assignment.getRExpression();
if (lambdaCandidate == null || !equivalence.expressionsAreEquivalent(assignment.getLExpression(), valueExpression)) return null;
if (!LambdaGenerationUtil.canBeUncheckedLambda(lambdaCandidate)) return null;
return lambdaCandidate;
}
@Contract("null -> null")
@Nullable
private static PsiMethodCallExpression extractPutCall(PsiStatement statement) {
if(!(statement instanceof PsiExpressionStatement)) return null;
PsiExpression expression = ((PsiExpressionStatement)statement).getExpression();
if (!(expression instanceof PsiMethodCallExpression)) return null;
PsiMethodCallExpression putCall = (PsiMethodCallExpression)expression;
if (!Java8CollectionsApiInspection.isJavaUtilMapMethodWithName(putCall, "put")) return null;
return putCall;
}
@Nullable
private static PsiExpression extractPutValue(PsiStatement statement, PsiExpression mapExpression, PsiExpression keyExpression) {
PsiMethodCallExpression putCall = extractPutCall(statement);
if (putCall == null) return null;
PsiExpression[] putArguments = putCall.getArgumentList().getExpressions();
EquivalenceChecker equivalence = EquivalenceChecker.getCanonicalPsiEquivalence();
return putArguments.length == 2 &&
equivalence.expressionsAreEquivalent(putCall.getMethodExpression().getQualifierExpression(), mapExpression) &&
equivalence.expressionsAreEquivalent(keyExpression, putArguments[0]) ? putArguments[1] : null;
}
@Contract("null -> null")
@Nullable
private static PsiReferenceExpression getReferenceComparedWithNull(PsiExpression condition) {
if(!(condition instanceof PsiBinaryExpression)) return null;
PsiBinaryExpression binOp = (PsiBinaryExpression)condition;
if(!binOp.getOperationTokenType().equals(JavaTokenType.EQEQ)) return null;
PsiExpression value = Java8CollectionsApiInspection.getValueComparedWithNull(binOp);
if(!(value instanceof PsiReferenceExpression)) return null;
return (PsiReferenceExpression)value;
}
@Nullable
@Contract("_, null -> null")
static PsiMethodCallExpression tryExtractMapGetCall(PsiReferenceExpression target, PsiElement element) {
if(element instanceof PsiDeclarationStatement) {
PsiDeclarationStatement declaration = (PsiDeclarationStatement)element;
PsiElement[] elements = declaration.getDeclaredElements();
if(elements.length > 0) {
PsiElement lastDeclaration = elements[elements.length - 1];
if(lastDeclaration instanceof PsiLocalVariable && target.isReferenceTo(lastDeclaration)) {
PsiLocalVariable var = (PsiLocalVariable)lastDeclaration;
PsiExpression initializer = PsiUtil.skipParenthesizedExprDown(var.getInitializer());
if (initializer instanceof PsiMethodCallExpression &&
Java8CollectionsApiInspection.isJavaUtilMapMethodWithName((PsiMethodCallExpression)initializer, "get")) {
return (PsiMethodCallExpression)initializer;
}
}
}
}
PsiAssignmentExpression assignment = ExpressionUtils.getAssignment(element);
if(assignment != null) {
PsiExpression lValue = assignment.getLExpression();
if (lValue instanceof PsiReferenceExpression &&
EquivalenceChecker.getCanonicalPsiEquivalence().expressionsAreEquivalent(target, lValue)) {
PsiExpression rValue = PsiUtil.skipParenthesizedExprDown(assignment.getRExpression());
if (rValue instanceof PsiMethodCallExpression &&
Java8CollectionsApiInspection.isJavaUtilMapMethodWithName((PsiMethodCallExpression)rValue, "get")) {
return (PsiMethodCallExpression)rValue;
}
}
}
return null;
}
private static class ReplaceGetNullCheck implements LocalQuickFix {
private final String myMethodName;
ReplaceGetNullCheck(String methodName) {
myMethodName = methodName;
}
@Nls
@NotNull
@Override
public String getName() {
return QuickFixBundle.message("java.8.collections.api.inspection.fix.text", myMethodName);
}
@Nls
@NotNull
@Override
public String getFamilyName() {
return QuickFixBundle.message("java.8.replace.map.get.inspection.fix.family.name");
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
PsiElement element = descriptor.getStartElement();
PsiIfStatement ifStatement = PsiTreeUtil.getParentOfType(element, PsiIfStatement.class);
if(ifStatement == null) return;
PsiReferenceExpression value = getReferenceComparedWithNull(ifStatement.getCondition());
if(value == null) return;
PsiElement statement = PsiTreeUtil.skipSiblingsBackward(ifStatement, PsiWhiteSpace.class, PsiComment.class);
PsiMethodCallExpression getCall = tryExtractMapGetCall(value, statement);
if(getCall == null || !Java8CollectionsApiInspection.isJavaUtilMapMethodWithName(getCall, "get")) return;
PsiExpression[] args = getCall.getArgumentList().getExpressions();
if(args.length != 1) return;
PsiStatement thenBranch = ControlFlowUtils.stripBraces(ifStatement.getThenBranch());
PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
PsiAssignmentExpression assignment = ExpressionUtils.getAssignment(thenBranch);
CommentTracker ct = new CommentTracker();
PsiReferenceExpression methodExpression = getCall.getMethodExpression();
if(assignment != null) {
PsiExpression defaultValue = assignment.getRExpression();
if (!ExpressionUtils.isSimpleExpression(defaultValue)) return;
methodExpression.handleElementRename(myMethodName);
getCall.getArgumentList().add(ct.markUnchanged(defaultValue));
} else {
PsiExpression lambdaCandidate = extractLambdaCandidate(thenBranch, methodExpression.getQualifierExpression(), args[0], value);
if (lambdaCandidate == null) {
PsiExpression valueExpression = extractPutValue(thenBranch, methodExpression.getQualifierExpression(), args[0]);
if(ExpressionUtils.isSimpleExpression(valueExpression)) {
methodExpression.handleElementRename(myMethodName);
getCall.getArgumentList().add(ct.markUnchanged(valueExpression));
}
} else {
methodExpression.handleElementRename(myMethodName);
String varName = JavaCodeStyleManager.getInstance(project).suggestUniqueVariableName("k", lambdaCandidate, true);
PsiExpression lambda = factory.createExpressionFromText(varName + " -> " + ct.text(lambdaCandidate), lambdaCandidate);
getCall.getArgumentList().add(lambda);
}
}
ct.deleteAndRestoreComments(ifStatement);
CodeStyleManager.getInstance(project).reformat(statement);
}
}
}
@@ -1,156 +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.daemon.QuickFixBundle;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Couple;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
/**
* @author Dmitry Batkovich
*/
public class ReplaceConditionalMapPutFix implements LocalQuickFix {
private static final String COMPUTE_IF_ABSENT_METHOD = "computeIfAbsent";
private static final String PUT_IF_ABSENT_METHOD = "putIfAbsent";
private final SmartPsiElementPointer<PsiMethodCallExpression> myPutExpressionPointer;
private final String myMethodName;
public ReplaceConditionalMapPutFix(PsiMethodCallExpression putExpression, boolean useComputeIfAbsent) {
final SmartPointerManager smartPointerManager = SmartPointerManager.getInstance(putExpression.getProject());
myPutExpressionPointer = smartPointerManager.createSmartPsiElementPointer(putExpression);
myMethodName = useComputeIfAbsent ? COMPUTE_IF_ABSENT_METHOD : PUT_IF_ABSENT_METHOD;
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
final PsiElement conditionalOperator = descriptor.getPsiElement();
if (conditionalOperator == null) return;
final ConditionalOperatorHelper operatorHelper = getHelper(conditionalOperator);
final PsiMethodCallExpression putExpression = myPutExpressionPointer.getElement();
if (putExpression == null) return;
PsiElement putContainingBranch = null;
for (PsiElement branch : operatorHelper.getBranches(conditionalOperator)) {
if (branch != null && PsiTreeUtil.isAncestor(branch, putExpression, false)) {
putContainingBranch = branch;
break;
}
}
if (putContainingBranch == null) return;
final PsiExpression[] arguments = putExpression.getArgumentList().getExpressions();
final PsiElement qualifier = putExpression.getMethodExpression().getQualifier();
if (qualifier == null) {
return;
}
final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project);
final PsiElement putContainingElement = operatorHelper.getPutContainingElement(putExpression);
final Couple<String> boundText = getBoundText(putContainingElement, putExpression);
String valueArgument = putExpression.getArgumentList().getExpressions()[1].getText();
if(myMethodName.equals(COMPUTE_IF_ABSENT_METHOD)) {
String varName = JavaCodeStyleManager.getInstance(project).suggestUniqueVariableName("k", putExpression, true);
valueArgument = varName + " -> " + valueArgument;
}
final PsiStatement newStatement =
elementFactory.createStatementFromText(boundText.getFirst() + qualifier.getText() + "." + myMethodName +
"(" + arguments[0].getText() + "," + valueArgument + ")" + boundText.getSecond(),
conditionalOperator);
conditionalOperator.replace(newStatement);
}
private static Couple<String> getBoundText(@NotNull PsiElement parent, @NotNull PsiElement child) {
final TextRange childRange = child.getTextRange();
final int parentStartOffset = parent.getTextRange().getStartOffset();
final String parentText = parent.getText();
return Couple.of(parentText.substring(0, childRange.getStartOffset() - parentStartOffset),
parentText.substring(childRange.getEndOffset() - parentStartOffset));
}
@Nls
@NotNull
@Override
public String getFamilyName() {
return QuickFixBundle.message("java.8.collections.api.inspection.fix.family.name");
}
@Nls
@NotNull
@Override
public String getName() {
return QuickFixBundle.message("java.8.collections.api.inspection.fix.text", myMethodName);
}
private static ConditionalOperatorHelper getHelper(PsiElement element) {
return element instanceof PsiConditionalExpression ? new ConditionalExpressionHelper() : new IfStatementHelper();
}
interface ConditionalOperatorHelper {
@NotNull
PsiElement[] getBranches(PsiElement element);
@NotNull
PsiElement getPutContainingElement(PsiElement putElement);
}
private static class ConditionalExpressionHelper implements ConditionalOperatorHelper {
@NotNull
@Override
public PsiElement[] getBranches(PsiElement element) {
final PsiConditionalExpression conditionalExpression = (PsiConditionalExpression)element;
return new PsiElement[]{conditionalExpression.getThenExpression(), conditionalExpression.getElseExpression()};
}
@NotNull
@Override
public PsiElement getPutContainingElement(PsiElement putElement) {
for (PsiElement element : getBranches(PsiTreeUtil.getParentOfType(putElement, PsiConditionalExpression.class))) {
if (PsiTreeUtil.isAncestor(element, putElement, false)) {
return element;
}
}
throw new AssertionError();
}
}
private static class IfStatementHelper implements ConditionalOperatorHelper {
@NotNull
@Override
public PsiElement[] getBranches(PsiElement element) {
final PsiIfStatement ifStatement = (PsiIfStatement)element;
return new PsiElement[]{ifStatement.getThenBranch(), ifStatement.getElseBranch()};
}
@NotNull
@Override
public PsiElement getPutContainingElement(PsiElement putElement) {
return PsiTreeUtil.getParentOfType(putElement, PsiStatement.class);
}
}
}
@@ -0,0 +1,492 @@
/*
* 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.PsiEquivalenceUtil;
import com.intellij.codeInsight.daemon.QuickFixBundle;
import com.intellij.codeInspection.*;
import com.intellij.codeInspection.ui.MultipleCheckboxOptionsPanel;
import com.intellij.codeInspection.util.LambdaGenerationUtil;
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.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.siyeh.ig.psiutils.*;
import one.util.streamex.IntStreamEx;
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 javax.swing.*;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
/**
* @author Tagir Valeev
*/
public class Java8MapApiInspection extends BaseJavaBatchLocalInspectionTool {
private static final Logger LOG = Logger.getInstance(Java8MapApiInspection.class);
public static final String SHORT_NAME = "Java8MapApi";
public boolean mySuggestMapGetOrDefault = true;
public boolean mySuggestMapComputeIfAbsent = true;
public boolean mySuggestMapPutIfAbsent = true;
public boolean myTreatGetNullAsContainsKey = false;
@Nullable
@Override
public JComponent createOptionsPanel() {
MultipleCheckboxOptionsPanel panel = new MultipleCheckboxOptionsPanel(this);
panel.addCheckbox("Suggest conversion to Map.computeIfAbsent", "mySuggestMapComputeIfAbsent");
panel.addCheckbox("Suggest conversion to Map.getOrDefault", "mySuggestMapGetOrDefault");
panel.addCheckbox("Suggest conversion to Map.putIfAbsent", "mySuggestMapPutIfAbsent");
panel.addCheckbox("Treat 'get(k) != null' the same as 'containsKey(k)' (may change semantics)", "myTreatGetNullAsContainsKey");
return panel;
}
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
if (!PsiUtil.isLanguageLevel8OrHigher(holder.getFile())) {
return PsiElementVisitor.EMPTY_VISITOR;
}
return new JavaElementVisitor() {
@Override
public void visitConditionalExpression(PsiConditionalExpression expression) {
MapCheckCondition condition = fromTernary(expression);
if(condition == null || condition.hasVariable()) return;
PsiExpression existsBranch = condition.getExistsBranch(expression.getThenExpression(), expression.getElseExpression());
PsiExpression noneBranch = condition.getNoneBranch(expression.getThenExpression(), expression.getElseExpression());
processGetPut(condition, expression, existsBranch, existsBranch, noneBranch);
}
@Override
public void visitIfStatement(PsiIfStatement statement) {
MapCheckCondition condition = fromIfStatement(statement);
if(condition == null) return;
PsiStatement existsBranch = ControlFlowUtils.stripBraces(condition.getExistsBranch(statement.getThenBranch(), statement.getElseBranch()));
PsiStatement noneBranch = ControlFlowUtils.stripBraces(condition.getNoneBranch(statement.getThenBranch(), statement.getElseBranch()));
if(existsBranch == null) {
processSingleBranch(statement, condition, noneBranch);
} else {
if(condition.hasVariable()) return;
EquivalenceChecker.Decision decision =
EquivalenceChecker.getCanonicalPsiEquivalence().statementsAreEquivalentDecision(noneBranch, existsBranch);
processGetPut(condition, statement, existsBranch, decision.getRightDiff(), decision.getLeftDiff());
}
}
private void processGetPut(MapCheckCondition condition, PsiElement toRemove, PsiElement result, PsiElement exists, PsiElement none) {
if(!(exists instanceof PsiExpression)) return;
PsiMethodCallExpression getCall = extractMapMethodCall((PsiExpression)exists, "get");
if (getCall == null || !condition.isMap(getCall.getMethodExpression().getQualifierExpression())) return;
PsiExpression[] getArgs = getCall.getArgumentList().getExpressions();
if (getArgs.length != 1 || !condition.isKey(getArgs[0])) return;
if(!(none instanceof PsiExpression)) return;
PsiExpression noneExpression = (PsiExpression)none;
PsiMethodCallExpression putCall = extractMapMethodCall(noneExpression, "put");
if (mySuggestMapPutIfAbsent &&
putCall != null &&
condition.isGetNull() &&
condition.isMap(putCall.getMethodExpression().getQualifierExpression())) {
PsiExpression[] putArgs = putCall.getArgumentList().getExpressions();
if (putArgs.length != 2 || !condition.isKey(putArgs[0]) || !ExpressionUtils.isSimpleExpression(putArgs[1])) return;
condition.register(holder, new ReplaceWithSingleMapOperation("putIfAbsent", getCall, putArgs[1], toRemove, result));
}
if (mySuggestMapGetOrDefault && condition.isContainsKey() && ExpressionUtils.isSimpleExpression(noneExpression) &&
!(getCall.getType() instanceof PsiCapturedWildcardType)) {
condition.register(holder, new ReplaceWithSingleMapOperation("getOrDefault", getCall, noneExpression, toRemove,
result));
}
}
private void processSingleBranch(PsiIfStatement statement, MapCheckCondition condition, PsiStatement noneBranch) {
PsiAssignmentExpression assignment = ExpressionUtils.getAssignment(noneBranch);
if(assignment != null && mySuggestMapGetOrDefault && condition.isContainsKey()) {
/*
value = map.get(key);
if(value == null) {
value = ...
}
*/
if (ExpressionUtils.isSimpleExpression(assignment.getRExpression()) &&
condition.isValueReference(assignment.getLExpression()) &&
!condition.isValueReference(assignment.getRExpression())) {
condition
.register(holder, ReplaceWithSingleMapOperation.fromIf("getOrDefault", condition, statement, assignment.getRExpression()));
}
} else if (condition.isGetNull()) {
/*
value = map.get(key);
if(value == null) {
value = ...
map.put(key, value);
}
*/
PsiExpression lambdaCandidate = extractLambdaCandidate(condition, noneBranch);
if (lambdaCandidate != null && mySuggestMapComputeIfAbsent) {
condition.register(holder, ReplaceWithSingleMapOperation.fromIf("computeIfAbsent", condition, statement, lambdaCandidate));
}
if (lambdaCandidate == null) {
PsiExpression expression = extractPutValue(condition, noneBranch);
if(expression != null) {
String replacement = null;
if (mySuggestMapPutIfAbsent && ExpressionUtils.isSimpleExpression(expression) && !condition.isValueReference(expression)) {
replacement = "putIfAbsent";
}
else if (mySuggestMapComputeIfAbsent && !condition.hasVariable()) {
replacement = "computeIfAbsent";
}
if(replacement != null) {
if(condition.hasVariable()) {
condition.register(holder, ReplaceWithSingleMapOperation.fromIf(replacement, condition, statement, expression));
} else {
PsiMethodCallExpression call = PsiTreeUtil.getParentOfType(expression, PsiMethodCallExpression.class);
LOG.assertTrue(call != null);
condition.register(holder, new ReplaceWithSingleMapOperation(replacement, call, expression, statement, noneBranch));
}
}
}
}
}
}
};
}
@Nullable
static PsiExpression getValueComparedWithNull(PsiBinaryExpression binOp) {
if(!binOp.getOperationTokenType().equals(JavaTokenType.EQEQ) &&
!binOp.getOperationTokenType().equals(JavaTokenType.NE)) return null;
PsiExpression left = binOp.getLOperand();
PsiExpression right = binOp.getROperand();
if(ExpressionUtils.isNullLiteral(right)) return left;
if(ExpressionUtils.isNullLiteral(left)) return right;
return null;
}
@Nullable
static PsiExpression extractLambdaCandidate(MapCheckCondition condition, PsiStatement statement) {
PsiAssignmentExpression assignment;
PsiExpression putValue = extractPutValue(condition, statement);
if(putValue != null) {
// like map.put(key, val = new ArrayList<>());
assignment = ExpressionUtils.getAssignment(putValue);
}
else {
if (!(statement instanceof PsiBlockStatement)) return null;
// like val = new ArrayList<>(); map.put(key, val);
PsiStatement[] statements = ((PsiBlockStatement)statement).getCodeBlock().getStatements();
if (statements.length != 2) return null;
putValue = extractPutValue(condition, statements[1]);
if (!condition.isValueReference(putValue)) return null;
assignment = ExpressionUtils.getAssignment(statements[0]);
}
if (assignment == null) return null;
PsiExpression lambdaCandidate = assignment.getRExpression();
if (lambdaCandidate == null || !condition.isValueReference(assignment.getLExpression())) return null;
if (!LambdaGenerationUtil.canBeUncheckedLambda(lambdaCandidate)) return null;
return lambdaCandidate;
}
@Contract("null, _ -> null")
static PsiMethodCallExpression extractMapMethodCall(PsiExpression expression, @NotNull String expectedName) {
expression = PsiUtil.skipParenthesizedExprDown(expression);
if (!(expression instanceof PsiMethodCallExpression)) return null;
PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)expression;
if (!expectedName.equals(methodCallExpression.getMethodExpression().getReferenceName())) return null;
final PsiMethod method = methodCallExpression.resolveMethod();
if (method == null) return null;
PsiMethod[] superMethods = method.findDeepestSuperMethods();
if (superMethods.length == 0) {
superMethods = new PsiMethod[]{method};
}
return StreamEx.of(superMethods).map(PsiMember::getContainingClass).nonNull().map(PsiClass::getQualifiedName)
.has(CommonClassNames.JAVA_UTIL_MAP) ? methodCallExpression : null;
}
@Contract("_, null -> null")
@Nullable
private static PsiExpression extractPutValue(MapCheckCondition condition, PsiStatement statement) {
if(!(statement instanceof PsiExpressionStatement)) return null;
PsiMethodCallExpression putCall = extractMapMethodCall(((PsiExpressionStatement)statement).getExpression(), "put");
if (putCall == null) return null;
PsiExpression[] putArguments = putCall.getArgumentList().getExpressions();
return putArguments.length == 2 &&
condition.isMap(putCall.getMethodExpression().getQualifierExpression()) &&
condition.isKey(putArguments[0]) ? putArguments[1] : null;
}
@Nullable
@Contract("_, null -> null")
static PsiMethodCallExpression tryExtractMapGetCall(PsiReferenceExpression target, PsiElement element) {
if(element instanceof PsiDeclarationStatement) {
PsiDeclarationStatement declaration = (PsiDeclarationStatement)element;
PsiElement[] elements = declaration.getDeclaredElements();
if(elements.length > 0) {
PsiElement lastDeclaration = elements[elements.length - 1];
if(lastDeclaration instanceof PsiLocalVariable && target.isReferenceTo(lastDeclaration)) {
PsiLocalVariable var = (PsiLocalVariable)lastDeclaration;
return extractMapMethodCall(var.getInitializer(), "get");
}
}
}
PsiAssignmentExpression assignment = ExpressionUtils.getAssignment(element);
if(assignment != null) {
PsiExpression lValue = assignment.getLExpression();
if (lValue instanceof PsiReferenceExpression &&
EquivalenceChecker.getCanonicalPsiEquivalence().expressionsAreEquivalent(target, lValue)) {
return extractMapMethodCall(assignment.getRExpression(), "get");
}
}
return null;
}
@Nullable
MapCheckCondition fromIfStatement(PsiIfStatement ifStatement) {
return tryExtract(ifStatement.getCondition(), ifStatement);
}
@Nullable
MapCheckCondition fromTernary(PsiConditionalExpression ternary) {
PsiElement parent = ternary.getParent().getParent();
return tryExtract(ternary.getCondition(), parent instanceof PsiStatement ? (PsiStatement)parent : null);
}
@Nullable
private MapCheckCondition tryExtract(PsiExpression fullCondition, @Nullable PsiStatement statement) {
PsiExpression condition = PsiUtil.skipParenthesizedExprDown(fullCondition);
boolean negated = false;
while(condition != null && BoolUtils.isNegation(condition)) {
negated ^= true;
condition = BoolUtils.getNegated(condition);
}
if(condition == null) return null;
PsiReferenceExpression valueReference = null;
boolean containsKey = false;
PsiMethodCallExpression call;
if(condition instanceof PsiBinaryExpression) {
negated ^= ((PsiBinaryExpression)condition).getOperationTokenType().equals(JavaTokenType.EQEQ);
PsiExpression value = getValueComparedWithNull((PsiBinaryExpression)condition);
if(value instanceof PsiReferenceExpression && statement != null) {
valueReference = (PsiReferenceExpression)value;
PsiElement previous = PsiTreeUtil.skipSiblingsBackward(statement, PsiWhiteSpace.class, PsiComment.class);
call = tryExtractMapGetCall(valueReference, previous);
} else {
call = extractMapMethodCall(value, "get");
}
} else {
call = extractMapMethodCall(condition, "containsKey");
containsKey = true;
}
if(call == null) return null;
PsiExpression mapExpression = call.getMethodExpression().getQualifierExpression();
if(mapExpression == null) return null;
PsiExpression[] args = call.getArgumentList().getExpressions();
if(args.length != 1) return null;
PsiExpression keyExpression = args[0];
return new MapCheckCondition(valueReference, mapExpression, keyExpression, fullCondition, negated, containsKey);
}
private static class ReplaceWithSingleMapOperation implements LocalQuickFix {
private final String myMethodName;
private final SmartPsiElementPointer<PsiMethodCallExpression> myCallPointer;
private final SmartPsiElementPointer<PsiExpression> myValuePointer;
private final SmartPsiElementPointer<PsiElement> myRemovedPointer;
private final SmartPsiElementPointer<PsiElement> myResultPointer;
ReplaceWithSingleMapOperation(String methodName, PsiMethodCallExpression call, PsiExpression value, PsiElement removed, PsiElement result) {
myMethodName = methodName;
SmartPointerManager manager = SmartPointerManager.getInstance(value.getProject());
myCallPointer = manager.createSmartPsiElementPointer(call);
myValuePointer = manager.createSmartPsiElementPointer(value);
myRemovedPointer = manager.createSmartPsiElementPointer(removed);
myResultPointer = manager.createSmartPsiElementPointer(result);
}
@NotNull
static ReplaceWithSingleMapOperation fromIf(String methodName, MapCheckCondition condition, PsiStatement ifStatement, PsiExpression value) {
PsiMethodCallExpression call = condition.getCheckCall();
PsiStatement result = PsiTreeUtil.getParentOfType(call, PsiStatement.class);
LOG.assertTrue(result != null);
return new ReplaceWithSingleMapOperation(methodName, call, value, ifStatement, result);
}
@Nls
@NotNull
@Override
public String getName() {
return QuickFixBundle.message("java.8.map.api.inspection.fix.text", myMethodName);
}
@Nls
@NotNull
@Override
public String getFamilyName() {
return QuickFixBundle.message("java.8.map.api.inspection.fix.family.name");
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
PsiMethodCallExpression call = myCallPointer.getElement();
if (call == null) return;
PsiExpressionList argsList = call.getArgumentList();
PsiExpression[] args = argsList.getExpressions();
if(args.length == 0) return;
PsiExpression value = myValuePointer.getElement();
if (value == null) return;
PsiElement removed = myRemovedPointer.getElement();
if (removed == null) return;
PsiElement result = myResultPointer.getElement();
if(result == null) return;
PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
CommentTracker ct = new CommentTracker();
call.getMethodExpression().handleElementRename(myMethodName);
PsiExpression replacement;
if(myMethodName.equals("computeIfAbsent")) {
PsiExpression key = args[0];
List<PsiReferenceExpression> refs = Collections.emptyList();
String nameCandidate = "k";
if(key instanceof PsiReferenceExpression && ((PsiReferenceExpression)key).getQualifier() == null) {
// try to use lambda parameter if key is simple reference and has the same type as map keys
PsiMethod method = call.resolveMethod();
if(method != null) {
PsiType argType = method.getParameterList().getParameters()[0].getType();
PsiType mapKeyType = call.resolveMethodGenerics().getSubstitutor().substitute(argType);
PsiType keyType = key.getType();
if(mapKeyType != null && keyType != null && keyType.isAssignableFrom(mapKeyType)) {
PsiElement element = ((PsiReferenceExpression)key).resolve();
refs = StreamEx.of(PsiTreeUtil.collectElementsOfType(value, PsiReferenceExpression.class))
.filter(ref -> ref.getQualifierExpression() == null && ref.isReferenceTo(element)).toList();
if (!refs.isEmpty()) {
String name = ((PsiReferenceExpression)key).getReferenceName();
// like "myVariableName" => "mvn"
nameCandidate = IntStreamEx.ofChars(name).mapFirst(Character::toUpperCase).filter(Character::isUpperCase).charsToString()
.toLowerCase(Locale.ENGLISH);
if (nameCandidate.isEmpty()) {
nameCandidate = "k";
}
}
}
}
}
String varName = JavaCodeStyleManager.getInstance(project).suggestUniqueVariableName(nameCandidate, value, true);
for(PsiReferenceExpression ref : refs) {
ref.handleElementRename(varName);
}
replacement = factory.createExpressionFromText(varName + " -> " + ct.text(value), value);
} else {
replacement = ct.markUnchanged(value);
}
if(args.length == 2) {
ct.replace(args[1], replacement);
} else {
argsList.add(replacement);
}
PsiExpression expression = argsList.getExpressions()[1];
if(expression instanceof PsiLambdaExpression) {
LambdaCanBeMethodReferenceInspection.replaceLambdaWithMethodReference((PsiLambdaExpression)expression);
}
if(PsiTreeUtil.isAncestor(removed, result, true)) {
result = ct.replaceAndRestoreComments(removed, ct.markUnchanged(result));
} else {
ct.deleteAndRestoreComments(removed);
}
CodeStyleManager.getInstance(project).reformat(result);
}
}
class MapCheckCondition {
private final @Nullable PsiReferenceExpression myValueReference;
private final PsiExpression myMapExpression;
private final PsiExpression myKeyExpression;
private final PsiExpression myFullCondition;
private final boolean myNegated;
private final boolean myContainsKey;
private MapCheckCondition(@Nullable PsiReferenceExpression valueReference,
PsiExpression mapExpression,
PsiExpression keyExpression,
PsiExpression fullCondition,
boolean negated,
boolean containsKey) {
myValueReference = valueReference;
myMapExpression = mapExpression;
myKeyExpression = keyExpression;
myFullCondition = fullCondition;
myNegated = negated;
myContainsKey = containsKey;
}
boolean isContainsKey() {
return myContainsKey || myTreatGetNullAsContainsKey;
}
boolean isGetNull() {
return !myContainsKey || myTreatGetNullAsContainsKey;
}
@Contract("null -> false")
boolean isMap(PsiExpression expression) {
return expression != null && PsiEquivalenceUtil.areElementsEquivalent(myMapExpression, expression);
}
@Contract("null -> false")
boolean isKey(PsiExpression expression) {
return expression != null && PsiEquivalenceUtil.areElementsEquivalent(myKeyExpression, expression);
}
@Contract("null -> false")
boolean isValueReference(PsiExpression expression) {
return expression != null && myValueReference != null && PsiEquivalenceUtil.areElementsEquivalent(expression, myValueReference);
}
<T extends PsiElement> T getExistsBranch(T thenBranch, T elseBranch) {
return myNegated ? elseBranch : thenBranch;
}
<T extends PsiElement> T getNoneBranch(T thenBranch, T elseBranch) {
return myNegated ? thenBranch : elseBranch;
}
boolean hasVariable() {
return myValueReference != null;
}
PsiMethodCallExpression getCheckCall() {
return PsiTreeUtil.getParentOfType(myMapExpression, PsiMethodCallExpression.class);
}
public PsiExpression getFullCondition() {
return myFullCondition;
}
public void register(ProblemsHolder holder, LocalQuickFix fix) {
//noinspection DialogTitleCapitalization
holder.registerProblem(getFullCondition(), QuickFixBundle.message("java.8.map.api.inspection.description"), fix);
}
}
}
@@ -0,0 +1,33 @@
/*
* 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.codeInspection.ex.InspectionElementsMerger;
public class Java8MapApiInspectionMerger extends InspectionElementsMerger {
private static final String COLLECTION_API_INSPECTION = "Java8CollectionsApi";
private static final String REPLACE_MAP_GET_INSPECTION = "Java8ReplaceMapGet";
@Override
public String getMergedToolName() {
return Java8MapApiInspection.SHORT_NAME;
}
@Override
public String[] getSourceToolNames() {
return new String[] {COLLECTION_API_INSPECTION, REPLACE_MAP_GET_INSPECTION};
}
}
@@ -0,0 +1,21 @@
// "Replace with 'computeIfAbsent' method call" "true"
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class Main {
static class MyItem {
String k;
int i;
MyItem(String k, int i) {
this.k = k;
this.i = i;
}
}
public MyItem testMap(Map<String, MyItem> map, String token) {
MyItem item = map.computeIfAbsent(token, t -> new MyItem(t, 1));
return item;
}
}
@@ -0,0 +1,19 @@
// "Replace with 'computeIfAbsent' method call" "true"
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class Main {
static class MyItem {
String k;
MyItem(String k) {
this.k = k;
}
}
public MyItem testMap(Map<String, MyItem> map, String token) {
MyItem item = map.computeIfAbsent(token, MyItem::new);
return item;
}
}
@@ -0,0 +1,20 @@
// "Replace with 'computeIfAbsent' method call" "true"
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class Main {
static class MyItem {
String k;
MyItem(String k) {
this.k = k;
}
}
public MyItem testMap(Map<CharSequence, MyItem> map, String token) {
// Cannot create method reference here as MyItem wants a String, but lambda will receive a CharSequence
MyItem item = map.computeIfAbsent(token, k -> new MyItem(token));
return item;
}
}
@@ -0,0 +1,13 @@
// "Replace with 'getOrDefault' method call" "true"
import java.util.Map;
public class Main {
private static final String NONE = "none";
private String str;
public void testGetOrDefault(Map<String, String> map, String key, Main other) {
/* output none */
System.out.println(/* output map value */ map.getOrDefault("k", NONE));
}
}
@@ -0,0 +1,12 @@
// "Replace with 'getOrDefault' method call" "true"
import java.util.Map;
public class Main {
private static final String NONE = "none";
private String str;
public String testGetOrDefault(Map<String, String> map, String key, Main other) {
return map.getOrDefault(key, "oops");
}
}
@@ -0,0 +1,7 @@
// "Replace with 'putIfAbsent' method call" "true"
import java.util.Map;
class Test {
void m1(Map<String, Integer> map) {
map.putIfAbsent("ads", i);
}
}
@@ -0,0 +1,24 @@
// "Replace with 'computeIfAbsent' method call" "true"
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class Main {
static class MyItem {
String k;
int i;
MyItem(String k, int i) {
this.k = k;
this.i = i;
}
}
public MyItem testMap(Map<String, MyItem> map, String token) {
MyItem item = map.get(token);
if(item == nul<caret>l) {
map.put(token, item = new MyItem(token, 1));
}
return item;
}
}
@@ -0,0 +1,22 @@
// "Replace with 'computeIfAbsent' method call" "true"
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class Main {
static class MyItem {
String k;
MyItem(String k) {
this.k = k;
}
}
public MyItem testMap(Map<String, MyItem> map, String token) {
MyItem item = map.get(token);
if(item == nul<caret>l) {
map.put(token, item = new MyItem(token));
}
return item;
}
}
@@ -0,0 +1,23 @@
// "Replace with 'computeIfAbsent' method call" "true"
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class Main {
static class MyItem {
String k;
MyItem(String k) {
this.k = k;
}
}
public MyItem testMap(Map<CharSequence, MyItem> map, String token) {
// Cannot create method reference here as MyItem wants a String, but lambda will receive a CharSequence
MyItem item = map.get(token);
if(item == nul<caret>l) {
map.put(token, item = new MyItem(token));
}
return item;
}
}
@@ -2,6 +2,6 @@
import java.util.Map;
class Test {
void m1111(Map<String, Integer> map) {
int i = !map.containsKey("asd") ? map.<caret>put("asd", 123) : map.get("asd");
int i = !map.cont<caret>ainsKey("asd") ? map.put("asd", 123) : map.get("asd");
}
}
@@ -0,0 +1,13 @@
// "Replace with 'getOrDefault' method call" "false"
import java.util.Map;
public class Main {
public CharSequence test(Map<String, ? extends CharSequence> map) {
// cannot replace as map.getOrDefault("xyz", "none") will result in compilation error
if(map.co<caret>ntainsKey("xyz")) {
return map.get("xyz");
} else {
return "none";
}
}
}
@@ -8,7 +8,7 @@ public class Main {
public void testGetOrDefault(Map<String, String> map, String key, Main other) {
str = map.get(key);
if(str == nul<caret>l) {
if(!(str != nul<caret>l)) {
/*
block comment
*/
@@ -0,0 +1,16 @@
// "Replace with 'getOrDefault' method call" "true"
import java.util.Map;
public class Main {
private static final String NONE = "none";
private String str;
public void testGetOrDefault(Map<String, String> map, String key, Main other) {
if(map.conta<caret>insKey("k")) {
System.out.println(/* output map value */ map.get("k"));
} else {
System.out.println(/* output none */ NONE);
}
}
}
@@ -0,0 +1,12 @@
// "Replace with 'getOrDefault' method call" "true"
import java.util.Map;
public class Main {
private static final String NONE = "none";
private String str;
public String testGetOrDefault(Map<String, String> map, String key, Main other) {
return map.conta<caret>insKey(key) ? map.get(key) : "oops";
}
}
@@ -0,0 +1,9 @@
// "Replace with 'putIfAbsent' method call" "true"
import java.util.Map;
class Test {
void m1(Map<String, Integer> map) {
if (!map.contai<caret>nsKey("ads")) {
map.put("ads", i);
}
}
}
@@ -1,48 +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.daemon.quickFix.LightQuickFixParameterizedTestCase;
import com.intellij.codeInspection.java18api.Java8CollectionsApiInspection;
import org.jetbrains.annotations.NotNull;
/**
* @author Dmitry Batkovich
*/
public class Java8CollectionsApiInspectionTest extends LightQuickFixParameterizedTestCase {
private Java8CollectionsApiInspection myInspection;
@NotNull
@Override
protected LocalInspectionTool[] configureLocalInspectionTools() {
return new LocalInspectionTool[]{myInspection};
}
public void setUp() throws Exception {
myInspection = new Java8CollectionsApiInspection();
myInspection.myReportContainsCondition = true;
super.setUp();
}
public void test() throws Exception {
doAllTests();
}
@Override
protected String getBasePath() {
return "/inspection/java8CollectionsApi";
}
}
@@ -16,17 +16,19 @@
package com.intellij.codeInspection;
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
import com.intellij.codeInspection.java18api.Java8ReplaceMapGetInspection;
import com.intellij.codeInspection.java18api.Java8MapApiInspection;
import org.jetbrains.annotations.NotNull;
/**
* @author Tagir Valeev
*/
public class Java8ReplaceMapGetInspectionTest extends LightQuickFixParameterizedTestCase {
public class Java8MapApiInspectionTest extends LightQuickFixParameterizedTestCase {
@NotNull
@Override
protected LocalInspectionTool[] configureLocalInspectionTools() {
return new LocalInspectionTool[]{new Java8ReplaceMapGetInspection()};
Java8MapApiInspection inspection = new Java8MapApiInspection();
inspection.myTreatGetNullAsContainsKey = true;
return new LocalInspectionTool[]{inspection};
}
public void test() throws Exception {
@@ -35,6 +37,6 @@ public class Java8ReplaceMapGetInspectionTest extends LightQuickFixParameterized
@Override
protected String getBasePath() {
return "/inspection/java8MapGet";
return "/inspection/java8MapApi";
}
}
@@ -283,11 +283,9 @@ wrap.long.with.math.to.int.parameter.multiple.text=Wrap {0, choice, 1#1st|2#2nd|
add.exception.from.field.initializer.to.constructor.throws.text=Add exception to class {0, choice, 0#default constructor|1#constructor|2#constructors} signature
add.exception.from.field.initializer.to.constructor.throws.family.text=Add exception to class constructors signature
java.8.collections.api.inspection.description=If statement could be replaced with single method
java.8.collections.api.inspection.fix.family.name=Replace with single method call
java.8.collections.api.inspection.fix.text=Replace with ''{0}'' method call
java.8.replace.map.get.inspection.description=Map.get and condition could be replaced with single method call
java.8.replace.map.get.inspection.fix.family.name=Replace Map.get and condition with single method call
java.8.map.api.inspection.fix.text=Replace with ''{0}'' method call
java.8.map.api.inspection.description=Can be replaced with single Map method call
java.8.map.api.inspection.fix.family.name=Replace with single Map method call
java.8.collection.removeif.inspection.description=The loop could be replaced with Collection.removeIf
java.8.collection.removeif.inspection.fix.name=Replace the loop with Collection.removeIf
java.8.list.sort.inspection.description=Collections.sort could be replaced with List.sort
+4 -8
View File
@@ -589,6 +589,7 @@
<externalProjectDataService implementation="com.intellij.externalSystem.JavaProjectDataService"/>
<inspectionElementsMerger implementation="com.intellij.codeInspection.deadCode.UnusedDeclarationInspectionMerger"/>
<inspectionElementsMerger implementation="com.intellij.codeInspection.java18api.Java8MapApiInspectionMerger"/>
<globalInspection groupPath="Java" language="JAVA" shortName="unused" displayName="Unused declaration" groupBundle="messages.InspectionsBundle"
groupKey="group.names.declaration.redundancy" enabledByDefault="true" level="WARNING"
@@ -824,11 +825,6 @@
<localInspection groupPath="Java" language="JAVA" shortName="AnonymousHasLambdaAlternative" displayName="Anonymous type has shorter lambda alternative"
groupKey="group.names.language.level.specific.issues.and.migration.aids" groupBundle="messages.InspectionsBundle" enabledByDefault="true" level="WARNING"
implementationClass="com.intellij.codeInspection.AnonymousHasLambdaAlternativeInspection" />
<localInspection groupPath="Java" language="JAVA" shortName="Java8CollectionsApi"
groupBundle="messages.InspectionsBundle"
groupKey="group.names.language.level.specific.issues.and.migration.aids" enabledByDefault="true" level="WARNING"
implementationClass="com.intellij.codeInspection.java18api.Java8CollectionsApiInspection"
displayName="Map.putIfAbsent() can be used"/>
<localInspection groupPath="Java" language="JAVA" shortName="Java8ListSort"
groupBundle="messages.InspectionsBundle"
groupKey="group.names.language.level.specific.issues.and.migration.aids" enabledByDefault="true" level="WARNING"
@@ -839,11 +835,11 @@
groupKey="group.names.language.level.specific.issues.and.migration.aids" enabledByDefault="true" level="WARNING"
implementationClass="com.intellij.codeInspection.java18api.Java8CollectionRemoveIfInspection"
displayName="Loop can be replaced with Collection.removeIf()"/>
<localInspection groupPath="Java" language="JAVA" shortName="Java8ReplaceMapGet"
<localInspection groupPath="Java" language="JAVA" shortName="Java8MapApi"
groupBundle="messages.InspectionsBundle"
groupKey="group.names.language.level.specific.issues.and.migration.aids" enabledByDefault="true" level="WARNING"
implementationClass="com.intellij.codeInspection.java18api.Java8ReplaceMapGetInspection"
displayName="Simplifiable conditional usage of Map.get()"/>
implementationClass="com.intellij.codeInspection.java18api.Java8MapApiInspection"
displayName="Replace with single Map method"/>
<localInspection groupPath="Java" language="JAVA" shortName="SimplifyStreamApiCallChains"
groupBundle="messages.InspectionsBundle"
groupKey="group.names.declaration.redundancy" enabledByDefault="true" level="WARNING"