mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-19 13:02:30 +07:00
StreamToLoopInspection: added ExplicitCollectTerminalOperation; StreamVariable simplified; guard checks added (IDEA-CR-15249)
This commit is contained in:
@@ -39,7 +39,6 @@ import java.util.function.Consumer;
|
||||
*
|
||||
* @author Tagir Valeev
|
||||
*/
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
abstract class FunctionHelper {
|
||||
private static final Logger LOG = Logger.getInstance(FunctionHelper.class);
|
||||
|
||||
@@ -88,18 +87,18 @@ abstract class FunctionHelper {
|
||||
}
|
||||
}
|
||||
|
||||
@Contract("null, _, _ -> null")
|
||||
@Contract("null, _ -> null")
|
||||
@Nullable
|
||||
static FunctionHelper create(PsiExpression expression, int paramCount, boolean singleExpression) {
|
||||
static FunctionHelper create(PsiExpression expression, int paramCount) {
|
||||
if (expression instanceof PsiLambdaExpression) {
|
||||
PsiLambdaExpression lambda = (PsiLambdaExpression)expression;
|
||||
PsiType functionalInterfaceType = lambda.getFunctionalInterfaceType();
|
||||
if(functionalInterfaceType == null) return null;
|
||||
PsiParameterList list = lambda.getParameterList();
|
||||
if (list.getParametersCount() != paramCount) {
|
||||
return null;
|
||||
}
|
||||
if (list.getParametersCount() != paramCount) return null;
|
||||
String[] parameters = StreamEx.of(list.getParameters()).map(PsiVariable::getName).toArray(String[]::new);
|
||||
PsiExpression body = LambdaUtil.extractSingleExpressionFromBody(lambda.getBody());
|
||||
if (body == null && singleExpression) return null;
|
||||
if (body == null) return null;
|
||||
return new LambdaFunctionHelper(body, parameters);
|
||||
}
|
||||
if (expression instanceof PsiMethodReferenceExpression) {
|
||||
@@ -177,6 +176,8 @@ abstract class FunctionHelper {
|
||||
@Override
|
||||
String tryLightTransform(PsiType type) {
|
||||
if(myMethodRef.isConstructor()) return null;
|
||||
type = GenericsUtil.getVariableTypeByExpressionType(type);
|
||||
if(type == null) return null;
|
||||
PsiElement element = myMethodRef.resolve();
|
||||
if(!(element instanceof PsiMethod)) return null;
|
||||
PsiMethod method = (PsiMethod)element;
|
||||
|
||||
@@ -65,11 +65,11 @@ abstract class Operation {
|
||||
return new LimitOperation(args[0]);
|
||||
}
|
||||
if(name.equals("filter") && args.length == 1) {
|
||||
FunctionHelper fn = FunctionHelper.create(args[0], 1, true);
|
||||
FunctionHelper fn = FunctionHelper.create(args[0], 1);
|
||||
return fn == null ? null : new FilterOperation(fn);
|
||||
}
|
||||
if(name.equals("peek") && args.length == 1) {
|
||||
FunctionHelper fn = FunctionHelper.create(args[0], 1, true);
|
||||
FunctionHelper fn = FunctionHelper.create(args[0], 1);
|
||||
return fn == null ? null : new PeekOperation(fn);
|
||||
}
|
||||
if((name.equals("boxed") || name.equals("asLongStream") || name.equals("asDoubleStream")) && args.length == 0) {
|
||||
@@ -84,7 +84,7 @@ abstract class Operation {
|
||||
name.equals("mapToLong") ||
|
||||
name.equals("mapToDouble") ||
|
||||
name.equals("mapToObj")) && args.length == 1) {
|
||||
FunctionHelper fn = FunctionHelper.create(args[0], 1, true);
|
||||
FunctionHelper fn = FunctionHelper.create(args[0], 1);
|
||||
return fn == null ? null : new MapOperation(fn);
|
||||
}
|
||||
return null;
|
||||
@@ -225,7 +225,7 @@ abstract class Operation {
|
||||
|
||||
@Nullable
|
||||
public static FlatMapOperation from(StreamVariable outVar, PsiExpression arg, PsiType inType) {
|
||||
FunctionHelper fn = FunctionHelper.create(arg, 1, true);
|
||||
FunctionHelper fn = FunctionHelper.create(arg, 1);
|
||||
if(fn == null) return null;
|
||||
String varName = fn.tryLightTransform(inType);
|
||||
if(varName == null) return null;
|
||||
|
||||
@@ -81,12 +81,12 @@ abstract class SourceOperation extends Operation {
|
||||
}
|
||||
if (name.equals("generate") && args.length == 1 && method.getModifierList().hasExplicitModifier(
|
||||
PsiModifier.STATIC) && className.startsWith("java.util.stream.")) {
|
||||
FunctionHelper fn = FunctionHelper.create(args[0], 0, true);
|
||||
FunctionHelper fn = FunctionHelper.create(args[0], 0);
|
||||
return fn == null ? null : new GenerateSource(fn);
|
||||
}
|
||||
if (name.equals("iterate") && args.length == 2 && method.getModifierList().hasExplicitModifier(
|
||||
PsiModifier.STATIC) && className.startsWith("java.util.stream.")) {
|
||||
FunctionHelper fn = FunctionHelper.create(args[1], 1, true);
|
||||
FunctionHelper fn = FunctionHelper.create(args[1], 1);
|
||||
return fn == null ? null : new IterateSource(args[0], fn);
|
||||
}
|
||||
if (name.equals("stream") && args.length == 0 &&
|
||||
|
||||
@@ -210,7 +210,7 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
|
||||
List<OperationRecord> operations = extractOperations(StreamVariable.STUB, terminalCall);
|
||||
TerminalOperation terminal = getTerminal(operations);
|
||||
if (terminal == null) return;
|
||||
annotateOperations(project, operations);
|
||||
allOperations(operations).forEach(or -> or.myOperation.suggestNames(or.myInVar, or.myOutVar));
|
||||
PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
|
||||
if(!FileModificationService.getInstance().preparePsiElementForWrite(element)) return;
|
||||
terminalCall = ensureCodeBlock(terminalCall, factory);
|
||||
@@ -279,14 +279,6 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
|
||||
.flatMap(or -> or.myOperation.nestedOperations().append(or));
|
||||
}
|
||||
|
||||
private static void annotateOperations(@NotNull Project project, List<OperationRecord> operations) {
|
||||
JavaCodeStyleManager javaCodeStyleManager = JavaCodeStyleManager.getInstance(project);
|
||||
allOperations(operations)
|
||||
.peek(or -> or.myOperation.suggestNames(or.myInVar, or.myOutVar))
|
||||
.flatMap(or -> StreamEx.of(or.myInVar, or.myOutVar)).distinct()
|
||||
.forEach(var -> var.addCandidatesFromType(javaCodeStyleManager));
|
||||
}
|
||||
|
||||
private static void registerVariables(List<OperationRecord> operations, StreamToLoopReplacementContext context) {
|
||||
allOperations(operations).map(or -> or.myOperation).forEach(op -> op.registerUsedNames(context::addUsedVar));
|
||||
allOperations(operations).map(or -> or.myInVar).distinct().forEach(var -> var.register(context));
|
||||
|
||||
@@ -23,7 +23,6 @@ import com.intellij.psi.codeStyle.VariableKind;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
@@ -32,10 +31,8 @@ import java.util.List;
|
||||
* This class represents a variable which holds stream element. Its lifecycle is the following:
|
||||
* 1. Construction: fast, in case you don't need to perform a fix actually
|
||||
* 2. Gather name candidates (addBestNameCandidate/addOtherNameCandidate can be called).
|
||||
* 3. addCandidatesFromType called which adds name candidates based on type
|
||||
* (until this stage no changes in original file should be performed)
|
||||
* 4. Register variable in {@code StreamToLoopReplacementContext}: actual variable name is assigned here
|
||||
* 5. Usage in code generation: getName()/getType() could be called.
|
||||
* 3. Register variable in {@code StreamToLoopReplacementContext}: actual variable name is assigned here
|
||||
* 4. Usage in code generation: getName()/getType() could be called.
|
||||
*
|
||||
* @author Tagir Valeev
|
||||
*/
|
||||
@@ -47,10 +44,6 @@ class StreamVariable {
|
||||
public void addBestNameCandidate(String candidate) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCandidatesFromType(JavaCodeStyleManager manager) {
|
||||
}
|
||||
|
||||
@Override
|
||||
void register(StreamToLoopReplacementContext context) {
|
||||
}
|
||||
@@ -62,14 +55,13 @@ class StreamVariable {
|
||||
};
|
||||
|
||||
String myName;
|
||||
String myType;
|
||||
PsiType myPsiType;
|
||||
@NotNull String myType;
|
||||
|
||||
private Collection<String> myBestCandidates = new LinkedHashSet<>();
|
||||
private Collection<String> myOtherCandidates = new LinkedHashSet<>();
|
||||
|
||||
StreamVariable(@NotNull PsiType type) {
|
||||
myPsiType = type;
|
||||
myType = type.getCanonicalText();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,20 +82,6 @@ class StreamVariable {
|
||||
myOtherCandidates.add(candidate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register name candidates based on variable type.
|
||||
* This must be called only once and only after addBestNameCandidate/addOtherNameCandidate.
|
||||
*
|
||||
* @param manager project-specific {@link JavaCodeStyleManager}
|
||||
*/
|
||||
void addCandidatesFromType(JavaCodeStyleManager manager) {
|
||||
LOG.assertTrue(myPsiType != null);
|
||||
LOG.assertTrue(myType == null);
|
||||
myOtherCandidates.addAll(Arrays.asList(manager.suggestVariableName(VariableKind.LOCAL_VARIABLE, null, null, myPsiType, true).names));
|
||||
myType = myPsiType.getCanonicalText();
|
||||
myPsiType = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register variable within {@link StreamToLoopReplacementContext}.
|
||||
* Must be called once after all name candidates are registered. Now variable gets an actual name.
|
||||
@@ -112,7 +90,9 @@ class StreamVariable {
|
||||
*/
|
||||
void register(StreamToLoopReplacementContext context) {
|
||||
LOG.assertTrue(myName == null);
|
||||
List<String> variants = StreamEx.of(myBestCandidates).append(myOtherCandidates).distinct().toList();
|
||||
String[] fromType = JavaCodeStyleManager.getInstance(context.getProject())
|
||||
.suggestVariableName(VariableKind.LOCAL_VARIABLE, null, null, context.createType(myType), true).names;
|
||||
List<String> variants = StreamEx.of(myBestCandidates).append(myOtherCandidates).append(fromType).distinct().toList();
|
||||
if (variants.isEmpty()) variants.add("val");
|
||||
myName = context.registerVarName(variants);
|
||||
myBestCandidates = myOtherCandidates = null;
|
||||
@@ -123,8 +103,8 @@ class StreamVariable {
|
||||
return myName;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
String getType() {
|
||||
LOG.assertTrue(myType != null);
|
||||
return myType;
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ abstract class TerminalOperation extends Operation {
|
||||
@NotNull PsiType elementType, @NotNull PsiType resultType, boolean isVoid) {
|
||||
if(isVoid) {
|
||||
if ((name.equals("forEach") || name.equals("forEachOrdered")) && args.length == 1) {
|
||||
FunctionHelper fn = FunctionHelper.create(args[0], 1, true);
|
||||
FunctionHelper fn = FunctionHelper.create(args[0], 1);
|
||||
return fn == null ? null : new ForEachTerminalOperation(fn);
|
||||
}
|
||||
return null;
|
||||
@@ -94,19 +94,19 @@ abstract class TerminalOperation extends Operation {
|
||||
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);
|
||||
FunctionHelper fn = FunctionHelper.create(args[0], 1);
|
||||
return fn == null ? null : new MatchTerminalOperation(fn, name);
|
||||
}
|
||||
if(name.equals("reduce")) {
|
||||
if(args.length == 2 || args.length == 3) {
|
||||
FunctionHelper fn = FunctionHelper.create(args[1], 2, true);
|
||||
FunctionHelper fn = FunctionHelper.create(args[1], 2);
|
||||
if(fn != null) {
|
||||
return new ReduceTerminalOperation(args[0], fn, resultType.getCanonicalText());
|
||||
}
|
||||
}
|
||||
if(args.length == 1) {
|
||||
PsiType optionalElementType = getOptionalElementType(resultType);
|
||||
FunctionHelper fn = FunctionHelper.create(args[0], 2, true);
|
||||
FunctionHelper fn = FunctionHelper.create(args[0], 2);
|
||||
if(fn != null && optionalElementType != null) {
|
||||
return new ReduceToOptionalTerminalOperation(fn, optionalElementType.getCanonicalText());
|
||||
}
|
||||
@@ -135,6 +135,13 @@ abstract class TerminalOperation extends Operation {
|
||||
"{acc}.toArray("+arr+")");
|
||||
}
|
||||
}
|
||||
if(name.equals("collect") && args.length == 3) {
|
||||
FunctionHelper supplier = FunctionHelper.create(args[0], 0);
|
||||
if(supplier == null) return null;
|
||||
FunctionHelper accumulator = FunctionHelper.create(args[1], 2);
|
||||
if(accumulator == null) return null;
|
||||
return new ExplicitCollectTerminalOperation(supplier, accumulator, resultType.getCanonicalText());
|
||||
}
|
||||
if(name.equals("collect") && args.length == 1) {
|
||||
if(args[0] instanceof PsiMethodCallExpression) {
|
||||
PsiMethodCallExpression collectorCall = (PsiMethodCallExpression)args[0];
|
||||
@@ -150,20 +157,20 @@ abstract class TerminalOperation extends Operation {
|
||||
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);
|
||||
FunctionHelper fn = FunctionHelper.create(collectorArgs[0], 0);
|
||||
if(fn != null) {
|
||||
return new ToCollectionTerminalOperation(fn, resultType);
|
||||
}
|
||||
}
|
||||
if(collector.getName().equals("reducing") && collectorArgs.length == 2) {
|
||||
FunctionHelper fn = FunctionHelper.create(collectorArgs[1], 2, true);
|
||||
FunctionHelper fn = FunctionHelper.create(collectorArgs[1], 2);
|
||||
if(fn != null) {
|
||||
return new ReduceTerminalOperation(collectorArgs[0], fn, resultType.getCanonicalText());
|
||||
}
|
||||
}
|
||||
if(collector.getName().equals("reducing") && collectorArgs.length == 1) {
|
||||
PsiType optionalElementType = getOptionalElementType(resultType);
|
||||
FunctionHelper fn = FunctionHelper.create(collectorArgs[0], 2, true);
|
||||
FunctionHelper fn = FunctionHelper.create(collectorArgs[0], 2);
|
||||
if(fn != null && optionalElementType != null) {
|
||||
return new ReduceToOptionalTerminalOperation(fn, optionalElementType.getCanonicalText());
|
||||
}
|
||||
@@ -265,6 +272,38 @@ abstract class TerminalOperation extends Operation {
|
||||
}
|
||||
}
|
||||
|
||||
static class ExplicitCollectTerminalOperation extends TerminalOperation {
|
||||
private final FunctionHelper mySupplier;
|
||||
private final FunctionHelper myAccumulator;
|
||||
private final String myResultType;
|
||||
|
||||
public ExplicitCollectTerminalOperation(FunctionHelper supplier, FunctionHelper accumulator, String resultType) {
|
||||
mySupplier = supplier;
|
||||
myAccumulator = accumulator;
|
||||
myResultType = resultType;
|
||||
}
|
||||
|
||||
@Override
|
||||
void registerUsedNames(Consumer<String> usedNameConsumer) {
|
||||
mySupplier.registerUsedNames(usedNameConsumer);
|
||||
myAccumulator.registerUsedNames(usedNameConsumer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void suggestNames(StreamVariable inVar, StreamVariable outVar) {
|
||||
myAccumulator.suggestVariableName(inVar, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
String generate(StreamVariable inVar, StreamToLoopReplacementContext context) {
|
||||
mySupplier.transform(context);
|
||||
String candidate = myAccumulator.getParameterName(0);
|
||||
String acc = context.declareResult(candidate == null ? "acc" : candidate, myResultType, mySupplier.getText());
|
||||
myAccumulator.transform(context, acc, inVar.getName());
|
||||
return myAccumulator.getText()+";\n";
|
||||
}
|
||||
}
|
||||
|
||||
static class AverageTerminalOperation extends TerminalOperation {
|
||||
private boolean myDoubleAccumulator;
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
// "Replace Stream API chain with loop" "true"
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class Main {
|
||||
private static void test(List<String> list) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String str : list) {
|
||||
if (Objects.nonNull(str)) {
|
||||
sb.append(str);
|
||||
}
|
||||
}
|
||||
String result = sb.toString();
|
||||
System.out.println(result);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
test(Arrays.asList("aa", "bbb", "c", null, "dd"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// "Replace Stream API chain with loop" "true"
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class Main {
|
||||
private static long test(List<? extends String> list) {
|
||||
long count = 0;
|
||||
for (Object o : Arrays.<Object>asList(0, null, "1", list)) {
|
||||
for (Object o1 : Arrays.<Object>asList(o)) {
|
||||
for (Object o2 : Arrays.<Object>asList(o1)) {
|
||||
for (Object o3 : Arrays.<Object>asList(o2)) {
|
||||
for (Object o4 : Arrays.<Object>asList(o3)) {
|
||||
for (Object o5 : Arrays.<Object>asList(o4)) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
test(Arrays.asList("aa", "bbb", "c", null, "dd"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// "Replace Stream API chain with loop" "true"
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class Main {
|
||||
private static void test(List<String> list) {
|
||||
long count1 = 0;
|
||||
for (String s : list) {
|
||||
if (!s.isEmpty()) {
|
||||
count1++;
|
||||
}
|
||||
}
|
||||
long count = count1;
|
||||
if(count > 10) {
|
||||
long result = count*2;
|
||||
System.out.println(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// "Replace Stream API chain with loop" "true"
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class Main {
|
||||
private static void test(List<String> list) {
|
||||
String result = list.stream().filter(Objects::nonNull).coll<caret>ect(StringBuilder::new, (sb, str) -> sb.append(str), StringBuilder::append).toString();
|
||||
System.out.println(result);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
test(Arrays.asList("aa", "bbb", "c", null, "dd"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// "Replace Stream API chain with loop" "true"
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class Main {
|
||||
private static long test(List<? extends String> list) {
|
||||
return Stream.of(0, null, "1", list).flatMap(Stream::of).flatMap(Stream::of).flatMap(Stream::of).flatMap(Stream::of).flatMap(Stream::of).coun<caret>t();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
test(Arrays.asList("aa", "bbb", "c", null, "dd"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// "Replace Stream API chain with loop" "true"
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class Main {
|
||||
private static void test(List<String> list) {
|
||||
long count = list.stream().filter(s -> !s.isEmpty()).co<caret>unt();
|
||||
if(count > 10) {
|
||||
long result = count*2;
|
||||
System.out.println(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user