Files
openide/java/java-impl/src/com/intellij/codeInspection/streamToLoop/StreamToLoopInspection.java
T

732 lines
32 KiB
Java

/*
* Copyright 2000-2017 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.streamToLoop;
import com.intellij.codeInspection.BaseJavaBatchLocalInspectionTool;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel;
import com.intellij.lang.java.lexer.JavaLexer;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.impl.PsiDiamondTypeUtil;
import com.intellij.psi.impl.source.PsiImmediateClassType;
import com.intellij.psi.search.searches.ReferencesSearch;
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.RedundantCastUtil;
import com.intellij.refactoring.util.RefactoringUtil;
import com.intellij.util.containers.ContainerUtil;
import com.siyeh.ig.psiutils.*;
import one.util.streamex.IntStreamEx;
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.*;
import static com.intellij.codeInspection.streamToLoop.Operation.FlatMapOperation;
/**
* @author Tagir Valeev
*/
public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
private static final Logger LOG = Logger.getInstance(StreamToLoopInspection.class);
// To quickly filter out most of the non-interesting method calls
private static final Set<String> SUPPORTED_TERMINALS = ContainerUtil.set(
"count", "sum", "summaryStatistics", "reduce", "collect", "findFirst", "findAny", "anyMatch", "allMatch", "noneMatch", "toArray",
"average", "forEach", "forEachOrdered", "min", "max", "toList", "toSet");
public boolean SUPPORT_UNKNOWN_SOURCES = false;
@Nullable
@Override
public JComponent createOptionsPanel() {
return new SingleCheckboxOptionsPanel("Iterate unknown Stream sources via Stream.iterator()", this, "SUPPORT_UNKNOWN_SOURCES");
}
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
if (!PsiUtil.isLanguageLevel8OrHigher(holder.getFile())) {
return PsiElementVisitor.EMPTY_VISITOR;
}
return new JavaElementVisitor() {
@Override
public void visitMethodCallExpression(PsiMethodCallExpression call) {
super.visitMethodCallExpression(call);
PsiReferenceExpression expression = call.getMethodExpression();
PsiElement nameElement = expression.getReferenceNameElement();
if (nameElement == null || !SUPPORTED_TERMINALS.contains(nameElement.getText()) || !isSupportedCodeLocation(call)) return;
PsiMethod method = call.resolveMethod();
if(method == null) return;
PsiClass aClass = method.getContainingClass();
if (InheritanceUtil.isInheritor(aClass, CommonClassNames.JAVA_UTIL_STREAM_BASE_STREAM)) {
if (extractOperations(StreamVariable.STUB, call, SUPPORT_UNKNOWN_SOURCES) != null) {
register(call, nameElement, "Replace Stream API chain with loop");
}
}
else if (extractIterableForEach(call) != null) {
register(call, nameElement, "Replace 'forEach' call with loop");
}
}
private void register(PsiMethodCallExpression call, PsiElement nameElement, String message) {
TextRange range;
if (isOnTheFly && InspectionProjectProfileManager.isInformationLevel(getShortName(), call)) {
range = new TextRange(0, call.getTextLength());
}
else {
range = nameElement.getTextRange().shiftRight(-call.getTextOffset());
}
holder.registerProblem(call, range, message, new ReplaceStreamWithLoopFix(message));
}
};
}
private static boolean isSupportedCodeLocation(PsiMethodCallExpression call) {
PsiElement cur = call;
PsiElement parent = cur.getParent();
while(parent instanceof PsiExpression || parent instanceof PsiExpressionList) {
if(parent instanceof PsiLambdaExpression) {
return true;
}
if(parent instanceof PsiPolyadicExpression) {
PsiPolyadicExpression polyadicExpression = (PsiPolyadicExpression)parent;
IElementType type = polyadicExpression.getOperationTokenType();
if ((type.equals(JavaTokenType.ANDAND) || type.equals(JavaTokenType.OROR)) && polyadicExpression.getOperands()[0] != cur) {
// not the first in the &&/|| chain: we cannot properly generate code which would short-circuit as well
return false;
}
}
if(parent instanceof PsiConditionalExpression && ((PsiConditionalExpression)parent).getCondition() != cur) {
return false;
}
if(parent instanceof PsiMethodCallExpression) {
PsiReferenceExpression methodExpression = ((PsiMethodCallExpression)parent).getMethodExpression();
if(methodExpression.textMatches("this") || methodExpression.textMatches("super")) {
return false;
}
}
cur = parent;
parent = cur.getParent();
}
if(parent instanceof PsiReturnStatement || parent instanceof PsiExpressionStatement) return true;
if(parent instanceof PsiLocalVariable) {
PsiElement grandParent = parent.getParent();
if(grandParent instanceof PsiDeclarationStatement && ((PsiDeclarationStatement)grandParent).getDeclaredElements().length == 1) {
return true;
}
}
if(parent instanceof PsiForeachStatement && ((PsiForeachStatement)parent).getIteratedValue() == cur) return true;
if(parent instanceof PsiIfStatement && ((PsiIfStatement)parent).getCondition() == cur) return true;
return false;
}
@Nullable
static Operation createOperationFromCall(StreamVariable outVar, PsiMethodCallExpression call, boolean supportUnknownSources) {
PsiMethod method = call.resolveMethod();
if(method == null) return null;
PsiClass aClass = method.getContainingClass();
if(aClass == null) return null;
PsiExpression[] args = call.getArgumentList().getExpressions();
String name = method.getName();
String className = aClass.getQualifiedName();
if(className == null) return null;
PsiType callType = call.getType();
if(callType == null) return null;
if(InheritanceUtil.isInheritor(aClass, CommonClassNames.JAVA_UTIL_STREAM_BASE_STREAM) &&
!method.getModifierList().hasExplicitModifier(PsiModifier.STATIC)) {
PsiExpression qualifier = call.getMethodExpression().getQualifierExpression();
if(qualifier != null) {
PsiType elementType = StreamApiUtil.getStreamElementType(qualifier.getType());
if (!isValidElementType(elementType, call)) return null;
Operation op = Operation.createIntermediate(name, args, outVar, elementType, supportUnknownSources);
if (op != null) return op;
op = TerminalOperation.createTerminal(name, args, elementType, callType, isVoidContext(call.getParent()));
if (op != null) return op;
}
return null;
}
return SourceOperation.createSource(call, supportUnknownSources);
}
private static boolean isValidElementType(PsiType elementType, PsiElement context) {
if(elementType == null || ((elementType instanceof PsiClassType) && ((PsiClassType)elementType).isRaw())) {
// Raw type in any stream step is not supported
return false;
}
if(elementType instanceof PsiImmediateClassType) {
PsiType typeFromText =
JavaPsiFacade.getElementFactory(context.getProject()).createTypeFromText(elementType.getCanonicalText(), context);
if(!(typeFromText instanceof PsiClassType) || ((PsiClassType)typeFromText).resolve() == null) {
return false;
}
}
return true;
}
private static boolean isVoidContext(PsiElement element) {
return element instanceof PsiExpressionStatement ||
(element instanceof PsiLambdaExpression &&
PsiType.VOID.equals(LambdaUtil.getFunctionalInterfaceReturnType((PsiLambdaExpression)element)));
}
@Nullable
static List<OperationRecord> extractIterableForEach(PsiMethodCallExpression terminalCall) {
if (MethodCallUtils.isCallToMethod(terminalCall, CommonClassNames.JAVA_LANG_ITERABLE, PsiType.VOID, "forEach", new PsiType[1])
&& isVoidContext(terminalCall.getParent())) {
PsiExpression qualifier = terminalCall.getMethodExpression().getQualifierExpression();
if (qualifier == null) return null;
// Do not visit this path if some class implements both Iterable and Stream
PsiType type = qualifier.getType();
if (InheritanceUtil.isInheritor(type, CommonClassNames.JAVA_UTIL_STREAM_BASE_STREAM)) return null;
PsiExpression[] args = terminalCall.getArgumentList().getExpressions();
if (args.length != 1) return null;
FunctionHelper fn = FunctionHelper.create(args[0], 1, true);
if (fn == null) return null;
PsiType elementType = PsiUtil.substituteTypeParameter(type, CommonClassNames.JAVA_LANG_ITERABLE, 0, false);
if(!isValidElementType(elementType, terminalCall)) return null;
elementType = GenericsUtil.getVariableTypeByExpressionType(elementType);
TerminalOperation terminal = new TerminalOperation.ForEachTerminalOperation(fn);
SourceOperation source = new SourceOperation.ForEachSource(qualifier);
OperationRecord terminalRecord = new OperationRecord();
OperationRecord sourceRecord = new OperationRecord();
terminalRecord.myOperation = terminal;
sourceRecord.myOperation = source;
sourceRecord.myOutVar = terminalRecord.myInVar = new StreamVariable(elementType.getCanonicalText());
sourceRecord.myInVar = terminalRecord.myOutVar = StreamVariable.STUB;
return Arrays.asList(sourceRecord, terminalRecord);
}
return null;
}
@Nullable
static List<OperationRecord> extractOperations(StreamVariable outVar,
PsiMethodCallExpression terminalCall,
boolean supportUnknownSources) {
List<OperationRecord> operations = new ArrayList<>();
PsiMethodCallExpression currentCall = terminalCall;
StreamVariable lastVar = outVar;
Operation next = null;
while(true) {
Operation op = createOperationFromCall(lastVar, currentCall, supportUnknownSources);
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;
operations.add(or);
if(op instanceof SourceOperation) {
or.myInVar = StreamVariable.STUB;
Collections.reverse(operations);
return operations;
}
currentCall = MethodCallUtils.getQualifierMethodCall(currentCall);
if(currentCall == null) return null;
if(op.changesVariable()) {
PsiType type = StreamApiUtil.getStreamElementType(currentCall.getType());
if(type == null) return null;
lastVar = new StreamVariable(type.getCanonicalText());
}
or.myInVar = lastVar;
next = op;
}
}
@Contract("null -> null")
@Nullable
static TerminalOperation getTerminal(List<OperationRecord> operations) {
if (operations == null || operations.isEmpty()) return null;
OperationRecord record = operations.get(operations.size()-1);
if(record.myOperation instanceof TerminalOperation) {
return (TerminalOperation)record.myOperation;
}
return null;
}
static class ReplaceStreamWithLoopFix implements LocalQuickFix {
private String myMessage;
public ReplaceStreamWithLoopFix(String message) {
myMessage = message;
}
@Nls
@NotNull
@Override
public String getName() {
return myMessage;
}
@Nls
@NotNull
@Override
public String getFamilyName() {
return "Replace Stream API chain with loop";
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
PsiElement element = descriptor.getStartElement();
if(!(element instanceof PsiMethodCallExpression)) return;
PsiMethodCallExpression terminalCall = (PsiMethodCallExpression)element;
if(!isSupportedCodeLocation(terminalCall)) return;
PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
terminalCall = RefactoringUtil.ensureCodeBlock(terminalCall);
if (terminalCall == null) return;
PsiType resultType = terminalCall.getType();
if (resultType == null) return;
List<OperationRecord> operations = extractOperations(StreamVariable.STUB, terminalCall, true);
if (operations == null) {
operations = extractIterableForEach(terminalCall);
}
TerminalOperation terminal = getTerminal(operations);
if (terminal == null) return;
PsiStatement statement = PsiTreeUtil.getParentOfType(terminalCall, PsiStatement.class);
LOG.assertTrue(statement != null);
CommentTracker ct = new CommentTracker();
PsiExpression temporaryStreamPlaceholder =
(PsiExpression)ct.replace(terminalCall, "((" + resultType.getCanonicalText() + ")$streamReplacement$)");
try {
StreamToLoopReplacementContext context =
new StreamToLoopReplacementContext(statement, operations, temporaryStreamPlaceholder, ct);
registerVariables(operations, context);
String replacement = "";
for (OperationRecord or : StreamEx.ofReversed(operations)) {
replacement = or.myOperation.wrap(or.myInVar, or.myOutVar, replacement, context);
}
ct.insertCommentsBefore(statement);
for (PsiStatement addedStatement : ((PsiBlockStatement)factory.createStatementFromText("{" + replacement + "}", statement))
.getCodeBlock().getStatements()) {
addStatement(project, statement, addedStatement);
}
PsiElement result = context.makeFinalReplacement();
if(result != null) {
normalize(project, result);
}
}
catch (Exception ex) {
String text = terminalCall.getText();
if(temporaryStreamPlaceholder.isPhysical()) {
// Just in case if something went wrong: at least try to restore the original stream code
temporaryStreamPlaceholder.replace(factory.createExpressionFromText(text, temporaryStreamPlaceholder));
}
LOG.error("Error converting Stream to loop: "+text, ex);
}
}
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);
RedundantCastUtil.getRedundantCastsInside(element).forEach(RedundantCastUtil::removeCast);
}
private static StreamEx<OperationRecord> allOperations(List<OperationRecord> operations) {
return StreamEx.of(operations)
.flatMap(or -> or.myOperation.nestedOperations().append(or));
}
private static void registerVariables(List<OperationRecord> operations, StreamToLoopReplacementContext context) {
allOperations(operations).forEach(or -> or.myOperation.preprocessVariables(context, or.myInVar, or.myOutVar));
allOperations(operations).map(or -> or.myOperation).forEach(op -> op.registerReusedElements(context::registerReusedElement));
allOperations(operations).map(or -> or.myInVar).distinct().forEach(var -> var.register(context));
}
}
enum ResultKind {
/**
* Result variable is used as complete stream result and not modified after declaration
* E.g. {@code Collectors.toList()} creates such result.
*/
FINAL,
/**
* Result variable is used as complete stream result, but could be modified in loop
* E.g. {@code Stream.count()} creates such result.
*/
NON_FINAL,
/**
* Result variable is not directly used as stream result: additional transformations are possible.
*/
UNKNOWN
}
static class StreamToLoopReplacementContext {
private final boolean myHasNestedLoops;
private final String mySuffix;
private final PsiStatement myStatement;
private final Set<String> myUsedNames;
private final Set<String> myUsedLabels;
private final List<String> myBeforeSteps = new ArrayList<>();
private final List<String> myAfterSteps = new ArrayList<>();
private final CommentTracker myCommentTracker;
private PsiElement myPlaceholder;
private final PsiElementFactory myFactory;
private String myLabel;
private String myFinisher;
StreamToLoopReplacementContext(PsiStatement statement,
List<OperationRecord> records,
@NotNull PsiExpression placeholder,
CommentTracker ct) {
myStatement = statement;
myFactory = JavaPsiFacade.getElementFactory(myStatement.getProject());
myHasNestedLoops = records.stream().anyMatch(or -> or.myOperation instanceof FlatMapOperation);
myPlaceholder = placeholder;
mySuffix = myHasNestedLoops ? "Outer" : "";
myCommentTracker = ct;
myUsedNames = new HashSet<>();
myUsedLabels = StreamEx.iterate(statement, Objects::nonNull, PsiElement::getParent).select(PsiLabeledStatement.class)
.map(PsiLabeledStatement::getName).toSet();
}
StreamToLoopReplacementContext(StreamToLoopReplacementContext parentContext, List<OperationRecord> records) {
myUsedNames = parentContext.myUsedNames;
myUsedLabels = parentContext.myUsedLabels;
myPlaceholder = null;
myStatement = parentContext.myStatement;
myFactory = parentContext.myFactory;
myCommentTracker = parentContext.myCommentTracker;
myHasNestedLoops = records.stream().anyMatch(or -> or.myOperation instanceof FlatMapOperation);
mySuffix = "Inner";
}
public void registerReusedElement(@Nullable PsiElement element) {
if(element == null) return;
element.accept(new JavaRecursiveElementVisitor() {
@Override
public void visitVariable(PsiVariable variable) {
super.visitVariable(variable);
myUsedNames.add(variable.getName());
}
});
myCommentTracker.markUnchanged(element);
}
@Nullable
private String allocateLabel() {
if(!myHasNestedLoops) return null;
if(myLabel == null) {
String base = mySuffix.toUpperCase(Locale.ENGLISH);
myLabel = IntStreamEx.ints().mapToObj(i -> i == 0 ? base : base + i)
.remove(myUsedLabels::contains).findFirst().orElseThrow(IllegalArgumentException::new);
myUsedLabels.add(myLabel);
}
return myLabel;
}
public String getLoopLabel() {
return myLabel == null ? "" : myLabel + ":\n";
}
public String getBreakStatement() {
String label = allocateLabel();
return label == null ? "break;\n" : "break "+label+";\n";
}
public String registerVarName(Collection<String> variants) {
if(variants.isEmpty()) {
return registerVarName(Collections.singleton("val"));
}
for(int idx = 0; ; idx++) {
for(String variant : variants) {
String name = idx == 0 ? variant : variant + idx;
if(!isUsed(name)) {
myUsedNames.add(name);
return name;
}
}
}
}
private boolean isUsed(String varName) {
return myUsedNames.contains(varName) || JavaLexer.isKeyword(varName, LanguageLevel.HIGHEST) ||
!varName.equals(JavaCodeStyleManager.getInstance(myStatement.getProject()).suggestUniqueVariableName(varName, myStatement, true));
}
public String declare(String desiredName, String type, String initializer) {
String name = registerVarName(
mySuffix.isEmpty() ? Collections.singleton(desiredName) : Arrays.asList(desiredName, desiredName + mySuffix));
myBeforeSteps.add(type + " " + name + " = " + initializer + ";");
return name;
}
public void addBeforeStep(String beforeStatement) {
myBeforeSteps.add(beforeStatement);
}
public void addAfterStep(String afterStatement) {
myAfterSteps.add(0, afterStatement);
}
public String drainAfterSteps() {
String afterSteps = String.join("", myAfterSteps);
myAfterSteps.clear();
return afterSteps;
}
public String drainBeforeSteps() {
String beforeSteps = String.join("", myBeforeSteps);
myBeforeSteps.clear();
return beforeSteps;
}
public String declareResult(String desiredName, String type, String initializer, @NotNull ResultKind kind) {
if (kind != ResultKind.UNKNOWN && myPlaceholder.getParent() instanceof PsiVariable) {
PsiVariable var = (PsiVariable)myPlaceholder.getParent();
if(var.getType().equalsToText(type) && var.getParent() instanceof PsiDeclarationStatement
&& (kind == ResultKind.FINAL || canUseAsNonFinal(var))) {
PsiDeclarationStatement declaration = (PsiDeclarationStatement)var.getParent();
if(declaration.getDeclaredElements().length == 1) {
myPlaceholder = declaration;
PsiVariable copy = (PsiVariable)var.copy();
if (kind == ResultKind.NON_FINAL) {
PsiModifierList modifierList = copy.getModifierList();
if (modifierList != null) {
modifierList.setModifierProperty(PsiModifier.FINAL, false);
}
}
PsiExpression oldInitializer = copy.getInitializer();
LOG.assertTrue(oldInitializer != null);
oldInitializer.replace(createExpression(initializer));
myBeforeSteps.add(copy.getText());
return var.getName();
}
}
}
String name = registerVarName(Arrays.asList(desiredName, "result"));
myBeforeSteps.add(type + " " + name + " = " + initializer + ";");
if(myFinisher != null) {
throw new IllegalStateException("Finisher is already defined");
}
setFinisher(name);
return name;
}
@Contract("null -> false")
private static boolean canUseAsNonFinal(PsiVariable var) {
if (!(var instanceof PsiLocalVariable)) return false;
PsiElement block = PsiUtil.getVariableCodeBlock(var, null);
return block != null && ReferencesSearch.search(var).forEach(ref -> {
PsiElement context = PsiTreeUtil.getParentOfType(ref.getElement(), PsiClass.class, PsiLambdaExpression.class);
return context == null || PsiTreeUtil.isAncestor(context, block, false);
});
}
public PsiElement makeFinalReplacement() {
LOG.assertTrue(myPlaceholder != null);
if (myFinisher == null || myPlaceholder instanceof PsiStatement) {
myPlaceholder.delete();
return null;
}
else {
PsiExpression expression = myFactory.createExpressionFromText(myFinisher, myPlaceholder);
PsiElement parent = myPlaceholder.getParent();
if (parent instanceof PsiExpression && ParenthesesUtils.areParenthesesNeeded(expression, (PsiExpression)parent, false)) {
expression = myFactory.createExpressionFromText("("+myFinisher+")", myPlaceholder);
}
return myPlaceholder.replace(expression);
}
}
public void setFinisher(String finisher) {
myFinisher = finisher;
}
public void setFinisher(ConditionalExpression conditionalExpression) {
if(conditionalExpression instanceof ConditionalExpression.Optional) {
conditionalExpression = tryUnwrapOptional((ConditionalExpression.Optional)conditionalExpression, true);
}
setFinisher(conditionalExpression.asExpression());
}
public String assignAndBreak(ConditionalExpression conditionalExpression) {
PsiStatement statement = PsiTreeUtil.getParentOfType(myPlaceholder, PsiStatement.class);
boolean inReturn = statement instanceof PsiReturnStatement;
if(conditionalExpression instanceof ConditionalExpression.Optional) {
conditionalExpression = tryUnwrapOptional((ConditionalExpression.Optional)conditionalExpression, inReturn);
}
if (conditionalExpression instanceof ConditionalExpression.Boolean) {
conditionalExpression = tryUnwrapBoolean((ConditionalExpression.Boolean)conditionalExpression, inReturn);
}
if (inReturn) {
setFinisher(conditionalExpression.getFalseBranch());
Object mark = new Object();
PsiTreeUtil.mark(myPlaceholder, mark);
PsiElement returnCopy = statement.copy();
PsiElement placeHolderCopy = PsiTreeUtil.releaseMark(returnCopy, mark);
LOG.assertTrue(placeHolderCopy != null);
PsiElement replacement = placeHolderCopy.replace(createExpression(conditionalExpression.getTrueBranch()));
return (placeHolderCopy == returnCopy ? replacement : returnCopy).getText();
}
PsiElement parent = PsiUtil.skipParenthesizedExprUp(myPlaceholder.getParent());
if(parent instanceof PsiIfStatement && conditionalExpression instanceof ConditionalExpression.Boolean &&
!((ConditionalExpression.Boolean)conditionalExpression).isInverted()) {
PsiIfStatement ifStatement = (PsiIfStatement)parent;
if(ifStatement.getElseBranch() == null) {
PsiStatement thenStatement = ControlFlowUtils.stripBraces(ifStatement.getThenBranch());
if(thenStatement instanceof PsiReturnStatement || thenStatement instanceof PsiThrowStatement) {
myPlaceholder = parent;
return thenStatement.getText();
}
if(thenStatement instanceof PsiExpressionStatement) {
myPlaceholder = parent;
return thenStatement.getText() + "\n" + getBreakStatement();
}
}
}
if(conditionalExpression instanceof ConditionalExpression.Optional && myPlaceholder instanceof PsiExpression) {
PsiMethodCallExpression call = ExpressionUtils.getCallForQualifier((PsiExpression)myPlaceholder);
if(call != null && call.getParent() instanceof PsiExpressionStatement) {
PsiExpression[] args = call.getArgumentList().getExpressions();
if(args.length == 1 && "ifPresent".equals(call.getMethodExpression().getReferenceName())) {
FunctionHelper fn = FunctionHelper.create(args[0], 1);
if(fn != null) {
fn.transform(this, ((ConditionalExpression.Optional)conditionalExpression).unwrap("").getTrueBranch());
myPlaceholder = call.getParent();
return fn.getStatementText() + getBreakStatement();
}
}
}
}
String found =
declareResult(conditionalExpression.getCondition(), conditionalExpression.getType(), conditionalExpression.getFalseBranch(),
ResultKind.NON_FINAL);
return found + " = " + conditionalExpression.getTrueBranch() + ";\n" + getBreakStatement();
}
private ConditionalExpression tryUnwrapBoolean(ConditionalExpression.Boolean condition, boolean unwrapLazilyEvaluated) {
if (myPlaceholder instanceof PsiExpression) {
PsiExpression negation = BoolUtils.findNegation((PsiExpression)myPlaceholder);
if (negation != null) {
myPlaceholder = negation;
condition = condition.negate();
}
PsiElement parent = PsiUtil.skipParenthesizedExprUp(myPlaceholder.getParent());
ConditionalExpression candidate = null;
if (parent instanceof PsiPolyadicExpression) {
PsiPolyadicExpression expression = (PsiPolyadicExpression)parent;
PsiExpression[] operands = expression.getOperands();
if (operands.length > 1 && PsiTreeUtil.isAncestor(operands[0], myPlaceholder, false)) {
IElementType type = expression.getOperationTokenType();
if (type.equals(JavaTokenType.ANDAND)) {
candidate = condition
.toPlain("boolean", StreamEx.of(operands, 1, operands.length).map(PsiExpression::getText).joining(" && "), "false");
} else if (type.equals(JavaTokenType.OROR)) {
candidate = condition
.toPlain("boolean", "true", StreamEx.of(operands, 1, operands.length).map(PsiExpression::getText).joining(" || "));
}
}
} else if (parent instanceof PsiConditionalExpression) {
PsiConditionalExpression ternary = (PsiConditionalExpression)parent;
if (PsiTreeUtil.isAncestor(ternary.getCondition(), myPlaceholder, false)) {
PsiType type = ternary.getType();
PsiExpression thenExpression = ternary.getThenExpression();
PsiExpression elseExpression = ternary.getElseExpression();
if (type != null && thenExpression != null && elseExpression != null) {
candidate = condition.toPlain(type.getCanonicalText(), thenExpression.getText(), elseExpression.getText());
}
}
}
if (candidate != null &&
(unwrapLazilyEvaluated || ExpressionUtils.isSimpleExpression(createExpression(candidate.getFalseBranch())))) {
myPlaceholder = parent;
return candidate;
}
}
return condition;
}
@NotNull
private ConditionalExpression tryUnwrapOptional(ConditionalExpression.Optional condition, boolean unwrapLazilyEvaluated) {
if (myPlaceholder instanceof PsiExpression) {
PsiMethodCallExpression call = ExpressionUtils.getCallForQualifier((PsiExpression)myPlaceholder);
if (call != null && !(call.getParent() instanceof PsiExpressionStatement)) {
String name = call.getMethodExpression().getReferenceName();
PsiExpression[] args = call.getArgumentList().getExpressions();
if (args.length == 0 && "isPresent".equals(name)) {
myPlaceholder = call;
return new ConditionalExpression.Boolean(condition.getCondition(), false);
}
if (args.length == 1) {
String absentExpression = null;
if ("orElse".equals(name)) {
absentExpression = args[0].getText();
}
else if (unwrapLazilyEvaluated && "orElseGet".equals(name)) {
FunctionHelper helper = FunctionHelper.create(args[0], 0);
if (helper != null) {
helper.transform(this);
absentExpression = helper.getText();
}
}
if (absentExpression != null) {
myPlaceholder = call;
return condition.unwrap(absentExpression);
}
}
}
}
return condition;
}
public Project getProject() {
return myStatement.getProject();
}
public PsiExpression createExpression(String text) {
return myFactory.createExpressionFromText(text, myStatement);
}
public PsiStatement createStatement(String text) {
return myFactory.createStatementFromText(text, myStatement);
}
public PsiType createType(String text) {
return myFactory.createTypeFromText(text, myStatement);
}
}
static class OperationRecord {
Operation myOperation;
StreamVariable myInVar, myOutVar;
}
}