StreamToLoopInspection fixes: toArray intermediate list type fixed; keywords are filtered out from possible var names; context added to expression passed to BoolUtils; supported inside method calls

This commit is contained in:
Tagir Valeev
2016-11-02 11:41:10 +07:00
parent f9d0b8dadf
commit 0fb67d4f9d
12 changed files with 173 additions and 28 deletions

View File

@@ -20,8 +20,10 @@ import com.intellij.codeInspection.BaseJavaBatchLocalInspectionTool;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.lang.java.lexer.JavaLexer;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
@@ -87,7 +89,7 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
private static boolean isSupportedCodeLocation(PsiMethodCallExpression call) {
PsiElement cur = call;
PsiElement parent = cur.getParent();
while(parent instanceof PsiExpression) {
while(parent instanceof PsiExpression || parent instanceof PsiExpressionList) {
// TODO: support in single expression lambdas
if(parent instanceof PsiLambdaExpression) {
return false;
@@ -133,9 +135,10 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
if(InheritanceUtil.isInheritor(aClass, CommonClassNames.JAVA_UTIL_STREAM_BASE_STREAM)) {
PsiExpression qualifier = call.getMethodExpression().getQualifierExpression();
if(qualifier != null) {
Operation op = Operation.createIntermediate(name, args, outVar, getStreamElementType(qualifier.getType()));
PsiType elementType = getStreamElementType(qualifier.getType());
Operation op = Operation.createIntermediate(name, args, outVar, elementType);
if (op != null) return op;
op = TerminalOperation.createTerminal(name, args, callType, className, call.getParent() instanceof PsiExpressionStatement);
op = TerminalOperation.createTerminal(name, args, elementType, callType, call.getParent() instanceof PsiExpressionStatement);
if (op != null) return op;
}
}
@@ -232,11 +235,12 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
}
}
catch (Exception ex) {
String text = terminalCall.getText();
if(temporaryStreamPlaceholder.isPhysical()) {
// Just in case if something went wrong: at least try to restore the original stream code
temporaryStreamPlaceholder.replace(factory.createExpressionFromText(terminalCall.getText(), temporaryStreamPlaceholder));
temporaryStreamPlaceholder.replace(factory.createExpressionFromText(text, temporaryStreamPlaceholder));
}
throw ex;
throw new RuntimeException("Error converting Stream to loop: "+text, ex);
}
}
@@ -380,9 +384,9 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
}
private boolean isUsed(String varName) {
if(myUsedNames.contains(varName)) return true;
// TODO: cleaner solution
return !varName.equals(JavaCodeStyleManager.getInstance(myStatement.getProject()).suggestUniqueVariableName(varName, myStatement, true));
return myUsedNames.contains(varName) || JavaLexer.isKeyword(varName, LanguageLevel.HIGHEST) ||
// TODO: cleaner solution
!varName.equals(JavaCodeStyleManager.getInstance(myStatement.getProject()).suggestUniqueVariableName(varName, myStatement, true));
}
public String declare(String desiredName, String type, String initializer) {

View File

@@ -63,7 +63,7 @@ abstract class TerminalOperation extends Operation {
}
@Nullable
static TerminalOperation createTerminal(String name, PsiExpression[] args, PsiType callType, String className, boolean isVoid) {
static TerminalOperation createTerminal(String name, PsiExpression[] args, PsiType elementType, PsiType resultType, boolean isVoid) {
if(isVoid) {
if ((name.equals("forEach") || name.equals("forEachOrdered")) && args.length == 1) {
FunctionHelper fn = FunctionHelper.create(args[0], 1, true);
@@ -75,23 +75,22 @@ abstract class TerminalOperation extends Operation {
return new AccumulatedTerminalOperation("count", "long", "0", "{acc}++;");
}
if(name.equals("sum") && args.length == 0) {
return new AccumulatedTerminalOperation("sum", callType.getCanonicalText(), "0", "{acc}+={item};");
return new AccumulatedTerminalOperation("sum", resultType.getCanonicalText(), "0", "{acc}+={item};");
}
if(name.equals("average") && args.length == 0) {
if(className.equals(CommonClassNames.JAVA_UTIL_STREAM_DOUBLE_STREAM)) {
if(elementType.equals(PsiType.DOUBLE)) {
return new AverageTerminalOperation(true);
}
else if(className.equals(CommonClassNames.JAVA_UTIL_STREAM_INT_STREAM) ||
className.equals(CommonClassNames.JAVA_UTIL_STREAM_LONG_STREAM)) {
else if(elementType.equals(PsiType.INT) || elementType.equals(PsiType.LONG)) {
return new AverageTerminalOperation(false);
}
}
if(name.equals("summaryStatistics") && args.length == 0) {
return new AccumulatedTerminalOperation("stat", callType.getCanonicalText(), "new " + callType.getCanonicalText() + "()",
return new AccumulatedTerminalOperation("stat", resultType.getCanonicalText(), "new " + resultType.getCanonicalText() + "()",
"{acc}.accept({item});");
}
if((name.equals("findFirst") || name.equals("findAny")) && args.length == 0) {
return new FindTerminalOperation(callType.getCanonicalText());
return new FindTerminalOperation(resultType.getCanonicalText());
}
if((name.equals("anyMatch") || name.equals("allMatch") || name.equals("noneMatch")) && args.length == 1) {
FunctionHelper fn = FunctionHelper.create(args[0], 1, true);
@@ -101,11 +100,11 @@ abstract class TerminalOperation extends Operation {
if(args.length == 2 || args.length == 3) {
FunctionHelper fn = FunctionHelper.create(args[1], 2, true);
if(fn != null) {
return new ReduceTerminalOperation(args[0], fn, callType.getCanonicalText());
return new ReduceTerminalOperation(args[0], fn, resultType.getCanonicalText());
}
}
if(args.length == 1) {
PsiType optionalElementType = getOptionalElementType(callType);
PsiType optionalElementType = getOptionalElementType(resultType);
FunctionHelper fn = FunctionHelper.create(args[0], 2, true);
if(fn != null && optionalElementType != null) {
return new ReduceToOptionalTerminalOperation(fn, optionalElementType.getCanonicalText());
@@ -113,8 +112,8 @@ abstract class TerminalOperation extends Operation {
}
}
if(name.equals("toArray") && args.length < 2) {
if(!(callType instanceof PsiArrayType)) return null;
PsiType componentType = ((PsiArrayType)callType).getComponentType();
if(!(resultType instanceof PsiArrayType)) return null;
PsiType componentType = ((PsiArrayType)resultType).getComponentType();
if (componentType instanceof PsiPrimitiveType) {
if(args.length == 0) return new ToPrimitiveArrayTerminalOperation(componentType.getCanonicalText());
}
@@ -130,7 +129,7 @@ abstract class TerminalOperation extends Operation {
if(!(type instanceof PsiArrayType)) return null;
arr = "new "+type.getCanonicalText().replaceFirst("\\[]", "[0]");
}
return new AccumulatedTerminalOperation("list", CommonClassNames.JAVA_UTIL_LIST + "<" + componentType.getCanonicalText() + ">",
return new AccumulatedTerminalOperation("list", CommonClassNames.JAVA_UTIL_LIST + "<" + elementType.getCanonicalText() + ">",
"new "+ CommonClassNames.JAVA_UTIL_ARRAY_LIST+"<>()", "{acc}.add({item});",
"{acc}.toArray("+arr+")");
}
@@ -144,25 +143,25 @@ abstract class TerminalOperation extends Operation {
PsiClass collectorClass = collector.getContainingClass();
if(collectorClass != null && CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS.equals(collectorClass.getQualifiedName())) {
if(collector.getName().equals("toList") && collectorArgs.length == 0) {
return AccumulatedTerminalOperation.toCollection(callType, CommonClassNames.JAVA_UTIL_ARRAY_LIST, "list");
return AccumulatedTerminalOperation.toCollection(resultType, CommonClassNames.JAVA_UTIL_ARRAY_LIST, "list");
}
if(collector.getName().equals("toSet") && collectorArgs.length == 0) {
return AccumulatedTerminalOperation.toCollection(callType, CommonClassNames.JAVA_UTIL_HASH_SET, "set");
return AccumulatedTerminalOperation.toCollection(resultType, CommonClassNames.JAVA_UTIL_HASH_SET, "set");
}
if(collector.getName().equals("toCollection") && collectorArgs.length == 1) {
FunctionHelper fn = FunctionHelper.create(collectorArgs[0], 0, true);
if(fn != null) {
return new ToCollectionTerminalOperation(fn, callType);
return new ToCollectionTerminalOperation(fn, resultType);
}
}
if(collector.getName().equals("reducing") && collectorArgs.length == 2) {
FunctionHelper fn = FunctionHelper.create(collectorArgs[1], 2, true);
if(fn != null) {
return new ReduceTerminalOperation(collectorArgs[0], fn, callType.getCanonicalText());
return new ReduceTerminalOperation(collectorArgs[0], fn, resultType.getCanonicalText());
}
}
if(collector.getName().equals("reducing") && collectorArgs.length == 1) {
PsiType optionalElementType = getOptionalElementType(callType);
PsiType optionalElementType = getOptionalElementType(resultType);
FunctionHelper fn = FunctionHelper.create(collectorArgs[0], 2, true);
if(fn != null && optionalElementType != null) {
return new ReduceToOptionalTerminalOperation(fn, optionalElementType.getCanonicalText());
@@ -355,7 +354,14 @@ abstract class TerminalOperation extends Operation {
@Override
String generate(StreamVariable inVar, StreamToLoopReplacementContext context) {
myFn.transform(context, inVar.getName());
String expression = myNegatePredicate ? BoolUtils.getNegatedExpressionText(myFn.getExpression()) : myFn.getText();
String expression;
if (myNegatePredicate) {
PsiLambdaExpression lambda = (PsiLambdaExpression)context.createExpression("(" + inVar.getDeclaration() + ")->" + myFn.getText());
expression = BoolUtils.getNegatedExpressionText((PsiExpression)lambda.getBody());
}
else {
expression = myFn.getText();
}
return "if(" + expression + ") {\n" +
context.assignAndBreak(myName, PsiType.BOOLEAN.getCanonicalText(), String.valueOf(!myDefaultValue), String.valueOf(myDefaultValue)) +
"}\n";

View File

@@ -0,0 +1,19 @@
// "Replace Stream API chain with loop" "true"
import java.util.Arrays;
import java.util.List;
public class Main {
private static boolean test(List<String> list) {
for (String s : list) {
if (s.trim() != s.toLowerCase()) {
return false;
}
}
return true;
}
public static void main(String[] args) {
System.out.println(test(Arrays.asList("a", "b", "c")));
}
}

View File

@@ -0,0 +1,22 @@
// "Replace Stream API chain with loop" "true"
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public class Main {
private static test(List<String> packages) {
Optional<String> found = Optional.empty();
for (String s : packages) {
if (s.startsWith("xyz")) {
found = Optional.of(s);
break;
}
}
return found.filter(pkg -> pkg.endsWith("abc")).isPresent();
}
public static void main(String[] args) {
System.out.println(test(Arrays.asList("xyzabc", "xyz123", "123abc", "123")));
}
}

View File

@@ -0,0 +1,18 @@
// "Replace Stream API chain with loop" "true"
import java.util.*;
import java.util.stream.*;
public class Main {
private static void test(List<String> test) {
StringBuilder sb = new StringBuilder();
for (String s : test) {
sb.append(s);
}
System.out.println("x"+ sb.toString() +"y");
}
public static void main(String[] args) {
System.out.println(test(Arrays.asList("a", "b", "c")));
}
}

View File

@@ -7,7 +7,7 @@ import java.util.List;
public class Main {
private static List<?>[] test(int[] numbers) {
List<List<?>> list = new ArrayList<>();
List<List<Integer>> list = new ArrayList<>();
for (int number : numbers) {
Integer n = number;
List<Integer> integers = Collections.singletonList(n);

View File

@@ -6,7 +6,7 @@ import java.util.List;
public class Main {
private static Object[] test(int[] numbers) {
List<Object> list = new ArrayList<>();
List<Integer> list = new ArrayList<>();
for (int number : numbers) {
Integer integer = number;
list.add(integer);

View File

@@ -0,0 +1,21 @@
// "Replace Stream API chain with loop" "true"
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
public class Main {
private static Number[] test(Object[] objects) {
List<Object> list = new ArrayList<>();
for (Object object : objects) {
if (Number.class.isInstance(object)) {
list.add(object);
}
}
return list.toArray(new Number[0]);
}
public static void main(String[] args) {
System.out.println(Arrays.asList(test(new Object[]{1, 2, 3, "string", 4})));
}
}

View File

@@ -0,0 +1,14 @@
// "Replace Stream API chain with loop" "true"
import java.util.Arrays;
import java.util.List;
public class Main {
private static boolean test(List<String> list) {
return list.stream().al<caret>lMatch(s -> s.trim() == s.toLowerCase());
}
public static void main(String[] args) {
System.out.println(test(Arrays.asList("a", "b", "c")));
}
}

View File

@@ -0,0 +1,14 @@
// "Replace Stream API chain with loop" "true"
import java.util.Arrays;
import java.util.List;
public class Main {
private static test(List<String> packages) {
return packages.stream().filter(pkg -> pkg.startsWith("xyz")).fin<caret>dAny().filter(pkg -> pkg.endsWith("abc")).isPresent();
}
public static void main(String[] args) {
System.out.println(test(Arrays.asList("xyzabc", "xyz123", "123abc", "123")));
}
}

View File

@@ -0,0 +1,14 @@
// "Replace Stream API chain with loop" "true"
import java.util.*;
import java.util.stream.*;
public class Main {
private static void test(List<String> test) {
System.out.println("x"+test.stream().c<caret>ollect(Collectors.joining())+"y");
}
public static void main(String[] args) {
System.out.println(test(Arrays.asList("a", "b", "c")));
}
}

View File

@@ -0,0 +1,13 @@
// "Replace Stream API chain with loop" "true"
import java.util.stream.Stream;
public class Main {
private static Number[] test(Object[] objects) {
return Stream.of(objects).filter(Number.class::isInstance).toArr<caret>ay(Number[]::new);
}
public static void main(String[] args) {
System.out.println(Arrays.asList(test(new Object[]{1, 2, 3, "string", 4})));
}
}