Files
openide/java/java-analysis-impl/src/com/intellij/codeInspection/StreamApiMigrationInspection.java
T

1273 lines
57 KiB
Java

/*
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.codeInspection;
import com.intellij.codeHighlighting.HighlightDisplayLevel;
import com.intellij.codeInsight.ExceptionUtil;
import com.intellij.codeInsight.FileModificationService;
import com.intellij.codeInsight.daemon.GroupNames;
import com.intellij.codeInsight.daemon.HighlightDisplayKey;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil;
import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.codeStyle.SuggestedNameInfo;
import com.intellij.psi.codeStyle.VariableKind;
import com.intellij.psi.controlFlow.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.*;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.IntArrayList;
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.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.*;
/**
* User: anna
*/
public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTool {
private static final Logger LOG = Logger.getInstance("#" + StreamApiMigrationInspection.class.getName());
public boolean REPLACE_TRIVIAL_FOREACH;
private HighlightDisplayKey myKey;
@Nullable
@Override
public JComponent createOptionsPanel() {
return new SingleCheckboxOptionsPanel(
"Replace trivial foreach statements",
this,
"REPLACE_TRIVIAL_FOREACH"
);
}
@Nls
@NotNull
@Override
public String getGroupDisplayName() {
return GroupNames.LANGUAGE_LEVEL_SPECIFIC_GROUP_NAME;
}
@Nls
@NotNull
@Override
public String getDisplayName() {
return "foreach loop can be collapsed with Stream API";
}
@Override
public boolean isEnabledByDefault() {
return true;
}
@NotNull
@Override
public String getShortName() {
return "Convert2streamapi";
}
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new JavaElementVisitor() {
@Override
public void visitForeachStatement(PsiForeachStatement statement) {
super.visitForeachStatement(statement);
if (PsiUtil.getLanguageLevel(statement).isAtLeast(LanguageLevel.JDK_1_8)) {
final PsiExpression iteratedValue = statement.getIteratedValue();
final PsiStatement body = statement.getBody();
if (iteratedValue != null && body != null) {
final PsiType iteratedValueType = iteratedValue.getType();
final PsiClass iteratorClass = PsiUtil.resolveClassInClassTypeOnly(iteratedValueType);
PsiClass collectionClass = null;
final boolean isArray;
if(iteratedValueType instanceof PsiArrayType) {
// Do not handle primitive types now
if(((PsiArrayType)iteratedValueType).getComponentType() instanceof PsiPrimitiveType) return;
isArray = true;
} else {
collectionClass = JavaPsiFacade.getInstance(body.getProject()).findClass(CommonClassNames.JAVA_UTIL_COLLECTION, statement.getResolveScope());
if (collectionClass != null && InheritanceUtil.isInheritorOrSelf(iteratorClass, collectionClass, true)) {
isArray = false;
} else return;
}
try {
if (ExceptionUtil.getThrownCheckedExceptions(new PsiElement[]{body}).isEmpty()) {
TerminalBlock tb = TerminalBlock.from(statement.getIterationParameter(), body);
List<Operation> operations = tb.extractOperations();
if(tb.isEmpty()) return;
final ControlFlow controlFlow = ControlFlowFactory.getInstance(holder.getProject())
.getControlFlow(body, LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance());
final Collection<PsiStatement> exitPoints = ControlFlowUtil
.findExitPointsAndStatements(controlFlow, tb.getStartOffset(controlFlow), tb.getEndOffset(controlFlow),
new IntArrayList(), PsiContinueStatement.class,
PsiBreakStatement.class, PsiReturnStatement.class, PsiThrowStatement.class);
int startOffset = controlFlow.getStartOffset(body);
int endOffset = controlFlow.getEndOffset(body);
final List<PsiVariable> nonFinalVariables = StreamEx
.of(ControlFlowUtil.getUsedVariables(controlFlow, startOffset, endOffset))
.remove(variable -> HighlightControlFlowUtil.isEffectivelyFinal(variable, body, null))
.toList();
if (exitPoints.isEmpty()) {
if(getIncrementedVariable(tb, operations, nonFinalVariables) != null) {
registerProblem(holder, statement, "count", new ReplaceWithCountFix());
}
if(getAccumulatedVariable(tb, operations, nonFinalVariables) != null) {
registerProblem(holder, statement, "sum", new ReplaceWithSumFix());
}
if(!nonFinalVariables.isEmpty()) {
return;
}
if ((isArray || !isRawSubstitution(iteratedValueType, collectionClass)) && isCollectCall(tb, operations)) {
boolean addAll = operations.isEmpty() && isAddAllCall(tb);
String methodName;
if(addAll) {
methodName = "addAll";
} else {
PsiMethodCallExpression methodCallExpression = tb.getSingleMethodCall();
if(methodCallExpression != null && extractReplaceableCollectionInitializer(
methodCallExpression.getMethodExpression().getQualifierExpression(), statement) != null) {
methodName = "collect";
} else {
methodName = "forEach";
}
}
registerProblem(holder, statement, methodName, new ReplaceWithCollectFix(methodName));
}
// do not replace for(T e : arr) {} with Arrays.stream(arr).forEach(e -> {}) even if flag is set
else if (!operations.isEmpty() ||
(!isArray && (REPLACE_TRIVIAL_FOREACH || !isTrivial(body, statement.getIterationParameter())))) {
final List<LocalQuickFix> fixes = new ArrayList<>();
fixes.add(new ReplaceWithForeachCallFix("forEach"));
if (!operations.isEmpty()) {
//for .stream()
fixes.add(new ReplaceWithForeachCallFix("forEachOrdered"));
}
registerProblem(holder, statement, "forEach", fixes.toArray(new LocalQuickFix[fixes.size()]));
}
} else {
if(nonFinalVariables.isEmpty() && tb.getSingleStatement() instanceof PsiReturnStatement) {
PsiReturnStatement returnStatement = (PsiReturnStatement)tb.getSingleStatement();
PsiExpression value = returnStatement.getReturnValue();
if(isLiteral(value, Boolean.TRUE) || isLiteral(value, Boolean.FALSE)) {
boolean foundResult = (boolean)((PsiLiteralExpression)value).getValue();
PsiElement nextStatement = PsiTreeUtil.skipSiblingsForward(statement, PsiWhiteSpace.class, PsiComment.class);
if(nextStatement instanceof PsiReturnStatement) {
PsiReturnStatement nextReturnStatement = (PsiReturnStatement)nextStatement;
if(isLiteral(nextReturnStatement.getReturnValue(), !foundResult)) {
String methodName;
if (foundResult) {
methodName = "anyMatch";
}
else {
methodName = "noneMatch";
if(!operations.isEmpty()) {
Operation lastOp = operations.get(operations.size() - 1);
if(lastOp instanceof FilterOp && BoolUtils.isNegation(lastOp.getExpression())) {
methodName = "allMatch";
}
}
}
registerProblem(holder, statement, methodName, new ReplaceWithMatchFix(methodName));
}
}
}
}
}
}
}
catch (AnalysisCanceledException ignored) {
}
}
}
}
private boolean isRawSubstitution(PsiType iteratedValueType, PsiClass collectionClass) {
return iteratedValueType instanceof PsiClassType && PsiUtil
.isRawSubstitutor(collectionClass, TypeConversionUtil.getSuperClassSubstitutor(collectionClass, (PsiClassType)iteratedValueType));
}
};
}
@NotNull
private TextRange getRange(PsiForeachStatement statement) {
if(myKey == null) {
myKey = HighlightDisplayKey.find(getShortName());
}
boolean wholeStatement = false;
if(myKey != null) {
InspectionProfile profile = InspectionProjectProfileManager.getInstance(statement.getProject()).getCurrentProfile();
HighlightDisplayLevel level = profile.getErrorLevel(myKey, statement);
wholeStatement = HighlightDisplayLevel.DO_NOT_SHOW.equals(level);
}
PsiExpression iteratedValue = statement.getIteratedValue();
LOG.assertTrue(iteratedValue != null);
PsiJavaToken rParenth = statement.getRParenth();
if(wholeStatement && rParenth != null) {
return new TextRange(statement.getTextOffset(), rParenth.getTextOffset() + 1);
}
return iteratedValue.getTextRange();
}
private void registerProblem(ProblemsHolder holder, PsiForeachStatement statement, String methodName, LocalQuickFix... fixes) {
PsiExpression iteratedValue = statement.getIteratedValue();
LOG.assertTrue(iteratedValue != null);
holder.registerProblem(statement, getRange(statement).shiftRight(-statement.getTextOffset()),
"Can be replaced with '" + methodName + "' call", fixes);
}
@Contract("null, _ -> false")
private static boolean isLiteral(PsiElement element, Object value) {
return element instanceof PsiLiteralExpression && value.equals(((PsiLiteralExpression)element).getValue());
}
@Contract("null, null -> true; null, !null -> false")
private static boolean sameReference(PsiExpression expr1, PsiExpression expr2) {
if(expr1 == null && expr2 == null) return true;
if (!(expr1 instanceof PsiReferenceExpression) || !(expr2 instanceof PsiReferenceExpression)) return false;
PsiReferenceExpression ref1 = (PsiReferenceExpression)expr1;
PsiReferenceExpression ref2 = (PsiReferenceExpression)expr2;
return Objects.equals(ref1.getReferenceName(), ref2.getReferenceName()) && sameReference(ref1.getQualifierExpression(),
ref2.getQualifierExpression());
}
@Nullable
private static PsiExpression extractAddend(PsiAssignmentExpression assignment) {
if(JavaTokenType.PLUSEQ.equals(assignment.getOperationTokenType())) {
return assignment.getRExpression();
} else if(JavaTokenType.EQ.equals(assignment.getOperationTokenType())) {
if (assignment.getRExpression() instanceof PsiBinaryExpression) {
PsiBinaryExpression binOp = (PsiBinaryExpression)assignment.getRExpression();
if(JavaTokenType.PLUS.equals(binOp.getOperationTokenType())) {
if(sameReference(binOp.getLOperand(), assignment.getLExpression())) {
return binOp.getROperand();
}
if(sameReference(binOp.getROperand(), assignment.getLExpression())) {
return binOp.getLOperand();
}
}
}
}
return null;
}
@Nullable
private static PsiLocalVariable extractAccumulator(PsiAssignmentExpression assignment) {
if(!(assignment.getLExpression() instanceof PsiReferenceExpression)) return null;
PsiReferenceExpression lExpr = (PsiReferenceExpression)assignment.getLExpression();
PsiElement accumulator = lExpr.resolve();
if(!(accumulator instanceof PsiLocalVariable)) return null;
PsiLocalVariable var = (PsiLocalVariable)accumulator;
if(JavaTokenType.PLUSEQ.equals(assignment.getOperationTokenType())) {
return var;
} else if(JavaTokenType.EQ.equals(assignment.getOperationTokenType())) {
if (assignment.getRExpression() instanceof PsiBinaryExpression) {
PsiBinaryExpression binOp = (PsiBinaryExpression)assignment.getRExpression();
if(JavaTokenType.PLUS.equals(binOp.getOperationTokenType())) {
PsiExpression left = binOp.getLOperand();
PsiExpression right = binOp.getROperand();
if (sameReference(left, lExpr) || sameReference(right, lExpr)) {
return var;
}
}
}
}
return null;
}
@Contract("null -> null")
private static PsiExpression extractIncrementedLValue(PsiExpression expression) {
if(expression instanceof PsiPostfixExpression) {
if(JavaTokenType.PLUSPLUS.equals(((PsiPostfixExpression)expression).getOperationTokenType())) {
return ((PsiPostfixExpression)expression).getOperand();
}
} else if(expression instanceof PsiPrefixExpression) {
if(JavaTokenType.PLUSPLUS.equals(((PsiPrefixExpression)expression).getOperationTokenType())) {
return ((PsiPrefixExpression)expression).getOperand();
}
} else if(expression instanceof PsiAssignmentExpression) {
PsiAssignmentExpression assignment = (PsiAssignmentExpression)expression;
if(isLiteral(extractAddend(assignment), 1)) {
return assignment.getLExpression();
}
}
return null;
}
@Nullable
private static PsiLocalVariable getIncrementedVariable(TerminalBlock tb,
List<Operation> operations,
List<PsiVariable> variables) {
// have only one non-final variable
if(variables.size() != 1) return null;
// have single expression which is either ++x or x++ or x+=1 or x=x+1
PsiExpression operand = extractIncrementedLValue(tb.getSingleExpression(PsiExpression.class));
if(!(operand instanceof PsiReferenceExpression)) return null;
PsiElement element = ((PsiReferenceExpression)operand).resolve();
// the referred variable is the same as non-final variable
if(!(element instanceof PsiLocalVariable) || !variables.contains(element)) return null;
// the referred variable is not used in intermediate operations
for(Operation operation : operations) {
if(ReferencesSearch.search(element, new LocalSearchScope(operation.getExpression())).findFirst() != null) return null;
}
return (PsiLocalVariable)element;
}
@Nullable
private static PsiLocalVariable getAccumulatedVariable(TerminalBlock tb,
List<Operation> operations,
List<PsiVariable> variables) {
// have only one non-final variable
if(variables.size() != 1) return null;
PsiAssignmentExpression assignment = tb.getSingleExpression(PsiAssignmentExpression.class);
if(assignment == null) return null;
PsiLocalVariable var = extractAccumulator(assignment);
// the referred variable is the same as non-final variable
if(var == null || !variables.contains(var)) return null;
if (!(var.getType() instanceof PsiPrimitiveType) || var.getType().equalsToText("float")) return null;
// the referred variable is not used in intermediate operations
for(Operation operation : operations) {
if(ReferencesSearch.search(var, new LocalSearchScope(operation.getExpression())).findFirst() != null) return null;
}
PsiExpression addend = extractAddend(assignment);
LOG.assertTrue(addend != null);
if(ReferencesSearch.search(var, new LocalSearchScope(addend)).findFirst() != null) return null;
return var;
}
private static boolean isAddAllCall(TerminalBlock tb) {
final PsiVariable variable = tb.getVariable();
final PsiMethodCallExpression methodCallExpression = tb.getSingleMethodCall();
LOG.assertTrue(methodCallExpression != null);
return isIdentityMapping(variable, methodCallExpression.getArgumentList().getExpressions()[0]);
}
private static boolean isCollectCall(TerminalBlock tb, final List<Operation> operations) {
final PsiMethodCallExpression methodCallExpression = tb.getSingleMethodCall();
if (methodCallExpression != null) {
final PsiReferenceExpression methodExpression = methodCallExpression.getMethodExpression();
final PsiExpression qualifierExpression = methodExpression.getQualifierExpression();
PsiClass qualifierClass = null;
if (qualifierExpression instanceof PsiReferenceExpression) {
if (ReferencesSearch.search(tb.getVariable(), new LocalSearchScope(qualifierExpression)).findFirst() != null) {
return false;
}
final PsiElement resolve = ((PsiReferenceExpression)qualifierExpression).resolve();
if (resolve instanceof PsiVariable) {
if (ReferencesSearch.search(resolve, new LocalSearchScope(methodCallExpression.getArgumentList())).findFirst() != null) {
return false;
}
}
qualifierClass = PsiUtil.resolveClassInType(qualifierExpression.getType());
}
else if (qualifierExpression == null) {
final PsiClass enclosingClass = PsiTreeUtil.getParentOfType(methodCallExpression, PsiClass.class);
if (PsiUtil.getEnclosingStaticElement(methodCallExpression, enclosingClass) == null) {
qualifierClass = enclosingClass;
}
}
if (qualifierClass != null &&
InheritanceUtil.isInheritor(qualifierClass, false, CommonClassNames.JAVA_UTIL_COLLECTION)) {
for(Operation op : operations) {
final PsiExpression expression = op.getExpression();
if (expression != null && isExpressionDependsOnUpdatedCollections(expression, qualifierExpression)) return false;
}
final PsiElement resolve = methodExpression.resolve();
if (resolve instanceof PsiMethod &&
"add".equals(((PsiMethod)resolve).getName()) &&
((PsiMethod)resolve).getParameterList().getParametersCount() == 1) {
final PsiExpression[] args = methodCallExpression.getArgumentList().getExpressions();
if (args.length == 1) {
if (args[0] instanceof PsiCallExpression) {
final PsiMethod method = ((PsiCallExpression)args[0]).resolveMethod();
return method != null && !method.hasTypeParameters() && !isThrowsCompatible(method);
}
return true;
}
}
}
}
return false;
}
private static boolean isExpressionDependsOnUpdatedCollections(PsiExpression condition,
PsiExpression qualifierExpression) {
final PsiElement collection = qualifierExpression instanceof PsiReferenceExpression
? ((PsiReferenceExpression)qualifierExpression).resolve()
: null;
if (collection != null) {
return ReferencesSearch.search(collection, new LocalSearchScope(condition)).findFirst() != null;
}
final boolean[] dependsOnCollection = {false};
condition.accept(new JavaRecursiveElementWalkingVisitor() {
@Override
public void visitMethodCallExpression(PsiMethodCallExpression expression) {
super.visitMethodCallExpression(expression);
final PsiExpression callQualifier = expression.getMethodExpression().getQualifierExpression();
if (callQualifier == null ||
callQualifier instanceof PsiThisExpression && ((PsiThisExpression)callQualifier).getQualifier() == null ||
callQualifier instanceof PsiSuperExpression && ((PsiSuperExpression)callQualifier).getQualifier() == null) {
dependsOnCollection[0] = true;
}
}
@Override
public void visitThisExpression(PsiThisExpression expression) {
super.visitThisExpression(expression);
if (expression.getQualifier() == null && expression.getParent() instanceof PsiExpressionList) {
dependsOnCollection[0] = true;
}
}
@Override
public void visitClass(PsiClass aClass) {}
@Override
public void visitLambdaExpression(PsiLambdaExpression expression) {}
});
return dependsOnCollection[0];
}
private static boolean isTrivial(PsiStatement body, PsiParameter parameter) {
//method reference
final PsiExpression candidate = LambdaCanBeMethodReferenceInspection
.canBeMethodReferenceProblem(body instanceof PsiBlockStatement ? ((PsiBlockStatement)body).getCodeBlock() : body,
new PsiParameter[]{parameter},
createDefaultConsumerType(parameter.getProject(), parameter));
if (!(candidate instanceof PsiCallExpression)) {
return true;
}
final PsiMethod method = ((PsiCallExpression)candidate).resolveMethod();
return method != null && isThrowsCompatible(method);
}
private static boolean isThrowsCompatible(PsiMethod method) {
return ContainerUtil.find(method.getThrowsList().getReferencedTypes(), type -> !ExceptionUtil.isUncheckedException(type)) != null;
}
private static boolean isIdentityMapping(PsiVariable variable, PsiExpression mapperCall) {
return mapperCall instanceof PsiReferenceExpression && ((PsiReferenceExpression)mapperCall).resolve() == variable;
}
static String compoundLambdaOrMethodReference(PsiVariable variable,
PsiExpression expression,
String samQualifiedName,
PsiType[] samParamTypes) {
String result = "";
final Project project = variable.getProject();
final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project);
final PsiClass functionClass = psiFacade.findClass(samQualifiedName, expression.getResolveScope());
for (int i = 0; i < samParamTypes.length; i++) {
if (samParamTypes[i] instanceof PsiPrimitiveType) {
samParamTypes[i] = ((PsiPrimitiveType)samParamTypes[i]).getBoxedType(expression);
}
}
final PsiClassType functionalInterfaceType = functionClass != null ? psiFacade.getElementFactory().createType(functionClass, samParamTypes) : null;
final PsiVariable[] parameters = {variable};
String methodReferenceText = LambdaCanBeMethodReferenceInspection.convertToMethodReference(expression, parameters, functionalInterfaceType, null);
if (methodReferenceText != null) {
LOG.assertTrue(functionalInterfaceType != null);
result += "(" + functionalInterfaceType.getCanonicalText() + ")" + methodReferenceText;
} else {
result += variable.getName() + " -> " + expression.getText();
}
return result;
}
private static abstract class MigrateToStreamFix implements LocalQuickFix {
@NotNull
@Override
public String getName() {
return getFamilyName();
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
PsiElement element = descriptor.getPsiElement();
if (element instanceof PsiForeachStatement) {
PsiForeachStatement foreachStatement = (PsiForeachStatement)element;
PsiStatement body = foreachStatement.getBody();
final PsiExpression iteratedValue = foreachStatement.getIteratedValue();
if (body != null && iteratedValue != null) {
final PsiParameter parameter = foreachStatement.getIterationParameter();
TerminalBlock tb = TerminalBlock.from(parameter, body);
if (!FileModificationService.getInstance().preparePsiElementForWrite(foreachStatement)) return;
PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
List<String> replacements = tb.extractOperationReplacements(factory);
migrate(project, descriptor, foreachStatement, iteratedValue, body, tb, replacements);
}
}
}
abstract void migrate(@NotNull Project project,
@NotNull ProblemDescriptor descriptor,
@NotNull PsiForeachStatement foreachStatement,
@NotNull PsiExpression iteratedValue,
@NotNull PsiStatement body,
@NotNull TerminalBlock tb,
@NotNull List<String> replacements);
static void replaceWithNumericAddition(@NotNull Project project,
PsiForeachStatement foreachStatement,
PsiLocalVariable var,
StringBuilder builder,
String expressionType) {
PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project);
restoreComments(foreachStatement, foreachStatement.getBody());
if (isDeclarationJustBefore(var, foreachStatement)) {
PsiExpression initializer = var.getInitializer();
if (ExpressionUtils.isZero(initializer)) {
String typeStr = var.getType().getCanonicalText();
String replacement = (typeStr.equals(expressionType) ? "" : "(" + typeStr + ") ") + builder;
initializer.replace(elementFactory.createExpressionFromText(replacement, foreachStatement));
foreachStatement.delete();
simplifyAndFormat(project, var);
return;
}
}
PsiElement result =
foreachStatement.replace(elementFactory.createStatementFromText(var.getName() + "+=" + builder + ";", foreachStatement));
simplifyAndFormat(project, result);
}
static void simplifyAndFormat(@NotNull Project project, PsiElement result) {
if(result == null) return;
simplifyRedundantCast(result);
CodeStyleManager.getInstance(project).reformat(JavaCodeStyleManager.getInstance(project).shortenClassReferences(result));
}
static void simplifyRedundantCast(PsiElement result) {
for (PsiMethodReferenceExpression methodReferenceExpression : PsiTreeUtil
.findChildrenOfType(result, PsiMethodReferenceExpression.class)) {
final PsiElement parent = methodReferenceExpression.getParent();
if (parent instanceof PsiTypeCastExpression) {
if (RedundantCastUtil.isCastRedundant((PsiTypeCastExpression)parent)) {
final PsiExpression operand = ((PsiTypeCastExpression)parent).getOperand();
LOG.assertTrue(operand != null);
parent.replace(operand);
}
}
}
}
static void restoreComments(PsiForeachStatement foreachStatement, PsiStatement body) {
final PsiElement parent = foreachStatement.getParent();
for (PsiElement comment : PsiTreeUtil.findChildrenOfType(body, PsiComment.class)) {
parent.addBefore(comment, foreachStatement);
}
}
@NotNull
static StringBuilder generateStream(PsiExpression iteratedValue, List<String> intermediateOps) {
StringBuilder buffer = new StringBuilder();
final PsiType iteratedValueType = iteratedValue.getType();
if (iteratedValueType instanceof PsiArrayType) {
buffer.append("java.util.Arrays.stream(").append(iteratedValue.getText()).append(")");
}
else {
buffer.append(getIteratedValueText(iteratedValue));
if (!intermediateOps.isEmpty()) {
buffer.append(".stream()");
}
}
intermediateOps.forEach(buffer::append);
return buffer;
}
static String getIteratedValueText(PsiExpression iteratedValue) {
return iteratedValue instanceof PsiCallExpression ||
iteratedValue instanceof PsiReferenceExpression ||
iteratedValue instanceof PsiQualifiedExpression ||
iteratedValue instanceof PsiParenthesizedExpression ? iteratedValue.getText() : "(" + iteratedValue.getText() + ")";
}
}
private static class ReplaceWithForeachCallFix extends MigrateToStreamFix {
private final String myForEachMethodName;
protected ReplaceWithForeachCallFix(String forEachMethodName) {
myForEachMethodName = forEachMethodName;
}
@NotNull
@Override
public String getFamilyName() {
return "Replace with " + myForEachMethodName;
}
@Override
void migrate(@NotNull Project project,
@NotNull ProblemDescriptor descriptor,
@NotNull PsiForeachStatement foreachStatement,
@NotNull PsiExpression iteratedValue,
@NotNull PsiStatement body,
@NotNull TerminalBlock tb,
@NotNull List<String> intermediateOps) {
restoreComments(foreachStatement, body);
final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project);
StringBuilder buffer = generateStream(iteratedValue, intermediateOps);
PsiElement block = tb.convertToElement(elementFactory);
buffer.append(".").append(myForEachMethodName).append("(");
final String functionalExpressionText = createForEachFunctionalExpressionText(project, block, tb.getVariable());
PsiExpressionStatement callStatement = (PsiExpressionStatement)elementFactory.createStatementFromText(buffer.toString() + functionalExpressionText + ");", foreachStatement);
callStatement = (PsiExpressionStatement)foreachStatement.replace(callStatement);
final PsiExpressionList argumentList = ((PsiCallExpression)callStatement.getExpression()).getArgumentList();
LOG.assertTrue(argumentList != null, callStatement.getText());
final PsiExpression[] expressions = argumentList.getExpressions();
LOG.assertTrue(expressions.length == 1);
if (expressions[0] instanceof PsiFunctionalExpression && ((PsiFunctionalExpression)expressions[0]).getFunctionalInterfaceType() == null) {
callStatement =
(PsiExpressionStatement)callStatement.replace(elementFactory.createStatementFromText(
buffer.toString() + "(" + tb.getVariable().getText() + ") -> " + wrapInBlock(block) + ");", callStatement));
}
simplifyAndFormat(project, callStatement);
}
private static String createForEachFunctionalExpressionText(Project project, PsiElement block, PsiVariable variable) {
final PsiExpression methodRefCandidate = LambdaCanBeMethodReferenceInspection.extractMethodReferenceCandidateExpression(block);
if (methodRefCandidate != null) {
final PsiClassType functionalType = createDefaultConsumerType(project, variable);
final PsiVariable[] parameters = {variable};
String methodReferenceText =
LambdaCanBeMethodReferenceInspection.convertToMethodReference(block, parameters, functionalType, null);
if (methodReferenceText != null) {
return methodReferenceText;
}
}
return variable.getName() + " -> " + wrapInBlock(block);
}
private static String wrapInBlock(PsiElement block) {
if(block instanceof PsiExpressionStatement) {
return ((PsiExpressionStatement)block).getExpression().getText();
}
if(block instanceof PsiCodeBlock) {
return block.getText();
}
return "{" + block.getText() + "}";
}
}
private static PsiClassType createDefaultConsumerType(Project project, PsiVariable variable) {
final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project);
final PsiClass consumerClass = psiFacade.findClass("java.util.function.Consumer", GlobalSearchScope.allScope(project));
return consumerClass != null ? psiFacade.getElementFactory().createType(consumerClass, variable.getType()) : null;
}
@Contract("null, _ -> null")
static PsiExpression extractReplaceableCollectionInitializer(PsiExpression qualifierExpression, PsiStatement foreachStatement) {
if (qualifierExpression instanceof PsiReferenceExpression) {
final PsiElement resolve = ((PsiReferenceExpression)qualifierExpression).resolve();
if (resolve instanceof PsiLocalVariable) {
PsiLocalVariable var = (PsiLocalVariable)resolve;
if (isDeclarationJustBefore(var, foreachStatement)) {
final PsiExpression initializer = var.getInitializer();
if (initializer instanceof PsiNewExpression) {
final PsiExpressionList argumentList = ((PsiNewExpression)initializer).getArgumentList();
if (argumentList != null && argumentList.getExpressions().length == 0) {
return initializer;
}
}
}
}
}
return null;
}
private static class ReplaceWithCollectFix extends MigrateToStreamFix {
final String myMethodName;
protected ReplaceWithCollectFix(String methodName) {
myMethodName = methodName;
}
@NotNull
@Override
public String getFamilyName() {
return "Replace with " + myMethodName;
}
@Override
void migrate(@NotNull Project project,
@NotNull ProblemDescriptor descriptor,
@NotNull PsiForeachStatement foreachStatement,
@NotNull PsiExpression iteratedValue,
@NotNull PsiStatement body,
@NotNull TerminalBlock tb,
@NotNull List<String> intermediateOps) {
final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project);
final PsiType iteratedValueType = iteratedValue.getType();
final PsiMethodCallExpression methodCallExpression = tb.getSingleMethodCall();
if (methodCallExpression == null) return;
restoreComments(foreachStatement, body);
if (intermediateOps.isEmpty() && isAddAllCall(tb)) {
final PsiExpression qualifierExpression = methodCallExpression.getMethodExpression().getQualifierExpression();
final String qualifierText = qualifierExpression != null ? qualifierExpression.getText() : "";
final String collectionText =
iteratedValueType instanceof PsiArrayType ? "java.util.Arrays.asList(" + iteratedValue.getText() + ")" :
getIteratedValueText(iteratedValue);
final String callText = StringUtil.getQualifiedName(qualifierText, "addAll(" + collectionText + ");");
PsiElement result = foreachStatement.replace(elementFactory.createStatementFromText(callText, foreachStatement));
simplifyAndFormat(project, result);
return;
}
intermediateOps
.add(createMapperFunctionalExpressionText(tb.getVariable(), methodCallExpression.getArgumentList().getExpressions()[0]));
final StringBuilder builder = generateStream(iteratedValue, intermediateOps);
final PsiExpression qualifierExpression = methodCallExpression.getMethodExpression().getQualifierExpression();
final PsiExpression initializer = extractReplaceableCollectionInitializer(qualifierExpression, foreachStatement);
if(initializer != null) {
String callText = builder.append(".collect(java.util.stream.Collectors.")
.append(createInitializerReplacementText(qualifierExpression.getType(), initializer))
.append(")").toString();
PsiElement result = initializer.replace(elementFactory.createExpressionFromText(callText, null));
simplifyAndFormat(project, result);
foreachStatement.delete();
return;
}
final String qualifierText = qualifierExpression != null ? qualifierExpression.getText() + "." : "";
JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(project);
final SuggestedNameInfo suggestedNameInfo = codeStyleManager
.suggestVariableName(VariableKind.LOCAL_VARIABLE, "item", null, null, false);
String varName = codeStyleManager.suggestUniqueVariableName(suggestedNameInfo, qualifierExpression, false).names[0];
PsiExpression forEachBody =
elementFactory.createExpressionFromText(qualifierText + "add(" + varName + ")", qualifierExpression);
final String callText =
builder.append(".forEach(").append(varName).append("->").append(forEachBody.getText()).append(");").toString();
PsiElement result = foreachStatement.replace(elementFactory.createStatementFromText(callText, foreachStatement));
PsiLambdaExpression lambda =
(PsiLambdaExpression)((PsiMethodCallExpression)((PsiExpressionStatement)result).getExpression()).getArgumentList()
.getExpressions()[0];
String methodReference =
LambdaCanBeMethodReferenceInspection.convertToMethodReference(lambda.getBody(), lambda.getParameterList().getParameters(),
lambda.getFunctionalInterfaceType(), lambda);
if(methodReference != null) {
lambda.replace(elementFactory.createExpressionFromText(methodReference, lambda));
}
simplifyAndFormat(project, result);
}
private static String createInitializerReplacementText(PsiType varType, PsiExpression initializer) {
final PsiType initializerType = initializer.getType();
final PsiClassType rawType = initializerType instanceof PsiClassType ? ((PsiClassType)initializerType).rawType() : null;
final PsiClassType rawVarType = varType instanceof PsiClassType ? ((PsiClassType)varType).rawType() : null;
if (rawType != null && rawVarType != null &&
rawType.equalsToText(CommonClassNames.JAVA_UTIL_ARRAY_LIST) &&
(rawVarType.equalsToText(CommonClassNames.JAVA_UTIL_LIST) || rawVarType.equalsToText(CommonClassNames.JAVA_UTIL_COLLECTION))) {
return "toList()";
}
else if (rawType != null && rawVarType != null &&
rawType.equalsToText(CommonClassNames.JAVA_UTIL_HASH_SET) &&
(rawVarType.equalsToText(CommonClassNames.JAVA_UTIL_SET) || rawVarType.equalsToText(CommonClassNames.JAVA_UTIL_COLLECTION))) {
return "toSet()";
}
else if (rawType != null) {
return "toCollection(" + rawType.getClassName() + "::new)";
}
else {
return "toCollection(() -> " + initializer.getText() +")";
}
}
private static String createMapperFunctionalExpressionText(PsiVariable variable, PsiExpression expression) {
if (!isIdentityMapping(variable, expression)) {
return new MapOp(expression, variable).createReplacement(null);
}
return "";
}
}
private static class ReplaceWithCountFix extends MigrateToStreamFix {
@NotNull
@Override
public String getFamilyName() {
return "Replace with count()";
}
@Override
void migrate(@NotNull Project project,
@NotNull ProblemDescriptor descriptor,
@NotNull PsiForeachStatement foreachStatement,
@NotNull PsiExpression iteratedValue,
@NotNull PsiStatement body,
@NotNull TerminalBlock tb,
@NotNull List<String> intermediateOps) {
PsiExpression operand = extractIncrementedLValue(tb.getSingleExpression(PsiExpression.class));
if (!(operand instanceof PsiReferenceExpression)) return;
PsiElement element = ((PsiReferenceExpression)operand).resolve();
if (!(element instanceof PsiLocalVariable)) return;
PsiLocalVariable var = (PsiLocalVariable)element;
final StringBuilder builder = generateStream(iteratedValue, intermediateOps);
builder.append(".count()");
replaceWithNumericAddition(project, foreachStatement, var, builder, "long");
}
}
private static class ReplaceWithMatchFix extends MigrateToStreamFix {
private final String myMethodName;
public ReplaceWithMatchFix(String methodName) {
myMethodName = methodName;
}
@NotNull
@Override
public String getFamilyName() {
return "Replace with " + myMethodName + "()";
}
@Override
void migrate(@NotNull Project project,
@NotNull ProblemDescriptor descriptor,
@NotNull PsiForeachStatement foreachStatement,
@NotNull PsiExpression iteratedValue,
@NotNull PsiStatement body,
@NotNull TerminalBlock tb,
@NotNull List<String> intermediateOps) {
PsiReturnStatement returnStatement = (PsiReturnStatement)tb.getSingleStatement();
PsiExpression value = returnStatement.getReturnValue();
if(!isLiteral(value, Boolean.TRUE) && !isLiteral(value, Boolean.FALSE)) return;
boolean foundResult = (boolean)((PsiLiteralExpression)value).getValue();
PsiElement nextStatement = PsiTreeUtil.skipSiblingsForward(foreachStatement, PsiWhiteSpace.class, PsiComment.class);
if(!(nextStatement instanceof PsiReturnStatement)) return;
PsiReturnStatement nextReturnStatement = (PsiReturnStatement)nextStatement;
if(!isLiteral(nextReturnStatement.getReturnValue(), !foundResult)) return;
String methodName = foundResult ? "anyMatch" : "noneMatch";
final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project);
String streamText = generateStream(iteratedValue, intermediateOps).toString();
PsiExpression stream =
elementFactory.createExpressionFromText(streamText, foreachStatement);
if(!(stream instanceof PsiMethodCallExpression)) return;
PsiElement nameElement = ((PsiMethodCallExpression)stream).getMethodExpression().getReferenceNameElement();
if(nameElement != null && nameElement.getText().equals("filter")) {
if(!foundResult) {
PsiExpression[] expressions = ((PsiMethodCallExpression)stream).getArgumentList().getExpressions();
if(expressions.length == 1 && expressions[0] instanceof PsiLambdaExpression) {
PsiLambdaExpression lambda = (PsiLambdaExpression)expressions[0];
PsiElement lambdaBody = lambda.getBody();
if(lambdaBody instanceof PsiExpression && BoolUtils.isNegation((PsiExpression)lambdaBody)) {
PsiExpression negated = BoolUtils.getNegated((PsiExpression)lambdaBody);
LOG.assertTrue(negated != null);
String methodReferenceText = LambdaCanBeMethodReferenceInspection
.convertToMethodReference(negated, lambda.getParameterList().getParameters(), lambda.getFunctionalInterfaceType(), lambda);
if(methodReferenceText != null) {
lambda.replace(elementFactory.createExpressionFromText(methodReferenceText, lambda));
} else {
lambdaBody.replace(negated);
}
methodName = "allMatch";
}
}
}
nameElement.replace(elementFactory.createIdentifier(methodName));
streamText = stream.getText();
} else {
streamText += "."+methodName+"("+tb.getVariable().getName()+" -> true)";
}
PsiElement result = foreachStatement.replace(elementFactory.createStatementFromText("return " + streamText + ";", foreachStatement));
nextReturnStatement.delete();
simplifyAndFormat(project, result);
}
}
private static class ReplaceWithSumFix extends MigrateToStreamFix {
@NotNull
@Override
public String getFamilyName() {
return "Replace with sum()";
}
@Override
void migrate(@NotNull Project project,
@NotNull ProblemDescriptor descriptor,
@NotNull PsiForeachStatement foreachStatement,
@NotNull PsiExpression iteratedValue,
@NotNull PsiStatement body,
@NotNull TerminalBlock tb,
@NotNull List<String> intermediateOps) {
PsiAssignmentExpression assignment = tb.getSingleExpression(PsiAssignmentExpression.class);
if (assignment == null) return;
PsiLocalVariable var = extractAccumulator(assignment);
if (var == null) return;
PsiExpression addend = extractAddend(assignment);
if (addend == null) return;
PsiType type = var.getType();
if (!(type instanceof PsiPrimitiveType)) return;
PsiPrimitiveType primitiveType = (PsiPrimitiveType)type;
if (primitiveType.equalsToText("float")) return;
String typeName;
if (primitiveType.equalsToText("double")) {
typeName = "Double";
}
else if (primitiveType.equalsToText("long")) {
typeName = "Long";
}
else {
typeName = "Int";
}
intermediateOps.add(".mapTo" + typeName + "(" +
compoundLambdaOrMethodReference(tb.getVariable(), addend, "java.util.function.To" + typeName + "Function",
new PsiType[]{tb.getVariable().getType()})+")");
final StringBuilder builder = generateStream(iteratedValue, intermediateOps);
builder.append(".sum()");
replaceWithNumericAddition(project, foreachStatement, var, builder, typeName.toLowerCase(Locale.ENGLISH));
}
}
private static boolean isDeclarationJustBefore(PsiLocalVariable var, PsiStatement nextStatement) {
PsiElement declaration = var.getParent();
if(declaration instanceof PsiDeclarationStatement) {
PsiElement[] elements = ((PsiDeclarationStatement)declaration).getDeclaredElements();
if (ArrayUtil.getLastElement(elements) == var && nextStatement.equals(
PsiTreeUtil.skipSiblingsForward(declaration, PsiWhiteSpace.class, PsiComment.class))) {
return true;
}
}
return false;
}
/**
* Intermediate stream operation representation
*/
static abstract class Operation {
final PsiExpression myExpression;
final PsiVariable myVariable;
protected Operation(PsiExpression expression, PsiVariable variable) {
myExpression = expression;
myVariable = variable;
}
PsiExpression getExpression() {
return myExpression;
}
abstract String createReplacement(PsiElementFactory factory);
}
static class FilterOp extends Operation {
private final boolean myNegated;
FilterOp(PsiExpression condition, PsiVariable variable, boolean negated) {
super(condition, variable);
myNegated = negated;
}
@Override
public String createReplacement(PsiElementFactory factory) {
PsiExpression expression =
myNegated ? factory.createExpressionFromText(BoolUtils.getNegatedExpressionText(myExpression), myExpression) : myExpression;
return ".filter(" + compoundLambdaOrMethodReference(myVariable, expression,
"java.util.function.Predicate",
new PsiType[] {myVariable.getType()}) + ")";
}
}
static class MapOp extends Operation {
MapOp(PsiExpression expression, PsiVariable variable) {
super(expression, variable);
}
@Override
public String createReplacement(PsiElementFactory factory) {
return ".map(" + compoundLambdaOrMethodReference(myVariable, myExpression,
"java.util.function.Function",
new PsiType[] {myVariable.getType(), myExpression.getType()}) + ")";
}
}
static class FlatMapOp extends Operation {
FlatMapOp(PsiExpression expression, PsiVariable variable) {
super(expression, variable);
}
@Override
public String createReplacement(PsiElementFactory factory) {
PsiExpression replacement = factory.createExpressionFromText(myExpression.getText() + ".stream()", myExpression);
return ".flatMap(" + compoundLambdaOrMethodReference(myVariable, replacement,
"java.util.function.Function",
new PsiType[] {myVariable.getType(), replacement.getType()}) + ")";
}
}
static class ArrayFlatMapOp extends Operation {
ArrayFlatMapOp(PsiExpression expression, PsiVariable variable) {
super(expression, variable);
}
@Override
public String createReplacement(PsiElementFactory factory) {
PsiExpression replacement = factory.createExpressionFromText("java.util.Arrays.stream("+myExpression.getText() + ")", myExpression);
return ".flatMap(" + compoundLambdaOrMethodReference(myVariable, replacement,
"java.util.function.Function",
new PsiType[] {myVariable.getType(), replacement.getType()}) + ")";
}
}
/**
* This class represents the code which should be performed
* as a part of forEach operation of resulting stream.
*/
static class TerminalBlock {
private PsiVariable myVariable;
private PsiStatement[] myStatements;
private TerminalBlock(PsiVariable variable, PsiStatement[] statements) {
myVariable = variable;
myStatements = statements;
flatten();
}
private void flatten() {
while(myStatements.length == 1 && myStatements[0] instanceof PsiBlockStatement) {
myStatements = ((PsiBlockStatement)myStatements[0]).getCodeBlock().getStatements();
}
}
int getStartOffset(ControlFlow cf) {
return cf.getStartOffset(myStatements[0]);
}
int getEndOffset(ControlFlow cf) {
return cf.getEndOffset(myStatements[myStatements.length-1]);
}
PsiStatement getSingleStatement() {
return myStatements.length == 1 ? myStatements[0] : null;
}
@Nullable
<T extends PsiExpression> T getSingleExpression(Class<T> wantedType) {
PsiStatement statement = getSingleStatement();
if(statement instanceof PsiExpressionStatement) {
PsiExpression expression = ((PsiExpressionStatement)statement).getExpression();
if(wantedType.isInstance(expression))
return wantedType.cast(expression);
}
return null;
}
/**
* @return PsiMethodCallExpression if this TerminalBlock contains single method call, null otherwise
*/
@Nullable
PsiMethodCallExpression getSingleMethodCall() {
return getSingleExpression(PsiMethodCallExpression.class);
}
/**
* If possible, extract single intermediate stream operation from this
* {@code TerminalBlock} changing the TerminalBlock itself to exclude this operation
*
* @return extracted operation or null if extraction is not possible
*/
@Nullable
Operation extractOperation() {
// extract filter
if(getSingleStatement() instanceof PsiIfStatement) {
PsiIfStatement ifStatement = (PsiIfStatement)getSingleStatement();
if(ifStatement.getElseBranch() == null && ifStatement.getCondition() != null) {
replaceWith(ifStatement.getThenBranch());
return new FilterOp(ifStatement.getCondition(), myVariable, false);
}
}
// extract flatMap
if(getSingleStatement() instanceof PsiForeachStatement) {
PsiForeachStatement foreachStatement = (PsiForeachStatement)getSingleStatement();
final PsiExpression iteratedValue = foreachStatement.getIteratedValue();
final PsiStatement body = foreachStatement.getBody();
if (iteratedValue != null && body != null) {
final PsiType iteratedValueType = iteratedValue.getType();
Operation op = null;
if(iteratedValueType instanceof PsiArrayType) {
// do not handle flatMapToPrimitive
if (((PsiArrayType)iteratedValueType).getComponentType() instanceof PsiPrimitiveType)
return null;
op = new ArrayFlatMapOp(iteratedValue, myVariable);
} else {
final PsiClass iteratorClass = PsiUtil.resolveClassInClassTypeOnly(iteratedValueType);
final PsiClass collectionClass =
JavaPsiFacade.getInstance(body.getProject())
.findClass(CommonClassNames.JAVA_UTIL_COLLECTION, foreachStatement.getResolveScope());
if (collectionClass != null && InheritanceUtil.isInheritorOrSelf(iteratorClass, collectionClass, true)) {
op = new FlatMapOp(iteratedValue, myVariable);
}
}
if(op != null && ReferencesSearch.search(myVariable, new LocalSearchScope(body)).findFirst() == null) {
myVariable = foreachStatement.getIterationParameter();
replaceWith(body);
return op;
}
}
}
if(myStatements.length >= 1) {
PsiStatement first = myStatements[0];
// extract map
if(first instanceof PsiDeclarationStatement) {
PsiDeclarationStatement decl = (PsiDeclarationStatement)first;
PsiElement[] elements = decl.getDeclaredElements();
if(elements.length == 1) {
PsiElement element = elements[0];
if(element instanceof PsiLocalVariable) {
PsiLocalVariable declaredVar = (PsiLocalVariable)element;
// do not handle mapToPrimitive
if(!(declaredVar.getType() instanceof PsiPrimitiveType)) {
PsiExpression initializer = declaredVar.getInitializer();
PsiStatement[] leftOver = Arrays.copyOfRange(myStatements, 1, myStatements.length);
if (initializer != null &&
ReferencesSearch.search(myVariable, new LocalSearchScope(leftOver))
.findFirst() == null) {
MapOp op = new MapOp(initializer, myVariable);
myVariable = declaredVar;
myStatements = leftOver;
flatten();
return op;
}
}
}
}
}
// extract filter with negation
if(first instanceof PsiIfStatement) {
PsiIfStatement ifStatement = (PsiIfStatement)first;
if(ifStatement.getCondition() == null) return null;
PsiStatement branch = ifStatement.getThenBranch();
if(branch instanceof PsiBlockStatement) {
PsiStatement[] statements = ((PsiBlockStatement)branch).getCodeBlock().getStatements();
if(statements.length == 1)
branch = statements[0];
}
if(!(branch instanceof PsiContinueStatement) || ((PsiContinueStatement)branch).getLabelIdentifier() != null) return null;
if(ifStatement.getElseBranch() != null) {
myStatements[0] = ifStatement.getElseBranch();
} else {
myStatements = Arrays.copyOfRange(myStatements, 1, myStatements.length);
}
flatten();
return new FilterOp(ifStatement.getCondition(), myVariable, true);
}
}
return null;
}
@NotNull
List<Operation> extractOperations() {
List<Operation> result = new ArrayList<>();
while(true) {
Operation op = extractOperation();
if(op == null) return result;
result.add(op);
}
}
private void replaceWith(PsiStatement statement) {
myStatements = new PsiStatement[] {statement};
flatten();
}
public PsiVariable getVariable() {
return myVariable;
}
public boolean isEmpty() {
return myStatements.length == 0;
}
@Contract("_, _ -> !null")
static TerminalBlock from(PsiVariable variable, PsiStatement statement) {
return new TerminalBlock(variable, new PsiStatement[] {statement});
}
@NotNull
private List<String> extractOperationReplacements(PsiElementFactory factory) {
List<String> intermediateOps = new ArrayList<>();
while(true) {
Operation operation = extractOperation();
if(operation == null)
break;
intermediateOps.add(operation.createReplacement(factory));
}
return intermediateOps;
}
/**
* Converts this TerminalBlock to PsiElement (either PsiStatement or PsiCodeBlock)
*
* @param factory factory to use to create new element if necessary
* @return the PsiElement
*/
public PsiElement convertToElement(PsiElementFactory factory) {
if (myStatements.length == 1) {
return myStatements[0];
}
PsiCodeBlock block = factory.createCodeBlock();
for (PsiStatement statement : myStatements) {
block.add(statement);
}
return block;
}
}
}