IDEA-182206 Simplification for Arrays.asList().sublist().toArray()

This commit is contained in:
Tagir Valeev
2017-11-23 11:52:59 +07:00
parent 5d82bf9ea8
commit 0393b4fe2f
16 changed files with 394 additions and 0 deletions
@@ -0,0 +1,7 @@
<html>
<body>
Reports unnecessarily complex collection operations which have simpler alternatives.
<!-- tooltip end -->
<small>New in 2018.1</small>
</body>
</html>
@@ -0,0 +1,10 @@
// "Replace with 'clone()'" "true"
import java.util.Arrays;
class Test {
String[] arr;
String[] get() {
return arr.clone();
}
}
@@ -0,0 +1,10 @@
// "Replace with 'Arrays.copyOfRange'" "true"
import java.util.Arrays;
class Test {
String[] arr;
String[] get() {
return Arrays.copyOfRange(arr, 1, 3);
}
}
@@ -0,0 +1,10 @@
// "Replace with 'Arrays.copyOfRange'" "true"
import java.util.Arrays;
class Test {
String[] arr;
CharSequence[] get(int from, int to) {
return Arrays.copyOfRange(arr, from, to, CharSequence[].class);
}
}
@@ -0,0 +1,10 @@
// "Replace with 'Arrays.copyOf'" "true"
import java.util.Arrays;
class Test {
String[] arr;
String[] get(int newLength) {
return Arrays.copyOf(arr, newLength);
}
}
@@ -0,0 +1,10 @@
// "Replace with 'Arrays.copyOf'" "true"
import java.util.Arrays;
class Test {
String[] arr;
Object[] get(int newLength) {
return Arrays.copyOf(arr, newLength, Object[].class);
}
}
@@ -0,0 +1,10 @@
// "Replace with 'clone()'" "true"
import java.util.Arrays;
class Test {
String[] arr;
String[] get() {
return Arrays.asList(arr).toAr<caret>ray(new String[0]);
}
}
@@ -0,0 +1,10 @@
// "Replace with 'Arrays.copyOfRange'" "true"
import java.util.Arrays;
class Test {
String[] arr;
String[] get() {
return Arrays.asList(arr).subList(1, 3).toAr<caret>ray(new String[2]);
}
}
@@ -0,0 +1,10 @@
// "Replace with 'Arrays.copyOfRange'" "true"
import java.util.Arrays;
class Test {
String[] arr;
CharSequence[] get(int from, int to) {
return Arrays.asList(arr).subList(from, to).toAr<caret>ray(new CharSequence[to-from]);
}
}
@@ -0,0 +1,10 @@
// "Replace with 'Arrays.copyOfRange'" "false"
import java.util.Arrays;
class Test {
String[] arr;
String[] get() {
return Arrays.asList(arr).subList(1, 3).toAr<caret>ray(new String[3]);
}
}
@@ -0,0 +1,10 @@
// "Replace with 'Arrays.copyOf'" "true"
import java.util.Arrays;
class Test {
String[] arr;
String[] get(int newLength) {
return Arrays.asList(arr).subList(0, newLength).toAr<caret>ray(new String[newLength]);
}
}
@@ -0,0 +1,10 @@
// "Replace with 'Arrays.copyOf'" "true"
import java.util.Arrays;
class Test {
String[] arr;
Object[] get(int newLength) {
return Arrays.asList(arr).subList(0, newLength).toAr<caret>ray();
}
}
@@ -0,0 +1,11 @@
// "Replace with 'clone()'" "false"
import java.util.Arrays;
class Test {
String[] arr;
CharSequence[] get() {
// Array type mismatch
return Arrays.asList(arr).toAr<caret>ray(new CharSequence[0]);
}
}
@@ -0,0 +1,25 @@
// 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.
package com.intellij.java.codeInsight.daemon.quickFix;
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
import com.intellij.codeInspection.LocalInspectionTool;
import com.siyeh.ig.redundancy.RedundantCollectionOperationInspection;
import org.jetbrains.annotations.NotNull;
public class RedundantCollectionOperationInspectionTest extends LightQuickFixParameterizedTestCase {
@NotNull
@Override
protected LocalInspectionTool[] configureLocalInspectionTools() {
return new LocalInspectionTool[]{
new RedundantCollectionOperationInspection()
};
}
public void test() { doAllTests(); }
@Override
protected String getBasePath() {
return "/codeInsight/daemonCodeAnalyzer/quickFix/redundantCollectionOperation";
}
}
@@ -17,6 +17,7 @@ package com.siyeh.ig.psiutils;
import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.codeInsight.NullableNotNullManager;
import com.intellij.codeInsight.PsiEquivalenceUtil;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.search.searches.ReferencesSearch;
@@ -38,6 +39,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Predicate;
import java.util.stream.Stream;
import static com.intellij.util.ObjectUtils.tryCast;
public class ExpressionUtils {
@NonNls static final Set<String> convertableBoxedClassNames = new HashSet<>(3);
static {
@@ -1142,4 +1145,32 @@ public class ExpressionUtils {
}
return false;
}
/**
* Checks whether diff-expression represents a difference between from-expression and to-expression
*
* @param from from-expression
* @param to to-expression
* @param diff diff-expression
* @return true if diff = to - from
*/
public static boolean isDifference(@NotNull PsiExpression from, @NotNull PsiExpression to, @NotNull PsiExpression diff) {
diff = PsiUtil.skipParenthesizedExprDown(diff);
if (diff == null) return false;
if (isZero(from) && PsiEquivalenceUtil.areElementsEquivalent(to, diff)) return true;
if (diff instanceof PsiBinaryExpression && ((PsiBinaryExpression)diff).getOperationTokenType().equals(JavaTokenType.MINUS)) {
PsiExpression left = ((PsiBinaryExpression)diff).getLOperand();
PsiExpression right = ((PsiBinaryExpression)diff).getROperand();
if (right != null && PsiEquivalenceUtil.areElementsEquivalent(to, left) && PsiEquivalenceUtil.areElementsEquivalent(from, right)) {
return true;
}
}
Integer fromConstant = tryCast(computeConstantExpression(from), Integer.class);
if (fromConstant == null) return false;
Integer toConstant = tryCast(computeConstantExpression(to), Integer.class);
if (toConstant == null) return false;
Integer diffConstant = tryCast(computeConstantExpression(diff), Integer.class);
if (diffConstant == null) return false;
return diffConstant == toConstant - fromConstant;
}
}
@@ -0,0 +1,210 @@
// 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.
package com.siyeh.ig.redundancy;
import com.intellij.codeInsight.PsiEquivalenceUtil;
import com.intellij.codeInspection.*;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.ArrayUtil;
import com.siyeh.ig.callMatcher.CallMapper;
import com.siyeh.ig.callMatcher.CallMatcher;
import com.siyeh.ig.psiutils.CommentTracker;
import com.siyeh.ig.psiutils.ExpressionUtils;
import com.siyeh.ig.psiutils.MethodCallUtils;
import com.siyeh.ig.psiutils.ParenthesesUtils;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import java.util.function.Function;
import static com.intellij.util.ObjectUtils.tryCast;
import static com.siyeh.ig.callMatcher.CallMatcher.*;
public class RedundantCollectionOperationInspection extends AbstractBaseJavaLocalInspectionTool implements CleanupLocalInspectionTool {
private static final CallMatcher TO_ARRAY =
anyOf(
instanceCall(CommonClassNames.JAVA_UTIL_COLLECTION, "toArray").parameterCount(0),
instanceCall(CommonClassNames.JAVA_UTIL_COLLECTION, "toArray").parameterTypes("T[]"));
private static final CallMatcher SUBLIST =
instanceCall(CommonClassNames.JAVA_UTIL_LIST, "subList").parameterTypes("int", "int");
private static final CallMatcher AS_LIST =
staticCall(CommonClassNames.JAVA_UTIL_ARRAYS, "asList").parameterCount(1);
private static final CallMapper<RedundantCollectionOperationHandler> HANDLERS =
new CallMapper<RedundantCollectionOperationHandler>()
.register(TO_ARRAY, SimplifyToArrayHandler.handler());
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
if (!PsiUtil.isLanguageLevel6OrHigher(holder.getFile())) {
return PsiElementVisitor.EMPTY_VISITOR;
}
return new JavaElementVisitor() {
@Override
public void visitMethodCallExpression(PsiMethodCallExpression call) {
PsiElement nameElement = call.getMethodExpression().getReferenceNameElement();
if (nameElement == null) return;
RedundantCollectionOperationHandler handler = HANDLERS.mapFirst(call);
if (handler == null) return;
holder.registerProblem(nameElement, handler.getProblemName(), new RedundantCollectionOperationFix(handler));
}
};
}
interface RedundantCollectionOperationHandler {
String getProblemName();
String getFixName();
void performFix(@NotNull Project project, @NotNull PsiMethodCallExpression call);
}
private static class SimplifyToArrayHandler implements RedundantCollectionOperationHandler {
private final String myReplacementMethod;
@NotNull private final SmartPsiElementPointer<PsiExpression> myArrayPtr;
private final SmartPsiElementPointer<PsiExpression> myFromPtr;
private final SmartPsiElementPointer<PsiExpression> myToPtr;
@NotNull private final String mySourceComponentType;
@NotNull private final String myTargetComponentType;
private SimplifyToArrayHandler(PsiExpression from,
PsiExpression to,
@NotNull PsiExpression array,
@NotNull String sourceComponentType,
@NotNull String targetComponentType) {
SmartPointerManager manager = SmartPointerManager.getInstance(array.getProject());
myArrayPtr = manager.createSmartPsiElementPointer(array);
myFromPtr = from == null ? null : manager.createSmartPsiElementPointer(from);
myToPtr = to == null ? null : manager.createSmartPsiElementPointer(to);
mySourceComponentType = sourceComponentType;
myTargetComponentType = targetComponentType;
if (from == null && to == null) {
myReplacementMethod = "clone()";
}
else if (ExpressionUtils.isZero(from)) {
myReplacementMethod = "Arrays.copyOf";
}
else {
myReplacementMethod = "Arrays.copyOfRange";
}
}
@Override
public String getProblemName() {
return "Unnecessary collection created to copy an array";
}
@Override
public String getFixName() {
return "Replace with '" + myReplacementMethod + "'";
}
@Override
public void performFix(@NotNull Project project, @NotNull PsiMethodCallExpression call) {
PsiExpression array = myArrayPtr.getElement();
if (array == null) return;
PsiExpression from = myFromPtr == null ? null : myFromPtr.getElement();
PsiExpression to = myToPtr == null ? null : myToPtr.getElement();
if ((from == null) != (to == null)) return;
CommentTracker ct = new CommentTracker();
String replacement;
String suffix = "";
if (!mySourceComponentType.equals(myTargetComponentType)) {
suffix = "," + myTargetComponentType + "[].class";
}
if (from == null) {
replacement = ParenthesesUtils.getText(ct.markUnchanged(array), ParenthesesUtils.POSTFIX_PRECEDENCE) + ".clone()";
}
else if (ExpressionUtils.isZero(from)) {
replacement = CommonClassNames.JAVA_UTIL_ARRAYS + ".copyOf(" + ct.text(array) + "," + ct.text(to) + suffix + ")";
}
else {
replacement =
CommonClassNames.JAVA_UTIL_ARRAYS + ".copyOfRange(" + ct.text(array) + "," + ct.text(from) + "," + ct.text(to) + suffix + ")";
}
ct.replaceAndRestoreComments(call, replacement);
}
public static Function<PsiMethodCallExpression, RedundantCollectionOperationHandler> handler() {
return call -> {
PsiExpression arg = ArrayUtil.getFirstElement(call.getArgumentList().getExpressions());
PsiExpression arrayLength = null;
String targetComponentType;
if (arg != null) {
if (!(arg instanceof PsiNewExpression)) return null;
PsiJavaCodeReferenceElement classRef = ((PsiNewExpression)arg).getClassReference();
if (classRef == null) return null;
targetComponentType = classRef.getQualifiedName();
PsiExpression[] dimensions = ((PsiNewExpression)arg).getArrayDimensions();
if (dimensions.length != 1) return null;
if (!ExpressionUtils.isZero(dimensions[0])) {
arrayLength = dimensions[0];
}
}
else {
targetComponentType = CommonClassNames.JAVA_LANG_OBJECT;
}
PsiExpression from = null;
PsiExpression to = null;
PsiMethodCallExpression qualifier = MethodCallUtils.getQualifierMethodCall(call);
if (SUBLIST.test(qualifier)) {
PsiExpression[] subListArgs = qualifier.getArgumentList().getExpressions();
from = subListArgs[0];
to = subListArgs[1];
qualifier = MethodCallUtils.getQualifierMethodCall(qualifier);
}
if (!AS_LIST.test(qualifier) || MethodCallUtils.isVarArgCall(qualifier)) return null;
PsiExpression array = qualifier.getArgumentList().getExpressions()[0];
PsiArrayType sourceArrayType = tryCast(array.getType(), PsiArrayType.class);
if (sourceArrayType == null) return null;
PsiClass componentClass = PsiUtil.resolveClassInClassTypeOnly(sourceArrayType.getComponentType());
if (componentClass == null) return null;
String sourceComponentType = componentClass.getQualifiedName();
if (sourceComponentType == null) return null;
if (from != null && to != null) {
if (arrayLength != null && !ExpressionUtils.isDifference(from, to, arrayLength)) return null;
}
else {
if (!sourceComponentType.equals(targetComponentType)) return null;
if (arrayLength != null) {
PsiExpression arrayFromLength = ExpressionUtils.getArrayFromLengthExpression(arrayLength);
if (arrayFromLength == null || !PsiEquivalenceUtil.areElementsEquivalent(array, arrayFromLength)) return null;
}
}
return new SimplifyToArrayHandler(from, to, array, sourceComponentType, targetComponentType);
};
}
}
private static class RedundantCollectionOperationFix implements LocalQuickFix {
private RedundantCollectionOperationHandler myHandler;
public RedundantCollectionOperationFix(
RedundantCollectionOperationHandler handler) {myHandler = handler;}
@Nls
@NotNull
@Override
public String getName() {
return myHandler.getFixName();
}
@Nls
@NotNull
@Override
public String getFamilyName() {
return "Simplify collection operation";
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
PsiMethodCallExpression call = PsiTreeUtil.getParentOfType(descriptor.getStartElement(), PsiMethodCallExpression.class);
if (call == null) return;
myHandler.performFix(project, call);
}
}
}