[java-inspections] UseBulkOperationInspection: support Maps (IDEA-262786)

GitOrigin-RevId: 9aae98c287f6e2057b09bfda78fb007ba45eeee6
This commit is contained in:
Andrey.Cherkasov
2021-05-25 16:22:10 +03:00
committed by intellij-monorepo-bot
parent 480de17a92
commit f68f593aa4
15 changed files with 230 additions and 73 deletions

View File

@@ -1,18 +1,4 @@
/*
* 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.
*/
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInspection.bulkOperation;
import com.intellij.openapi.util.text.StringUtil;
@@ -22,6 +8,8 @@ import com.intellij.psi.util.PsiUtil;
import java.util.Objects;
import static com.intellij.psi.CommonClassNames.*;
public final class BulkMethodInfo {
private final String myClassName;
private final String mySimpleName;
@@ -39,12 +27,12 @@ public final class BulkMethodInfo {
if (!(element instanceof PsiMethod)) return false;
PsiMethod method = (PsiMethod)element;
PsiParameterList parameters = method.getParameterList();
if (parameters.getParametersCount() != 1) return false;
if (parameters.getParametersCount() != (myClassName.equals(JAVA_UTIL_MAP) ? 2 : 1)) return false;
PsiParameter parameter = Objects.requireNonNull(parameters.getParameter(0));
PsiClass parameterClass = PsiUtil.resolveClassInClassTypeOnly(parameter.getType());
if (parameterClass == null ||
CommonClassNames.JAVA_LANG_ITERABLE.equals(parameterClass.getQualifiedName()) ||
CommonClassNames.JAVA_UTIL_COLLECTION.equals(parameterClass.getQualifiedName())) {
JAVA_LANG_ITERABLE.equals(parameterClass.getQualifiedName()) ||
JAVA_UTIL_COLLECTION.equals(parameterClass.getQualifiedName())) {
return false;
}
PsiClass methodClass = method.getContainingClass();
@@ -61,9 +49,9 @@ public final class BulkMethodInfo {
if (type instanceof PsiArrayType) {
PsiType componentType = ((PsiArrayType)type).getComponentType();
if (!useArraysAsList || componentType instanceof PsiPrimitiveType) return false;
PsiClass listClass = psiFacade.findClass(CommonClassNames.JAVA_UTIL_LIST, iterable.getResolveScope());
PsiClass listClass = psiFacade.findClass(JAVA_UTIL_LIST, iterable.getResolveScope());
if (listClass == null) return false;
if (!listClass.hasTypeParameters()){
if (!listClass.hasTypeParameters()) {
// Raw List class - Java 1.4?
type = factory.createType(listClass);
} else if (listClass.getTypeParameters().length == 1) {
@@ -71,16 +59,21 @@ public final class BulkMethodInfo {
} else {
return false;
}
text = CommonClassNames.JAVA_UTIL_ARRAYS + ".asList(" + text + ")";
text = JAVA_UTIL_ARRAYS + ".asList(" + text + ")";
}
PsiClass aClass = PsiUtil.resolveClassInType(type);
if (aClass == null) return false;
PsiClass commonParent = psiFacade.findClass(CommonClassNames.JAVA_LANG_ITERABLE, aClass.getResolveScope());
if (commonParent == null) {
// No Iterable class in Java 1.4
commonParent = psiFacade.findClass(CommonClassNames.JAVA_UTIL_COLLECTION, aClass.getResolveScope());
PsiClass commonParent;
if (myClassName.equals(JAVA_UTIL_MAP)) {
commonParent = psiFacade.findClass(JAVA_UTIL_MAP, aClass.getResolveScope());
} else {
commonParent = psiFacade.findClass(JAVA_LANG_ITERABLE, aClass.getResolveScope());
if (commonParent == null) {
// No Iterable class in Java 1.4
commonParent = psiFacade.findClass(JAVA_UTIL_COLLECTION, aClass.getResolveScope());
}
}
if(!InheritanceUtil.isInheritorOrSelf(aClass, commonParent, true)) return false;
if (!InheritanceUtil.isInheritorOrSelf(aClass, commonParent, true)) return false;
PsiExpression expression = factory.createExpressionFromText(qualifier.getText() + "." + myBulkName + "(" + text + ")", iterable);
if (!(expression instanceof PsiMethodCallExpression)) return false;
PsiMethodCallExpression call = (PsiMethodCallExpression)expression;
@@ -91,9 +84,11 @@ public final class BulkMethodInfo {
PsiType parameterType = Objects.requireNonNull(parameters.getParameter(0)).getType();
parameterType = call.resolveMethodGenerics().getSubstitutor().substitute(parameterType);
PsiClass parameterClass = PsiUtil.resolveClassInClassTypeOnly(parameterType);
return parameterClass != null &&
(CommonClassNames.JAVA_LANG_ITERABLE.equals(parameterClass.getQualifiedName()) ||
CommonClassNames.JAVA_UTIL_COLLECTION.equals(parameterClass.getQualifiedName())) &&
if (parameterClass == null) return false;
String qualifiedName = parameterClass.getQualifiedName();
return (myClassName.equals(JAVA_UTIL_MAP)
? JAVA_UTIL_MAP.equals(qualifiedName)
: (JAVA_LANG_ITERABLE.equals(qualifiedName) || JAVA_UTIL_COLLECTION.equals(qualifiedName))) &&
parameterType.isAssignableFrom(type);
}

View File

@@ -1,18 +1,4 @@
/*
* 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.
*/
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInspection.bulkOperation;
import com.intellij.psi.CommonClassNames;
@@ -23,7 +9,8 @@ import java.util.stream.Stream;
public class JdkBulkMethodInfoProvider implements BulkMethodInfoProvider {
private static final BulkMethodInfo[] INFOS = {
new BulkMethodInfo(CommonClassNames.JAVA_UTIL_COLLECTION, "add", "addAll")
new BulkMethodInfo(CommonClassNames.JAVA_UTIL_COLLECTION, "add", "addAll"),
new BulkMethodInfo(CommonClassNames.JAVA_UTIL_MAP, "put", "putAll")
};
@NotNull

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInspection.bulkOperation;
import com.intellij.codeInspection.AbstractBaseJavaLocalInspectionTool;
@@ -15,6 +15,8 @@ import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.refactoring.util.RefactoringUtil;
import com.intellij.util.ObjectUtils;
import com.siyeh.ig.callMatcher.CallMatcher;
import com.siyeh.ig.psiutils.*;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.Contract;
@@ -28,6 +30,13 @@ import java.util.regex.Pattern;
public class UseBulkOperationInspection extends AbstractBaseJavaLocalInspectionTool {
private static final Pattern FOR_EACH_METHOD = Pattern.compile("forEach(Ordered)?");
private static final CallMatcher MAP_ENTRY_SET =
CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_MAP, "entrySet").parameterCount(0);
private static final CallMatcher ENTRY_GET_KEY =
CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_MAP_ENTRY, "getKey").parameterCount(0);
private static final CallMatcher ENTRY_GET_VALUE =
CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_MAP_ENTRY, "getValue").parameterCount(0);
public boolean USE_ARRAYS_AS_LIST = true;
@Nullable
@@ -44,13 +53,13 @@ public class UseBulkOperationInspection extends AbstractBaseJavaLocalInspectionT
}
@Nullable
private static PsiExpression findIterable(PsiMethodCallExpression expression) {
private static PsiExpression findIterable(PsiMethodCallExpression expression, BulkMethodInfo info) {
PsiExpression[] args = expression.getArgumentList().getExpressions();
if (args.length != 1) return null;
PsiExpression arg = args[0];
int simpleMethodParametersCount = info.getClassName().equals(CommonClassNames.JAVA_UTIL_MAP) ? 2 : 1;
if (args.length != simpleMethodParametersCount) return null;
PsiElement parent = expression.getParent();
if (parent instanceof PsiLambdaExpression) {
return findIterableForLambda((PsiLambdaExpression)parent, arg);
return findIterableForLambda((PsiLambdaExpression)parent, args, info);
}
if (parent instanceof PsiExpressionStatement) {
PsiExpressionStatement expressionStatement = (PsiExpressionStatement)parent;
@@ -60,19 +69,28 @@ public class UseBulkOperationInspection extends AbstractBaseJavaLocalInspectionT
if (codeBlock.getParent() instanceof PsiBlockStatement) {
PsiBlockStatement blockStatement = (PsiBlockStatement)codeBlock.getParent();
if (statements.length == 1) {
return findIterableForSingleStatement(blockStatement, arg);
return findIterableForSingleStatement(blockStatement, args);
}
PsiElement blockParent = blockStatement.getParent();
if (statements.length == 2 && statements[1] == parent && blockParent instanceof PsiLoopStatement) {
IteratorDeclaration declaration = IteratorDeclaration.fromLoop((PsiLoopStatement)blockParent);
if (declaration != null && ExpressionUtils.isReferenceTo(arg, declaration.getNextElementVariable(statements[0]))) {
return declaration.getIterable();
if (declaration != null) {
if (args.length == 1 && ExpressionUtils.isReferenceTo(args[0], declaration.getNextElementVariable(statements[0]))) {
return declaration.getIterable();
} else if (args.length == 2) {
if (isGetValueAndGetKey(args, declaration.getNextElementVariable(statements[0]))) {
PsiMethodCallExpression entrySetCandidate = ObjectUtils.tryCast(declaration.getIterable(), PsiMethodCallExpression.class);
if (MAP_ENTRY_SET.test(entrySetCandidate)) {
return entrySetCandidate.getMethodExpression().getQualifierExpression();
}
}
}
}
if(blockParent instanceof PsiForStatement && statements[0] instanceof PsiDeclarationStatement) {
if (blockParent instanceof PsiForStatement && statements[0] instanceof PsiDeclarationStatement) {
PsiElement[] elements = ((PsiDeclarationStatement)statements[0]).getDeclaredElements();
if(elements.length == 1 && elements[0] instanceof PsiLocalVariable) {
if (elements.length == 1 && elements[0] instanceof PsiLocalVariable) {
PsiLocalVariable var = (PsiLocalVariable)elements[0];
if (ExpressionUtils.isReferenceTo(arg, var)) {
if (ExpressionUtils.isReferenceTo(args[0], var)) {
return findIterableForIndexedLoop((PsiForStatement)blockParent, var.getInitializer());
}
}
@@ -80,44 +98,66 @@ public class UseBulkOperationInspection extends AbstractBaseJavaLocalInspectionT
}
}
else if (codeBlock.getParent() instanceof PsiLambdaExpression && statements.length == 1) {
return findIterableForLambda((PsiLambdaExpression)codeBlock.getParent(), arg);
return findIterableForLambda((PsiLambdaExpression)codeBlock.getParent(), args, info);
}
}
else {
return findIterableForSingleStatement(expressionStatement, arg);
return findIterableForSingleStatement(expressionStatement, args);
}
}
return null;
}
@Nullable
private static PsiExpression findIterableForLambda(PsiLambdaExpression lambda, PsiExpression arg) {
PsiParameterList parameters = lambda.getParameterList();
if (parameters.getParametersCount() != 1) return null;
PsiParameter parameter = parameters.getParameters()[0];
if (ExpressionUtils.isReferenceTo(arg, parameter)) {
return findIterableForFunction(lambda);
}
return null;
private static PsiExpression findIterableForLambda(PsiLambdaExpression lambda, PsiExpression[] args, BulkMethodInfo info) {
PsiParameterList parameterList = lambda.getParameterList();
PsiParameter[] parameters = parameterList.getParameters();
int lambdaParametersCount = parameterList.getParametersCount();
if (info.getClassName().equals(CommonClassNames.JAVA_UTIL_MAP)) {
if (lambdaParametersCount == 1) {
if (!isGetValueAndGetKey(args, parameters[0])) return null;
} else if (lambdaParametersCount == 2) {
if (!ExpressionUtils.isReferenceTo(args[0], parameters[0]) ||
!ExpressionUtils.isReferenceTo(args[1], parameters[1])) return null;
} else return null;
} else if (lambdaParametersCount != 1 || !ExpressionUtils.isReferenceTo(args[0], parameters[0])) return null;
return findIterableForFunction(lambda);
}
private static boolean isGetValueAndGetKey(PsiExpression[] args, PsiVariable variable) {
PsiMethodCallExpression getKeyCandidate = ObjectUtils.tryCast(args[0], PsiMethodCallExpression.class);
PsiMethodCallExpression getValueCandidate = ObjectUtils.tryCast(args[1], PsiMethodCallExpression.class);
if (!ENTRY_GET_KEY.test(getKeyCandidate) || !ENTRY_GET_VALUE.test(getValueCandidate)) return false;
PsiExpression getKeyQualifier = getKeyCandidate.getMethodExpression().getQualifierExpression();
PsiExpression getValueQualifier = getValueCandidate.getMethodExpression().getQualifierExpression();
return ExpressionUtils.isReferenceTo(getKeyQualifier, variable) && ExpressionUtils.isReferenceTo(getValueQualifier, variable);
}
@Nullable
private static PsiExpression findIterableForSingleStatement(PsiStatement statement, PsiExpression arg) {
private static PsiExpression findIterableForSingleStatement(PsiStatement statement, PsiExpression[] args) {
PsiElement parent = statement.getParent();
if (parent instanceof PsiForeachStatement) {
PsiForeachStatement foreachStatement = (PsiForeachStatement)parent;
if (ExpressionUtils.isReferenceTo(arg, foreachStatement.getIterationParameter())) {
return foreachStatement.getIteratedValue();
PsiExpression iteratedValue = foreachStatement.getIteratedValue();
if (args.length == 2) {
if (isGetValueAndGetKey(args, foreachStatement.getIterationParameter())) {
PsiMethodCallExpression entrySetCandidate = ObjectUtils.tryCast(iteratedValue, PsiMethodCallExpression.class);
if (MAP_ENTRY_SET.test(entrySetCandidate)) {
return entrySetCandidate.getMethodExpression().getQualifierExpression();
}
}
} else if (ExpressionUtils.isReferenceTo(args[0], foreachStatement.getIterationParameter())) {
return iteratedValue;
}
}
if (parent instanceof PsiLoopStatement) {
IteratorDeclaration declaration = IteratorDeclaration.fromLoop((PsiLoopStatement)parent);
if (declaration != null && declaration.isIteratorMethodCall(arg, "next")) {
if (declaration != null && declaration.isIteratorMethodCall(args[0], "next")) {
return declaration.getIterable();
}
}
if (parent instanceof PsiForStatement) {
return findIterableForIndexedLoop((PsiForStatement)parent, arg);
return findIterableForIndexedLoop((PsiForStatement)parent, args[0]);
}
return null;
}
@@ -150,6 +190,12 @@ public class UseBulkOperationInspection extends AbstractBaseJavaLocalInspectionT
PsiMethodCallExpression parentCall = (PsiMethodCallExpression)parent;
PsiExpression parentQualifier = PsiUtil.skipParenthesizedExprDown(parentCall.getMethodExpression().getQualifierExpression());
if (MethodCallUtils.isCallToMethod(parentCall, CommonClassNames.JAVA_LANG_ITERABLE, null, "forEach", new PsiType[]{null})) {
PsiMethodCallExpression entrySetCandidate = ObjectUtils.tryCast(parentQualifier, PsiMethodCallExpression.class);
return MAP_ENTRY_SET.test(entrySetCandidate)
? ExpressionUtils.getEffectiveQualifier(entrySetCandidate.getMethodExpression())
: ExpressionUtils.getEffectiveQualifier(parentCall.getMethodExpression());
}
if (MethodCallUtils.isCallToMethod(parentCall, CommonClassNames.JAVA_UTIL_MAP, null, "forEach", new PsiType[]{null})) {
return ExpressionUtils.getEffectiveQualifier(parentCall.getMethodExpression());
}
if (MethodCallUtils.isCallToMethod(parentCall, CommonClassNames.JAVA_UTIL_STREAM_STREAM, null, FOR_EACH_METHOD, new PsiType[]{null}) &&
@@ -184,7 +230,7 @@ public class UseBulkOperationInspection extends AbstractBaseJavaLocalInspectionT
super.visitMethodCallExpression(call);
BulkMethodInfo info = findInfo(call.getMethodExpression());
if (info == null) return;
PsiExpression iterable = findIterable(call);
PsiExpression iterable = findIterable(call, info);
if (iterable != null) {
register(iterable, info, call.getMethodExpression());
}
@@ -263,7 +309,7 @@ public class UseBulkOperationInspection extends AbstractBaseJavaLocalInspectionT
else {
PsiElement parent = element.getParent();
if (!(parent instanceof PsiMethodCallExpression)) return;
iterable = findIterable((PsiMethodCallExpression)parent);
iterable = findIterable((PsiMethodCallExpression)parent, myInfo);
}
if (iterable == null) return;
PsiElement parent = RefactoringUtil.getParentStatement(iterable, false);

View File

@@ -0,0 +1,10 @@
// "Replace iteration with bulk 'Map.putAll' call" "true"
import java.util.*;
class Main {
void test(Map<String, Integer> map) {
Map<String, Integer> result = new HashMap<>();
result.put("answer", 42);
result.putAll(map);
}
}

View File

@@ -0,0 +1,10 @@
// "Replace iteration with bulk 'Map.putAll' call" "true"
import java.util.*;
class Main {
void test(Map<String, Integer> map) {
Map<String, Integer> result = new HashMap<>();
result.put("answer", 42);
result.putAll(map);
}
}

View File

@@ -0,0 +1,10 @@
// "Replace iteration with bulk 'Map.putAll' call" "true"
import java.util.*;
class Main {
void test(Map<String, Integer> map) {
Map<String, Integer> result = new HashMap<>();
result.put("answer", 42);
result.putAll(map);
}
}

View File

@@ -0,0 +1,10 @@
// "Replace iteration with bulk 'Map.putAll' call" "true"
import java.util.*;
class Main {
void test(Map<String, Integer> map) {
Map<String, Integer> result = new HashMap<>();
result.put("answer", 42);
result.putAll(map);
}
}

View File

@@ -0,0 +1,10 @@
// "Replace iteration with bulk 'Map.putAll' call" "true"
import java.util.*;
class Main {
void test(Map<String, Integer> map) {
Map<String, Integer> result = new HashMap<>();
result.put("answer", 42);
result.putAll(map);
}
}

View File

@@ -0,0 +1,10 @@
// "Replace iteration with bulk 'Map.putAll' call" "true"
import java.util.*;
class Main {
void test(Map<String, Integer> map) {
Map<String, Integer> result = new HashMap<>();
result.put("answer", 42);
result.putAll(map);
}
}

View File

@@ -0,0 +1,10 @@
// "Replace iteration with bulk 'Map.putAll' call" "true"
import java.util.*;
class Main {
void test(Map<String, Integer> map) {
Map<String, Integer> result = new HashMap<>();
result.put("answer", 42);
map.entrySet().forEach(e -> result<caret>.put(e.getKey(), e.getValue()));
}
}

View File

@@ -0,0 +1,10 @@
// "Replace iteration with bulk 'Map.putAll' call" "true"
import java.util.*;
class Main {
void test(Map<String, Integer> map) {
Map<String, Integer> result = new HashMap<>();
result.put("answer", 42);
map.forEach((key, value) -> result<caret>.put(key, value));
}
}

View File

@@ -0,0 +1,12 @@
// "Replace iteration with bulk 'Map.putAll' call" "true"
import java.util.*;
class Main {
void test(Map<String, Integer> map) {
Map<String, Integer> result = new HashMap<>();
result.put("answer", 42);
for (Map.Entry<String, Integer> e : map.entrySet()) {
result<caret>.put(e.getKey(), e.getValue());
}
}
}

View File

@@ -0,0 +1,10 @@
// "Replace iteration with bulk 'Map.putAll' call" "true"
import java.util.*;
class Main {
void test(Map<String, Integer> map) {
Map<String, Integer> result = new HashMap<>();
result.put("answer", 42);
map.forEach(result::put<caret>);
}
}

View File

@@ -0,0 +1,13 @@
// "Replace iteration with bulk 'Map.putAll' call" "true"
import java.util.*;
class Main {
void test(Map<String, Integer> map) {
Map<String, Integer> result = new HashMap<>();
result.put("answer", 42);
for (Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator(); iterator.hasNext(); ) {
Map.Entry<String, Integer> e = iterator.next();
result<caret>.put(e.getKey(), e.getValue());
}
}
}

View File

@@ -0,0 +1,14 @@
// "Replace iteration with bulk 'Map.putAll' call" "true"
import java.util.*;
class Main {
void test(Map<String, Integer> map) {
Map<String, Integer> result = new HashMap<>();
result.put("answer", 42);
Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, Integer> e = iterator.next();
result<caret>.put(e.getKey(), e.getValue());
}
}
}