mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-10 13:17:09 +07:00
add new groupBy cases
This commit is contained in:
+33
-252
@@ -30,7 +30,6 @@ import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.siyeh.ig.psiutils.*;
|
||||
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;
|
||||
@@ -40,6 +39,9 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import static com.siyeh.ig.psiutils.Java8MigrationUtils.MapCheckCondition.fromConditional;
|
||||
import static com.siyeh.ig.psiutils.Java8MigrationUtils.*;
|
||||
|
||||
/**
|
||||
* @author Tagir Valeev
|
||||
*/
|
||||
@@ -125,7 +127,7 @@ public class Java8MapApiInspection extends BaseJavaBatchLocalInspectionTool {
|
||||
}
|
||||
boolean informationLevel =
|
||||
!mySideEffects && SideEffectChecker.mayHaveSideEffects(presentValue, ex -> condition.extractGetCall(ex) != null);
|
||||
condition.register(holder, informationLevel, new ReplaceWithSingleMapOperation("merge", PsiTreeUtil
|
||||
register(condition, holder, informationLevel, new ReplaceWithSingleMapOperation("merge", PsiTreeUtil
|
||||
.getParentOfType(absentValue, PsiMethodCallExpression.class), presentValue, noneBranch));
|
||||
}
|
||||
}
|
||||
@@ -143,11 +145,11 @@ public class Java8MapApiInspection extends BaseJavaBatchLocalInspectionTool {
|
||||
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, false, new ReplaceWithSingleMapOperation("putIfAbsent", getCall, putArgs[1], result));
|
||||
register(condition, holder, false, new ReplaceWithSingleMapOperation("putIfAbsent", getCall, putArgs[1], result));
|
||||
}
|
||||
if (mySuggestMapGetOrDefault && condition.isContainsKey() && ExpressionUtils.isSimpleExpression(noneExpression) &&
|
||||
condition.isMapValueType(noneExpression.getType())) {
|
||||
condition.register(holder, false, new ReplaceWithSingleMapOperation("getOrDefault", getCall, noneExpression, result));
|
||||
condition.isMapValueType(noneExpression.getType())) {
|
||||
register(condition, holder, false, new ReplaceWithSingleMapOperation("getOrDefault", getCall, noneExpression, result));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,7 +165,7 @@ public class Java8MapApiInspection extends BaseJavaBatchLocalInspectionTool {
|
||||
PsiExpression rValue = assignment.getRExpression();
|
||||
if (ExpressionUtils.isSimpleExpression(rValue) && condition.isValueReference(assignment.getLExpression()) &&
|
||||
!condition.isValueReference(rValue) && condition.isMapValueType(rValue.getType())) {
|
||||
condition.register(holder, false, ReplaceWithSingleMapOperation.fromIf("getOrDefault", condition, rValue));
|
||||
register(condition, holder, false, ReplaceWithSingleMapOperation.fromIf("getOrDefault", condition, rValue));
|
||||
}
|
||||
} else if (condition.isGetNull()) {
|
||||
/*
|
||||
@@ -176,8 +178,7 @@ public class Java8MapApiInspection extends BaseJavaBatchLocalInspectionTool {
|
||||
PsiExpression lambdaCandidate = extractLambdaCandidate(condition, noneBranch);
|
||||
if (lambdaCandidate != null && mySuggestMapComputeIfAbsent) {
|
||||
boolean informationLevel = !mySideEffects && SideEffectChecker.mayHaveSideEffects(lambdaCandidate);
|
||||
condition
|
||||
.register(holder, informationLevel, ReplaceWithSingleMapOperation.fromIf("computeIfAbsent", condition, lambdaCandidate));
|
||||
register(condition, holder, informationLevel, ReplaceWithSingleMapOperation.fromIf("computeIfAbsent", condition, lambdaCandidate));
|
||||
}
|
||||
if (lambdaCandidate == null) {
|
||||
PsiExpression expression = extractPutValue(condition, noneBranch);
|
||||
@@ -193,12 +194,11 @@ public class Java8MapApiInspection extends BaseJavaBatchLocalInspectionTool {
|
||||
}
|
||||
if(replacement != null) {
|
||||
if(condition.hasVariable()) {
|
||||
condition.register(holder, informationLevel, ReplaceWithSingleMapOperation.fromIf(replacement, condition, expression));
|
||||
register(condition, holder, informationLevel, ReplaceWithSingleMapOperation.fromIf(replacement, condition, expression));
|
||||
} else {
|
||||
PsiMethodCallExpression call = PsiTreeUtil.getParentOfType(expression, PsiMethodCallExpression.class);
|
||||
LOG.assertTrue(call != null);
|
||||
condition
|
||||
.register(holder, informationLevel, new ReplaceWithSingleMapOperation(replacement, call, expression, noneBranch));
|
||||
register(condition, holder, informationLevel, new ReplaceWithSingleMapOperation(replacement, call, expression, noneBranch));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -208,133 +208,6 @@ public class Java8MapApiInspection extends BaseJavaBatchLocalInspectionTool {
|
||||
};
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
@Contract("null, _ -> null")
|
||||
static MapCheckCondition fromConditional(PsiElement conditional, boolean treatGetNullAsContainsKey) {
|
||||
if(conditional instanceof PsiIfStatement) {
|
||||
PsiIfStatement ifStatement = (PsiIfStatement)conditional;
|
||||
return tryExtract(ifStatement.getCondition(), ifStatement, treatGetNullAsContainsKey);
|
||||
}
|
||||
if(conditional instanceof PsiConditionalExpression) {
|
||||
PsiConditionalExpression ternary = (PsiConditionalExpression)conditional;
|
||||
PsiElement parent = ternary.getParent().getParent();
|
||||
return tryExtract(ternary.getCondition(), parent instanceof PsiStatement ? (PsiStatement)parent : null, treatGetNullAsContainsKey);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static MapCheckCondition tryExtract(PsiExpression fullCondition, @Nullable PsiStatement statement, boolean treatGetNullAsContainsKey) {
|
||||
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 = ExpressionUtils.getValueComparedWithNull((PsiBinaryExpression)condition);
|
||||
if(value instanceof PsiReferenceExpression && statement != null) {
|
||||
valueReference = (PsiReferenceExpression)value;
|
||||
PsiElement previous = PsiTreeUtil.skipWhitespacesAndCommentsBackward(statement);
|
||||
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,
|
||||
treatGetNullAsContainsKey);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String getNameCandidate(String name) {
|
||||
@@ -373,28 +246,6 @@ public class Java8MapApiInspection extends BaseJavaBatchLocalInspectionTool {
|
||||
myResultPointer = manager.createSmartPsiElementPointer(result);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
static ReplaceWithSingleMapOperation fromIf(String methodName, MapCheckCondition condition, PsiExpression value) {
|
||||
PsiMethodCallExpression call = condition.getCheckCall();
|
||||
PsiStatement result = PsiTreeUtil.getParentOfType(call, PsiStatement.class);
|
||||
LOG.assertTrue(result != null);
|
||||
return new ReplaceWithSingleMapOperation(methodName, call, value, 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) {
|
||||
PsiElement conditional = PsiTreeUtil.getParentOfType(descriptor.getStartElement(), PsiIfStatement.class, PsiConditionalExpression.class);
|
||||
@@ -477,103 +328,33 @@ public class Java8MapApiInspection extends BaseJavaBatchLocalInspectionTool {
|
||||
}
|
||||
CodeStyleManager.getInstance(project).reformat(result);
|
||||
}
|
||||
}
|
||||
|
||||
static 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 final boolean myTreatGetNullAsContainsKey;
|
||||
|
||||
private MapCheckCondition(@Nullable PsiReferenceExpression valueReference,
|
||||
PsiExpression mapExpression,
|
||||
PsiExpression keyExpression,
|
||||
PsiExpression fullCondition,
|
||||
boolean negated,
|
||||
boolean containsKey,
|
||||
boolean treatGetNullAsContainsKey) {
|
||||
myValueReference = valueReference;
|
||||
myMapExpression = mapExpression;
|
||||
myKeyExpression = keyExpression;
|
||||
myFullCondition = fullCondition;
|
||||
myNegated = negated;
|
||||
myContainsKey = containsKey;
|
||||
myTreatGetNullAsContainsKey = treatGetNullAsContainsKey;
|
||||
@Nls
|
||||
@NotNull
|
||||
@Override
|
||||
public String getName() {
|
||||
return QuickFixBundle.message("java.8.map.api.inspection.fix.text", myMethodName);
|
||||
}
|
||||
|
||||
boolean isContainsKey() {
|
||||
return myContainsKey || myTreatGetNullAsContainsKey;
|
||||
@Nls
|
||||
@NotNull
|
||||
@Override
|
||||
public String getFamilyName() {
|
||||
return QuickFixBundle.message("java.8.map.api.inspection.fix.family.name");
|
||||
}
|
||||
|
||||
boolean isGetNull() {
|
||||
return !myContainsKey || myTreatGetNullAsContainsKey;
|
||||
}
|
||||
|
||||
@Contract("null -> false")
|
||||
boolean isMap(PsiElement element) {
|
||||
return element != null && PsiEquivalenceUtil.areElementsEquivalent(myMapExpression, element);
|
||||
}
|
||||
|
||||
@Contract("null -> false")
|
||||
boolean isKey(PsiElement element) {
|
||||
return element != null && PsiEquivalenceUtil.areElementsEquivalent(myKeyExpression, element);
|
||||
}
|
||||
|
||||
PsiMethodCallExpression extractGetCall(PsiElement element) {
|
||||
if(!(element instanceof PsiExpression)) return null;
|
||||
PsiMethodCallExpression getCall = extractMapMethodCall((PsiExpression)element, "get");
|
||||
if(getCall == null) return null;
|
||||
PsiExpression[] args = getCall.getArgumentList().getExpressions();
|
||||
return args.length == 1 && isKey(args[0]) && isMap(getCall.getMethodExpression().getQualifierExpression()) ? getCall : null;
|
||||
}
|
||||
|
||||
@Contract("null -> false")
|
||||
boolean isValueReference(PsiElement element) {
|
||||
return element != null && myValueReference != null && PsiEquivalenceUtil.areElementsEquivalent(element, 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;
|
||||
}
|
||||
|
||||
PsiVariable extractDeclaration() {
|
||||
if(myValueReference == null) return null;
|
||||
return PsiTreeUtil.getParentOfType(myKeyExpression, PsiVariable.class, true, PsiStatement.class);
|
||||
}
|
||||
|
||||
boolean hasVariable() {
|
||||
if(myValueReference == null) return false;
|
||||
PsiVariable var = extractDeclaration();
|
||||
// has variable, but it used only in condition
|
||||
return var == null || ReferencesSearch.search(var).findAll().size() != 1;
|
||||
}
|
||||
|
||||
PsiMethodCallExpression getCheckCall() {
|
||||
return PsiTreeUtil.getParentOfType(myMapExpression, PsiMethodCallExpression.class);
|
||||
}
|
||||
|
||||
public PsiExpression getFullCondition() {
|
||||
return myFullCondition;
|
||||
}
|
||||
|
||||
public void register(ProblemsHolder holder, boolean informationLevel, ReplaceWithSingleMapOperation fix) {
|
||||
//noinspection DialogTitleCapitalization
|
||||
holder.registerProblem(getFullCondition(), QuickFixBundle.message("java.8.map.api.inspection.description", fix.myMethodName),
|
||||
informationLevel ? ProblemHighlightType.INFORMATION : ProblemHighlightType.GENERIC_ERROR_OR_WARNING, fix);
|
||||
}
|
||||
|
||||
public boolean isMapValueType(@Nullable PsiType type) {
|
||||
if (type == null) return false;
|
||||
PsiType mapExpressionType = myMapExpression.getType();
|
||||
PsiType valueTypeParameter = PsiUtil.substituteTypeParameter(mapExpressionType, CommonClassNames.JAVA_UTIL_MAP, 1, false);
|
||||
return valueTypeParameter != null && valueTypeParameter.isAssignableFrom(type);
|
||||
@NotNull
|
||||
static ReplaceWithSingleMapOperation fromIf(String methodName, MapCheckCondition condition, PsiExpression value) {
|
||||
PsiMethodCallExpression call = condition.getCheckCall();
|
||||
PsiStatement result = PsiTreeUtil.getParentOfType(call, PsiStatement.class);
|
||||
LOG.assertTrue(result != null);
|
||||
return new ReplaceWithSingleMapOperation(methodName, call, value, result);
|
||||
}
|
||||
}
|
||||
|
||||
private static void register(MapCheckCondition condition, ProblemsHolder holder, boolean informationLevel, ReplaceWithSingleMapOperation fix) {
|
||||
holder.registerProblem(condition.getFullCondition(), QuickFixBundle.message("java.8.map.api.inspection.description", fix.getName()),
|
||||
informationLevel ? ProblemHighlightType.INFORMATION : ProblemHighlightType.GENERIC_ERROR_OR_WARNING, fix);
|
||||
}
|
||||
|
||||
}
|
||||
+92
-107
@@ -21,7 +21,6 @@ import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
|
||||
import com.intellij.psi.impl.source.PsiClassReferenceType;
|
||||
import com.intellij.psi.search.LocalSearchScope;
|
||||
import com.intellij.psi.search.searches.ReferencesSearch;
|
||||
import com.intellij.psi.util.InheritanceUtil;
|
||||
@@ -29,6 +28,7 @@ import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiTypesUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.siyeh.ig.callMatcher.CallMatcher;
|
||||
import com.siyeh.ig.psiutils.*;
|
||||
import com.siyeh.ig.psiutils.ControlFlowUtils.InitializerUsageStatus;
|
||||
import one.util.streamex.EntryStream;
|
||||
@@ -46,6 +46,8 @@ import java.util.function.BiFunction;
|
||||
import static com.intellij.codeInspection.streamMigration.StreamApiMigrationInspection.isCallOf;
|
||||
import static com.intellij.util.ObjectUtils.tryCast;
|
||||
import static com.siyeh.ig.psiutils.ControlFlowUtils.getInitializerUsageStatus;
|
||||
import static com.siyeh.ig.psiutils.Java8MigrationUtils.MapCheckCondition.fromConditional;
|
||||
import static com.siyeh.ig.psiutils.Java8MigrationUtils.extractLambdaCandidate;
|
||||
|
||||
/**
|
||||
* @author Tagir Valeev
|
||||
@@ -135,7 +137,7 @@ class CollectMigration extends BaseStreamApiMigration {
|
||||
if (tb.dependsOn(qualifierExpression)) return null;
|
||||
|
||||
List<BiFunction<TerminalBlock, PsiMethodCallExpression, CollectTerminal>> extractors = Arrays
|
||||
.asList(AddingTerminal::tryExtract, ToMapTerminal::tryExtract, AddingAllTerminal::tryExtractAddAll);
|
||||
.asList(AddingTerminal::tryExtract, GroupingTerminal::tryExtractJava8Style, ToMapTerminal::tryExtract, AddingAllTerminal::tryExtractAddAll);
|
||||
|
||||
CollectTerminal terminal = StreamEx.of(extractors).map(extractor -> extractor.apply(tb, call)).nonNull().findFirst().orElse(null);
|
||||
if (terminal != null) {
|
||||
@@ -387,13 +389,17 @@ class CollectMigration extends BaseStreamApiMigration {
|
||||
}
|
||||
|
||||
static class GroupingTerminal extends CollectTerminal {
|
||||
|
||||
private final static CallMatcher LIST_ADD = CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_LIST, "add").parameterCount(1);
|
||||
private final static CallMatcher COMPUTE_IF_ABSENT = CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_MAP, "computeIfAbsent").parameterCount(2);
|
||||
|
||||
private final AddingTerminal myDownstream;
|
||||
private final PsiExpression myKeyExpression;
|
||||
|
||||
GroupingTerminal(AddingTerminal downstream,
|
||||
PsiLocalVariable target,
|
||||
PsiExpression expression,
|
||||
InitializerUsageStatus status) {
|
||||
GroupingTerminal(@NotNull AddingTerminal downstream,
|
||||
@NotNull PsiLocalVariable target,
|
||||
@NotNull PsiExpression expression,
|
||||
@NotNull InitializerUsageStatus status) {
|
||||
super(target, downstream.myLoop, status);
|
||||
myDownstream = downstream;
|
||||
myKeyExpression = expression;
|
||||
@@ -432,9 +438,13 @@ class CollectMigration extends BaseStreamApiMigration {
|
||||
static GroupingTerminal tryExtract(@NotNull TerminalBlock tb, @Nullable List<PsiVariable> nonFinalVariables) {
|
||||
PsiStatement[] statements = tb.getStatements();
|
||||
if (statements.length == 1) {
|
||||
if(nonFinalVariables != null && nonFinalVariables.isEmpty()) return null;
|
||||
if(nonFinalVariables != null && !nonFinalVariables.isEmpty()) return null;
|
||||
PsiMethodCallExpression call = tb.getSingleExpression(PsiMethodCallExpression.class);
|
||||
return tryExtract(tb, call);
|
||||
return tryExtractJava8Style(tb, call);
|
||||
}
|
||||
if (statements.length == 2) {
|
||||
if(nonFinalVariables != null && !nonFinalVariables.isEmpty()) return null;
|
||||
return tryExtractWithIntermediateVariable(tb);
|
||||
}
|
||||
return tryExtractJava7Style(tb, statements, nonFinalVariables);
|
||||
}
|
||||
@@ -453,127 +463,103 @@ class CollectMigration extends BaseStreamApiMigration {
|
||||
@Nullable List<PsiVariable> nonFinalVariables) {
|
||||
if(nonFinalVariables != null && nonFinalVariables.size() != 1) return null;
|
||||
if (statements.length != 3) return null;
|
||||
PsiDeclarationStatement declaration = tryCast(statements[0], PsiDeclarationStatement.class);
|
||||
PsiIfStatement ifStatement = tryCast(statements[1], PsiIfStatement.class);
|
||||
if(ifStatement == null) return null;
|
||||
Java8MigrationUtils.MapCheckCondition condition = fromConditional(ifStatement, false);
|
||||
if(condition == null || !condition.hasVariable()) return null;
|
||||
PsiStatement existsBranch = ControlFlowUtils.stripBraces(condition.getExistsBranch(ifStatement.getThenBranch(), ifStatement.getElseBranch()));
|
||||
PsiStatement noneBranch = ControlFlowUtils.stripBraces(condition.getNoneBranch(ifStatement.getThenBranch(), ifStatement.getElseBranch()));
|
||||
if(existsBranch != null) return null;
|
||||
PsiExpression lambdaCandidate = extractLambdaCandidate(condition, noneBranch);
|
||||
if(lambdaCandidate == null) return null;
|
||||
|
||||
PsiExpressionStatement additionToListStatement = tryCast(statements[2], PsiExpressionStatement.class);
|
||||
if (declaration == null || ifStatement == null || additionToListStatement == null) return null;
|
||||
PsiElement[] declaredElements = declaration.getDeclaredElements();
|
||||
if (declaredElements.length != 1) return null;
|
||||
if (additionToListStatement == null) return null;
|
||||
|
||||
PsiLocalVariable target = tryCast(declaredElements[0], PsiLocalVariable.class);
|
||||
if (target == null) return null;
|
||||
if(nonFinalVariables != null && !target.equals(nonFinalVariables.get(0))) return null;
|
||||
|
||||
if (!InheritanceUtil.isInheritor(target.getType(), CommonClassNames.JAVA_UTIL_LIST)) return null;
|
||||
PsiExpression initializer = target.getInitializer();
|
||||
PsiMethodCallExpression getCall = tryCast(initializer, PsiMethodCallExpression.class);
|
||||
if (!isCallOf(getCall, CommonClassNames.JAVA_UTIL_MAP, "get")) return null;
|
||||
PsiExpression[] expressions = getCall.getArgumentList().getExpressions();
|
||||
if (expressions.length != 1) return null;
|
||||
PsiExpression keyExtractor = expressions[0];
|
||||
PsiReferenceExpression mapReference =
|
||||
tryCast(getCall.getMethodExpression().getQualifierExpression(), PsiReferenceExpression.class);
|
||||
if (mapReference == null) return null;
|
||||
PsiLocalVariable mapVariable = tryCast(mapReference.resolve(), PsiLocalVariable.class);
|
||||
|
||||
if (!isValidIf(ifStatement, target, keyExtractor, mapVariable)) return null;
|
||||
PsiReferenceExpression valueReference = condition.getValueReference();
|
||||
if(valueReference == null) return null;
|
||||
PsiLocalVariable listVar = tryCast(valueReference.resolve(), PsiLocalVariable.class);
|
||||
if (nonFinalVariables != null && !nonFinalVariables.get(0).equals(listVar) ||
|
||||
listVar == null ||
|
||||
!InheritanceUtil.isInheritor(listVar.getType(), CommonClassNames.JAVA_UTIL_LIST)) {
|
||||
return null;
|
||||
}
|
||||
PsiLocalVariable mapVariable = ExpressionUtils.resolveLocalVariable(condition.getMapExpression());
|
||||
if(mapVariable == null) return null;
|
||||
PsiMethodCallExpression addCall = extractAddMethod(terminalBlock, additionToListStatement);
|
||||
if(addCall == null) return null;
|
||||
PsiType type = target.getType();
|
||||
PsiClassReferenceType referenceType = tryCast(type, PsiClassReferenceType.class);
|
||||
if(referenceType == null) return null;
|
||||
PsiType[] parameters = referenceType.getParameters();
|
||||
if(parameters.length != 1) return null;
|
||||
PsiType typeParameter = parameters[0];
|
||||
InitializerUsageStatus status = getInitializerUsageStatus(terminalBlock.getVariable(), terminalBlock.getMainLoop());
|
||||
PsiElementFactory factory = JavaPsiFacade.getElementFactory(terminalBlock.getMainLoop().getProject());
|
||||
PsiExpression text = factory.createExpressionFromText("new ArrayList<String>()", additionToListStatement);
|
||||
AddingTerminal adding = new AddingTerminal(typeParameter, text, terminalBlock.getVariable(), addCall);
|
||||
return new GroupingTerminal(adding, mapVariable, keyExtractor, status);
|
||||
InitializerUsageStatus status = getInitializerUsageStatus(mapVariable, terminalBlock.getStreamSourceStatement());
|
||||
AddingTerminal adding = new AddingTerminal(listVar.getType(), lambdaCandidate, terminalBlock.getVariable(), addCall);
|
||||
return new GroupingTerminal(adding, mapVariable, condition.getKeyExpression(), status);
|
||||
}
|
||||
|
||||
private static PsiMethodCallExpression extractAddMethod(@NotNull TerminalBlock terminalBlock, PsiExpressionStatement additionToListStatement) {
|
||||
@Nullable
|
||||
private static PsiMethodCallExpression extractAddMethod(@NotNull TerminalBlock terminalBlock,
|
||||
@NotNull PsiExpressionStatement additionToListStatement) {
|
||||
PsiMethodCallExpression additionToList = tryCast(additionToListStatement.getExpression(), PsiMethodCallExpression.class);
|
||||
if (!isCallOf(additionToList, CommonClassNames.JAVA_UTIL_LIST, "add")) return null;
|
||||
if (!LIST_ADD.test(additionToList)) return null;
|
||||
PsiExpression[] additionArgs = additionToList.getArgumentList().getExpressions();
|
||||
if (additionArgs.length != 1) return null;
|
||||
PsiExpression arg = additionArgs[0];
|
||||
PsiReferenceExpression referenceExpression = tryCast(arg, PsiReferenceExpression.class);
|
||||
if (referenceExpression == null) return null;
|
||||
PsiVariable savedVar = tryCast(referenceExpression.resolve(), PsiVariable.class);
|
||||
if(savedVar == null) return null;
|
||||
if (!savedVar.equals(terminalBlock.getVariable())) return null;
|
||||
return additionToList;
|
||||
}
|
||||
|
||||
private static boolean isValidIf(PsiIfStatement ifStatement,
|
||||
PsiLocalVariable variable,
|
||||
PsiExpression keyExtractor,
|
||||
PsiVariable getMapVar) {
|
||||
if (ifStatement.getElseBranch() != null) return false;
|
||||
PsiExpression condition = ifStatement.getCondition();
|
||||
if (condition == null) return false;
|
||||
PsiVariable nullChecked = ExpressionUtils.getVariableFromNullComparison(condition, true);
|
||||
if (!variable.equals(nullChecked)) return false;
|
||||
PsiBlockStatement blockStatement = tryCast(ifStatement.getThenBranch(), PsiBlockStatement.class);
|
||||
if (blockStatement == null) return false;
|
||||
PsiStatement[] ifStatements = blockStatement.getCodeBlock().getStatements();
|
||||
if (ifStatements.length != 2) return false;
|
||||
@Nullable
|
||||
public static GroupingTerminal tryExtractJava8Style(@NotNull TerminalBlock tb, @Nullable PsiMethodCallExpression call) {
|
||||
if(call == null) return null;
|
||||
PsiExpression qualifier = call.getMethodExpression().getQualifierExpression();
|
||||
return tryExtractJava8Style(tb, tryCast(qualifier, PsiMethodCallExpression.class), tryCast(call, PsiMethodCallExpression.class));
|
||||
}
|
||||
|
||||
PsiExpression assignment = ExpressionUtils.getAssignmentTo(ifStatements[0], variable);
|
||||
PsiNewExpression newExpression = tryCast(assignment, PsiNewExpression.class);
|
||||
if (newExpression == null) return false;
|
||||
PsiJavaCodeReferenceElement reference = newExpression.getClassReference();
|
||||
if (reference == null) return false;
|
||||
PsiClass aClass = tryCast(reference.resolve(), PsiClass.class);
|
||||
if (!CommonClassNames.JAVA_UTIL_ARRAY_LIST.equals(aClass.getQualifiedName())) return false;
|
||||
/*
|
||||
Map<Integer, List<String>> map = new HashMap<>();
|
||||
for (String s : list) {
|
||||
List<String> strings = map.computeIfAbsent(s.length(), k -> new ArrayList<>());
|
||||
strings.add(s);
|
||||
}
|
||||
*/
|
||||
@Nullable
|
||||
private static GroupingTerminal tryExtractWithIntermediateVariable(@NotNull TerminalBlock terminalBlock) {
|
||||
PsiStatement[] statements = terminalBlock.getStatements();
|
||||
PsiDeclarationStatement assignmentStmt = tryCast(statements[0], PsiDeclarationStatement.class);
|
||||
if(assignmentStmt == null) return null;
|
||||
PsiElement[] elements = assignmentStmt.getDeclaredElements();
|
||||
if(elements.length != 1) return null;
|
||||
PsiLocalVariable variable = tryCast(elements[0], PsiLocalVariable.class);
|
||||
if(variable == null) return null;
|
||||
|
||||
PsiExpressionStatement mapPutStatement = tryCast(ifStatements[1], PsiExpressionStatement.class);
|
||||
if (mapPutStatement == null) return false;
|
||||
PsiMethodCallExpression mapPut = tryCast(mapPutStatement.getExpression(), PsiMethodCallExpression.class);
|
||||
if (!isCallOf(mapPut, CommonClassNames.JAVA_UTIL_MAP, "put")) return false;
|
||||
PsiExpressionStatement addStmt = tryCast(statements[1], PsiExpressionStatement.class);
|
||||
if(addStmt == null) return null;
|
||||
PsiMethodCallExpression maybeAddCall = tryCast(addStmt.getExpression(), PsiMethodCallExpression.class);
|
||||
if(maybeAddCall == null) return null;
|
||||
PsiExpression qualifier = maybeAddCall.getMethodExpression().getQualifierExpression();
|
||||
if(!ExpressionUtils.isReferenceTo(qualifier, variable)) return null;
|
||||
|
||||
PsiReferenceExpression mapPutQualifierReference =
|
||||
tryCast(mapPut.getMethodExpression().getQualifierExpression(), PsiReferenceExpression.class);
|
||||
if(mapPutQualifierReference == null) return false;
|
||||
PsiVariable putMapVar = tryCast(mapPutQualifierReference.resolve(), PsiVariable.class);
|
||||
if(putMapVar == null) return false;
|
||||
if (!putMapVar.equals(getMapVar)) return false;
|
||||
|
||||
PsiExpression[] mapPutArgs = mapPut.getArgumentList().getExpressions();
|
||||
if (mapPutArgs.length != 2) return false;
|
||||
PsiExpression putKeyExtractor = mapPutArgs[0];
|
||||
if (!EquivalenceChecker.getCanonicalPsiEquivalence().expressionsAreEquivalent(putKeyExtractor, keyExtractor)) return false;
|
||||
PsiReferenceExpression referenceExpression = tryCast(mapPutArgs[1], PsiReferenceExpression.class);
|
||||
if (referenceExpression == null) return false;
|
||||
PsiVariable savingVar = tryCast(referenceExpression.resolve(), PsiVariable.class);
|
||||
if (!variable.equals(savingVar)) return false;
|
||||
return true;
|
||||
return tryExtractJava8Style(terminalBlock, tryCast(variable.getInitializer(), PsiMethodCallExpression.class), maybeAddCall);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static GroupingTerminal tryExtract(@NotNull TerminalBlock tb, @Nullable PsiMethodCallExpression call) {
|
||||
if (!isCallOf(call, CommonClassNames.JAVA_UTIL_COLLECTION, "add")) return null;
|
||||
PsiReferenceExpression methodExpression = call.getMethodExpression();
|
||||
PsiExpression qualifierExpression = methodExpression.getQualifierExpression();
|
||||
|
||||
if (qualifierExpression instanceof PsiMethodCallExpression && tb.getCountExpression() == null) {
|
||||
PsiMethodCallExpression qualifierCall = (PsiMethodCallExpression)qualifierExpression;
|
||||
if (isCallOf(qualifierCall, CommonClassNames.JAVA_UTIL_MAP, "computeIfAbsent")) {
|
||||
PsiExpression[] args = qualifierCall.getArgumentList().getExpressions();
|
||||
if (args.length != 2 || !(args[1] instanceof PsiLambdaExpression)) return null;
|
||||
PsiLambdaExpression lambda = (PsiLambdaExpression)args[1];
|
||||
PsiExpression body = LambdaUtil.extractSingleExpressionFromBody(lambda.getBody());
|
||||
if (ConstructionUtils.isEmptyCollectionInitializer(body)) {
|
||||
PsiLocalVariable variable = extractQualifierVariable(tb, qualifierCall);
|
||||
if (hasLambdaCompatibleEmptyInitializer(variable)) {
|
||||
PsiType mapType = variable.getType();
|
||||
PsiType valueType = PsiUtil.substituteTypeParameter(mapType, CommonClassNames.JAVA_UTIL_MAP, 1, false);
|
||||
if (valueType == null) return null;
|
||||
AddingTerminal adding = new AddingTerminal(valueType, body, tb.getVariable(), call);
|
||||
InitializerUsageStatus status = getInitializerUsageStatus(variable, tb.getStreamSourceStatement());
|
||||
return new GroupingTerminal(adding, variable, args[0], status);
|
||||
}
|
||||
}
|
||||
public static GroupingTerminal tryExtractJava8Style(@NotNull TerminalBlock tb,
|
||||
@Nullable PsiMethodCallExpression computeIfAbsentCall,
|
||||
@Nullable PsiMethodCallExpression addCall) {
|
||||
if (!LIST_ADD.test(addCall) || !COMPUTE_IF_ABSENT.test(computeIfAbsentCall)) return null;
|
||||
PsiExpression[] args = computeIfAbsentCall.getArgumentList().getExpressions();
|
||||
if (args.length != 2 || !(args[1] instanceof PsiLambdaExpression)) return null;
|
||||
PsiLambdaExpression lambda = (PsiLambdaExpression)args[1];
|
||||
PsiExpression body = LambdaUtil.extractSingleExpressionFromBody(lambda.getBody());
|
||||
if (ConstructionUtils.isEmptyCollectionInitializer(body)) {
|
||||
PsiLocalVariable variable = extractQualifierVariable(tb, computeIfAbsentCall);
|
||||
if (hasLambdaCompatibleEmptyInitializer(variable)) {
|
||||
PsiType mapType = variable.getType();
|
||||
PsiType valueType = PsiUtil.substituteTypeParameter(mapType, CommonClassNames.JAVA_UTIL_MAP, 1, false);
|
||||
if (valueType == null) return null;
|
||||
AddingTerminal adding = new AddingTerminal(valueType, body, tb.getVariable(), addCall);
|
||||
InitializerUsageStatus status = getInitializerUsageStatus(variable, tb.getStreamSourceStatement());
|
||||
return new GroupingTerminal(adding, variable, args[0], status);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -644,7 +630,6 @@ class CollectMigration extends BaseStreamApiMigration {
|
||||
return new ToMapTerminal(call, tb.getVariable(), variable, tb.getStreamSourceStatement(), status);
|
||||
}
|
||||
}
|
||||
|
||||
static class SortingTerminal extends CollectTerminal {
|
||||
private final CollectTerminal myDownstream;
|
||||
private final PsiExpression myComparator;
|
||||
|
||||
+11
-13
@@ -480,19 +480,17 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
|
||||
if (isCountOperation(nonFinalVariables, tb)) {
|
||||
return new CountMigration(true);
|
||||
}
|
||||
if (nonFinalVariables.size() == 0) {
|
||||
CollectMigration.CollectTerminal terminal = CollectMigration.extractCollectTerminal(tb, nonFinalVariables);
|
||||
if (terminal != null) {
|
||||
boolean addAll = loop instanceof PsiForeachStatement && !tb.hasOperations() && isAddAllCall(tb);
|
||||
// Don't suggest to convert the loop which can be trivially replaced via addAll:
|
||||
// this is covered by UseBulkOperationInspection and ManualArrayToCollectionCopyInspection
|
||||
if (addAll) return null;
|
||||
boolean shouldWarn = replaceTrivialForEach ||
|
||||
tb.hasOperations() ||
|
||||
tb.getLastOperation() instanceof BufferedReaderLines ||
|
||||
!terminal.isTrivial();
|
||||
return new CollectMigration(shouldWarn, terminal.getMethodName());
|
||||
}
|
||||
CollectMigration.CollectTerminal terminal = CollectMigration.extractCollectTerminal(tb, nonFinalVariables);
|
||||
if (terminal != null) {
|
||||
boolean addAll = loop instanceof PsiForeachStatement && !tb.hasOperations() && isAddAllCall(tb);
|
||||
// Don't suggest to convert the loop which can be trivially replaced via addAll:
|
||||
// this is covered by UseBulkOperationInspection and ManualArrayToCollectionCopyInspection
|
||||
if (addAll) return null;
|
||||
boolean shouldWarn = replaceTrivialForEach ||
|
||||
tb.hasOperations() ||
|
||||
tb.getLastOperation() instanceof BufferedReaderLines ||
|
||||
!terminal.isTrivial();
|
||||
return new CollectMigration(shouldWarn, terminal.getMethodName());
|
||||
}
|
||||
if(JoiningMigration.extractTerminal(tb, nonFinalVariables) != null) {
|
||||
return new JoiningMigration(true);
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
// "Replace with collect" "true"
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class Main {
|
||||
private Map<Integer, List<String>> test(String... list) {
|
||||
Map<Integer, List<String>> map = Arrays.stream(list).filter(Objects::nonNull).collect(Collectors.groupingBy(String::length));
|
||||
return map;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println(new Main().test("a", "bbb", null, "cc", "dd", "eedasfasdfs", "dd"));
|
||||
}
|
||||
}
|
||||
+2
-3
@@ -4,9 +4,8 @@ import java.util.stream.Collectors;
|
||||
|
||||
public class Main {
|
||||
private Map<Integer, List<String>> test(String... list) {
|
||||
Map<Integer, List<String>> map = new HashMap<>();
|
||||
map = Arrays.stream(list).filter(Objects::nonNull).collect(Collectors.groupingBy(String::length, Collectors.toCollection(ArrayList::new)));
|
||||
return map;
|
||||
Map<Integer, List<String>> map = Arrays.stream(list).filter(Objects::nonNull).collect(Collectors.groupingBy(String::length));
|
||||
return map;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
// "Replace with collect" "true"
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class Main {
|
||||
Map<Integer, List<String>> test(List<String> list) {
|
||||
Map<Integer, List<String>> map = list.stream().collect(Collectors.groupingBy(String::length));
|
||||
return map;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println(new Main().test("a", "bbb", null, "cc", "dd", "eedasfasdfs", "dd"));
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
// "Replace with collect" "true"
|
||||
import java.util.*;
|
||||
|
||||
public class Main {
|
||||
private Map<Integer, List<String>> test(String... list) {
|
||||
Map<Integer, List<String>> map = new HashMap<>();
|
||||
for(String s : li<caret>st) {
|
||||
if(s != null) {
|
||||
List<String> tmp = map.get(s.length());
|
||||
if(tmp == null) map.put(s.length(), tmp = new ArrayList<>());
|
||||
tmp.add(s);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println(new Main().test("a", "bbb", null, "cc", "dd", "eedasfasdfs", "dd"));
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// "Replace with collect" "true"
|
||||
import java.util.*;
|
||||
|
||||
public class Main {
|
||||
Map<Integer, List<String>> test(List<String> list) {
|
||||
Map<Integer, List<String>> map = new HashMap<>();
|
||||
for<caret> (String s : list) {
|
||||
List<String> strings = map.computeIfAbsent(s.length(), k -> new ArrayList<>());
|
||||
strings.add(s);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println(new Main().test("a", "bbb", null, "cc", "dd", "eedasfasdfs", "dd"));
|
||||
}
|
||||
}
|
||||
+294
@@ -0,0 +1,294 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.siyeh.ig.psiutils;
|
||||
|
||||
import com.intellij.codeInsight.PsiEquivalenceUtil;
|
||||
import com.intellij.codeInsight.daemon.QuickFixBundle;
|
||||
import com.intellij.codeInspection.LocalQuickFix;
|
||||
import com.intellij.codeInspection.ProblemHighlightType;
|
||||
import com.intellij.codeInspection.ProblemsHolder;
|
||||
import com.intellij.codeInspection.util.LambdaGenerationUtil;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.search.searches.ReferencesSearch;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class Java8MigrationUtils {
|
||||
@Nullable
|
||||
private static Java8MigrationUtils.MapCheckCondition tryExtract(PsiExpression fullCondition,
|
||||
@Nullable PsiStatement statement,
|
||||
boolean treatGetNullAsContainsKey) {
|
||||
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 = ExpressionUtils.getValueComparedWithNull((PsiBinaryExpression)condition);
|
||||
if (value instanceof PsiReferenceExpression && statement != null) {
|
||||
valueReference = (PsiReferenceExpression)value;
|
||||
PsiElement previous = PsiTreeUtil.skipWhitespacesAndCommentsBackward(statement);
|
||||
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 Java8MigrationUtils.MapCheckCondition(valueReference, mapExpression, keyExpression, fullCondition, negated, containsKey,
|
||||
treatGetNullAsContainsKey);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Contract("_, null -> null")
|
||||
private 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* extracts the call of the Map class
|
||||
* @param expression expected to be {@link PsiMethodCallExpression}
|
||||
* @param expectedName name of the Map method
|
||||
*/
|
||||
@Contract("null, _ -> null")
|
||||
public 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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extracts expression, that can be lambda body from Map.put() call
|
||||
* @param statement - put call or block with expected put call inside
|
||||
*/
|
||||
@Nullable
|
||||
public static PsiExpression extractLambdaCandidate(Java8MigrationUtils.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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return put value
|
||||
*/
|
||||
@Contract("_, null -> null")
|
||||
@Nullable
|
||||
public static PsiExpression extractPutValue(Java8MigrationUtils.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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Class represents check when working with map: there is 2 ways - when there is a value that matches the key, and when value doesn't exists
|
||||
*/
|
||||
public static 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 final boolean myTreatGetNullAsContainsKey;
|
||||
|
||||
private MapCheckCondition(@Nullable PsiReferenceExpression valueReference,
|
||||
PsiExpression mapExpression,
|
||||
PsiExpression keyExpression,
|
||||
PsiExpression fullCondition,
|
||||
boolean negated,
|
||||
boolean containsKey,
|
||||
boolean treatGetNullAsContainsKey) {
|
||||
myValueReference = valueReference;
|
||||
myMapExpression = mapExpression;
|
||||
myKeyExpression = keyExpression;
|
||||
myFullCondition = fullCondition;
|
||||
myNegated = negated;
|
||||
myContainsKey = containsKey;
|
||||
myTreatGetNullAsContainsKey = treatGetNullAsContainsKey;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public PsiReferenceExpression getValueReference() {
|
||||
return myValueReference;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public PsiExpression getMapExpression() {
|
||||
return myMapExpression;
|
||||
}
|
||||
|
||||
public PsiExpression getKeyExpression() {
|
||||
return myKeyExpression;
|
||||
}
|
||||
|
||||
public boolean isContainsKey() {
|
||||
return myContainsKey || myTreatGetNullAsContainsKey;
|
||||
}
|
||||
|
||||
public boolean isGetNull() {
|
||||
return !myContainsKey || myTreatGetNullAsContainsKey;
|
||||
}
|
||||
|
||||
@Contract("null -> false")
|
||||
public boolean isMap(PsiElement element) {
|
||||
return element != null && PsiEquivalenceUtil.areElementsEquivalent(myMapExpression, element);
|
||||
}
|
||||
|
||||
@Contract("null -> false")
|
||||
public boolean isKey(PsiElement element) {
|
||||
return element != null && PsiEquivalenceUtil.areElementsEquivalent(myKeyExpression, element);
|
||||
}
|
||||
|
||||
public PsiMethodCallExpression extractGetCall(PsiElement element) {
|
||||
if (!(element instanceof PsiExpression)) return null;
|
||||
PsiMethodCallExpression getCall = extractMapMethodCall((PsiExpression)element, "get");
|
||||
if (getCall == null) return null;
|
||||
PsiExpression[] args = getCall.getArgumentList().getExpressions();
|
||||
return args.length == 1 && isKey(args[0]) && isMap(getCall.getMethodExpression().getQualifierExpression()) ? getCall : null;
|
||||
}
|
||||
|
||||
@Contract("null -> false")
|
||||
public boolean isValueReference(PsiElement element) {
|
||||
return element != null && myValueReference != null && PsiEquivalenceUtil.areElementsEquivalent(element, myValueReference);
|
||||
}
|
||||
|
||||
public <T extends PsiElement> T getExistsBranch(T thenBranch, T elseBranch) {
|
||||
return myNegated ? elseBranch : thenBranch;
|
||||
}
|
||||
|
||||
public <T extends PsiElement> T getNoneBranch(T thenBranch, T elseBranch) {
|
||||
return myNegated ? thenBranch : elseBranch;
|
||||
}
|
||||
|
||||
public PsiVariable extractDeclaration() {
|
||||
if (myValueReference == null) return null;
|
||||
return PsiTreeUtil.getParentOfType(myKeyExpression, PsiVariable.class, true, PsiStatement.class);
|
||||
}
|
||||
|
||||
public boolean hasVariable() {
|
||||
if (myValueReference == null) return false;
|
||||
PsiVariable var = extractDeclaration();
|
||||
// has variable, but it used only in condition
|
||||
return var == null || ReferencesSearch.search(var).findAll().size() != 1;
|
||||
}
|
||||
|
||||
public PsiMethodCallExpression getCheckCall() {
|
||||
return PsiTreeUtil.getParentOfType(myMapExpression, PsiMethodCallExpression.class);
|
||||
}
|
||||
|
||||
public PsiExpression getFullCondition() {
|
||||
return myFullCondition;
|
||||
}
|
||||
|
||||
public void register(ProblemsHolder holder, boolean informationLevel, LocalQuickFix fix, String methodName) {
|
||||
//noinspection DialogTitleCapitalization
|
||||
holder.registerProblem(getFullCondition(), QuickFixBundle.message("java.8.map.api.inspection.description", methodName),
|
||||
informationLevel ? ProblemHighlightType.INFORMATION : ProblemHighlightType.GENERIC_ERROR_OR_WARNING, fix);
|
||||
}
|
||||
|
||||
public boolean isMapValueType(@Nullable PsiType type) {
|
||||
if (type == null) return false;
|
||||
PsiType mapExpressionType = myMapExpression.getType();
|
||||
PsiType valueTypeParameter = PsiUtil.substituteTypeParameter(mapExpressionType, CommonClassNames.JAVA_UTIL_MAP, 1, false);
|
||||
return valueTypeParameter != null && valueTypeParameter.isAssignableFrom(type);
|
||||
}
|
||||
|
||||
@Contract("null, _ -> null")
|
||||
public static Java8MigrationUtils.MapCheckCondition fromConditional(PsiElement conditional, boolean treatGetNullAsContainsKey) {
|
||||
if (conditional instanceof PsiIfStatement) {
|
||||
PsiIfStatement ifStatement = (PsiIfStatement)conditional;
|
||||
return tryExtract(ifStatement.getCondition(), ifStatement, treatGetNullAsContainsKey);
|
||||
}
|
||||
if (conditional instanceof PsiConditionalExpression) {
|
||||
PsiConditionalExpression ternary = (PsiConditionalExpression)conditional;
|
||||
PsiElement parent = ternary.getParent().getParent();
|
||||
return tryExtract(ternary.getCondition(), parent instanceof PsiStatement ? (PsiStatement)parent : null, treatGetNullAsContainsKey);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user