move StreamApiMigrationInspection to separate package, extract inner classes, add "suggest foreach" option, restore comments in anyMatch/findFirst/etc., fix trivial replacements with anyMatch/findFirst

This commit is contained in:
Tagir Valeev
2016-09-20 13:37:14 +07:00
parent 8cbf610859
commit 70ab891daf
16 changed files with 735 additions and 514 deletions
@@ -0,0 +1,137 @@
/*
* 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.streamMigration;
import com.intellij.codeInsight.FileModificationService;
import com.intellij.codeInspection.LambdaCanBeMethodReferenceInspection;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.util.PsiTreeUtil;
import com.siyeh.ig.psiutils.ExpressionUtils;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* @author Tagir Valeev
*/
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();
StreamApiMigrationInspection.TerminalBlock tb = StreamApiMigrationInspection.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 StreamApiMigrationInspection.TerminalBlock tb,
@NotNull List<String> replacements);
static void replaceWithNumericAddition(@NotNull Project project,
PsiForeachStatement foreachStatement,
PsiVariable var,
StringBuilder builder,
String expressionType) {
PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project);
restoreComments(foreachStatement, foreachStatement.getBody());
if (StreamApiMigrationInspection.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));
removeLoop(foreachStatement);
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;
LambdaCanBeMethodReferenceInspection.replaceAllLambdasWithMethodReferences(result);
CodeStyleManager.getInstance(project).reformat(JavaCodeStyleManager.getInstance(project).shortenClassReferences(result));
}
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() + ")";
}
static void removeLoop(@NotNull PsiForeachStatement statement) {
PsiElement parent = statement.getParent();
if (parent instanceof PsiLabeledStatement) {
parent.delete();
}
else {
statement.delete();
}
}
}
@@ -0,0 +1,134 @@
/*
* 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.streamMigration;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.codeStyle.SuggestedNameInfo;
import com.intellij.psi.codeStyle.VariableKind;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* @author Tagir Valeev
*/
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 StreamApiMigrationInspection.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() && StreamApiMigrationInspection.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;
}
PsiExpression itemToAdd = methodCallExpression.getArgumentList().getExpressions()[0];
intermediateOps.add(createMapperFunctionalExpressionText(tb.getVariable(), itemToAdd));
final StringBuilder builder = generateStream(iteratedValue, intermediateOps);
final PsiExpression qualifierExpression = methodCallExpression.getMethodExpression().getQualifierExpression();
final PsiExpression initializer = StreamApiMigrationInspection
.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);
removeLoop(foreachStatement);
return;
}
final String qualifierText = qualifierExpression != null ? qualifierExpression.getText() + "." : "";
JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(project);
SuggestedNameInfo suggestedNameInfo =
codeStyleManager.suggestVariableName(VariableKind.LOCAL_VARIABLE, null, null, itemToAdd.getType(), false);
if (suggestedNameInfo.names.length == 0) {
suggestedNameInfo = codeStyleManager.suggestVariableName(VariableKind.LOCAL_VARIABLE, "item", null, itemToAdd.getType(), false);
}
String varName = codeStyleManager.suggestUniqueVariableName(suggestedNameInfo, methodCallExpression, 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));
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 (!StreamApiMigrationInspection.isIdentityMapping(variable, expression)) {
return new StreamApiMigrationInspection.MapOp(expression, variable).createReplacement(null);
}
return "";
}
}
@@ -0,0 +1,53 @@
/*
* 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.streamMigration;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* @author Tagir Valeev
*/
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 StreamApiMigrationInspection.TerminalBlock tb,
@NotNull List<String> intermediateOps) {
PsiExpression operand = StreamApiMigrationInspection.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");
}
}
@@ -0,0 +1,100 @@
/*
* 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.streamMigration;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.siyeh.ig.psiutils.ExpressionUtils;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* @author Tagir Valeev
*/
class ReplaceWithFindFirstFix extends MigrateToStreamFix {
@NotNull
@Override
public String getFamilyName() {
return "Replace with findFirst()";
}
@Override
void migrate(@NotNull Project project,
@NotNull ProblemDescriptor descriptor,
@NotNull PsiForeachStatement foreachStatement,
@NotNull PsiExpression iteratedValue,
@NotNull PsiStatement body,
@NotNull StreamApiMigrationInspection.TerminalBlock tb,
@NotNull List<String> intermediateOps) {
PsiStatement statement = tb.getSingleStatement();
PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project);
intermediateOps.add("");
if (statement instanceof PsiReturnStatement) {
PsiReturnStatement returnStatement = (PsiReturnStatement)statement;
PsiExpression value = returnStatement.getReturnValue();
if (value == null) return;
PsiReturnStatement nextReturnStatement = StreamApiMigrationInspection.getNextReturnStatement(foreachStatement);
if (nextReturnStatement == null) return;
PsiExpression orElseExpression = nextReturnStatement.getReturnValue();
if (!ExpressionUtils.isSimpleExpression(orElseExpression)) return;
StringBuilder builder = generateStream(iteratedValue, intermediateOps).append(".findFirst()");
if (!(value instanceof PsiReferenceExpression) || ((PsiReferenceExpression)value).resolve() != tb.getVariable()) {
builder.append(".map(").append(tb.getVariable().getName()).append(" -> ").append(value.getText()).append(")");
}
builder.append(".orElse(").append(orElseExpression.getText()).append(")");
restoreComments(foreachStatement, body);
boolean siblings = nextReturnStatement.getParent() == foreachStatement.getParent();
PsiElement result = foreachStatement.replace(elementFactory.createStatementFromText("return " + builder + ";", foreachStatement));
if (siblings) {
nextReturnStatement.delete();
}
simplifyAndFormat(project, result);
}
else {
PsiStatement[] statements = tb.getStatements();
if (statements.length != 2) return;
PsiAssignmentExpression assignment = ExpressionUtils.getAssignment(statements[0]);
if (assignment == null) return;
PsiExpression lValue = assignment.getLExpression();
if (!(lValue instanceof PsiReferenceExpression)) return;
PsiElement element = ((PsiReferenceExpression)lValue).resolve();
if (!(element instanceof PsiVariable)) return;
PsiVariable var = (PsiVariable)element;
PsiExpression value = assignment.getRExpression();
if (value == null) return;
StringBuilder builder = generateStream(iteratedValue, intermediateOps).append(".findFirst()");
if (!(value instanceof PsiReferenceExpression) || ((PsiReferenceExpression)value).resolve() != tb.getVariable()) {
builder.append(".map(").append(tb.getVariable().getName()).append(" -> ").append(value.getText()).append(")");
}
restoreComments(foreachStatement, body);
if (StreamApiMigrationInspection.isDeclarationJustBefore(var, foreachStatement)) {
PsiExpression initializer = var.getInitializer();
if (initializer != null) {
PsiElement result =
initializer.replace(elementFactory.createExpressionFromText(builder + ".orElse(" + initializer.getText() + ")", initializer));
removeLoop(foreachStatement);
simplifyAndFormat(project, result);
return;
}
}
PsiElement result = foreachStatement.replace(
elementFactory.createStatementFromText(var.getName() + " = " + builder + ".orElse(" + var.getName() + ");", foreachStatement));
simplifyAndFormat(project, result);
}
}
}
@@ -0,0 +1,90 @@
/*
* 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.streamMigration;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* @author Tagir Valeev
*/
class ReplaceWithForeachCallFix extends MigrateToStreamFix {
private static final Logger LOG = Logger.getInstance("#" + ReplaceWithForeachCallFix.class.getName());
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 StreamApiMigrationInspection.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 = tb.getVariable().getName() + " -> " + wrapInBlock(block);
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 wrapInBlock(PsiElement block) {
if (block instanceof PsiExpressionStatement) {
return ((PsiExpressionStatement)block).getExpression().getText();
}
if (block instanceof PsiCodeBlock) {
return block.getText();
}
return "{" + block.getText() + "}";
}
}
@@ -0,0 +1,110 @@
/*
* 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.streamMigration;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.siyeh.ig.psiutils.BoolUtils;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* @author Tagir Valeev
*/
class ReplaceWithMatchFix extends MigrateToStreamFix {
private static final Logger LOG = Logger.getInstance("#" + ReplaceWithMatchFix.class.getName());
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 StreamApiMigrationInspection.TerminalBlock tb,
@NotNull List<String> intermediateOps) {
PsiReturnStatement returnStatement = (PsiReturnStatement)tb.getSingleStatement();
PsiExpression value = returnStatement.getReturnValue();
PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project);
intermediateOps.add("");
restoreComments(foreachStatement, body);
if (StreamApiMigrationInspection.isLiteral(value, Boolean.TRUE) || StreamApiMigrationInspection.isLiteral(value, Boolean.FALSE)) {
boolean foundResult = (boolean)((PsiLiteralExpression)value).getValue();
PsiReturnStatement nextReturnStatement = StreamApiMigrationInspection.getNextReturnStatement(foreachStatement);
if (nextReturnStatement != null && StreamApiMigrationInspection.isLiteral(nextReturnStatement.getReturnValue(), !foundResult)) {
String methodName = foundResult ? "anyMatch" : "noneMatch";
String streamText = generateStream(iteratedValue, intermediateOps).toString();
streamText = addTerminalOperation(streamText, methodName, foreachStatement, tb);
boolean siblings = nextReturnStatement.getParent() == foreachStatement.getParent();
PsiElement result =
foreachStatement.replace(elementFactory.createStatementFromText("return " + streamText + ";", foreachStatement));
if (siblings) {
nextReturnStatement.delete();
}
simplifyAndFormat(project, result);
return;
}
}
if (!StreamApiMigrationInspection.isVariableReferenced(tb.getVariable(), value)) {
String streamText = generateStream(iteratedValue, intermediateOps).toString();
streamText = addTerminalOperation(streamText, "anyMatch", foreachStatement, tb);
String replacement = "if(" + streamText + "){" + returnStatement.getText() + "}";
PsiElement result = foreachStatement.replace(elementFactory.createStatementFromText(replacement, foreachStatement));
simplifyAndFormat(project, result);
}
}
private static String addTerminalOperation(String origStream, String methodName, @NotNull PsiElement contextElement,
@NotNull StreamApiMigrationInspection.TerminalBlock tb) {
PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(contextElement.getProject());
PsiExpression stream = elementFactory.createExpressionFromText(origStream, contextElement);
LOG.assertTrue(stream instanceof PsiMethodCallExpression);
PsiElement nameElement = ((PsiMethodCallExpression)stream).getMethodExpression().getReferenceNameElement();
if (nameElement != null && nameElement.getText().equals("filter")) {
if (methodName.equals("noneMatch")) {
// Try to reduce noneMatch(x -> !(condition)) to allMatch(x -> condition)
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, lambdaBody.getText());
lambdaBody.replace(negated);
methodName = "allMatch";
}
}
}
nameElement.replace(elementFactory.createIdentifier(methodName));
return stream.getText();
}
return origStream + "." + methodName + "(" + tb.getVariable().getName() + " -> true)";
}
}
@@ -0,0 +1,71 @@
/*
* 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.streamMigration;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.Locale;
/**
* @author Tagir Valeev
*/
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 StreamApiMigrationInspection.TerminalBlock tb,
@NotNull List<String> intermediateOps) {
PsiAssignmentExpression assignment = tb.getSingleExpression(PsiAssignmentExpression.class);
if (assignment == null) return;
PsiVariable var = StreamApiMigrationInspection.extractAccumulator(assignment);
if (var == null) return;
PsiExpression addend = StreamApiMigrationInspection.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 + "(" + StreamApiMigrationInspection.createLambda(tb.getVariable(), addend) + ")");
final StringBuilder builder = generateStream(iteratedValue, intermediateOps);
builder.append(".sum()");
replaceWithNumericAddition(project, foreachStatement, var, builder, typeName.toLowerCase(Locale.ENGLISH));
}
}
@@ -13,26 +13,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.codeInspection;
package com.intellij.codeInspection.streamMigration;
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.codeInspection.*;
import com.intellij.codeInspection.ui.MultipleCheckboxOptionsPanel;
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;
@@ -62,17 +57,17 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
private static final Logger LOG = Logger.getInstance("#" + StreamApiMigrationInspection.class.getName());
public boolean REPLACE_TRIVIAL_FOREACH;
public boolean SUGGEST_FOREACH;
private HighlightDisplayKey myKey;
@Nullable
@Override
public JComponent createOptionsPanel() {
return new SingleCheckboxOptionsPanel(
"Replace trivial foreach statements",
this,
"REPLACE_TRIVIAL_FOREACH"
);
MultipleCheckboxOptionsPanel panel = new MultipleCheckboxOptionsPanel(this);
panel.addCheckbox("Suggest to replace with forEach or forEachOrdered", "SUGGEST_FOREACH");
panel.addCheckbox("Replace trivial foreach statements", "REPLACE_TRIVIAL_FOREACH");
return panel;
}
@Nls
@@ -166,14 +161,15 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
methodCallExpression.getMethodExpression().getQualifierExpression(), statement) != null) {
methodName = "collect";
} else {
if (!SUGGEST_FOREACH) return;
methodName = "forEach";
}
}
registerProblem(holder, isOnTheFly, 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())))) {
else if (SUGGEST_FOREACH && (!operations.isEmpty() ||
(!isArray && (REPLACE_TRIVIAL_FOREACH || !isTrivial(body, statement.getIterationParameter()))))) {
final List<LocalQuickFix> fixes = new ArrayList<>();
fixes.add(new ReplaceWithForeachCallFix("forEach"));
if (!operations.isEmpty()) {
@@ -183,10 +179,11 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
registerProblem(holder, isOnTheFly, statement, "forEach", fixes.toArray(new LocalQuickFix[fixes.size()]));
}
} else {
if(nonFinalVariables.isEmpty() && tb.getSingleStatement() instanceof PsiReturnStatement) {
if (operations.isEmpty() && !REPLACE_TRIVIAL_FOREACH) return;
if (nonFinalVariables.isEmpty() && tb.getSingleStatement() instanceof PsiReturnStatement) {
handleSingleReturn(statement, tb, operations);
}
if(nonFinalVariables.size() == 1) {
if (nonFinalVariables.size() == 1) {
PsiStatement[] statements = tb.getStatements();
if (statements.length == 2) {
PsiStatement breakStatement = statements[1];
@@ -256,7 +253,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
}
@Contract("_, null -> false")
private static boolean isVariableReferenced(PsiVariable variable, PsiExpression value) {
static boolean isVariableReferenced(PsiVariable variable, PsiExpression value) {
return !PsiTreeUtil
.processElements(value, e -> !(e instanceof PsiReferenceExpression) || ((PsiReferenceExpression)e).resolve() != variable);
}
@@ -311,7 +308,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
}
@Contract("null, _ -> false")
private static boolean isLiteral(PsiElement element, Object value) {
static boolean isLiteral(PsiElement element, Object value) {
return element instanceof PsiLiteralExpression && value.equals(((PsiLiteralExpression)element).getValue());
}
@@ -326,7 +323,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
}
@Nullable
private static PsiExpression extractAddend(PsiAssignmentExpression assignment) {
static PsiExpression extractAddend(PsiAssignmentExpression assignment) {
if(JavaTokenType.PLUSEQ.equals(assignment.getOperationTokenType())) {
return assignment.getRExpression();
} else if(JavaTokenType.EQ.equals(assignment.getOperationTokenType())) {
@@ -346,7 +343,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
}
@Nullable
private static PsiVariable extractAccumulator(PsiAssignmentExpression assignment) {
static PsiVariable extractAccumulator(PsiAssignmentExpression assignment) {
if(!(assignment.getLExpression() instanceof PsiReferenceExpression)) return null;
PsiReferenceExpression lExpr = (PsiReferenceExpression)assignment.getLExpression();
PsiElement accumulator = lExpr.resolve();
@@ -370,7 +367,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
}
@Contract("null -> null")
private static PsiExpression extractIncrementedLValue(PsiExpression expression) {
static PsiExpression extractIncrementedLValue(PsiExpression expression) {
if(expression instanceof PsiPostfixExpression) {
if(JavaTokenType.PLUSPLUS.equals(((PsiPostfixExpression)expression).getOperationTokenType())) {
return ((PsiPostfixExpression)expression).getOperand();
@@ -435,7 +432,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
return var;
}
private static boolean isAddAllCall(TerminalBlock tb) {
static boolean isAddAllCall(TerminalBlock tb) {
final PsiVariable variable = tb.getVariable();
final PsiMethodCallExpression methodCallExpression = tb.getSingleMethodCall();
LOG.assertTrue(methodCallExpression != null);
@@ -549,7 +546,8 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
return ContainerUtil.find(method.getThrowsList().getReferencedTypes(), type -> !ExceptionUtil.isUncheckedException(type)) != null;
}
private static boolean isIdentityMapping(PsiVariable variable, PsiExpression mapperCall) {
@Contract("_, null -> false")
static boolean isIdentityMapping(PsiVariable variable, PsiExpression mapperCall) {
return mapperCall instanceof PsiReferenceExpression && ((PsiReferenceExpression)mapperCall).resolve() == variable;
}
@@ -557,169 +555,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
return variable.getName() + " -> " + expression.getText();
}
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,
PsiVariable 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));
removeLoop(foreachStatement);
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;
LambdaCanBeMethodReferenceInspection.replaceAllLambdasWithMethodReferences(result);
CodeStyleManager.getInstance(project).reformat(JavaCodeStyleManager.getInstance(project).shortenClassReferences(result));
}
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() + ")";
}
static void removeLoop(@NotNull PsiForeachStatement statement) {
PsiElement parent = statement.getParent();
if(parent instanceof PsiLabeledStatement) {
parent.delete();
} else {
statement.delete();
}
}
}
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 = tb.getVariable().getName() + " -> " + wrapInBlock(block);
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 wrapInBlock(PsiElement block) {
if(block instanceof PsiExpressionStatement) {
return ((PsiExpressionStatement)block).getExpression().getText();
}
if(block instanceof PsiCodeBlock) {
return block.getText();
}
return "{" + block.getText() + "}";
}
}
@Nullable
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));
@@ -746,329 +582,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
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;
}
PsiExpression itemToAdd = methodCallExpression.getArgumentList().getExpressions()[0];
intermediateOps.add(createMapperFunctionalExpressionText(tb.getVariable(), itemToAdd));
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);
removeLoop(foreachStatement);
return;
}
final String qualifierText = qualifierExpression != null ? qualifierExpression.getText() + "." : "";
JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(project);
SuggestedNameInfo suggestedNameInfo =
codeStyleManager.suggestVariableName(VariableKind.LOCAL_VARIABLE, null, null, itemToAdd.getType(), false);
if (suggestedNameInfo.names.length == 0) {
suggestedNameInfo = codeStyleManager.suggestVariableName(VariableKind.LOCAL_VARIABLE, "item", null, itemToAdd.getType(), false);
}
String varName = codeStyleManager.suggestUniqueVariableName(suggestedNameInfo, methodCallExpression, 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));
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();
PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project);
if (isLiteral(value, Boolean.TRUE) || isLiteral(value, Boolean.FALSE)) {
boolean foundResult = (boolean)((PsiLiteralExpression)value).getValue();
PsiReturnStatement nextReturnStatement = getNextReturnStatement(foreachStatement);
if (nextReturnStatement != null && isLiteral(nextReturnStatement.getReturnValue(), !foundResult)) {
String methodName = foundResult ? "anyMatch" : "noneMatch";
String streamText = generateStream(iteratedValue, intermediateOps).toString();
streamText = addTerminalOperation(streamText, methodName, foreachStatement, tb);
boolean siblings = nextReturnStatement.getParent() == foreachStatement.getParent();
PsiElement result =
foreachStatement.replace(elementFactory.createStatementFromText("return " + streamText + ";", foreachStatement));
if (siblings) {
nextReturnStatement.delete();
}
simplifyAndFormat(project, result);
return;
}
}
if(!isVariableReferenced(tb.getVariable(), value)) {
String streamText = generateStream(iteratedValue, intermediateOps).toString();
streamText = addTerminalOperation(streamText, "anyMatch", foreachStatement, tb);
String replacement = "if(" + streamText + "){" + returnStatement.getText() + "}";
PsiElement result = foreachStatement.replace(elementFactory.createStatementFromText(replacement, foreachStatement));
simplifyAndFormat(project, result);
}
}
private static String addTerminalOperation(String origStream, String methodName, @NotNull PsiElement contextElement,
@NotNull TerminalBlock tb) {
PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(contextElement.getProject());
PsiExpression stream = elementFactory.createExpressionFromText(origStream, contextElement);
LOG.assertTrue(stream instanceof PsiMethodCallExpression);
PsiElement nameElement = ((PsiMethodCallExpression)stream).getMethodExpression().getReferenceNameElement();
if (nameElement != null && nameElement.getText().equals("filter")) {
if (methodName.equals("noneMatch")) {
// Try to reduce noneMatch(x -> !(condition)) to allMatch(x -> condition)
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, lambdaBody.getText());
lambdaBody.replace(negated);
methodName = "allMatch";
}
}
}
nameElement.replace(elementFactory.createIdentifier(methodName));
return stream.getText();
}
return origStream + "." + methodName + "(" + tb.getVariable().getName() + " -> true)";
}
}
private static class ReplaceWithFindFirstFix extends MigrateToStreamFix {
@NotNull
@Override
public String getFamilyName() {
return "Replace with findFirst()";
}
@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) {
PsiStatement statement = tb.getSingleStatement();
PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project);
if(statement instanceof PsiReturnStatement) {
PsiReturnStatement returnStatement = (PsiReturnStatement)statement;
PsiExpression value = returnStatement.getReturnValue();
if (value == null) return;
PsiReturnStatement nextReturnStatement = getNextReturnStatement(foreachStatement);
if (nextReturnStatement == null) return;
PsiExpression orElseExpression = nextReturnStatement.getReturnValue();
if (!ExpressionUtils.isSimpleExpression(orElseExpression)) return;
StringBuilder builder = generateStream(iteratedValue, intermediateOps).append(".findFirst()");
if (!(value instanceof PsiReferenceExpression) || ((PsiReferenceExpression)value).resolve() != tb.getVariable()) {
builder.append(".map(").append(tb.getVariable().getName()).append(" -> ").append(value.getText()).append(")");
}
builder.append(".orElse(").append(orElseExpression.getText()).append(")");
boolean siblings = nextReturnStatement.getParent() == foreachStatement.getParent();
PsiElement result = foreachStatement.replace(elementFactory.createStatementFromText("return " + builder + ";", foreachStatement));
if (siblings) {
nextReturnStatement.delete();
}
simplifyAndFormat(project, result);
} else {
PsiStatement[] statements = tb.getStatements();
if(statements.length != 2) return;
PsiAssignmentExpression assignment = ExpressionUtils.getAssignment(statements[0]);
if(assignment == null) return;
PsiExpression lValue = assignment.getLExpression();
if(!(lValue instanceof PsiReferenceExpression)) return;
PsiElement element = ((PsiReferenceExpression)lValue).resolve();
if(!(element instanceof PsiVariable)) return;
PsiVariable var = (PsiVariable)element;
PsiExpression value = assignment.getRExpression();
if(value == null) return;
StringBuilder builder = generateStream(iteratedValue, intermediateOps).append(".findFirst()");
if (!(value instanceof PsiReferenceExpression) || ((PsiReferenceExpression)value).resolve() != tb.getVariable()) {
builder.append(".map(").append(tb.getVariable().getName()).append(" -> ").append(value.getText()).append(")");
}
if(isDeclarationJustBefore(var, foreachStatement)) {
PsiExpression initializer = var.getInitializer();
if(initializer != null) {
PsiElement result =
initializer.replace(elementFactory.createExpressionFromText(builder + ".orElse(" + initializer.getText() + ")", initializer));
removeLoop(foreachStatement);
simplifyAndFormat(project, result);
return;
}
}
PsiElement result = foreachStatement.replace(
elementFactory.createStatementFromText(var.getName() + " = " + builder + ".orElse(" + var.getName() + ");", foreachStatement));
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;
PsiVariable 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 + "(" + createLambda(tb.getVariable(), addend) + ")");
final StringBuilder builder = generateStream(iteratedValue, intermediateOps);
builder.append(".sum()");
replaceWithNumericAddition(project, foreachStatement, var, builder, typeName.toLowerCase(Locale.ENGLISH));
}
}
private static boolean isDeclarationJustBefore(PsiVariable var, PsiStatement nextStatement) {
static boolean isDeclarationJustBefore(PsiVariable var, PsiStatement nextStatement) {
PsiElement declaration = var.getParent();
PsiElement nextStatementParent = nextStatement.getParent();
if(nextStatementParent instanceof PsiLabeledStatement) {
@@ -1333,7 +847,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
}
@NotNull
private List<String> extractOperationReplacements(PsiElementFactory factory) {
List<String> extractOperationReplacements(PsiElementFactory factory) {
List<String> intermediateOps = new ArrayList<>();
while(true) {
Operation operation = extractOperation();
@@ -4,6 +4,7 @@ import java.util.Arrays;
public class Main {
boolean find(String[][] data) {
// Comment
return Arrays.stream(data).flatMap(Arrays::stream).allMatch(str -> str.startsWith("xyz"));
}
}
@@ -5,6 +5,9 @@ import java.util.List;
public class Main {
public static String find(List<List<String>> list) {
/*
Block comment
*/
return list.stream().flatMap(Collection::stream).filter(string -> string.startsWith("ABC")).findFirst().map(string -> string.substring(3)).orElse("");
}
}
@@ -7,6 +7,7 @@ import java.util.Objects;
public class Main {
public void testMap(Map<String, List<String>> map) throws Exception {
int firstSize = map.values().stream().filter(Objects::nonNull).findFirst().map(List::size).orElse(0);
// comment
System.out.println(firstSize);
}
}
@@ -5,6 +5,7 @@ public class Main {
for(String[] arr : da<caret>ta) {
for(String str : arr) {
if(!str.startsWith("xyz")) {
// Comment
return false;
}
}
@@ -8,6 +8,9 @@ public class Main {
for(String string : innerList) {
if(string.startsWith("ABC")) {
return string.substring(3);
/*
Block comment
*/
}
}
}
@@ -9,6 +9,7 @@ public class Main {
for(List<String> list : map.valu<caret>es()) {
if(list != null) {
firstSize = list.size();
// comment
break;
}
}
@@ -16,7 +16,7 @@
package com.intellij.codeInsight.daemon.quickFix;
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.codeInspection.StreamApiMigrationInspection;
import com.intellij.codeInspection.streamMigration.StreamApiMigrationInspection;
import org.jetbrains.annotations.NotNull;
@@ -24,8 +24,10 @@ public class StreamApiMigrationInspectionTest extends LightQuickFixParameterized
@NotNull
@Override
protected LocalInspectionTool[] configureLocalInspectionTools() {
StreamApiMigrationInspection inspection = new StreamApiMigrationInspection();
inspection.SUGGEST_FOREACH = true;
return new LocalInspectionTool[]{
new StreamApiMigrationInspection()
inspection
};
}
+1 -1
View File
@@ -756,7 +756,7 @@
implementationClass="com.intellij.codeInspection.AnonymousCanBeLambdaInspection" />
<localInspection groupPath="Java" language="JAVA" shortName="Convert2streamapi" displayName="foreach loop can be collapsed with Stream API"
groupKey="group.names.language.level.specific.issues.and.migration.aids" groupBundle="messages.InspectionsBundle" enabledByDefault="true" level="INFORMATION"
implementationClass="com.intellij.codeInspection.StreamApiMigrationInspection"/>
implementationClass="com.intellij.codeInspection.streamMigration.StreamApiMigrationInspection"/>
<localInspection groupPath="Java" language="JAVA" shortName="Anonymous2MethodRef" displayName="Anonymous type can be replaced with method reference"
groupKey="group.names.language.level.specific.issues.and.migration.aids" groupBundle="messages.InspectionsBundle" enabledByDefault="true" level="WARNING"
implementationClass="com.intellij.codeInspection.AnonymousCanBeMethodReferenceInspection" />