IDEA-165942 Inspection to replace method call in a loop with bulk operation

This commit is contained in:
Tagir Valeev
2016-12-30 13:01:31 +07:00
parent 53ebd17f38
commit 01d7ee7d3c
39 changed files with 813 additions and 76 deletions
@@ -0,0 +1,415 @@
/*
* 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.PsiEquivalenceUtil;
import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel;
import com.intellij.codeInspection.util.IteratorDeclaration;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.refactoring.util.RefactoringUtil;
import com.siyeh.ig.psiutils.CommentTracker;
import com.siyeh.ig.psiutils.ExpressionUtils;
import com.siyeh.ig.psiutils.MethodCallUtils;
import com.siyeh.ig.psiutils.VariableAccessUtils;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.List;
import java.util.regex.Pattern;
/**
* @author Tagir Valeev
*/
public class UseBulkOperationInspection extends BaseJavaBatchLocalInspectionTool {
private static BulkMethodInfo[] INFOS = {
new BulkMethodInfo(CommonClassNames.JAVA_UTIL_COLLECTION, "add", "addAll"),
new BulkMethodInfo(CommonClassNames.JAVA_UTIL_LIST, "remove", "removeAll"),
new BulkMethodInfo("org.springframework.data.repository.CrudRepository", "save", "save"),
new BulkMethodInfo("org.springframework.data.repository.CrudRepository", "delete", "delete"),
};
private List<BulkMethodInfo> myInfos = StreamEx.of(INFOS).toList();
public boolean USE_ARRAYS_AS_LIST = true;
@Nullable
@Override
public JComponent createOptionsPanel() {
return new SingleCheckboxOptionsPanel("Use Arrays.asList() to adapt arrays", this, "USE_ARRAYS_AS_LIST");
}
@Nullable
private BulkMethodInfo findInfo(PsiReferenceExpression ref) {
return StreamEx.of(myInfos).findFirst(info -> info.isMyMethod(ref)).orElse(null);
}
private static PsiExpression getQualifierOrThis(@NotNull PsiReferenceExpression ref) {
PsiExpression qualifier = ref.getQualifierExpression();
if (qualifier != null) return qualifier;
return JavaPsiFacade.getElementFactory(ref.getProject()).createExpressionFromText("this", ref);
}
@Nullable
private static PsiExpression findIterable(PsiMethodCallExpression expression) {
PsiElement parent = expression.getParent();
PsiExpression[] args = expression.getArgumentList().getExpressions();
if (args.length != 1) return null;
PsiExpression arg = args[0];
if (parent instanceof PsiLambdaExpression) {
return findIterableForLambda((PsiLambdaExpression)parent, arg);
}
if (parent instanceof PsiExpressionStatement) {
PsiExpressionStatement expressionStatement = (PsiExpressionStatement)parent;
if (expressionStatement.getParent() instanceof PsiCodeBlock) {
PsiCodeBlock codeBlock = (PsiCodeBlock)expressionStatement.getParent();
PsiStatement[] statements = codeBlock.getStatements();
if (codeBlock.getParent() instanceof PsiBlockStatement) {
PsiBlockStatement blockStatement = (PsiBlockStatement)codeBlock.getParent();
if (statements.length == 1) {
return findIterableForSingleStatement(blockStatement, arg);
}
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(blockParent instanceof PsiForStatement && statements[0] instanceof PsiDeclarationStatement) {
PsiElement[] elements = ((PsiDeclarationStatement)statements[0]).getDeclaredElements();
if(elements.length == 1 && elements[0] instanceof PsiLocalVariable) {
PsiLocalVariable var = (PsiLocalVariable)elements[0];
if (ExpressionUtils.isReferenceTo(arg, var)) {
return findIterableForIndexedLoop((PsiForStatement)blockParent, var.getInitializer());
}
}
}
}
}
else if (codeBlock.getParent() instanceof PsiLambdaExpression && statements.length == 1) {
return findIterableForLambda((PsiLambdaExpression)codeBlock.getParent(), arg);
}
}
else {
return findIterableForSingleStatement(expressionStatement, arg);
}
}
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;
}
@Nullable
private static PsiExpression findIterableForSingleStatement(PsiStatement statement, PsiExpression arg) {
PsiElement parent = statement.getParent();
if (parent instanceof PsiForeachStatement) {
PsiForeachStatement foreachStatement = (PsiForeachStatement)parent;
if (ExpressionUtils.isReferenceTo(arg, foreachStatement.getIterationParameter())) {
return foreachStatement.getIteratedValue();
}
}
if (parent instanceof PsiLoopStatement) {
IteratorDeclaration declaration = IteratorDeclaration.fromLoop((PsiLoopStatement)parent);
if (declaration != null && declaration.isIteratorMethodCall(arg, "next")) {
return declaration.getIterable();
}
}
if (parent instanceof PsiForStatement) {
return findIterableForIndexedLoop((PsiForStatement)parent, arg);
}
return null;
}
@Nullable
private static PsiExpression findIterableForIndexedLoop(PsiForStatement loop, PsiExpression getElementExpression) {
PsiExpression indexExpression = null;
PsiExpression iterable = null;
// Check that getElementExpression is either list.get(idx) or arr[idx] extracting idx and list/arr
if (getElementExpression instanceof PsiArrayAccessExpression) {
PsiArrayAccessExpression arrayAccess = (PsiArrayAccessExpression)getElementExpression;
indexExpression = arrayAccess.getIndexExpression();
iterable = arrayAccess.getArrayExpression();
}
else if (getElementExpression instanceof PsiMethodCallExpression) {
PsiMethodCallExpression call = (PsiMethodCallExpression)getElementExpression;
PsiExpression[] args = call.getArgumentList().getExpressions();
if (args.length != 1 || !MethodCallUtils.isCallToMethod(call, CommonClassNames.JAVA_UTIL_LIST, null, "get", PsiType.INT)) {
return null;
}
indexExpression = args[0];
iterable = getQualifierOrThis(call.getMethodExpression());
}
if (iterable == null) return null;
// Check that loop initialization is like `int idx = 0` and loop update is like `idx++`
PsiStatement initialization = loop.getInitialization();
if (!(initialization instanceof PsiDeclarationStatement)) return null;
PsiElement[] declaredElements = ((PsiDeclarationStatement)initialization).getDeclaredElements();
if (declaredElements.length != 1 || !(declaredElements[0] instanceof PsiLocalVariable)) return null;
PsiLocalVariable indexVariable = (PsiLocalVariable)declaredElements[0];
if (!ExpressionUtils.isReferenceTo(indexExpression, indexVariable) ||
!ExpressionUtils.isZero(indexVariable.getInitializer()) ||
!VariableAccessUtils.variableIsIncremented(indexVariable, loop.getUpdate())) {
return null;
}
// Check that loop condition is like `idx < arr.length` or `idx < list.size()`
PsiExpression condition = loop.getCondition();
if (!(condition instanceof PsiBinaryExpression)) return null;
PsiBinaryExpression binOp = (PsiBinaryExpression)condition;
if (!binOp.getOperationTokenType().equals(JavaTokenType.LT)) return null;
if (!ExpressionUtils.isReferenceTo(binOp.getLOperand(), indexVariable)) return null;
PsiExpression bound = binOp.getROperand();
if (bound instanceof PsiMethodCallExpression) {
PsiMethodCallExpression boundCall = (PsiMethodCallExpression)bound;
if (!MethodCallUtils.isCallToMethod(boundCall, CommonClassNames.JAVA_UTIL_LIST, PsiType.INT, "size")) return null;
PsiExpression sizeQualifier = getQualifierOrThis(boundCall.getMethodExpression());
if (PsiEquivalenceUtil.areElementsEquivalent(sizeQualifier, iterable)) return sizeQualifier;
} else {
PsiExpression arrayExpression = ExpressionUtils.getArrayFromLengthExpression(bound);
if (arrayExpression != null && PsiEquivalenceUtil.areElementsEquivalent(arrayExpression, iterable)) return arrayExpression;
}
return null;
}
@Nullable
private static PsiExpression findIterableForFunction(PsiFunctionalExpression function) {
PsiElement parent = PsiUtil.skipParenthesizedExprUp(function.getParent());
if (parent instanceof PsiTypeCastExpression) parent = PsiUtil.skipParenthesizedExprUp(parent.getParent());
if (!(parent instanceof PsiExpressionList)) return null;
PsiExpression[] args = ((PsiExpressionList)parent).getExpressions();
if (args.length != 1) return null;
parent = parent.getParent();
if (!(parent instanceof PsiMethodCallExpression)) return null;
PsiMethodCallExpression parentCall = (PsiMethodCallExpression)parent;
PsiExpression parentQualifier = PsiUtil.skipParenthesizedExprDown(parentCall.getMethodExpression().getQualifierExpression());
if (MethodCallUtils.isCallToMethod(parentCall, CommonClassNames.JAVA_LANG_ITERABLE, null,
"forEach", new PsiType[]{null})) {
return getQualifierOrThis(parentCall.getMethodExpression());
}
if (MethodCallUtils.isCallToMethod(parentCall, CommonClassNames.JAVA_UTIL_STREAM_STREAM, null,
Pattern.compile("forEach(Ordered)?"), new PsiType[]{null}) &&
parentQualifier instanceof PsiMethodCallExpression) {
PsiMethodCallExpression grandParentCall = (PsiMethodCallExpression)parentQualifier;
if (MethodCallUtils.isCallToMethod(grandParentCall, CommonClassNames.JAVA_UTIL_COLLECTION, null,
"stream", PsiType.EMPTY_ARRAY)) {
return getQualifierOrThis(grandParentCall.getMethodExpression());
}
PsiExpression[] grandParentArgs = grandParentCall.getArgumentList().getExpressions();
if (MethodCallUtils.isCallToStaticMethod(grandParentCall, CommonClassNames.JAVA_UTIL_ARRAYS, "stream", 1)) {
if (grandParentArgs.length == 1) {
return grandParentArgs[0];
}
}
if (MethodCallUtils.isCallToStaticMethod(grandParentCall, CommonClassNames.JAVA_UTIL_STREAM_STREAM, "of", 1)) {
if (grandParentArgs.length == 1) {
PsiExpression maybeArray = grandParentArgs[0];
PsiType type = maybeArray.getType();
if (type instanceof PsiArrayType) {
return maybeArray;
}
}
}
}
return null;
}
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
return new JavaElementVisitor() {
@Override
public void visitMethodCallExpression(PsiMethodCallExpression call) {
super.visitMethodCallExpression(call);
PsiExpression iterable = findIterable(call);
if (iterable == null) return;
BulkMethodInfo info = findInfo(call.getMethodExpression());
if (info != null) register(iterable, info, call.getMethodExpression());
}
@Override
public void visitMethodReferenceExpression(PsiMethodReferenceExpression methodReference) {
super.visitMethodReferenceExpression(methodReference);
BulkMethodInfo info = findInfo(methodReference);
if (info == null) return;
PsiExpression iterable = findIterableForFunction(methodReference);
if (iterable != null) register(iterable, info, methodReference);
}
private void register(@NotNull PsiExpression iterable,
@NotNull BulkMethodInfo info,
@NotNull PsiReferenceExpression methodExpression) {
PsiExpression qualifier = getQualifierOrThis(methodExpression);
if (qualifier instanceof PsiThisExpression) {
PsiMethod method = PsiTreeUtil.getParentOfType(iterable, PsiMethod.class);
// Likely we are inside of the bulk method implementation
if (method != null && method.getName().equals(info.myBulkName)) return;
}
if (qualifier != null && info.isSupportedIterable(qualifier, iterable, USE_ARRAYS_AS_LIST)) {
holder.registerProblem(methodExpression,
InspectionsBundle.message("inspection.replace.with.bulk.message", info.getReplacementName()),
new UseBulkOperationFix(info));
}
}
};
}
private static class UseBulkOperationFix implements LocalQuickFix {
private BulkMethodInfo myInfo;
public UseBulkOperationFix(BulkMethodInfo info) {
myInfo = info;
}
@Nls
@NotNull
@Override
public String getName() {
return InspectionsBundle.message("inspection.replace.with.bulk.fix.name", myInfo.getReplacementName());
}
@Nls
@NotNull
@Override
public String getFamilyName() {
return InspectionsBundle.message("inspection.replace.with.bulk.fix.family.name");
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
PsiElement element = descriptor.getStartElement();
if (!(element instanceof PsiReferenceExpression)) return;
PsiExpression qualifier = getQualifierOrThis((PsiReferenceExpression)element);
if (qualifier == null) return;
PsiExpression iterable;
if (element instanceof PsiMethodReferenceExpression) {
iterable = findIterableForFunction((PsiFunctionalExpression)element);
}
else {
PsiElement parent = element.getParent();
if (!(parent instanceof PsiMethodCallExpression)) return;
iterable = findIterable((PsiMethodCallExpression)parent);
}
if (iterable == null) return;
PsiElement parent = RefactoringUtil.getParentStatement(iterable, false);
if (parent == null) return;
CommentTracker ct = new CommentTracker();
PsiType type = iterable.getType();
String iterableText = ct.text(iterable);
if (type instanceof PsiArrayType) {
iterableText = CommonClassNames.JAVA_UTIL_ARRAYS + ".asList(" + iterableText + ")";
}
if (parent instanceof PsiDeclarationStatement) {
PsiLoopStatement loop = PsiTreeUtil.getParentOfType(element, PsiLoopStatement.class);
if (loop != null && loop.getParent() == parent.getParent()) {
ct.delete(loop);
}
}
PsiElement result = ct.replaceAndRestoreComments(parent, ct.text(qualifier) + "." + myInfo.myBulkName + "(" + iterableText + ")"
+ (parent instanceof PsiStatement ? ";" : ""));
result = JavaCodeStyleManager.getInstance(project).shortenClassReferences(result);
CodeStyleManager.getInstance(project).reformat(result);
}
}
private static class BulkMethodInfo {
private final String myClassName;
private final String mySimpleName;
private final String myBulkName;
private BulkMethodInfo(String className, String simpleName, String bulkName) {
myClassName = className;
mySimpleName = simpleName;
myBulkName = bulkName;
}
public boolean isMyMethod(PsiReferenceExpression ref) {
if (!mySimpleName.equals(ref.getReferenceName())) return false;
PsiElement element = ref.resolve();
if (!(element instanceof PsiMethod)) return false;
PsiMethod method = (PsiMethod)element;
PsiParameterList parameters = method.getParameterList();
if (parameters.getParametersCount() != 1) return false;
PsiParameter parameter = parameters.getParameters()[0];
PsiClass parameterClass = PsiUtil.resolveClassInClassTypeOnly(parameter.getType());
if (parameterClass == null ||
CommonClassNames.JAVA_LANG_ITERABLE.equals(parameterClass.getQualifiedName()) ||
CommonClassNames.JAVA_UTIL_COLLECTION.equals(parameterClass.getQualifiedName())) {
return false;
}
PsiClass methodClass = method.getContainingClass();
return methodClass != null && InheritanceUtil.isInheritor(methodClass, myClassName);
}
public boolean isSupportedIterable(PsiExpression qualifier, PsiExpression iterable, boolean useArraysAsList) {
PsiType qualifierType = qualifier.getType();
if (!(qualifierType instanceof PsiClassType)) return false;
PsiType type = iterable.getType();
PsiElementFactory factory = JavaPsiFacade.getElementFactory(iterable.getProject());
if (type instanceof PsiArrayType) {
PsiType componentType = ((PsiArrayType)type).getComponentType();
if (!useArraysAsList || componentType instanceof PsiPrimitiveType) return false;
PsiClass listClass =
JavaPsiFacade.getInstance(iterable.getProject()).findClass(CommonClassNames.JAVA_UTIL_LIST, iterable.getResolveScope());
if (listClass == null) return false;
type = factory.createType(listClass, componentType);
}
if (!InheritanceUtil.isInheritor(type, CommonClassNames.JAVA_LANG_ITERABLE)) return false;
PsiExpression expression =
factory.createExpressionFromText(qualifier.getText() + "." + myBulkName + "(" + iterable.getText() + ")", iterable);
if (!(expression instanceof PsiMethodCallExpression)) return false;
PsiMethodCallExpression call = (PsiMethodCallExpression)expression;
PsiMethod bulkMethod = call.resolveMethod();
if (bulkMethod == null) return false;
PsiParameterList parameters = bulkMethod.getParameterList();
if (parameters.getParametersCount() != 1) return false;
PsiType parameterType = parameters.getParameters()[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())) &&
parameterType.isAssignableFrom(type);
}
public String getReplacementName() {
return StringUtil.getShortName(myClassName) + "." + myBulkName;
}
@Override
public String toString() {
return myClassName + "::" + mySimpleName + " => " + myBulkName;
}
}
}
@@ -19,7 +19,6 @@ import com.intellij.codeInspection.streamMigration.StreamApiMigrationInspection.
import com.intellij.codeInspection.streamMigration.StreamApiMigrationInspection.MapOp;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.codeStyle.SuggestedNameInfo;
@@ -60,9 +59,6 @@ class CollectMigration extends BaseStreamApiMigration {
if (call == null) return null;
restoreComments(loopStatement, body);
if (!tb.hasOperations() && StreamApiMigrationInspection.isAddAllCall(tb) && loopStatement instanceof PsiForeachStatement) {
return handleAddAll(loopStatement, factory, call);
}
PsiExpression qualifierExpression = call.getMethodExpression().getQualifierExpression();
PsiLocalVariable variable = StreamApiMigrationInspection.extractCollectionVariable(qualifierExpression);
if(variable != null && InheritanceUtil.isInheritor(variable.getType(), CommonClassNames.JAVA_UTIL_MAP)) {
@@ -158,19 +154,6 @@ class CollectMigration extends BaseStreamApiMigration {
return replaceInitializer(loopStatement, variable, initializer, builder.toString(), status);
}
@Nullable
private static PsiElement handleAddAll(@NotNull PsiLoopStatement loopStatement, PsiElementFactory factory, PsiMethodCallExpression call) {
PsiExpression iteratedValue = ((PsiForeachStatement)loopStatement).getIteratedValue();
if (iteratedValue == null) return null;
PsiExpression qualifierExpression = call.getMethodExpression().getQualifierExpression();
String qualifierText = qualifierExpression != null ? qualifierExpression.getText() : "";
String collectionText = iteratedValue.getType() instanceof PsiArrayType
? CommonClassNames.JAVA_UTIL_ARRAYS + ".asList(" + iteratedValue.getText() + ")"
: iteratedValue.getText();
String callText = StringUtil.getQualifiedName(qualifierText, "addAll(" + collectionText + ");");
return loopStatement.replace(factory.createStatementFromText(callText, loopStatement));
}
@Nullable
private static PsiElement handleToMap(@NotNull PsiLoopStatement loopStatement,
@NotNull TerminalBlock tb,
@@ -140,6 +140,12 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
ref2.getQualifierExpression());
}
/**
* Extracts an addend from assignment expression like {@code x+=addend} or {@code x = x+addend}
*
* @param assignment assignment expression to extract an addend from
* @return extracted addend expression or null if supplied assignment statement is not an addition
*/
@Nullable
static PsiExpression extractAddend(PsiAssignmentExpression assignment) {
if(JavaTokenType.PLUSEQ.equals(assignment.getOperationTokenType())) {
@@ -184,6 +190,12 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
return null;
}
/**
* Extract incremented value from expression which looks like {@code x++}, {@code ++x}, {@code x = x + 1} or {@code x += 1}
*
* @param expression expression to extract the incremented value
* @return an extracted incremented value or null if increment pattern is not detected in the supplied expression
*/
@Contract("null -> null")
static PsiExpression extractIncrementedLValue(PsiExpression expression) {
if(expression instanceof PsiPostfixExpression) {
@@ -675,23 +687,22 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
@Nullable
private BaseStreamApiMigration findCollectMigration(PsiLoopStatement statement, TerminalBlock tb) {
boolean addAll = statement 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;
PsiMethodCallExpression call = tb.getSingleMethodCall();
if(call != null && call.getMethodExpression().getQualifierExpression() instanceof PsiMethodCallExpression) {
call = (PsiMethodCallExpression)call.getMethodExpression().getQualifierExpression();
}
String methodName;
if(addAll) {
methodName = "addAll";
if(canCollect(statement, call)) {
if(extractToArrayExpression(statement, call) != null)
methodName = "toArray";
else
methodName = "collect";
} else {
PsiMethodCallExpression call = tb.getSingleMethodCall();
if(call != null && call.getMethodExpression().getQualifierExpression() instanceof PsiMethodCallExpression) {
call = (PsiMethodCallExpression)call.getMethodExpression().getQualifierExpression();
}
if(canCollect(statement, call)) {
if(extractToArrayExpression(statement, call) != null)
methodName = "toArray";
else
methodName = "collect";
} else {
if (!SUGGEST_FOREACH || tb.getCountExpression() != null) return null;
methodName = "forEach";
}
if (!SUGGEST_FOREACH || tb.getCountExpression() != null) return null;
methodName = "forEach";
}
return new CollectMigration(methodName);
}
@@ -1276,9 +1287,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
if(initializer == null) return null;
// check that increment is like for(...;...;i++)
if(!(forStatement.getUpdate() instanceof PsiExpressionStatement)) return null;
PsiExpression lValue = extractIncrementedLValue(((PsiExpressionStatement)forStatement.getUpdate()).getExpression());
if(!ExpressionUtils.isReferenceTo(lValue, counter)) return null;
if(!VariableAccessUtils.variableIsIncremented(counter, forStatement.getUpdate())) return null;
// check that condition is like for(...;i<bound;...) or for(...;i<=bound;...)
if(!(forStatement.getCondition() instanceof PsiBinaryExpression)) return null;
@@ -1,12 +1,13 @@
// "Replace with addAll" "true"
// "Replace with collect" "false"
import java.util.*;
public class Collect {
class Person {}
void collectNames(List<Person> persons){
// Handled by UseBulkOperationInspection
List<Person> names = new ArrayList<>();
for (Person person : pers<caret>ons) {
for (Person person : p<caret>ersons) {
names.add(person);
}
}
@@ -0,0 +1,10 @@
// "Replace iteration with bulk 'Collection.addAll' call" "true"
import java.util.*;
public class Main {
public void test(Integer[] arr) {
List<Integer> result = new ArrayList<>();
result.add(1);
result.addAll(Arrays.asList(arr));
}
}
@@ -0,0 +1,11 @@
// "Replace iteration with bulk 'Collection.addAll' call" "true"
import java.util.*;
public class Collect {
class Person {}
void collectNames(List<Person> persons){
List<Person> names = new ArrayList<>();
names.addAll(persons);
}
}
@@ -1,4 +1,4 @@
// "Replace with addAll" "true"
// "Replace iteration with bulk 'Collection.addAll' call" "true"
import java.util.*;
@@ -0,0 +1,11 @@
// "Replace iteration with bulk 'Collection.addAll' call" "true"
import java.util.*;
public class Collect {
class Person {}
void collectNames(List<Person> persons){
List<Person> names = new ArrayList<>();
names.addAll(persons);
}
}
@@ -1,4 +1,4 @@
// "Replace with addAll" "true"
// "Replace iteration with bulk 'Collection.addAll' call" "true"
import java.util.ArrayList;
import java.util.List;
@@ -1,4 +1,4 @@
// "Replace with addAll" "true"
// "Replace iteration with bulk 'Collection.addAll' call" "true"
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -0,0 +1,12 @@
// "Replace iteration with bulk 'Collection.addAll' call" "true"
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Main {
public void test(Integer[] arr) {
List<Integer> result = new ArrayList<>();
result.add(1);
result.addAll(Arrays.asList(arr));
}
}
@@ -0,0 +1,13 @@
// "Replace iteration with bulk 'Collection.addAll' call" "true"
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class Main {
public void test(Integer[] arr) {
List<Integer> result = new ArrayList<>();
result.add(1);
result.addAll(Arrays.asList(arr));
}
}
@@ -1,4 +1,4 @@
// "Replace with addAll" "true"
// "Replace iteration with bulk 'Collection.addAll' call" "true"
import java.util.*;
public class Collect {
@@ -0,0 +1,15 @@
// "Replace iteration with bulk 'CrudRepository.save' call" "true"
package org.springframework.data.repository;
import java.io.Serializable;
interface CrudRepository<T,ID extends Serializable> {
<S extends T> Iterable<S> save(Iterable<S> entities);
<S extends T> S save(S entity);
}
public class Main {
public void test(CrudRepository<CharSequence, Integer> repo, Iterable<String> stringsToSave) {
repo.save(stringsToSave);
}
}
@@ -0,0 +1,16 @@
// "Replace iteration with bulk 'CrudRepository.save' call" "true"
package org.springframework.data.repository;
import java.io.Serializable;
import java.util.List;
interface CrudRepository<T,ID extends Serializable> {
<S extends T> Iterable<S> save(Iterable<S> entities);
<S extends T> S save(S entity);
}
public class Main {
public void test(CrudRepository<Iterable<String>, Integer> repo, Iterable<List<String>> stringsToSave) {
repo.save(stringsToSave);
}
}
@@ -0,0 +1,10 @@
// "Replace iteration with bulk 'List.removeAll' call" "true"
import java.util.*;
public class Collect {
static class Person {}
void collectNames(List<Person> persons, Collection<Person> toRemove){
persons.removeAll(toRemove);
}
}
@@ -0,0 +1,10 @@
// "Replace iteration with bulk 'List.removeAll' call" "true"
import java.util.*;
public class Collect {
static class Person {}
void collectNames(List<Person> persons, Collection<Person> toRemove){
persons.removeAll(toRemove);
}
}
@@ -0,0 +1,10 @@
// "Replace iteration with bulk 'Collection.addAll' call" "true"
import java.util.*;
public class Main {
public void test(Integer[] arr) {
List<Integer> result = new ArrayList<>();
result.add(1);
Arrays.stream(arr).forEach(result<caret>::add);
}
}
@@ -0,0 +1,14 @@
// "Replace iteration with bulk 'Collection.addAll' call" "true"
import java.util.*;
public class Collect {
class Person {}
void collectNames(List<Person> persons){
List<Person> names = new ArrayList<>();
for(int i = 0; i<persons.size(); i = i + 1) {
Person p = persons.get(i);
names.<caret>add(p);
}
}
}
@@ -1,4 +1,4 @@
// "Replace with addAll" "false"
// "Replace iteration with bulk 'Collection.addAll' call" "false"
import java.util.*;
@@ -16,8 +16,8 @@ public class Main {
@Override
public boolean addAll(Collection<? extends String> c) {
for (String e : <caret>c) {
add(e);
for (String e : c) {
a<caret>dd(e);
}
return true;
}
@@ -1,4 +1,4 @@
// "Replace with addAll" "true"
// "Replace iteration with bulk 'Collection.addAll' call" "true"
import java.util.*;
@@ -15,8 +15,8 @@ public class Main {
}
public boolean myAdd(Collection<? extends String> c) {
for (String e : <caret>c) {
this.add(e);
for (String e : c) {
this.<caret>add(e);
}
return true;
}
@@ -0,0 +1,13 @@
// "Replace iteration with bulk 'Collection.addAll' call" "false"
import java.util.*;
public class Collect {
class Person {}
void collectNames(Iterable<Person> persons){
List<Person> names = new ArrayList<>();
persons.forEach(p -> {
names<caret>.add(p);
});
}
}
@@ -0,0 +1,13 @@
// "Replace iteration with bulk 'Collection.addAll' call" "true"
import java.util.*;
public class Collect {
class Person {}
void collectNames(List<Person> persons){
List<Person> names = new ArrayList<>();
persons.forEach(p -> {
names<caret>.add(p);
});
}
}
@@ -1,4 +1,4 @@
// "Replace with addAll" "true"
// "Replace iteration with bulk 'Collection.addAll' call" "true"
import java.util.ArrayList;
import java.util.List;
@@ -6,8 +6,8 @@ class Sample {
List<String> foo = new ArrayList<>();
String foo(){
Sample sm = new Sample();
for (String s : fo<caret>o) {
sm.foo.add(s);
for (String s : foo) {
<caret>sm.foo.add(s);
}
return null;
}
@@ -1,4 +1,4 @@
// "Replace with addAll" "true"
// "Replace iteration with bulk 'Collection.addAll' call" "true"
import java.util.ArrayList;
import java.util.List;
@@ -6,7 +6,7 @@ public class Main {
public void test(Integer[] arr) {
List<Integer> result = new ArrayList<>();
result.add(1);
for(Integer i : ar<caret>r)
result.add(i);
for(Integer i : arr)
result.<caret>add(i);
}
}
@@ -0,0 +1,13 @@
// "Replace iteration with bulk 'Collection.addAll' call" "true"
import java.util.ArrayList;
import java.util.List;
public class Main {
public void test(Integer[] arr) {
List<Integer> result = new ArrayList<>();
result.add(1);
for (int idx = 0; idx < arr.length; idx++) {
result.<caret>add(arr[idx]);
}
}
}
@@ -0,0 +1,12 @@
// "Replace iteration with bulk 'Collection.addAll' call" "true"
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
public class Main {
public void test(Integer[] arr) {
List<Integer> result = new ArrayList<>();
result.add(1);
Stream.of(arr).forEachOrdered(<caret>result::add);
}
}
@@ -0,0 +1,13 @@
// "Replace iteration with bulk 'Collection.addAll' call" "true"
import java.util.*;
public class Collect {
class Person {}
void collectNames(List<Person> persons){
List<Person> names = new ArrayList<>();
for (Person person : persons) {
<caret>names.add(person);
}
}
}
@@ -0,0 +1,15 @@
// "Replace iteration with bulk 'CrudRepository.save' call" "true"
package org.springframework.data.repository;
import java.io.Serializable;
interface CrudRepository<T,ID extends Serializable> {
<S extends T> Iterable<S> save(Iterable<S> entities);
<S extends T> S save(S entity);
}
public class Main {
public void test(CrudRepository<CharSequence, Integer> repo, Iterable<String> stringsToSave) {
stringsToSave.forEach(<caret>repo::save);
}
}
@@ -0,0 +1,16 @@
// "Replace iteration with bulk 'CrudRepository.save' call" "false"
package org.springframework.data.repository;
import java.io.Serializable;
import java.util.List;
interface CrudRepository<T,ID extends Serializable> {
<S extends T> Iterable<S> save(Iterable<S> entities);
<S extends T> S save(S entity);
}
public class Main {
public void test(CrudRepository<CharSequence, Integer> repo, Iterable<List<String>> stringsToSave) {
stringsToSave.forEach(<caret>repo::save);
}
}
@@ -0,0 +1,16 @@
// "Replace iteration with bulk 'CrudRepository.save' call" "true"
package org.springframework.data.repository;
import java.io.Serializable;
import java.util.List;
interface CrudRepository<T,ID extends Serializable> {
<S extends T> Iterable<S> save(Iterable<S> entities);
<S extends T> S save(S entity);
}
public class Main {
public void test(CrudRepository<Iterable<String>, Integer> repo, Iterable<List<String>> stringsToSave) {
stringsToSave.forEach(<caret>repo::save);
}
}
@@ -0,0 +1,11 @@
// "Replace iteration with bulk 'List.removeAll' call" "true"
import java.util.*;
public class Collect {
static class Person {}
void collectNames(List<Person> persons, Collection<Person> toRemove){
for (Iterator<Person> it = toRemove.iterator(); it.hasNext(); )
persons<caret>.remove(it.next());
}
}
@@ -0,0 +1,14 @@
// "Replace iteration with bulk 'List.removeAll' call" "true"
import java.util.*;
public class Collect {
static class Person {}
void collectNames(List<Person> persons, Collection<Person> toRemove){
Iterator<Person> it = toRemove.iterator();
while (it.hasNext()) {
Person person = it.next();
persons<caret>.remove(person);
}
}
}
@@ -0,0 +1,40 @@
/*
* 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.codeInsight.daemon.quickFix;
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.codeInspection.UseBulkOperationInspection;
import org.jetbrains.annotations.NotNull;
public class UseBulkOperationInspectionTest extends LightQuickFixParameterizedTestCase {
@NotNull
@Override
protected LocalInspectionTool[] configureLocalInspectionTools() {
UseBulkOperationInspection inspection = new UseBulkOperationInspection();
inspection.USE_ARRAYS_AS_LIST = true;
return new LocalInspectionTool[]{
inspection
};
}
public void test() throws Exception { doAllTests(); }
@Override
protected String getBasePath() {
return "/codeInsight/daemonCodeAnalyzer/quickFix/useBulkOperation";
}
}
@@ -752,4 +752,8 @@ inspection.lambda.to.method.call.fix.name=Replace lambda expression with ''{0}''
inspection.module.exports.package.to.itself.message=Module exports package to itself
inspection.module.exports.package.to.itself.only.message=Module exports package only to itself
exports.to.itself.delete.module.fix.name=Delete reference to module ''{0}''
exports.to.itself.delete.module.fix.family.name=Delete reference to module
exports.to.itself.delete.module.fix.family.name=Delete reference to module
inspection.replace.with.bulk.message=Iteration can be replaced with bulk ''{0}'' call
inspection.replace.with.bulk.fix.name=Replace iteration with bulk ''{0}'' call
inspection.replace.with.bulk.fix.family.name=Replace with bulk method call
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
* 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.
@@ -25,6 +25,7 @@ import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.BaseInspection;
import com.siyeh.ig.BaseInspectionVisitor;
import com.siyeh.ig.psiutils.*;
import org.intellij.lang.annotations.Pattern;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -347,7 +348,7 @@ public class ForCanBeForeachInspectionBase extends BaseInspection {
else {
return null;
}
if (!expressionIsArrayLengthLookup(referenceExpression)) {
if (ExpressionUtils.getArrayFromLengthExpression(referenceExpression) == null) {
final PsiElement target = referenceExpression.resolve();
if (secondDeclaredElement != null && !secondDeclaredElement.equals(target)) {
return null;
@@ -366,7 +367,7 @@ public class ForCanBeForeachInspectionBase extends BaseInspection {
return null;
}
referenceExpression = (PsiReferenceExpression)expression;
if (!expressionIsArrayLengthLookup(referenceExpression)) {
if (ExpressionUtils.getArrayFromLengthExpression(referenceExpression) == null) {
return null;
}
}
@@ -503,25 +504,7 @@ public class ForCanBeForeachInspectionBase extends BaseInspection {
return new Holder(variable);
}
private static boolean expressionIsArrayLengthLookup(PsiExpression expression) {
expression = ParenthesesUtils.stripParentheses(expression);
if (!(expression instanceof PsiReferenceExpression)) {
return false;
}
final PsiReferenceExpression reference =
(PsiReferenceExpression)expression;
final String referenceName = reference.getReferenceName();
if (!HardcodedMethodConstants.LENGTH.equals(referenceName)) {
return false;
}
final PsiExpression qualifier = reference.getQualifierExpression();
if (!(qualifier instanceof PsiReferenceExpression)) {
return false;
}
final PsiType type = qualifier.getType();
return type != null && type.getArrayDimensions() > 0;
}
@Pattern(VALID_ID_PATTERN)
@Override
@NotNull
public String getID() {
@@ -25,6 +25,7 @@ import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.ArrayUtil;
import com.siyeh.HardcodedMethodConstants;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
@@ -864,4 +865,24 @@ public class ExpressionUtils {
}
return null;
}
/**
* Returns an array expression from array length retrieval expression
* @param expression expression to extract an array expression from
* @return an array expression or null if supplied expression is not array length retrieval
*/
@Nullable
public static PsiExpression getArrayFromLengthExpression(PsiExpression expression) {
expression = ParenthesesUtils.stripParentheses(expression);
if (!(expression instanceof PsiReferenceExpression)) return null;
final PsiReferenceExpression reference =
(PsiReferenceExpression)expression;
final String referenceName = reference.getReferenceName();
if (!HardcodedMethodConstants.LENGTH.equals(referenceName)) return null;
final PsiExpression qualifier = reference.getQualifierExpression();
if (qualifier == null) return null;
final PsiType type = qualifier.getType();
if (type == null || type.getArrayDimensions() <= 0) return null;
return qualifier;
}
}
@@ -0,0 +1,8 @@
<html>
<body>
This inspection warns when calling some method in a loop (e.g. <code>collection.add(x)</code>) could be replaced when calling a bulk method
(e.g. <code>collection.addAll(listOfX)</code>.
<!-- tooltip end -->
<p><small>New in 2017.1</small></p>
</body>
</html>
+5
View File
@@ -858,6 +858,11 @@
groupKey="group.names.declaration.redundancy" enabledByDefault="true" level="WARNING"
implementationClass="com.intellij.codeInspection.SimplifyStreamApiCallChainsInspection"
displayName="Simplify stream API call chains"/>
<localInspection groupPath="Java" language="JAVA" shortName="UseBulkOperation"
groupBundle="messages.InspectionsBundle"
groupKey="group.names.performance.issues" enabledByDefault="true" level="WARNING"
implementationClass="com.intellij.codeInspection.UseBulkOperationInspection"
displayName="Use bulk operation instead of iteration"/>
<localInspection groupPath="Java" language="JAVA" shortName="ComparatorCombinators"
groupBundle="messages.InspectionsBundle"
groupKey="group.names.language.level.specific.issues.and.migration.aids" enabledByDefault="true" level="WARNING"