diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/streamMigration/MigrateToStreamFix.java b/java/java-analysis-impl/src/com/intellij/codeInspection/streamMigration/MigrateToStreamFix.java new file mode 100644 index 000000000000..fa3b1b765702 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/streamMigration/MigrateToStreamFix.java @@ -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 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 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 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(); + } + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/streamMigration/ReplaceWithCollectFix.java b/java/java-analysis-impl/src/com/intellij/codeInspection/streamMigration/ReplaceWithCollectFix.java new file mode 100644 index 000000000000..6735ab979e76 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/streamMigration/ReplaceWithCollectFix.java @@ -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 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 ""; + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/streamMigration/ReplaceWithCountFix.java b/java/java-analysis-impl/src/com/intellij/codeInspection/streamMigration/ReplaceWithCountFix.java new file mode 100644 index 000000000000..f2a5367499e7 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/streamMigration/ReplaceWithCountFix.java @@ -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 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"); + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/streamMigration/ReplaceWithFindFirstFix.java b/java/java-analysis-impl/src/com/intellij/codeInspection/streamMigration/ReplaceWithFindFirstFix.java new file mode 100644 index 000000000000..a08fb2c51fd1 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/streamMigration/ReplaceWithFindFirstFix.java @@ -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 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); + } + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/streamMigration/ReplaceWithForeachCallFix.java b/java/java-analysis-impl/src/com/intellij/codeInspection/streamMigration/ReplaceWithForeachCallFix.java new file mode 100644 index 000000000000..ac21d3a20f17 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/streamMigration/ReplaceWithForeachCallFix.java @@ -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 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() + "}"; + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/streamMigration/ReplaceWithMatchFix.java b/java/java-analysis-impl/src/com/intellij/codeInspection/streamMigration/ReplaceWithMatchFix.java new file mode 100644 index 000000000000..87248f6f06e0 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/streamMigration/ReplaceWithMatchFix.java @@ -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 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)"; + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/streamMigration/ReplaceWithSumFix.java b/java/java-analysis-impl/src/com/intellij/codeInspection/streamMigration/ReplaceWithSumFix.java new file mode 100644 index 000000000000..94cd6663c9d6 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/streamMigration/ReplaceWithSumFix.java @@ -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 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)); + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/StreamApiMigrationInspection.java b/java/java-analysis-impl/src/com/intellij/codeInspection/streamMigration/StreamApiMigrationInspection.java similarity index 59% rename from java/java-analysis-impl/src/com/intellij/codeInspection/StreamApiMigrationInspection.java rename to java/java-analysis-impl/src/com/intellij/codeInspection/streamMigration/StreamApiMigrationInspection.java index 0c329d21620c..a1b841123c9f 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/StreamApiMigrationInspection.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/streamMigration/StreamApiMigrationInspection.java @@ -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 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 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 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 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 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 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 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 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 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 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 extractOperationReplacements(PsiElementFactory factory) { + List extractOperationReplacements(PsiElementFactory factory) { List intermediateOps = new ArrayList<>(); while(true) { Operation operation = extractOperation(); diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterAllMatch.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterAllMatch.java index c3855eff9095..bb7231667c55 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterAllMatch.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterAllMatch.java @@ -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")); } } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterFindFirst.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterFindFirst.java index 30d217244b04..cb93b210381c 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterFindFirst.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterFindFirst.java @@ -5,6 +5,9 @@ import java.util.List; public class Main { public static String find(List> list) { + /* + Block comment + */ return list.stream().flatMap(Collection::stream).filter(string -> string.startsWith("ABC")).findFirst().map(string -> string.substring(3)).orElse(""); } } \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterFindFirstAssignment.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterFindFirstAssignment.java index 6d2085e355d2..9ff0c0efd750 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterFindFirstAssignment.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterFindFirstAssignment.java @@ -7,6 +7,7 @@ import java.util.Objects; public class Main { public void testMap(Map> map) throws Exception { int firstSize = map.values().stream().filter(Objects::nonNull).findFirst().map(List::size).orElse(0); + // comment System.out.println(firstSize); } } \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeAllMatch.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeAllMatch.java index 905ebecc4581..5da0ca7282f5 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeAllMatch.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeAllMatch.java @@ -5,6 +5,7 @@ public class Main { for(String[] arr : data) { for(String str : arr) { if(!str.startsWith("xyz")) { + // Comment return false; } } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeFindFirst.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeFindFirst.java index 8aac00f55962..58ffe8330371 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeFindFirst.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeFindFirst.java @@ -8,6 +8,9 @@ public class Main { for(String string : innerList) { if(string.startsWith("ABC")) { return string.substring(3); + /* + Block comment + */ } } } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeFindFirstAssignment.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeFindFirstAssignment.java index f502b379e643..ce2560c53518 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeFindFirstAssignment.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeFindFirstAssignment.java @@ -9,6 +9,7 @@ public class Main { for(List list : map.values()) { if(list != null) { firstSize = list.size(); + // comment break; } } diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/quickFix/StreamApiMigrationInspectionTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/quickFix/StreamApiMigrationInspectionTest.java index ec6bd7bae578..5555b43c1376 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/quickFix/StreamApiMigrationInspectionTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/quickFix/StreamApiMigrationInspectionTest.java @@ -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 }; } diff --git a/resources/src/META-INF/IdeaPlugin.xml b/resources/src/META-INF/IdeaPlugin.xml index 4641d6d218f9..059751108a06 100644 --- a/resources/src/META-INF/IdeaPlugin.xml +++ b/resources/src/META-INF/IdeaPlugin.xml @@ -756,7 +756,7 @@ implementationClass="com.intellij.codeInspection.AnonymousCanBeLambdaInspection" /> + implementationClass="com.intellij.codeInspection.streamMigration.StreamApiMigrationInspection"/>