mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
refactor StreamApiMigrationInspection: extract visitor to inner class
This commit is contained in:
+191
-191
@@ -25,7 +25,6 @@ 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.pom.java.LanguageLevel;
|
||||
import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.controlFlow.*;
|
||||
@@ -98,159 +97,10 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
|
||||
@NotNull
|
||||
@Override
|
||||
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
|
||||
return new JavaElementVisitor() {
|
||||
@Override
|
||||
public void visitForeachStatement(PsiForeachStatement statement) {
|
||||
super.visitForeachStatement(statement);
|
||||
if (PsiUtil.getLanguageLevel(statement).isAtLeast(LanguageLevel.JDK_1_8)) {
|
||||
final PsiExpression iteratedValue = statement.getIteratedValue();
|
||||
final PsiStatement body = statement.getBody();
|
||||
if (iteratedValue != null && body != null) {
|
||||
final PsiType iteratedValueType = iteratedValue.getType();
|
||||
final PsiClass iteratorClass = PsiUtil.resolveClassInClassTypeOnly(iteratedValueType);
|
||||
PsiClass collectionClass = null;
|
||||
final boolean isArray;
|
||||
if(iteratedValueType instanceof PsiArrayType) {
|
||||
if(!isSupported(((PsiArrayType)iteratedValueType).getComponentType())) return;
|
||||
isArray = true;
|
||||
} else {
|
||||
collectionClass = JavaPsiFacade.getInstance(body.getProject()).findClass(CommonClassNames.JAVA_UTIL_COLLECTION, statement.getResolveScope());
|
||||
if (collectionClass != null && InheritanceUtil.isInheritorOrSelf(iteratorClass, collectionClass, true)) {
|
||||
isArray = false;
|
||||
} else return;
|
||||
}
|
||||
try {
|
||||
if (ExceptionUtil.getThrownCheckedExceptions(new PsiElement[]{body}).isEmpty()) {
|
||||
TerminalBlock tb = TerminalBlock.from(statement.getIterationParameter(), body);
|
||||
List<Operation> operations = tb.extractOperations();
|
||||
|
||||
if(tb.isEmpty()) return;
|
||||
|
||||
final ControlFlow controlFlow = ControlFlowFactory.getInstance(holder.getProject())
|
||||
.getControlFlow(body, LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance());
|
||||
final Collection<PsiStatement> exitPoints = ControlFlowUtil
|
||||
.findExitPointsAndStatements(controlFlow, tb.getStartOffset(controlFlow), tb.getEndOffset(controlFlow),
|
||||
new IntArrayList(), PsiContinueStatement.class,
|
||||
PsiBreakStatement.class, PsiReturnStatement.class, PsiThrowStatement.class);
|
||||
int startOffset = controlFlow.getStartOffset(body);
|
||||
int endOffset = controlFlow.getEndOffset(body);
|
||||
final List<PsiVariable> nonFinalVariables = StreamEx
|
||||
.of(ControlFlowUtil.getUsedVariables(controlFlow, startOffset, endOffset))
|
||||
.remove(variable -> HighlightControlFlowUtil.isEffectivelyFinal(variable, body, null))
|
||||
.toList();
|
||||
|
||||
if (exitPoints.isEmpty()) {
|
||||
if(getIncrementedVariable(tb, operations, nonFinalVariables) != null) {
|
||||
registerProblem(holder, isOnTheFly, statement, "count", new ReplaceWithCountFix());
|
||||
}
|
||||
if(getAccumulatedVariable(tb, operations, nonFinalVariables) != null) {
|
||||
registerProblem(holder, isOnTheFly, statement, "sum", new ReplaceWithSumFix());
|
||||
}
|
||||
if(!nonFinalVariables.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if ((isArray || !isRawSubstitution(iteratedValueType, collectionClass)) && isCollectCall(tb, operations)) {
|
||||
boolean addAll = operations.isEmpty() && isAddAllCall(tb);
|
||||
String methodName;
|
||||
if(addAll) {
|
||||
methodName = "addAll";
|
||||
} else {
|
||||
PsiMethodCallExpression methodCallExpression = tb.getSingleMethodCall();
|
||||
if(methodCallExpression != null && extractReplaceableCollectionInitializer(
|
||||
methodCallExpression.getMethodExpression().getQualifierExpression(), statement) != null) {
|
||||
methodName = "collect";
|
||||
} else {
|
||||
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 (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()) {
|
||||
//for .stream()
|
||||
fixes.add(new ReplaceWithForeachCallFix("forEachOrdered"));
|
||||
}
|
||||
registerProblem(holder, isOnTheFly, statement, "forEach", fixes.toArray(new LocalQuickFix[fixes.size()]));
|
||||
}
|
||||
} else {
|
||||
if (operations.isEmpty() && !REPLACE_TRIVIAL_FOREACH) return;
|
||||
if (nonFinalVariables.isEmpty() && tb.getSingleStatement() instanceof PsiReturnStatement) {
|
||||
handleSingleReturn(statement, tb, operations);
|
||||
}
|
||||
if (nonFinalVariables.size() == 1) {
|
||||
PsiStatement[] statements = tb.getStatements();
|
||||
if (statements.length == 2) {
|
||||
PsiStatement breakStatement = statements[1];
|
||||
if (!(breakStatement instanceof PsiBreakStatement) ||
|
||||
((PsiBreakStatement)breakStatement).findExitedStatement() != statement) {
|
||||
return;
|
||||
}
|
||||
PsiAssignmentExpression assignment = ExpressionUtils.getAssignment(statements[0]);
|
||||
if(assignment == null) return;
|
||||
PsiExpression lValue = assignment.getLExpression();
|
||||
if (!(lValue instanceof PsiReferenceExpression)) return;
|
||||
PsiElement var = ((PsiReferenceExpression)lValue).resolve();
|
||||
if(!(var instanceof PsiVariable) || !nonFinalVariables.contains(var)) return;
|
||||
PsiExpression rValue = assignment.getRExpression();
|
||||
if(rValue == null || isVariableReferenced((PsiVariable)var, rValue)) return;
|
||||
if(tb.getVariable() instanceof PsiPrimitiveType && !isIdentityMapping(tb.getVariable(), rValue)) return;
|
||||
registerProblem(holder, isOnTheFly, statement, "findFirst", new ReplaceWithFindFirstFix());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (AnalysisCanceledException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void handleSingleReturn(PsiForeachStatement statement,
|
||||
TerminalBlock tb,
|
||||
List<Operation> operations) {
|
||||
PsiReturnStatement returnStatement = (PsiReturnStatement)tb.getSingleStatement();
|
||||
PsiExpression value = returnStatement.getReturnValue();
|
||||
PsiReturnStatement nextReturnStatement = getNextReturnStatement(statement);
|
||||
if(nextReturnStatement != null && (isLiteral(value, Boolean.TRUE) || isLiteral(value, Boolean.FALSE))) {
|
||||
boolean foundResult = (boolean)((PsiLiteralExpression)value).getValue();
|
||||
if(isLiteral(nextReturnStatement.getReturnValue(), !foundResult)) {
|
||||
String methodName;
|
||||
if (foundResult) {
|
||||
methodName = "anyMatch";
|
||||
}
|
||||
else {
|
||||
methodName = "noneMatch";
|
||||
if(!operations.isEmpty()) {
|
||||
Operation lastOp = operations.get(operations.size() - 1);
|
||||
if(lastOp instanceof FilterOp && BoolUtils.isNegation(lastOp.getExpression())) {
|
||||
methodName = "allMatch";
|
||||
}
|
||||
}
|
||||
}
|
||||
registerProblem(holder, isOnTheFly, statement, methodName, new ReplaceWithMatchFix(methodName));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!isVariableReferenced(tb.getVariable(), value)) {
|
||||
registerProblem(holder, isOnTheFly, statement, "anyMatch", new ReplaceWithMatchFix("anyMatch"));
|
||||
}
|
||||
if(nextReturnStatement != null && ExpressionUtils.isSimpleExpression(nextReturnStatement.getReturnValue())
|
||||
&& (!(tb.getVariable().getType() instanceof PsiPrimitiveType) || isIdentityMapping(tb.getVariable(), value))) {
|
||||
registerProblem(holder, isOnTheFly, statement, "findFirst", new ReplaceWithFindFirstFix());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isRawSubstitution(PsiType iteratedValueType, PsiClass collectionClass) {
|
||||
return iteratedValueType instanceof PsiClassType && PsiUtil
|
||||
.isRawSubstitutor(collectionClass, TypeConversionUtil.getSuperClassSubstitutor(collectionClass, (PsiClassType)iteratedValueType));
|
||||
}
|
||||
};
|
||||
if (!PsiUtil.isLanguageLevel8OrHigher(holder.getFile())) {
|
||||
return PsiElementVisitor.EMPTY_VISITOR;
|
||||
}
|
||||
return new StreamApiMigrationVisitor(holder, isOnTheFly);
|
||||
}
|
||||
|
||||
@Contract("_, null -> false")
|
||||
@@ -274,39 +124,6 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private TextRange getRange(PsiForeachStatement statement, boolean isOnTheFly) {
|
||||
boolean wholeStatement = false;
|
||||
if(isOnTheFly) {
|
||||
if (myKey == null) {
|
||||
myKey = HighlightDisplayKey.find(getShortName());
|
||||
}
|
||||
if (myKey != null) {
|
||||
InspectionProfile profile = InspectionProjectProfileManager.getInstance(statement.getProject()).getCurrentProfile();
|
||||
HighlightDisplayLevel level = profile.getErrorLevel(myKey, statement);
|
||||
wholeStatement = HighlightDisplayLevel.DO_NOT_SHOW.equals(level);
|
||||
}
|
||||
}
|
||||
PsiExpression iteratedValue = statement.getIteratedValue();
|
||||
LOG.assertTrue(iteratedValue != null);
|
||||
PsiJavaToken rParenth = statement.getRParenth();
|
||||
if(wholeStatement && rParenth != null) {
|
||||
return new TextRange(statement.getTextOffset(), rParenth.getTextOffset() + 1);
|
||||
}
|
||||
return iteratedValue.getTextRange();
|
||||
}
|
||||
|
||||
private void registerProblem(ProblemsHolder holder,
|
||||
boolean isOnTheFly,
|
||||
PsiForeachStatement statement,
|
||||
String methodName,
|
||||
LocalQuickFix... fixes) {
|
||||
PsiExpression iteratedValue = statement.getIteratedValue();
|
||||
LOG.assertTrue(iteratedValue != null);
|
||||
holder.registerProblem(statement, getRange(statement, isOnTheFly).shiftRight(-statement.getTextOffset()),
|
||||
"Can be replaced with '" + methodName + "' call", fixes);
|
||||
}
|
||||
|
||||
@Contract("null, _ -> false")
|
||||
static boolean isLiteral(PsiElement element, Object value) {
|
||||
return element instanceof PsiLiteralExpression && value.equals(((PsiLiteralExpression)element).getValue());
|
||||
@@ -605,6 +422,193 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
|
||||
return false;
|
||||
}
|
||||
|
||||
private class StreamApiMigrationVisitor extends JavaElementVisitor {
|
||||
private final ProblemsHolder myHolder;
|
||||
private final boolean myIsOnTheFly;
|
||||
|
||||
public StreamApiMigrationVisitor(ProblemsHolder holder, boolean isOnTheFly) {
|
||||
myHolder = holder;
|
||||
myIsOnTheFly = isOnTheFly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitForeachStatement(PsiForeachStatement statement) {
|
||||
super.visitForeachStatement(statement);
|
||||
final PsiExpression iteratedValue = statement.getIteratedValue();
|
||||
final PsiStatement body = statement.getBody();
|
||||
if (iteratedValue == null || body == null) return;
|
||||
|
||||
final PsiType iteratedValueType = iteratedValue.getType();
|
||||
final PsiClass iteratorClass = PsiUtil.resolveClassInClassTypeOnly(iteratedValueType);
|
||||
PsiClass collectionClass = null;
|
||||
final boolean isArray;
|
||||
if(iteratedValueType instanceof PsiArrayType) {
|
||||
if(!isSupported(((PsiArrayType)iteratedValueType).getComponentType())) return;
|
||||
isArray = true;
|
||||
} else {
|
||||
collectionClass = JavaPsiFacade.getInstance(body.getProject()).findClass(CommonClassNames.JAVA_UTIL_COLLECTION, statement.getResolveScope());
|
||||
if (collectionClass != null && InheritanceUtil.isInheritorOrSelf(iteratorClass, collectionClass, true)) {
|
||||
isArray = false;
|
||||
} else return;
|
||||
}
|
||||
if (!ExceptionUtil.getThrownCheckedExceptions(new PsiElement[]{body}).isEmpty()) return;
|
||||
|
||||
TerminalBlock tb = TerminalBlock.from(statement.getIterationParameter(), body);
|
||||
List<Operation> operations = tb.extractOperations();
|
||||
if(tb.isEmpty()) return;
|
||||
|
||||
final ControlFlow controlFlow;
|
||||
try {
|
||||
controlFlow = ControlFlowFactory.getInstance(myHolder.getProject())
|
||||
.getControlFlow(body, LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance());
|
||||
}
|
||||
catch (AnalysisCanceledException ignored) {
|
||||
return;
|
||||
}
|
||||
final Collection<PsiStatement> exitPoints = ControlFlowUtil
|
||||
.findExitPointsAndStatements(controlFlow, tb.getStartOffset(controlFlow), tb.getEndOffset(controlFlow),
|
||||
new IntArrayList(), PsiContinueStatement.class,
|
||||
PsiBreakStatement.class, PsiReturnStatement.class, PsiThrowStatement.class);
|
||||
int startOffset = controlFlow.getStartOffset(body);
|
||||
int endOffset = controlFlow.getEndOffset(body);
|
||||
final List<PsiVariable> nonFinalVariables = StreamEx.of(ControlFlowUtil.getUsedVariables(controlFlow, startOffset, endOffset))
|
||||
.remove(variable -> HighlightControlFlowUtil.isEffectivelyFinal(variable, body, null)).toList();
|
||||
|
||||
if (exitPoints.isEmpty()) {
|
||||
if(getIncrementedVariable(tb, operations, nonFinalVariables) != null) {
|
||||
registerProblem(statement, "count", new ReplaceWithCountFix());
|
||||
}
|
||||
if(getAccumulatedVariable(tb, operations, nonFinalVariables) != null) {
|
||||
registerProblem(statement, "sum", new ReplaceWithSumFix());
|
||||
}
|
||||
if(!nonFinalVariables.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if ((isArray || !isRawSubstitution(iteratedValueType, collectionClass)) && isCollectCall(tb, operations)) {
|
||||
boolean addAll = operations.isEmpty() && isAddAllCall(tb);
|
||||
String methodName;
|
||||
if(addAll) {
|
||||
methodName = "addAll";
|
||||
} else {
|
||||
PsiMethodCallExpression methodCallExpression = tb.getSingleMethodCall();
|
||||
if(methodCallExpression != null && extractReplaceableCollectionInitializer(
|
||||
methodCallExpression.getMethodExpression().getQualifierExpression(), statement) != null) {
|
||||
methodName = "collect";
|
||||
} else {
|
||||
if (!SUGGEST_FOREACH) return;
|
||||
methodName = "forEach";
|
||||
}
|
||||
}
|
||||
registerProblem(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 (SUGGEST_FOREACH && (!operations.isEmpty() ||
|
||||
(!isArray && (REPLACE_TRIVIAL_FOREACH || !isTrivial(body, statement.getIterationParameter()))))) {
|
||||
ReplaceWithForeachCallFix forEachFix = new ReplaceWithForeachCallFix("forEach");
|
||||
LocalQuickFix[] fixes = {forEachFix};
|
||||
if (!operations.isEmpty()) { //for .stream()
|
||||
fixes = new LocalQuickFix[] {forEachFix, new ReplaceWithForeachCallFix("forEachOrdered")};
|
||||
}
|
||||
registerProblem(statement, "forEach", fixes);
|
||||
}
|
||||
} else {
|
||||
if (operations.isEmpty() && !REPLACE_TRIVIAL_FOREACH) return;
|
||||
if (nonFinalVariables.isEmpty() && tb.getSingleStatement() instanceof PsiReturnStatement) {
|
||||
handleSingleReturn(statement, tb, operations);
|
||||
}
|
||||
if (nonFinalVariables.size() == 1) {
|
||||
PsiStatement[] statements = tb.getStatements();
|
||||
if (statements.length == 2) {
|
||||
PsiStatement breakStatement = statements[1];
|
||||
if (!(breakStatement instanceof PsiBreakStatement) ||
|
||||
((PsiBreakStatement)breakStatement).findExitedStatement() != statement) {
|
||||
return;
|
||||
}
|
||||
PsiAssignmentExpression assignment = ExpressionUtils.getAssignment(statements[0]);
|
||||
if(assignment == null) return;
|
||||
PsiExpression lValue = assignment.getLExpression();
|
||||
if (!(lValue instanceof PsiReferenceExpression)) return;
|
||||
PsiElement var = ((PsiReferenceExpression)lValue).resolve();
|
||||
if(!(var instanceof PsiVariable) || !nonFinalVariables.contains(var)) return;
|
||||
PsiExpression rValue = assignment.getRExpression();
|
||||
if(rValue == null || isVariableReferenced((PsiVariable)var, rValue)) return;
|
||||
if(tb.getVariable() instanceof PsiPrimitiveType && !isIdentityMapping(tb.getVariable(), rValue)) return;
|
||||
registerProblem(statement, "findFirst", new ReplaceWithFindFirstFix());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void handleSingleReturn(PsiForeachStatement statement,
|
||||
TerminalBlock tb,
|
||||
List<Operation> operations) {
|
||||
PsiReturnStatement returnStatement = (PsiReturnStatement)tb.getSingleStatement();
|
||||
PsiExpression value = returnStatement.getReturnValue();
|
||||
PsiReturnStatement nextReturnStatement = getNextReturnStatement(statement);
|
||||
if(nextReturnStatement != null && (isLiteral(value, Boolean.TRUE) || isLiteral(value, Boolean.FALSE))) {
|
||||
boolean foundResult = (boolean)((PsiLiteralExpression)value).getValue();
|
||||
if(isLiteral(nextReturnStatement.getReturnValue(), !foundResult)) {
|
||||
String methodName;
|
||||
if (foundResult) {
|
||||
methodName = "anyMatch";
|
||||
}
|
||||
else {
|
||||
methodName = "noneMatch";
|
||||
if(!operations.isEmpty()) {
|
||||
Operation lastOp = operations.get(operations.size() - 1);
|
||||
if(lastOp instanceof FilterOp && BoolUtils.isNegation(lastOp.getExpression())) {
|
||||
methodName = "allMatch";
|
||||
}
|
||||
}
|
||||
}
|
||||
registerProblem(statement, methodName, new ReplaceWithMatchFix(methodName));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!isVariableReferenced(tb.getVariable(), value)) {
|
||||
registerProblem(statement, "anyMatch", new ReplaceWithMatchFix("anyMatch"));
|
||||
}
|
||||
if(nextReturnStatement != null && ExpressionUtils.isSimpleExpression(nextReturnStatement.getReturnValue())
|
||||
&& (!(tb.getVariable().getType() instanceof PsiPrimitiveType) || isIdentityMapping(tb.getVariable(), value))) {
|
||||
registerProblem(statement, "findFirst", new ReplaceWithFindFirstFix());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isRawSubstitution(PsiType iteratedValueType, PsiClass collectionClass) {
|
||||
return iteratedValueType instanceof PsiClassType && PsiUtil
|
||||
.isRawSubstitutor(collectionClass, TypeConversionUtil.getSuperClassSubstitutor(collectionClass, (PsiClassType)iteratedValueType));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private TextRange getRange(PsiForeachStatement statement) {
|
||||
boolean wholeStatement = false;
|
||||
if(myIsOnTheFly) {
|
||||
if (myKey == null) {
|
||||
myKey = HighlightDisplayKey.find(getShortName());
|
||||
}
|
||||
if (myKey != null) {
|
||||
InspectionProfile profile = InspectionProjectProfileManager.getInstance(statement.getProject()).getCurrentProfile();
|
||||
HighlightDisplayLevel level = profile.getErrorLevel(myKey, statement);
|
||||
wholeStatement = HighlightDisplayLevel.DO_NOT_SHOW.equals(level);
|
||||
}
|
||||
}
|
||||
PsiExpression iteratedValue = statement.getIteratedValue();
|
||||
LOG.assertTrue(iteratedValue != null);
|
||||
PsiJavaToken rParenth = statement.getRParenth();
|
||||
if(wholeStatement && rParenth != null) {
|
||||
return new TextRange(statement.getTextOffset(), rParenth.getTextOffset() + 1);
|
||||
}
|
||||
return iteratedValue.getTextRange();
|
||||
}
|
||||
|
||||
private void registerProblem(PsiForeachStatement statement, String methodName, LocalQuickFix... fixes) {
|
||||
PsiExpression iteratedValue = statement.getIteratedValue();
|
||||
LOG.assertTrue(iteratedValue != null);
|
||||
myHolder.registerProblem(statement, getRange(statement).shiftRight(-statement.getTextOffset()),
|
||||
"Can be replaced with '" + methodName + "' call", fixes);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Intermediate stream operation representation
|
||||
*/
|
||||
@@ -648,10 +652,6 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
|
||||
myType = targetType;
|
||||
}
|
||||
|
||||
MapOp(PsiExpression expression, PsiVariable variable) {
|
||||
this(expression, variable, expression.getType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createReplacement(PsiElementFactory factory) {
|
||||
if (isIdentityMapping(myVariable, myExpression)) {
|
||||
|
||||
Reference in New Issue
Block a user