mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-10-06 06:21:16 +07:00
IDEA-163405 Migration from Stream API back to for loops: iteration#2
This commit is contained in:
@@ -17,6 +17,7 @@ package com.intellij.codeInspection.streamToLoop;
|
||||
|
||||
import com.intellij.codeInspection.streamToLoop.StreamToLoopInspection.StreamToLoopReplacementContext;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
|
||||
import com.intellij.psi.codeStyle.SuggestedNameInfo;
|
||||
@@ -24,14 +25,16 @@ import com.intellij.psi.codeStyle.VariableKind;
|
||||
import com.intellij.psi.search.LocalSearchScope;
|
||||
import com.intellij.psi.search.searches.ReferencesSearch;
|
||||
import com.intellij.refactoring.util.LambdaRefactoringUtil;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.siyeh.ig.psiutils.ExpressionUtils;
|
||||
import com.siyeh.ig.psiutils.MethodCallUtils;
|
||||
import one.util.streamex.EntryStream;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.OptionalLong;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
@@ -42,6 +45,16 @@ import java.util.function.Consumer;
|
||||
abstract class FunctionHelper {
|
||||
private static final Logger LOG = Logger.getInstance(FunctionHelper.class);
|
||||
|
||||
private String myResultType;
|
||||
|
||||
FunctionHelper(PsiType resultType) {
|
||||
myResultType = resultType.getCanonicalText();
|
||||
}
|
||||
|
||||
String getResultType() {
|
||||
return myResultType;
|
||||
}
|
||||
|
||||
String getText() {
|
||||
return getExpression().getText();
|
||||
}
|
||||
@@ -55,7 +68,9 @@ abstract class FunctionHelper {
|
||||
* @return SAM argument name or null if function helper refused to perform a transformation.
|
||||
* @param type
|
||||
*/
|
||||
abstract String tryLightTransform(PsiType type);
|
||||
String tryLightTransform(PsiType type) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform an adaptation of current function helper to the replacement context with given parameter names.
|
||||
@@ -73,12 +88,14 @@ abstract class FunctionHelper {
|
||||
* @param newName new variable name
|
||||
* @param context a context
|
||||
*/
|
||||
abstract void rename(String oldName, String newName, StreamToLoopReplacementContext context);
|
||||
void rename(String oldName, String newName, StreamToLoopReplacementContext context) {}
|
||||
|
||||
abstract void registerUsedNames(Consumer<String> consumer);
|
||||
void registerUsedNames(Consumer<String> consumer) {}
|
||||
|
||||
@Nullable
|
||||
abstract String getParameterName(int index);
|
||||
String getParameterName(int index) {
|
||||
return null;
|
||||
}
|
||||
|
||||
void suggestVariableName(StreamVariable var, int index) {
|
||||
String name = getParameterName(index);
|
||||
@@ -87,36 +104,72 @@ abstract class FunctionHelper {
|
||||
}
|
||||
}
|
||||
|
||||
void suggestOutputNames(StreamVariable var) {}
|
||||
|
||||
List<String> suggestFinalOutputNames(StreamToLoopReplacementContext context, String desiredName, String worstCaseName) {
|
||||
List<String> candidates = Arrays.asList(JavaCodeStyleManager.getInstance(context.getProject())
|
||||
.suggestVariableName(VariableKind.LOCAL_VARIABLE, desiredName,
|
||||
context.createExpression(getText()),
|
||||
context.createType(getResultType())).names);
|
||||
if(candidates.isEmpty() && worstCaseName != null) candidates = Collections.singletonList(worstCaseName);
|
||||
return candidates;
|
||||
}
|
||||
|
||||
@Contract("null, _ -> null")
|
||||
@Nullable
|
||||
static FunctionHelper create(PsiExpression expression, int paramCount) {
|
||||
if(expression == null) return null;
|
||||
PsiType type = expression instanceof PsiFunctionalExpression
|
||||
? ((PsiFunctionalExpression)expression).getFunctionalInterfaceType()
|
||||
: expression.getType();
|
||||
if(!(type instanceof PsiClassType)) return null;
|
||||
PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(type);
|
||||
if (interfaceMethod == null || interfaceMethod.getParameterList().getParametersCount() != paramCount) return null;
|
||||
PsiType returnType = interfaceMethod.getReturnType();
|
||||
if (returnType == null) return null;
|
||||
returnType = ((PsiClassType)type).resolveGenerics().getSubstitutor().substitute(returnType);
|
||||
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;
|
||||
String[] parameters = StreamEx.of(list.getParameters()).map(PsiVariable::getName).toArray(String[]::new);
|
||||
PsiExpression body = LambdaUtil.extractSingleExpressionFromBody(lambda.getBody());
|
||||
if (body == null) return null;
|
||||
return new LambdaFunctionHelper(body, parameters);
|
||||
return new LambdaFunctionHelper(returnType, body, parameters);
|
||||
}
|
||||
if (expression instanceof PsiMethodReferenceExpression) {
|
||||
PsiMethodReferenceExpression methodRef = (PsiMethodReferenceExpression)expression;
|
||||
if (methodRef.resolve() == null) return null;
|
||||
PsiType functionalInterfaceType = methodRef.getFunctionalInterfaceType();
|
||||
PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(functionalInterfaceType);
|
||||
if (interfaceMethod == null) return null;
|
||||
if (interfaceMethod.getParameterList().getParametersCount() != paramCount) return null;
|
||||
return new MethodReferenceFunctionHelper(functionalInterfaceType, methodRef);
|
||||
return new MethodReferenceFunctionHelper(returnType, type, methodRef);
|
||||
}
|
||||
if (expression instanceof PsiReferenceExpression && ExpressionUtils.isSimpleExpression(expression)) {
|
||||
PsiType functionalInterfaceType = expression.getType();
|
||||
PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(functionalInterfaceType);
|
||||
if (interfaceMethod == null || interfaceMethod.getParameterList().getParametersCount() != paramCount) return null;
|
||||
return new SimpleReferenceFunctionHelper(expression, interfaceMethod.getName());
|
||||
return new SimpleReferenceFunctionHelper(returnType, expression, interfaceMethod.getName());
|
||||
}
|
||||
return null;
|
||||
if (expression instanceof PsiMethodCallExpression &&
|
||||
MethodCallUtils
|
||||
.isCallToStaticMethod((PsiMethodCallExpression)expression, CommonClassNames.JAVA_UTIL_FUNCTION_FUNCTION, "identity", 0)) {
|
||||
return paramCount == 1 ? new IdentityFunctionHelper(returnType) : null;
|
||||
}
|
||||
return new ComplexExpressionFunctionHelper(returnType, type, interfaceMethod.getName(), expression);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Contract(pure = true)
|
||||
static FunctionHelper hashMapSupplier(PsiType type) {
|
||||
return new FunctionHelper(type) {
|
||||
PsiExpression myExpression;
|
||||
|
||||
@Override
|
||||
PsiExpression getExpression() {
|
||||
return myExpression;
|
||||
}
|
||||
|
||||
@Override
|
||||
void transform(StreamToLoopReplacementContext context, String... newNames) {
|
||||
LOG.assertTrue(newNames.length == 0);
|
||||
myExpression = context.createExpression("new java.util.HashMap<>()");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -165,7 +218,8 @@ abstract class FunctionHelper {
|
||||
private PsiMethodReferenceExpression myMethodRef;
|
||||
private PsiExpression myExpression;
|
||||
|
||||
public MethodReferenceFunctionHelper(PsiType functionalInterfaceType, PsiMethodReferenceExpression methodRef) {
|
||||
public MethodReferenceFunctionHelper(PsiType returnType, PsiType functionalInterfaceType, PsiMethodReferenceExpression methodRef) {
|
||||
super(returnType);
|
||||
myMethodRef = methodRef;
|
||||
myType = functionalInterfaceType.getCanonicalText();
|
||||
PsiExpression qualifier = methodRef.getQualifierExpression();
|
||||
@@ -258,11 +312,61 @@ abstract class FunctionHelper {
|
||||
qualifier = renameVarReference(qualifier, oldName, newName, context);
|
||||
myMethodRef = fromText(context, qualifier.getText()+"::"+myMethodRef.getReferenceName());
|
||||
}
|
||||
}
|
||||
|
||||
private static class ComplexExpressionFunctionHelper extends FunctionHelper {
|
||||
private final String myMethodName;
|
||||
private final String myNameCandidate;
|
||||
private final String myFnType;
|
||||
private PsiExpression myExpression;
|
||||
private PsiExpression myFinalExpression;
|
||||
|
||||
private ComplexExpressionFunctionHelper(PsiType type, PsiType functionalInterface, String name, PsiExpression expression) {
|
||||
super(type);
|
||||
myMethodName = name;
|
||||
myExpression = expression;
|
||||
myNameCandidate = getNameCandidate(functionalInterface);
|
||||
myFnType = functionalInterface.getCanonicalText();
|
||||
}
|
||||
|
||||
private String getNameCandidate(PsiType functionalInterface) {
|
||||
PsiElement parent = myExpression.getParent();
|
||||
if(parent instanceof PsiExpressionList) {
|
||||
int idx = ArrayUtil.indexOf(((PsiExpressionList)parent).getExpressions(), myExpression);
|
||||
PsiElement gParent = parent.getParent();
|
||||
if(gParent instanceof PsiMethodCallExpression && idx >= 0) {
|
||||
PsiMethod method = ((PsiMethodCallExpression)gParent).resolveMethod();
|
||||
if(method != null) {
|
||||
PsiParameter[] parameters = method.getParameterList().getParameters();
|
||||
if(idx < parameters.length) {
|
||||
return parameters[idx].getName();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return functionalInterface.getPresentableText().toLowerCase(Locale.ENGLISH);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
String getParameterName(int index) {
|
||||
return null;
|
||||
PsiExpression getExpression() {
|
||||
LOG.assertTrue(myFinalExpression != null);
|
||||
return myFinalExpression;
|
||||
}
|
||||
|
||||
@Override
|
||||
void rename(String oldName, String newName, StreamToLoopReplacementContext context) {
|
||||
myExpression = renameVarReference(myExpression, oldName, newName, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
void registerUsedNames(Consumer<String> consumer) {
|
||||
processUsedNames(myExpression, consumer);
|
||||
}
|
||||
|
||||
@Override
|
||||
void transform(StreamToLoopReplacementContext context, String... newNames) {
|
||||
String varName = context.declare(myNameCandidate, myFnType, myExpression.getText());
|
||||
myFinalExpression = context.createExpression(varName + "." + myMethodName + "(" + String.join(",", newNames) + ")");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,16 +375,12 @@ abstract class FunctionHelper {
|
||||
private final String myName;
|
||||
private PsiExpression myExpression;
|
||||
|
||||
public SimpleReferenceFunctionHelper(PsiExpression reference, String methodName) {
|
||||
public SimpleReferenceFunctionHelper(PsiType returnType, PsiExpression reference, String methodName) {
|
||||
super(returnType);
|
||||
myReference = reference;
|
||||
myName = methodName;
|
||||
}
|
||||
|
||||
@Override
|
||||
String tryLightTransform(PsiType type) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
PsiExpression getExpression() {
|
||||
LOG.assertTrue(myExpression != null);
|
||||
@@ -301,11 +401,25 @@ abstract class FunctionHelper {
|
||||
void registerUsedNames(Consumer<String> consumer) {
|
||||
processUsedNames(myReference, consumer);
|
||||
}
|
||||
}
|
||||
|
||||
private static class IdentityFunctionHelper extends FunctionHelper {
|
||||
private PsiExpression myExpression;
|
||||
|
||||
public IdentityFunctionHelper(PsiType type) {
|
||||
super(type);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
String getParameterName(int index) {
|
||||
return null;
|
||||
PsiExpression getExpression() {
|
||||
LOG.assertTrue(myExpression != null);
|
||||
return myExpression;
|
||||
}
|
||||
|
||||
@Override
|
||||
void transform(StreamToLoopReplacementContext context, String... newNames) {
|
||||
LOG.assertTrue(newNames.length == 1);
|
||||
myExpression = context.createExpression(newNames[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,7 +427,8 @@ abstract class FunctionHelper {
|
||||
private String[] myParameters;
|
||||
private PsiExpression myBody;
|
||||
|
||||
LambdaFunctionHelper(PsiExpression body, String[] parameters) {
|
||||
LambdaFunctionHelper(PsiType returnType, PsiExpression body, String[] parameters) {
|
||||
super(returnType);
|
||||
myParameters = parameters;
|
||||
myBody = body;
|
||||
}
|
||||
@@ -365,5 +480,17 @@ abstract class FunctionHelper {
|
||||
String getParameterName(int index) {
|
||||
return myParameters[index];
|
||||
}
|
||||
|
||||
@Override
|
||||
void suggestOutputNames(StreamVariable var) {
|
||||
String text = "("+var.getType()+")"+getText();
|
||||
Project project = myBody.getProject();
|
||||
PsiExpression expr = JavaPsiFacade.getElementFactory(project).createExpressionFromText(text, myBody);
|
||||
SuggestedNameInfo info =
|
||||
JavaCodeStyleManager.getInstance(project).suggestVariableName(VariableKind.LOCAL_VARIABLE, null, expr, null, true);
|
||||
for (String name : info.names) {
|
||||
var.addOtherNameCandidate(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,10 +46,12 @@ abstract class Operation {
|
||||
String code,
|
||||
StreamToLoopReplacementContext context);
|
||||
|
||||
void registerUsedNames(Consumer<String> usedNameConsumer) {
|
||||
|
||||
Operation combineWithNext(Operation next) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void registerUsedNames(Consumer<String> usedNameConsumer) {}
|
||||
|
||||
public void suggestNames(StreamVariable inVar, StreamVariable outVar) {}
|
||||
|
||||
@Nullable
|
||||
@@ -98,7 +100,7 @@ abstract class Operation {
|
||||
}
|
||||
|
||||
@Override
|
||||
void registerUsedNames(Consumer<String> usedNameConsumer) {
|
||||
public void registerUsedNames(Consumer<String> usedNameConsumer) {
|
||||
myFn.registerUsedNames(usedNameConsumer);
|
||||
}
|
||||
|
||||
@@ -148,6 +150,12 @@ abstract class Operation {
|
||||
super(fn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void suggestNames(StreamVariable inVar, StreamVariable outVar) {
|
||||
super.suggestNames(inVar, outVar);
|
||||
myFn.suggestOutputNames(outVar);
|
||||
}
|
||||
|
||||
@Override
|
||||
String wrap(StreamVariable outVar, String code, StreamToLoopReplacementContext context) {
|
||||
return outVar.getDeclaration() + " = " + myFn.getText() + ";\n" + code;
|
||||
@@ -201,7 +209,7 @@ abstract class Operation {
|
||||
}
|
||||
|
||||
@Override
|
||||
void registerUsedNames(Consumer<String> usedNameConsumer) {
|
||||
public void registerUsedNames(Consumer<String> usedNameConsumer) {
|
||||
myRecords.forEach(or -> or.myOperation.registerUsedNames(usedNameConsumer));
|
||||
}
|
||||
|
||||
@@ -260,7 +268,7 @@ abstract class Operation {
|
||||
}
|
||||
|
||||
@Override
|
||||
void registerUsedNames(Consumer<String> usedNameConsumer) {
|
||||
public void registerUsedNames(Consumer<String> usedNameConsumer) {
|
||||
FunctionHelper.processUsedNames(myExpression, usedNameConsumer);
|
||||
}
|
||||
|
||||
@@ -272,25 +280,25 @@ abstract class Operation {
|
||||
}
|
||||
|
||||
static class LimitOperation extends Operation {
|
||||
PsiExpression myExpression;
|
||||
PsiExpression myLimit;
|
||||
|
||||
LimitOperation(PsiExpression expression) {
|
||||
myExpression = expression;
|
||||
myLimit = expression;
|
||||
}
|
||||
|
||||
@Override
|
||||
void rename(String oldName, String newName, StreamToLoopReplacementContext context) {
|
||||
myExpression = FunctionHelper.renameVarReference(myExpression, oldName, newName, context);
|
||||
myLimit = FunctionHelper.renameVarReference(myLimit, oldName, newName, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
void registerUsedNames(Consumer<String> usedNameConsumer) {
|
||||
FunctionHelper.processUsedNames(myExpression, usedNameConsumer);
|
||||
public void registerUsedNames(Consumer<String> usedNameConsumer) {
|
||||
FunctionHelper.processUsedNames(myLimit, usedNameConsumer);
|
||||
}
|
||||
|
||||
@Override
|
||||
String wrap(StreamVariable inVar, StreamVariable outVar, String code, StreamToLoopReplacementContext context) {
|
||||
String limit = context.declare("limit", "long", myExpression.getText());
|
||||
String limit = context.declare("limit", "long", myLimit.getText());
|
||||
return "if(" + limit + "--==0) " + context.getBreakStatement() + code;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ 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);
|
||||
return fn == null ? null : new GenerateSource(fn);
|
||||
return fn == null ? null : new GenerateSource(fn, null);
|
||||
}
|
||||
if (name.equals("iterate") && args.length == 2 && method.getModifierList().hasExplicitModifier(
|
||||
PsiModifier.STATIC) && className.startsWith("java.util.stream.")) {
|
||||
@@ -113,7 +113,7 @@ abstract class SourceOperation extends Operation {
|
||||
}
|
||||
|
||||
@Override
|
||||
void registerUsedNames(Consumer<String> usedNameConsumer) {
|
||||
public void registerUsedNames(Consumer<String> usedNameConsumer) {
|
||||
processUsedNames(myQualifier, usedNameConsumer);
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ abstract class SourceOperation extends Operation {
|
||||
}
|
||||
|
||||
@Override
|
||||
void registerUsedNames(Consumer<String> usedNameConsumer) {
|
||||
public void registerUsedNames(Consumer<String> usedNameConsumer) {
|
||||
for(PsiExpression arg : myArgList) {
|
||||
processUsedNames(arg, usedNameConsumer);
|
||||
}
|
||||
@@ -170,26 +170,47 @@ abstract class SourceOperation extends Operation {
|
||||
|
||||
static class GenerateSource extends SourceOperation {
|
||||
private FunctionHelper myFn;
|
||||
private PsiExpression myLimit;
|
||||
|
||||
GenerateSource(FunctionHelper fn) {
|
||||
GenerateSource(FunctionHelper fn, PsiExpression limit) {
|
||||
myFn = fn;
|
||||
myLimit = limit;
|
||||
}
|
||||
|
||||
@Override
|
||||
Operation combineWithNext(Operation next) {
|
||||
if(myLimit == null && next instanceof LimitOperation) {
|
||||
return new GenerateSource(myFn, ((LimitOperation)next).myLimit);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
void rename(String oldName, String newName, StreamToLoopReplacementContext context) {
|
||||
myFn.rename(oldName, newName, context);
|
||||
if(myLimit != null) {
|
||||
myLimit = renameVarReference(myLimit, oldName, newName, context);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void registerUsedNames(Consumer<String> usedNameConsumer) {
|
||||
public void registerUsedNames(Consumer<String> usedNameConsumer) {
|
||||
myFn.registerUsedNames(usedNameConsumer);
|
||||
if(myLimit != null) {
|
||||
processUsedNames(myLimit, usedNameConsumer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
String wrap(StreamVariable outVar, String code, StreamToLoopReplacementContext context) {
|
||||
myFn.transform(context);
|
||||
String loop = "while(true)";
|
||||
if(myLimit != null) {
|
||||
String loopIdx = context.registerVarName(Arrays.asList("count", "limit"));
|
||||
loop = "for(long "+loopIdx+"="+myLimit.getText()+";"+loopIdx+">0;"+loopIdx+"--)";
|
||||
}
|
||||
return context.getLoopLabel() +
|
||||
"while(true) {\n" +
|
||||
loop+"{\n" +
|
||||
outVar.getDeclaration() + "=" + myFn.getText() + ";\n" + code +
|
||||
"}\n";
|
||||
}
|
||||
@@ -211,7 +232,7 @@ abstract class SourceOperation extends Operation {
|
||||
}
|
||||
|
||||
@Override
|
||||
void registerUsedNames(Consumer<String> usedNameConsumer) {
|
||||
public void registerUsedNames(Consumer<String> usedNameConsumer) {
|
||||
processUsedNames(myInitializer, usedNameConsumer);
|
||||
myFn.registerUsedNames(usedNameConsumer);
|
||||
}
|
||||
@@ -248,7 +269,7 @@ abstract class SourceOperation extends Operation {
|
||||
}
|
||||
|
||||
@Override
|
||||
void registerUsedNames(Consumer<String> usedNameConsumer) {
|
||||
public void registerUsedNames(Consumer<String> usedNameConsumer) {
|
||||
processUsedNames(myOrigin, usedNameConsumer);
|
||||
processUsedNames(myBound, usedNameConsumer);
|
||||
}
|
||||
|
||||
+34
-10
@@ -20,6 +20,7 @@ import com.intellij.codeInspection.BaseJavaBatchLocalInspectionTool;
|
||||
import com.intellij.codeInspection.LocalQuickFix;
|
||||
import com.intellij.codeInspection.ProblemDescriptor;
|
||||
import com.intellij.codeInspection.ProblemsHolder;
|
||||
import com.intellij.codeInspection.util.OptionalUtil;
|
||||
import com.intellij.lang.java.lexer.JavaLexer;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.project.Project;
|
||||
@@ -27,11 +28,9 @@ import com.intellij.pom.java.LanguageLevel;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.CodeStyleManager;
|
||||
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
|
||||
import com.intellij.psi.impl.PsiDiamondTypeUtil;
|
||||
import com.intellij.psi.tree.IElementType;
|
||||
import com.intellij.psi.util.InheritanceUtil;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.psi.util.PsiUtilCore;
|
||||
import com.intellij.psi.util.*;
|
||||
import com.siyeh.ig.psiutils.StreamApiUtil;
|
||||
import one.util.streamex.IntStreamEx;
|
||||
import one.util.streamex.StreamEx;
|
||||
@@ -53,7 +52,7 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
|
||||
// To quickly filter out most of the non-interesting method calls
|
||||
private static final Set<String> SUPPORTED_TERMINALS = StreamEx.of("count", "sum", "summaryStatistics", "reduce", "collect",
|
||||
"findFirst", "findAny", "anyMatch", "allMatch", "noneMatch",
|
||||
"toArray", "average", "forEach", "forEachOrdered").toSet();
|
||||
"toArray", "average", "forEach", "forEachOrdered", "min", "max").toSet();
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
@@ -157,9 +156,17 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
|
||||
List<OperationRecord> operations = new ArrayList<>();
|
||||
PsiMethodCallExpression currentCall = terminalCall;
|
||||
StreamVariable lastVar = outVar;
|
||||
Operation next = null;
|
||||
while(true) {
|
||||
Operation op = createOperationFromCall(lastVar, currentCall);
|
||||
if(op == null) return null;
|
||||
if(next != null) {
|
||||
Operation combined = op.combineWithNext(next);
|
||||
if (combined != null) {
|
||||
op = combined;
|
||||
operations.remove(operations.size() - 1);
|
||||
}
|
||||
}
|
||||
OperationRecord or = new OperationRecord();
|
||||
or.myOperation = op;
|
||||
or.myOutVar = lastVar;
|
||||
@@ -175,9 +182,10 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
|
||||
if(op.changesVariable()) {
|
||||
PsiType type = StreamApiUtil.getStreamElementType(currentCall.getType());
|
||||
if(type == null) return null;
|
||||
lastVar = new StreamVariable(type);
|
||||
lastVar = new StreamVariable(type.getCanonicalText());
|
||||
}
|
||||
or.myInVar = lastVar;
|
||||
next = op;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,7 +245,7 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
|
||||
temporaryStreamPlaceholder.delete();
|
||||
}
|
||||
else {
|
||||
temporaryStreamPlaceholder.replace(factory.createExpressionFromText(finisher, temporaryStreamPlaceholder));
|
||||
normalize(project, temporaryStreamPlaceholder.replace(factory.createExpressionFromText(finisher, temporaryStreamPlaceholder)));
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
@@ -250,9 +258,15 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
|
||||
}
|
||||
}
|
||||
|
||||
void addStatement(@NotNull Project project, PsiStatement statement, PsiStatement context) {
|
||||
CodeStyleManager.getInstance(project)
|
||||
.reformat(JavaCodeStyleManager.getInstance(project).shortenClassReferences(statement.getParent().addBefore(context, statement)));
|
||||
private static void addStatement(@NotNull Project project, PsiStatement statement, PsiStatement context) {
|
||||
PsiElement element = statement.getParent().addBefore(context, statement);
|
||||
normalize(project, element);
|
||||
}
|
||||
|
||||
private static void normalize(@NotNull Project project, PsiElement element) {
|
||||
element = JavaCodeStyleManager.getInstance(project).shortenClassReferences(element);
|
||||
PsiDiamondTypeUtil.removeRedundantTypeArguments(element);
|
||||
CodeStyleManager.getInstance(project).reformat(element);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -381,6 +395,10 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
|
||||
return found + " = " +foundValue+";\n" + getBreakStatement();
|
||||
}
|
||||
|
||||
public void addInitStep(String initStatement) {
|
||||
myDeclarations.add(initStatement);
|
||||
}
|
||||
|
||||
public String declareResult(String desiredName, String type, String initializer) {
|
||||
String name = registerVarName(Arrays.asList(desiredName, "result"));
|
||||
myDeclarations.add(type + " " + name + " = " + initializer + ";");
|
||||
@@ -399,6 +417,12 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
|
||||
myFinisher = finisher;
|
||||
}
|
||||
|
||||
public void setOptionalUnwrapperFinisher(String seenVariable, String accVariable, String type) {
|
||||
String optionalClass = OptionalUtil.getOptionalClass(type);
|
||||
setFinisher("(" + seenVariable + "?" + optionalClass + ".of(" + accVariable + "):" + optionalClass +
|
||||
"." + (TypeConversionUtil.isPrimitive(type) ? "" : "<" + type + ">") + "empty())");
|
||||
}
|
||||
|
||||
public Project getProject() {
|
||||
return myStatement.getProject();
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ package com.intellij.codeInspection.streamToLoop;
|
||||
|
||||
import com.intellij.codeInspection.streamToLoop.StreamToLoopInspection.StreamToLoopReplacementContext;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.psi.PsiType;
|
||||
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
|
||||
import com.intellij.psi.codeStyle.VariableKind;
|
||||
import one.util.streamex.StreamEx;
|
||||
@@ -39,7 +38,7 @@ import java.util.List;
|
||||
class StreamVariable {
|
||||
private static final Logger LOG = Logger.getInstance(StreamVariable.class);
|
||||
|
||||
static StreamVariable STUB = new StreamVariable(PsiType.VOID) {
|
||||
static StreamVariable STUB = new StreamVariable("") {
|
||||
@Override
|
||||
public void addBestNameCandidate(String candidate) {
|
||||
}
|
||||
@@ -60,8 +59,13 @@ class StreamVariable {
|
||||
private Collection<String> myBestCandidates = new LinkedHashSet<>();
|
||||
private Collection<String> myOtherCandidates = new LinkedHashSet<>();
|
||||
|
||||
StreamVariable(@NotNull PsiType type) {
|
||||
myType = type.getCanonicalText();
|
||||
StreamVariable(@NotNull String type) {
|
||||
myType = type;
|
||||
}
|
||||
|
||||
StreamVariable(@NotNull String type, @NotNull String name) {
|
||||
myType = type;
|
||||
myName = name;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+573
-98
@@ -18,13 +18,20 @@ package com.intellij.codeInspection.streamToLoop;
|
||||
import com.intellij.codeInspection.streamToLoop.StreamToLoopInspection.StreamToLoopReplacementContext;
|
||||
import com.intellij.codeInspection.util.OptionalUtil;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.InheritanceUtil;
|
||||
import com.intellij.psi.util.PsiTypesUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.psi.util.TypeConversionUtil;
|
||||
import com.siyeh.ig.psiutils.BoolUtils;
|
||||
import com.siyeh.ig.psiutils.ExpressionUtils;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* @author Tagir Valeev
|
||||
@@ -45,6 +52,10 @@ abstract class TerminalOperation extends Operation {
|
||||
return true;
|
||||
}
|
||||
|
||||
CollectorOperation asCollector() {
|
||||
return null;
|
||||
}
|
||||
|
||||
abstract String generate(StreamVariable inVar, StreamToLoopReplacementContext context);
|
||||
|
||||
@Nullable
|
||||
@@ -61,19 +72,18 @@ abstract class TerminalOperation extends Operation {
|
||||
return new AccumulatedTerminalOperation("count", "long", "0", "{acc}++;");
|
||||
}
|
||||
if(name.equals("sum") && args.length == 0) {
|
||||
return new AccumulatedTerminalOperation("sum", resultType.getCanonicalText(), "0", "{acc}+={item};");
|
||||
return AccumulatedTerminalOperation.summing(resultType);
|
||||
}
|
||||
if(name.equals("average") && args.length == 0) {
|
||||
if(elementType.equals(PsiType.DOUBLE)) {
|
||||
return new AverageTerminalOperation(true);
|
||||
return new AverageTerminalOperation(true, true);
|
||||
}
|
||||
else if(elementType.equals(PsiType.INT) || elementType.equals(PsiType.LONG)) {
|
||||
return new AverageTerminalOperation(false);
|
||||
return new AverageTerminalOperation(false, true);
|
||||
}
|
||||
}
|
||||
if(name.equals("summaryStatistics") && args.length == 0) {
|
||||
return new AccumulatedTerminalOperation("stat", resultType.getCanonicalText(), "new " + resultType.getCanonicalText() + "()",
|
||||
"{acc}.accept({item});");
|
||||
return AccumulatedTerminalOperation.summarizing(resultType);
|
||||
}
|
||||
if((name.equals("findFirst") || name.equals("findAny")) && args.length == 0) {
|
||||
return new FindTerminalOperation(resultType.getCanonicalText());
|
||||
@@ -90,11 +100,7 @@ abstract class TerminalOperation extends Operation {
|
||||
}
|
||||
}
|
||||
if(args.length == 1) {
|
||||
PsiType optionalElementType = OptionalUtil.getOptionalElementType(resultType);
|
||||
FunctionHelper fn = FunctionHelper.create(args[0], 2);
|
||||
if(fn != null && optionalElementType != null) {
|
||||
return new ReduceToOptionalTerminalOperation(fn, optionalElementType.getCanonicalText());
|
||||
}
|
||||
return ReduceToOptionalTerminalOperation.create(args[0], resultType);
|
||||
}
|
||||
}
|
||||
if(name.equals("toArray") && args.length < 2) {
|
||||
@@ -120,60 +126,173 @@ 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("max") || name.equals("min")) && args.length < 2) {
|
||||
return MinMaxTerminalOperation.create(args.length == 1 ? args[0] : null, elementType.getCanonicalText(), name.equals("max"));
|
||||
}
|
||||
if(name.equals("collect") && args.length == 1) {
|
||||
if(args[0] instanceof PsiMethodCallExpression) {
|
||||
PsiMethodCallExpression collectorCall = (PsiMethodCallExpression)args[0];
|
||||
PsiExpression[] collectorArgs = collectorCall.getArgumentList().getExpressions();
|
||||
PsiMethod collector = collectorCall.resolveMethod();
|
||||
if(collector == null) return null;
|
||||
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(resultType, CommonClassNames.JAVA_UTIL_ARRAY_LIST, "list");
|
||||
}
|
||||
if(collector.getName().equals("toSet") && collectorArgs.length == 0) {
|
||||
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);
|
||||
if(fn != null) {
|
||||
return new ToCollectionTerminalOperation(fn, resultType);
|
||||
}
|
||||
}
|
||||
if(collector.getName().equals("reducing") && collectorArgs.length == 2) {
|
||||
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 = OptionalUtil.getOptionalElementType(resultType);
|
||||
FunctionHelper fn = FunctionHelper.create(collectorArgs[0], 2);
|
||||
if(fn != null && optionalElementType != null) {
|
||||
return new ReduceToOptionalTerminalOperation(fn, optionalElementType.getCanonicalText());
|
||||
}
|
||||
}
|
||||
if(collector.getName().equals("joining")) {
|
||||
if(collectorArgs.length == 0) {
|
||||
return new AccumulatedTerminalOperation("sb", CommonClassNames.JAVA_LANG_STRING_BUILDER,
|
||||
"new " + CommonClassNames.JAVA_LANG_STRING_BUILDER + "()", "{acc}.append({item});",
|
||||
"{acc}.toString()");
|
||||
}
|
||||
if(collectorArgs.length == 1 || collectorArgs.length == 3) {
|
||||
String initializer = "new java.util.StringJoiner(" + StreamEx.of(collectorArgs).map(PsiElement::getText).joining(",") + ")";
|
||||
return new AccumulatedTerminalOperation("joiner", "java.util.StringJoiner", initializer,
|
||||
"{acc}.add({item});", "{acc}.toString()");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (name.equals("collect")) {
|
||||
if (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);
|
||||
}
|
||||
if (args.length == 1) {
|
||||
return fromCollector(elementType.getCanonicalText(), resultType, args[0]);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Contract("_, _, null -> null")
|
||||
@Nullable
|
||||
private static TerminalOperation fromCollector(@NotNull String elementType, @NotNull PsiType resultType, PsiExpression expr) {
|
||||
if (!(expr instanceof PsiMethodCallExpression)) return null;
|
||||
PsiMethodCallExpression collectorCall = (PsiMethodCallExpression)expr;
|
||||
PsiExpression[] collectorArgs = collectorCall.getArgumentList().getExpressions();
|
||||
PsiMethod collector = collectorCall.resolveMethod();
|
||||
if (collector == null) return null;
|
||||
PsiClass collectorClass = collector.getContainingClass();
|
||||
if (collectorClass != null && CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS.equals(collectorClass.getQualifiedName())) {
|
||||
return fromCollector(elementType, resultType, collector, collectorArgs);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static TerminalOperation fromCollector(@NotNull String elementType,
|
||||
@NotNull PsiType resultType,
|
||||
PsiMethod collector,
|
||||
PsiExpression[] collectorArgs) {
|
||||
String collectorName = collector.getName();
|
||||
FunctionHelper fn;
|
||||
switch (collectorName) {
|
||||
case "toList":
|
||||
if (collectorArgs.length != 0) return null;
|
||||
return AccumulatedTerminalOperation.toList(resultType);
|
||||
case "toSet":
|
||||
if (collectorArgs.length != 0) return null;
|
||||
return AccumulatedTerminalOperation.toCollection(resultType, CommonClassNames.JAVA_UTIL_HASH_SET, "set");
|
||||
case "toCollection":
|
||||
if (collectorArgs.length != 1) return null;
|
||||
fn = FunctionHelper.create(collectorArgs[0], 0);
|
||||
return fn == null ? null : new ToCollectionTerminalOperation(fn);
|
||||
case "toMap": {
|
||||
if (collectorArgs.length < 2 || collectorArgs.length > 4) return null;
|
||||
FunctionHelper key = FunctionHelper.create(collectorArgs[0], 1);
|
||||
FunctionHelper value = FunctionHelper.create(collectorArgs[1], 1);
|
||||
if(key == null || value == null) return null;
|
||||
PsiExpression merger = collectorArgs.length > 2 ? collectorArgs[2] : null;
|
||||
FunctionHelper supplier = collectorArgs.length == 4
|
||||
? FunctionHelper.create(collectorArgs[3], 0)
|
||||
: FunctionHelper.hashMapSupplier(resultType);
|
||||
if(supplier == null) return null;
|
||||
return new ToMapTerminalOperation(key, value, merger, supplier, resultType);
|
||||
}
|
||||
case "reducing":
|
||||
switch (collectorArgs.length) {
|
||||
case 1:
|
||||
return ReduceToOptionalTerminalOperation.create(collectorArgs[0], resultType);
|
||||
case 2:
|
||||
fn = FunctionHelper.create(collectorArgs[1], 2);
|
||||
return fn == null ? null : new ReduceTerminalOperation(collectorArgs[0], fn, resultType.getCanonicalText());
|
||||
case 3:
|
||||
FunctionHelper mapper = FunctionHelper.create(collectorArgs[1], 1);
|
||||
fn = FunctionHelper.create(collectorArgs[2], 2);
|
||||
return fn == null || mapper == null
|
||||
? null
|
||||
: new MappingTerminalOperation(mapper, new ReduceTerminalOperation(collectorArgs[0], fn, resultType.getCanonicalText()));
|
||||
}
|
||||
return null;
|
||||
case "counting":
|
||||
if (collectorArgs.length != 0) return null;
|
||||
return new AccumulatedTerminalOperation("count", "long", "0", "{acc}++;");
|
||||
case "summingInt":
|
||||
case "summingLong":
|
||||
case "summingDouble": {
|
||||
if (collectorArgs.length != 1) return null;
|
||||
fn = FunctionHelper.create(collectorArgs[0], 1);
|
||||
PsiPrimitiveType type = PsiPrimitiveType.getUnboxedType(resultType);
|
||||
return fn == null || type == null ? null : new InlineMappingTerminalOperation(fn, AccumulatedTerminalOperation.summing(type));
|
||||
}
|
||||
case "summarizingInt":
|
||||
case "summarizingLong":
|
||||
case "summarizingDouble": {
|
||||
if (collectorArgs.length != 1) return null;
|
||||
fn = FunctionHelper.create(collectorArgs[0], 1);
|
||||
return fn == null ? null : new InlineMappingTerminalOperation(fn, AccumulatedTerminalOperation.summarizing(resultType));
|
||||
}
|
||||
case "averagingInt":
|
||||
case "averagingLong":
|
||||
case "averagingDouble": {
|
||||
if (collectorArgs.length != 1) return null;
|
||||
fn = FunctionHelper.create(collectorArgs[0], 1);
|
||||
return fn == null
|
||||
? null
|
||||
: new InlineMappingTerminalOperation(fn, new AverageTerminalOperation(collectorName.equals("averagingDouble"), false));
|
||||
}
|
||||
case "mapping": {
|
||||
if (collectorArgs.length != 2) return null;
|
||||
fn = FunctionHelper.create(collectorArgs[0], 1);
|
||||
if (fn == null) return null;
|
||||
TerminalOperation downstreamOp = fromCollector(fn.getResultType(), resultType, collectorArgs[1]);
|
||||
return downstreamOp == null ? null : new MappingTerminalOperation(fn, downstreamOp);
|
||||
}
|
||||
case "groupingBy":
|
||||
case "partitioningBy": {
|
||||
if (collectorArgs.length == 0 || collectorArgs.length > 3
|
||||
|| collectorArgs.length == 3 && collectorName.equals("partitioningBy")) return null;
|
||||
fn = FunctionHelper.create(collectorArgs[0], 1);
|
||||
if (fn == null) return null;
|
||||
if (!(resultType instanceof PsiClassType)) return null;
|
||||
PsiClass aClass = ((PsiClassType)resultType).resolve();
|
||||
if (aClass == null) return null;
|
||||
PsiSubstitutor substitutor = ((PsiClassType)resultType).resolveGenerics().getSubstitutor();
|
||||
PsiClass mapClass =
|
||||
JavaPsiFacade.getInstance(aClass.getProject()).findClass(CommonClassNames.JAVA_UTIL_MAP, aClass.getResolveScope());
|
||||
if (mapClass == null) return null;
|
||||
PsiTypeParameter[] parameters = mapClass.getTypeParameters();
|
||||
if (parameters.length != 2) return null;
|
||||
PsiType resultSubType = substitutor.substitute(parameters[1]);
|
||||
if (resultSubType == null) return null;
|
||||
CollectorOperation downstreamCollector;
|
||||
if (collectorArgs.length == 1) {
|
||||
downstreamCollector = AccumulatedTerminalOperation.toList(resultSubType).asCollector();
|
||||
}
|
||||
else {
|
||||
PsiExpression downstream = collectorArgs[collectorArgs.length - 1];
|
||||
TerminalOperation downstreamOp = fromCollector(elementType, resultSubType, downstream);
|
||||
if (downstreamOp == null) return null;
|
||||
downstreamCollector = downstreamOp.asCollector();
|
||||
}
|
||||
if (downstreamCollector == null) return null;
|
||||
if (collectorName.equals("partitioningBy")) {
|
||||
return new PartitionByTerminalOperation(fn, resultType, downstreamCollector);
|
||||
}
|
||||
FunctionHelper supplier = collectorArgs.length == 3
|
||||
? FunctionHelper.create(collectorArgs[1], 0)
|
||||
: FunctionHelper.hashMapSupplier(resultType);
|
||||
return new GroupByTerminalOperation(fn, supplier, resultType, downstreamCollector);
|
||||
}
|
||||
case "minBy":
|
||||
case "maxBy":
|
||||
if (collectorArgs.length != 1) return null;
|
||||
return MinMaxTerminalOperation.create(collectorArgs[0], elementType, collectorName.equals("maxBy"));
|
||||
case "joining":
|
||||
switch (collectorArgs.length) {
|
||||
case 0:
|
||||
return new AccumulatedTerminalOperation("sb", CommonClassNames.JAVA_LANG_STRING_BUILDER,
|
||||
"new " + CommonClassNames.JAVA_LANG_STRING_BUILDER + "()",
|
||||
"{acc}.append({item});",
|
||||
"{acc}.toString()");
|
||||
case 1:
|
||||
case 3:
|
||||
String initializer =
|
||||
"new java.util.StringJoiner(" + StreamEx.of(collectorArgs).map(PsiElement::getText).joining(",") + ")";
|
||||
return new AccumulatedTerminalOperation("joiner", "java.util.StringJoiner", initializer,
|
||||
"{acc}.add({item});", "{acc}.toString()");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -190,7 +309,7 @@ abstract class TerminalOperation extends Operation {
|
||||
}
|
||||
|
||||
@Override
|
||||
void registerUsedNames(Consumer<String> usedNameConsumer) {
|
||||
public void registerUsedNames(Consumer<String> usedNameConsumer) {
|
||||
FunctionHelper.processUsedNames(myIdentity, usedNameConsumer);
|
||||
myUpdater.registerUsedNames(usedNameConsumer);
|
||||
}
|
||||
@@ -213,7 +332,7 @@ abstract class TerminalOperation extends Operation {
|
||||
}
|
||||
|
||||
@Override
|
||||
void registerUsedNames(Consumer<String> usedNameConsumer) {
|
||||
public void registerUsedNames(Consumer<String> usedNameConsumer) {
|
||||
myUpdater.registerUsedNames(usedNameConsumer);
|
||||
}
|
||||
|
||||
@@ -222,8 +341,7 @@ abstract class TerminalOperation extends Operation {
|
||||
String seen = context.declare("seen", "boolean", "false");
|
||||
String accumulator = context.declareResult("acc", myType, TypeConversionUtil.isPrimitive(myType) ? "0" : "null");
|
||||
myUpdater.transform(context, accumulator, inVar.getName());
|
||||
String optionalClass = OptionalUtil.getOptionalClass(myType);
|
||||
context.setFinisher("(" + seen + "?" + optionalClass + ".of(" + accumulator + "):" + optionalClass + ".empty())");
|
||||
context.setOptionalUnwrapperFinisher(seen, accumulator, myType);
|
||||
return "if(!" + seen + ") {\n" +
|
||||
seen + "=true;\n" +
|
||||
accumulator + "=" + inVar + ";\n" +
|
||||
@@ -231,21 +349,29 @@ abstract class TerminalOperation extends Operation {
|
||||
accumulator + "=" + myUpdater.getText() + ";\n" +
|
||||
"}\n";
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static ReduceToOptionalTerminalOperation create(PsiExpression arg, PsiType resultType) {
|
||||
PsiType optionalElementType = OptionalUtil.getOptionalElementType(resultType);
|
||||
FunctionHelper fn = FunctionHelper.create(arg, 2);
|
||||
if(fn != null && optionalElementType != null) {
|
||||
return new ReduceToOptionalTerminalOperation(fn, optionalElementType.getCanonicalText());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
public ExplicitCollectTerminalOperation(FunctionHelper supplier, FunctionHelper accumulator) {
|
||||
mySupplier = supplier;
|
||||
myAccumulator = accumulator;
|
||||
myResultType = resultType;
|
||||
}
|
||||
|
||||
@Override
|
||||
void registerUsedNames(Consumer<String> usedNameConsumer) {
|
||||
public void registerUsedNames(Consumer<String> usedNameConsumer) {
|
||||
mySupplier.registerUsedNames(usedNameConsumer);
|
||||
myAccumulator.registerUsedNames(usedNameConsumer);
|
||||
}
|
||||
@@ -258,26 +384,32 @@ abstract class TerminalOperation extends Operation {
|
||||
@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());
|
||||
String candidate = mySupplier.suggestFinalOutputNames(context, myAccumulator.getParameterName(0), "acc").get(0);
|
||||
String acc = context.declareResult(candidate, mySupplier.getResultType(), mySupplier.getText());
|
||||
myAccumulator.transform(context, acc, inVar.getName());
|
||||
return myAccumulator.getText()+";\n";
|
||||
}
|
||||
}
|
||||
|
||||
static class AverageTerminalOperation extends TerminalOperation {
|
||||
private boolean myDoubleAccumulator;
|
||||
private final boolean myDoubleAccumulator;
|
||||
private final boolean myUseOptional;
|
||||
|
||||
public AverageTerminalOperation(boolean doubleAccumulator) {
|
||||
public AverageTerminalOperation(boolean doubleAccumulator, boolean useOptional) {
|
||||
myDoubleAccumulator = doubleAccumulator;
|
||||
myUseOptional = useOptional;
|
||||
}
|
||||
|
||||
@Override
|
||||
String generate(StreamVariable inVar, StreamToLoopReplacementContext context) {
|
||||
String sum = context.declareResult("sum", myDoubleAccumulator ? "double" : "long", "0");
|
||||
String count = context.declare("count", "long", "0");
|
||||
context.setFinisher("("+count+"==0?java.util.OptionalDouble.empty():"
|
||||
+"java.util.OptionalDouble.of("+(myDoubleAccumulator?"":"(double)")+sum+"/"+count+"))");
|
||||
String emptyCheck = count + "==0";
|
||||
String result = (myDoubleAccumulator ? "" : "(double)") + sum + "/" + count;
|
||||
context.setFinisher(myUseOptional
|
||||
? "(" + emptyCheck + "?java.util.OptionalDouble.empty():"
|
||||
+ "java.util.OptionalDouble.of(" + result + "))"
|
||||
: "(" + emptyCheck + "?0.0:" + result + ")");
|
||||
return sum + "+=" + inVar + ";\n" + count + "++;\n";
|
||||
}
|
||||
}
|
||||
@@ -343,7 +475,7 @@ abstract class TerminalOperation extends Operation {
|
||||
}
|
||||
|
||||
@Override
|
||||
void registerUsedNames(Consumer<String> usedNameConsumer) {
|
||||
public void registerUsedNames(Consumer<String> usedNameConsumer) {
|
||||
myFn.registerUsedNames(usedNameConsumer);
|
||||
}
|
||||
|
||||
@@ -369,7 +501,56 @@ abstract class TerminalOperation extends Operation {
|
||||
}
|
||||
}
|
||||
|
||||
static class AccumulatedTerminalOperation extends TerminalOperation {
|
||||
interface CollectorOperation {
|
||||
// Non-trivial finishers are not supported
|
||||
default void transform(StreamToLoopReplacementContext context, String item) {}
|
||||
default void suggestNames(StreamVariable inVar, StreamVariable outVar) {}
|
||||
default void registerUsedNames(Consumer<String> usedNameConsumer) {}
|
||||
String getSupplier();
|
||||
String getAccumulator(String acc, String item);
|
||||
}
|
||||
|
||||
abstract static class CollectorBasedTerminalOperation extends TerminalOperation implements CollectorOperation {
|
||||
final String myType;
|
||||
final Function<StreamToLoopReplacementContext, String> myAccNameSupplier;
|
||||
final FunctionHelper mySupplier;
|
||||
|
||||
CollectorBasedTerminalOperation(String type, Function<StreamToLoopReplacementContext, String> accNameSupplier,
|
||||
FunctionHelper accSupplier) {
|
||||
myType = type;
|
||||
myAccNameSupplier = accNameSupplier;
|
||||
mySupplier = accSupplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
String generate(StreamVariable inVar, StreamToLoopReplacementContext context) {
|
||||
transform(context, inVar.getName());
|
||||
String acc = context.declareResult(myAccNameSupplier.apply(context), myType, getSupplier());
|
||||
return getAccumulator(acc, inVar.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
CollectorOperation asCollector() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerUsedNames(Consumer<String> usedNameConsumer) {
|
||||
mySupplier.registerUsedNames(usedNameConsumer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transform(StreamToLoopReplacementContext context, String item) {
|
||||
mySupplier.transform(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSupplier() {
|
||||
return mySupplier.getText();
|
||||
}
|
||||
}
|
||||
|
||||
static class AccumulatedTerminalOperation extends TerminalOperation implements CollectorOperation {
|
||||
private String myAccName;
|
||||
private String myAccType;
|
||||
private String myAccInitializer;
|
||||
@@ -404,32 +585,326 @@ abstract class TerminalOperation extends Operation {
|
||||
return myUpdateTemplate.replace("{item}", inVar.getName()).replace("{acc}", varName);
|
||||
}
|
||||
|
||||
public static AccumulatedTerminalOperation toCollection(PsiType collectionType, String implementationType, String varName) {
|
||||
return new AccumulatedTerminalOperation(varName, collectionType.getCanonicalText(), "new " + implementationType + "<>()",
|
||||
"{acc}.add({item});");
|
||||
}
|
||||
}
|
||||
|
||||
static class ToCollectionTerminalOperation extends TerminalOperation {
|
||||
private final String myType;
|
||||
private final FunctionHelper myFn;
|
||||
|
||||
public ToCollectionTerminalOperation(FunctionHelper fn, PsiType callType) {
|
||||
myFn = fn;
|
||||
myType = callType.getCanonicalText();
|
||||
@Override
|
||||
CollectorOperation asCollector() {
|
||||
return myFinisherTemplate.equals("{acc}") && PsiTypesUtil.boxIfPossible(myAccType).equals(myAccType) ? this : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
void registerUsedNames(Consumer<String> usedNameConsumer) {
|
||||
myFn.registerUsedNames(usedNameConsumer);
|
||||
public String getSupplier() {
|
||||
return myAccInitializer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAccumulator(String acc, String item) {
|
||||
return myUpdateTemplate.replace("{acc}", acc).replace("{item}", item);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
static AccumulatedTerminalOperation toCollection(PsiType collectionType, String implementationType, String varName) {
|
||||
return new AccumulatedTerminalOperation(varName, collectionType.getCanonicalText(), "new " + implementationType + "<>()",
|
||||
"{acc}.add({item});");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static AccumulatedTerminalOperation toList(@NotNull PsiType resultType) {
|
||||
return toCollection(resultType, CommonClassNames.JAVA_UTIL_ARRAY_LIST, "list");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
static AccumulatedTerminalOperation summing(PsiType type) {
|
||||
return new AccumulatedTerminalOperation("sum", type.getCanonicalText(), "0", "{acc}+={item};");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
static AccumulatedTerminalOperation summarizing(@NotNull PsiType resultType) {
|
||||
return new AccumulatedTerminalOperation("stat", resultType.getCanonicalText(), "new " + resultType.getCanonicalText() + "()",
|
||||
"{acc}.accept({item});");
|
||||
}
|
||||
}
|
||||
|
||||
static class ToCollectionTerminalOperation extends CollectorBasedTerminalOperation {
|
||||
public ToCollectionTerminalOperation(FunctionHelper fn) {
|
||||
super(fn.getResultType(), context -> fn.suggestFinalOutputNames(context, null, "collection").get(0), fn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAccumulator(String acc, String item) {
|
||||
return acc+".add("+item+");\n";
|
||||
}
|
||||
}
|
||||
|
||||
static class MinMaxTerminalOperation extends TerminalOperation {
|
||||
private String myType;
|
||||
private String myTemplate;
|
||||
private String myComparatorType;
|
||||
private @Nullable PsiExpression myComparator;
|
||||
|
||||
public MinMaxTerminalOperation(String type, String template, @Nullable PsiExpression comparator) {
|
||||
myType = type;
|
||||
myTemplate = template;
|
||||
myComparator = comparator;
|
||||
if(comparator != null) {
|
||||
PsiType comparatorType = comparator.getType();
|
||||
if(comparatorType != null) {
|
||||
myComparatorType = comparatorType.getCanonicalText();
|
||||
} else {
|
||||
myComparatorType = CommonClassNames.JAVA_UTIL_COMPARATOR+"<"+myType+">";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerUsedNames(Consumer<String> usedNameConsumer) {
|
||||
if(myComparator != null) {
|
||||
FunctionHelper.processUsedNames(myComparator, usedNameConsumer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
String generate(StreamVariable inVar, StreamToLoopReplacementContext context) {
|
||||
// TODO: remove redundant type arguments
|
||||
myFn.transform(context);
|
||||
String collection = context.declareResult("collection", myType, myFn.getText());
|
||||
return collection+".add("+inVar+");\n";
|
||||
String comparator = "";
|
||||
if(myComparator != null) {
|
||||
if(ExpressionUtils.isSimpleExpression(myComparator)) {
|
||||
comparator = myComparator.getText();
|
||||
} else {
|
||||
comparator = context.declare("comparator", myComparatorType, myComparator.getText());
|
||||
}
|
||||
}
|
||||
String seen = context.declare("seen", "boolean", "false");
|
||||
String best = context.declareResult("best", myType, TypeConversionUtil.isPrimitive(myType) ? "0" : "null");
|
||||
String type = myType;
|
||||
context.setOptionalUnwrapperFinisher(seen, best, type);
|
||||
return "if(!"+seen+" || "+myTemplate.replace("{best}", best).replace("{item}", inVar.getName()).replace("{comparator}", comparator)+") {\n" +
|
||||
seen+"=true;\n"+
|
||||
best+"="+inVar+";\n}\n";
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static MinMaxTerminalOperation create(@Nullable PsiExpression comparator, String elementType, boolean max) {
|
||||
String sign = max ? ">" : "<";
|
||||
if(comparator == null) {
|
||||
if ("int".equals(elementType) || "long".equals(elementType)) {
|
||||
return new MinMaxTerminalOperation(elementType, "{item}" + sign + "{best}", null);
|
||||
}
|
||||
if ("double".equals(elementType)) {
|
||||
return new MinMaxTerminalOperation(elementType, "java.lang.Double.compare({item},{best})" + sign + "0", null);
|
||||
}
|
||||
} else if(InheritanceUtil.isInheritor(PsiUtil.resolveClassInClassTypeOnly(comparator.getType()), false,
|
||||
CommonClassNames.JAVA_UTIL_COMPARATOR)) {
|
||||
return new MinMaxTerminalOperation(elementType, "{comparator}.compare({item},{best})" + sign + "0", comparator);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static class ToMapTerminalOperation extends CollectorBasedTerminalOperation {
|
||||
private final FunctionHelper myKeyExtractor, myValueExtractor;
|
||||
private final PsiExpression myMerger;
|
||||
|
||||
ToMapTerminalOperation(FunctionHelper keyExtractor,
|
||||
FunctionHelper valueExtractor,
|
||||
PsiExpression merger,
|
||||
FunctionHelper supplier,
|
||||
PsiType resultType) {
|
||||
super(resultType.getCanonicalText(), context -> "map", supplier);
|
||||
myKeyExtractor = keyExtractor;
|
||||
myValueExtractor = valueExtractor;
|
||||
myMerger = merger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerUsedNames(Consumer<String> usedNameConsumer) {
|
||||
super.registerUsedNames(usedNameConsumer);
|
||||
myKeyExtractor.registerUsedNames(usedNameConsumer);
|
||||
myValueExtractor.registerUsedNames(usedNameConsumer);
|
||||
if(myMerger != null) FunctionHelper.processUsedNames(myMerger, usedNameConsumer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void suggestNames(StreamVariable inVar, StreamVariable outVar) {
|
||||
myKeyExtractor.suggestVariableName(inVar, 0);
|
||||
myValueExtractor.suggestVariableName(inVar, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transform(StreamToLoopReplacementContext context, String item) {
|
||||
super.transform(context, item);
|
||||
myKeyExtractor.transform(context, item);
|
||||
myValueExtractor.transform(context, item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAccumulator(String map, String item) {
|
||||
if(myMerger == null) {
|
||||
return "if("+map+".put("+myKeyExtractor.getText()+","+myValueExtractor.getText()+")!=null) {\n"+
|
||||
"throw new java.lang.IllegalStateException(\"Duplicate key\");\n}\n";
|
||||
}
|
||||
return map+".merge("+myKeyExtractor.getText()+","+myValueExtractor.getText()+","+myMerger.getText()+");\n";
|
||||
}
|
||||
}
|
||||
|
||||
static class GroupByTerminalOperation extends CollectorBasedTerminalOperation {
|
||||
private final CollectorOperation myCollector;
|
||||
private FunctionHelper myKeyExtractor;
|
||||
private String myKeyVar;
|
||||
|
||||
public GroupByTerminalOperation(FunctionHelper keyExtractor, FunctionHelper supplier, PsiType resultType, CollectorOperation collector) {
|
||||
super(resultType.getCanonicalText(), context -> "map", supplier);
|
||||
myKeyExtractor = keyExtractor;
|
||||
myCollector = collector;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerUsedNames(Consumer<String> usedNameConsumer) {
|
||||
super.registerUsedNames(usedNameConsumer);
|
||||
myKeyExtractor.registerUsedNames(usedNameConsumer);
|
||||
myCollector.registerUsedNames(usedNameConsumer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void suggestNames(StreamVariable inVar, StreamVariable outVar) {
|
||||
myKeyExtractor.suggestVariableName(inVar, 0);
|
||||
myCollector.suggestNames(inVar, outVar);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transform(StreamToLoopReplacementContext context, String item) {
|
||||
super.transform(context, item);
|
||||
myKeyExtractor.transform(context, item);
|
||||
myCollector.transform(context, item);
|
||||
myKeyVar = context.registerVarName(Arrays.asList("k", "key"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAccumulator(String map, String item) {
|
||||
String acc = map+".computeIfAbsent("+myKeyExtractor.getText()+","+myKeyVar+"->"+myCollector.getSupplier()+")";
|
||||
return myCollector.getAccumulator(acc, item);
|
||||
}
|
||||
}
|
||||
|
||||
static class PartitionByTerminalOperation extends TerminalOperation {
|
||||
private final String myResultType;
|
||||
private final CollectorOperation myCollector;
|
||||
private FunctionHelper myPredicate;
|
||||
|
||||
public PartitionByTerminalOperation(FunctionHelper predicate, PsiType resultType, CollectorOperation collector) {
|
||||
myPredicate = predicate;
|
||||
myResultType = resultType.getCanonicalText();
|
||||
myCollector = collector;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerUsedNames(Consumer<String> usedNameConsumer) {
|
||||
myPredicate.registerUsedNames(usedNameConsumer);
|
||||
myCollector.registerUsedNames(usedNameConsumer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void suggestNames(StreamVariable inVar, StreamVariable outVar) {
|
||||
myPredicate.suggestVariableName(inVar, 0);
|
||||
myCollector.suggestNames(inVar, outVar);
|
||||
}
|
||||
|
||||
@Override
|
||||
String generate(StreamVariable inVar, StreamToLoopReplacementContext context) {
|
||||
String map = context.declareResult("map", myResultType, "new java.util.HashMap<>()");
|
||||
myPredicate.transform(context, inVar.getName());
|
||||
myCollector.transform(context, inVar.getName());
|
||||
context.addInitStep(map+".put(false, "+myCollector.getSupplier()+");");
|
||||
context.addInitStep(map+".put(true, "+myCollector.getSupplier()+");");
|
||||
return myCollector.getAccumulator(map + ".get(" + myPredicate.getText() + ")", inVar.getName());
|
||||
}
|
||||
}
|
||||
|
||||
abstract static class AbstractMappingTerminalOperation extends TerminalOperation implements CollectorOperation {
|
||||
final FunctionHelper myMapper;
|
||||
final TerminalOperation myDownstream;
|
||||
final CollectorOperation myDownstreamCollector;
|
||||
|
||||
AbstractMappingTerminalOperation(FunctionHelper mapper, TerminalOperation downstream) {
|
||||
myMapper = mapper;
|
||||
myDownstream = downstream;
|
||||
myDownstreamCollector = downstream.asCollector();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerUsedNames(Consumer<String> usedNameConsumer) {
|
||||
myMapper.registerUsedNames(usedNameConsumer);
|
||||
myDownstream.registerUsedNames(usedNameConsumer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void suggestNames(StreamVariable inVar, StreamVariable outVar) {
|
||||
myMapper.suggestVariableName(inVar, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
CollectorOperation asCollector() {
|
||||
return myDownstreamCollector == null ? null : this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSupplier() {
|
||||
return myDownstreamCollector.getSupplier();
|
||||
}
|
||||
}
|
||||
|
||||
static class MappingTerminalOperation extends AbstractMappingTerminalOperation {
|
||||
private StreamVariable myVariable;
|
||||
|
||||
MappingTerminalOperation(FunctionHelper mapper, TerminalOperation downstream) {
|
||||
super(mapper, downstream);
|
||||
}
|
||||
|
||||
@Override
|
||||
String generate(StreamVariable inVar, StreamToLoopReplacementContext context) {
|
||||
createVariable(context, inVar.getName());
|
||||
return myVariable.getDeclaration() + "=" + myMapper.getText() + ";\n" + myDownstream.generate(myVariable, context);
|
||||
}
|
||||
|
||||
private void createVariable(StreamToLoopReplacementContext context, String item) {
|
||||
myMapper.transform(context, item);
|
||||
myVariable = new StreamVariable(myMapper.getResultType());
|
||||
myDownstream.suggestNames(myVariable, StreamVariable.STUB);
|
||||
myMapper.suggestFinalOutputNames(context, null, null).forEach(myVariable::addOtherNameCandidate);
|
||||
myVariable.register(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transform(StreamToLoopReplacementContext context, String item) {
|
||||
createVariable(context, item);
|
||||
myDownstreamCollector.transform(context, myVariable.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAccumulator(String acc, String item) {
|
||||
return myVariable.getDeclaration() + "=" + myMapper.getText() + ";\n" +
|
||||
myDownstreamCollector.getAccumulator(acc, myVariable.getName());
|
||||
}
|
||||
}
|
||||
|
||||
static class InlineMappingTerminalOperation extends AbstractMappingTerminalOperation {
|
||||
InlineMappingTerminalOperation(FunctionHelper mapper, TerminalOperation downstream) {
|
||||
super(mapper, downstream);
|
||||
}
|
||||
|
||||
@Override
|
||||
String generate(StreamVariable inVar, StreamToLoopReplacementContext context) {
|
||||
myMapper.transform(context, inVar.getName());
|
||||
StreamVariable updatedVar = new StreamVariable(myMapper.getResultType(), myMapper.getText());
|
||||
return myDownstream.generate(updatedVar, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transform(StreamToLoopReplacementContext context, String item) {
|
||||
myMapper.transform(context, item);
|
||||
myDownstreamCollector.transform(context, myMapper.getText());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAccumulator(String acc, String item) {
|
||||
return myDownstreamCollector.getAccumulator(acc, myMapper.getText());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -446,7 +921,7 @@ abstract class TerminalOperation extends Operation {
|
||||
}
|
||||
|
||||
@Override
|
||||
void registerUsedNames(Consumer<String> usedNameConsumer) {
|
||||
public void registerUsedNames(Consumer<String> usedNameConsumer) {
|
||||
myFn.registerUsedNames(usedNameConsumer);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user