mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-182206 Simplification for Arrays.asList().sublist().toArray()
This commit is contained in:
@@ -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>
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// "Replace with 'clone()'" "true"
|
||||
import java.util.Arrays;
|
||||
|
||||
class Test {
|
||||
String[] arr;
|
||||
|
||||
String[] get() {
|
||||
return arr.clone();
|
||||
}
|
||||
}
|
||||
+10
@@ -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);
|
||||
}
|
||||
}
|
||||
+10
@@ -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);
|
||||
}
|
||||
}
|
||||
+10
@@ -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);
|
||||
}
|
||||
}
|
||||
+10
@@ -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);
|
||||
}
|
||||
}
|
||||
+10
@@ -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]);
|
||||
}
|
||||
}
|
||||
+10
@@ -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]);
|
||||
}
|
||||
}
|
||||
+10
@@ -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]);
|
||||
}
|
||||
}
|
||||
+10
@@ -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]);
|
||||
}
|
||||
}
|
||||
+10
@@ -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]);
|
||||
}
|
||||
}
|
||||
+10
@@ -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();
|
||||
}
|
||||
}
|
||||
+11
@@ -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]);
|
||||
}
|
||||
}
|
||||
+25
@@ -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";
|
||||
}
|
||||
}
|
||||
+31
@@ -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;
|
||||
}
|
||||
}
|
||||
+210
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user