StreamToLoopInspection: get rid of placeholder; instead pass a predicate to suggestUniqueVariableName to be able to reuse existing names

This commit is contained in:
Tagir Valeev
2017-03-21 12:14:55 +07:00
parent 09fa0c7a73
commit 69632b09da
4 changed files with 157 additions and 119 deletions
@@ -319,46 +319,50 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
PsiStatement statement = PsiTreeUtil.getParentOfType(terminalCall, PsiStatement.class);
LOG.assertTrue(statement != null);
CommentTracker ct = new CommentTracker();
PsiExpression temporaryStreamPlaceholder =
(PsiExpression)ct.replace(terminalCall, "((" + resultType.getCanonicalText() + ")$streamReplacement$)");
try {
StreamToLoopReplacementContext context =
new StreamToLoopReplacementContext(statement, operations, temporaryStreamPlaceholder, ct);
new StreamToLoopReplacementContext(statement, operations, terminalCall, ct);
registerVariables(operations, context);
String replacement = "";
for (OperationRecord or : StreamEx.ofReversed(operations)) {
replacement = or.myOperation.wrap(or.myInVar, or.myOutVar, replacement, context);
}
ct.insertCommentsBefore(statement);
PsiElement firstAdded = null;
for (PsiStatement addedStatement : ((PsiBlockStatement)factory.createStatementFromText("{" + replacement + "}", statement))
.getCodeBlock().getStatements()) {
addStatement(project, statement, addedStatement);
PsiElement res = addStatement(project, statement, addedStatement);
if (firstAdded == null) {
firstAdded = res;
}
}
PsiElement result = context.makeFinalReplacement();
if(result != null) {
normalize(project, result);
result = normalize(project, result);
if (firstAdded == null) {
firstAdded = result;
}
}
if (firstAdded != null) {
ct.insertCommentsBefore(firstAdded);
}
}
catch (Exception ex) {
String text = terminalCall.getText();
if(temporaryStreamPlaceholder.isPhysical()) {
// Just in case if something went wrong: at least try to restore the original stream code
temporaryStreamPlaceholder.replace(factory.createExpressionFromText(text, temporaryStreamPlaceholder));
}
LOG.error(LogMessageEx.createEvent("Error converting Stream to loop", ExceptionUtil.getThrowableText(ex),
new Attachment("Stream_code.txt", text)));
}
}
private static void addStatement(@NotNull Project project, PsiStatement statement, PsiStatement context) {
private static PsiElement addStatement(@NotNull Project project, PsiStatement statement, PsiStatement context) {
PsiElement element = statement.getParent().addBefore(context, statement);
normalize(project, element);
return normalize(project, element);
}
private static void normalize(@NotNull Project project, PsiElement element) {
private static PsiElement normalize(@NotNull Project project, PsiElement element) {
element = JavaCodeStyleManager.getInstance(project).shortenClassReferences(element);
PsiDiamondTypeUtil.removeRedundantTypeArguments(element);
RedundantCastUtil.getRedundantCastsInside(element).forEach(RedundantCastUtil::removeCast);
return element;
}
private static StreamEx<OperationRecord> allOperations(List<OperationRecord> operations) {
@@ -399,19 +403,19 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
private final List<String> myBeforeSteps = new ArrayList<>();
private final List<String> myAfterSteps = new ArrayList<>();
private final CommentTracker myCommentTracker;
private PsiElement myPlaceholder;
private PsiElement myStreamExpression;
private final PsiElementFactory myFactory;
private String myLabel;
private String myFinisher;
StreamToLoopReplacementContext(PsiStatement statement,
List<OperationRecord> records,
@NotNull PsiExpression placeholder,
@NotNull PsiExpression streamExpression,
CommentTracker ct) {
myStatement = statement;
myFactory = JavaPsiFacade.getElementFactory(myStatement.getProject());
myHasNestedLoops = records.stream().anyMatch(or -> or.myOperation instanceof FlatMapOperation);
myPlaceholder = placeholder;
myStreamExpression = streamExpression;
mySuffix = myHasNestedLoops ? "Outer" : "";
myCommentTracker = ct;
myUsedNames = new HashSet<>();
@@ -422,7 +426,7 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
StreamToLoopReplacementContext(StreamToLoopReplacementContext parentContext, List<OperationRecord> records) {
myUsedNames = parentContext.myUsedNames;
myUsedLabels = parentContext.myUsedLabels;
myPlaceholder = null;
myStreamExpression = parentContext.myStreamExpression;
myStatement = parentContext.myStatement;
myFactory = parentContext.myFactory;
myCommentTracker = parentContext.myCommentTracker;
@@ -480,7 +484,8 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
private boolean isUsed(String varName) {
return myUsedNames.contains(varName) || JavaLexer.isKeyword(varName, LanguageLevel.HIGHEST) ||
!varName.equals(JavaCodeStyleManager.getInstance(myStatement.getProject()).suggestUniqueVariableName(varName, myStatement, true));
!varName.equals(JavaCodeStyleManager.getInstance(myStatement.getProject())
.suggestUniqueVariableName(varName, myStatement, v -> PsiTreeUtil.isAncestor(myStreamExpression, v, true)));
}
public String declare(String desiredName, String type, String initializer) {
@@ -511,13 +516,13 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
}
public String declareResult(String desiredName, String type, String initializer, @NotNull ResultKind kind) {
if (kind != ResultKind.UNKNOWN && myPlaceholder.getParent() instanceof PsiVariable) {
PsiVariable var = (PsiVariable)myPlaceholder.getParent();
if (kind != ResultKind.UNKNOWN && myStreamExpression.getParent() instanceof PsiVariable) {
PsiVariable var = (PsiVariable)myStreamExpression.getParent();
if(var.getType().equalsToText(type) && var.getParent() instanceof PsiDeclarationStatement
&& (kind == ResultKind.FINAL || canUseAsNonFinal(var))) {
PsiDeclarationStatement declaration = (PsiDeclarationStatement)var.getParent();
if(declaration.getDeclaredElements().length == 1) {
myPlaceholder = declaration;
myStreamExpression = declaration;
PsiVariable copy = (PsiVariable)var.copy();
if (kind == ResultKind.NON_FINAL) {
PsiModifierList modifierList = copy.getModifierList();
@@ -553,18 +558,18 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
}
public PsiElement makeFinalReplacement() {
LOG.assertTrue(myPlaceholder != null);
if (myFinisher == null || myPlaceholder instanceof PsiStatement) {
myPlaceholder.delete();
LOG.assertTrue(myStreamExpression != null);
if (myFinisher == null || myStreamExpression instanceof PsiStatement) {
myCommentTracker.delete(myStreamExpression);
return null;
}
else {
PsiExpression expression = myFactory.createExpressionFromText(myFinisher, myPlaceholder);
PsiElement parent = myPlaceholder.getParent();
PsiExpression expression = myFactory.createExpressionFromText(myFinisher, myStreamExpression);
PsiElement parent = myStreamExpression.getParent();
if (parent instanceof PsiExpression && ParenthesesUtils.areParenthesesNeeded(expression, (PsiExpression)parent, false)) {
expression = myFactory.createExpressionFromText("("+myFinisher+")", myPlaceholder);
expression = myFactory.createExpressionFromText("("+myFinisher+")", myStreamExpression);
}
return myPlaceholder.replace(expression);
return myCommentTracker.replace(myStreamExpression, expression);
}
}
@@ -580,7 +585,7 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
}
public String assignAndBreak(ConditionalExpression conditionalExpression) {
PsiStatement statement = PsiTreeUtil.getParentOfType(myPlaceholder, PsiStatement.class);
PsiStatement statement = PsiTreeUtil.getParentOfType(myStreamExpression, PsiStatement.class);
boolean inReturn = statement instanceof PsiReturnStatement;
if(conditionalExpression instanceof ConditionalExpression.Optional) {
conditionalExpression = tryUnwrapOptional((ConditionalExpression.Optional)conditionalExpression, inReturn);
@@ -591,38 +596,38 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
if (inReturn) {
setFinisher(conditionalExpression.getFalseBranch());
Object mark = new Object();
PsiTreeUtil.mark(myPlaceholder, mark);
PsiTreeUtil.mark(myStreamExpression, mark);
PsiElement returnCopy = statement.copy();
PsiElement placeHolderCopy = PsiTreeUtil.releaseMark(returnCopy, mark);
LOG.assertTrue(placeHolderCopy != null);
PsiElement replacement = placeHolderCopy.replace(createExpression(conditionalExpression.getTrueBranch()));
return (placeHolderCopy == returnCopy ? replacement : returnCopy).getText();
}
PsiElement parent = PsiUtil.skipParenthesizedExprUp(myPlaceholder.getParent());
PsiElement parent = PsiUtil.skipParenthesizedExprUp(myStreamExpression.getParent());
if(parent instanceof PsiIfStatement && conditionalExpression instanceof ConditionalExpression.Boolean &&
!((ConditionalExpression.Boolean)conditionalExpression).isInverted()) {
PsiIfStatement ifStatement = (PsiIfStatement)parent;
if(ifStatement.getElseBranch() == null) {
PsiStatement thenStatement = ControlFlowUtils.stripBraces(ifStatement.getThenBranch());
if(thenStatement instanceof PsiReturnStatement || thenStatement instanceof PsiThrowStatement) {
myPlaceholder = parent;
myStreamExpression = parent;
return thenStatement.getText();
}
if(thenStatement instanceof PsiExpressionStatement) {
myPlaceholder = parent;
myStreamExpression = parent;
return thenStatement.getText() + "\n" + getBreakStatement();
}
}
}
if(conditionalExpression instanceof ConditionalExpression.Optional && myPlaceholder instanceof PsiExpression) {
PsiMethodCallExpression call = ExpressionUtils.getCallForQualifier((PsiExpression)myPlaceholder);
if(conditionalExpression instanceof ConditionalExpression.Optional && myStreamExpression instanceof PsiExpression) {
PsiMethodCallExpression call = ExpressionUtils.getCallForQualifier((PsiExpression)myStreamExpression);
if(call != null && call.getParent() instanceof PsiExpressionStatement) {
PsiExpression[] args = call.getArgumentList().getExpressions();
if(args.length == 1 && "ifPresent".equals(call.getMethodExpression().getReferenceName())) {
FunctionHelper fn = FunctionHelper.create(args[0], 1);
if(fn != null) {
fn.transform(this, ((ConditionalExpression.Optional)conditionalExpression).unwrap("").getTrueBranch());
myPlaceholder = call.getParent();
myStreamExpression = call.getParent();
return fn.getStatementText() + getBreakStatement();
}
}
@@ -635,19 +640,19 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
}
private ConditionalExpression tryUnwrapBoolean(ConditionalExpression.Boolean condition, boolean unwrapLazilyEvaluated) {
if (myPlaceholder instanceof PsiExpression) {
PsiExpression negation = BoolUtils.findNegation((PsiExpression)myPlaceholder);
if (myStreamExpression instanceof PsiExpression) {
PsiExpression negation = BoolUtils.findNegation((PsiExpression)myStreamExpression);
if (negation != null) {
myPlaceholder = negation;
myStreamExpression = negation;
condition = condition.negate();
}
PsiElement parent = PsiUtil.skipParenthesizedExprUp(myPlaceholder.getParent());
PsiElement parent = PsiUtil.skipParenthesizedExprUp(myStreamExpression.getParent());
ConditionalExpression candidate = null;
if (parent instanceof PsiPolyadicExpression) {
PsiPolyadicExpression expression = (PsiPolyadicExpression)parent;
PsiExpression[] operands = expression.getOperands();
if (operands.length > 1 && PsiTreeUtil.isAncestor(operands[0], myPlaceholder, false)) {
if (operands.length > 1 && PsiTreeUtil.isAncestor(operands[0], myStreamExpression, false)) {
IElementType type = expression.getOperationTokenType();
if (type.equals(JavaTokenType.ANDAND)) {
candidate = condition
@@ -659,7 +664,7 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
}
} else if (parent instanceof PsiConditionalExpression) {
PsiConditionalExpression ternary = (PsiConditionalExpression)parent;
if (PsiTreeUtil.isAncestor(ternary.getCondition(), myPlaceholder, false)) {
if (PsiTreeUtil.isAncestor(ternary.getCondition(), myStreamExpression, false)) {
PsiType type = ternary.getType();
PsiExpression thenExpression = ternary.getThenExpression();
PsiExpression elseExpression = ternary.getElseExpression();
@@ -670,7 +675,7 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
}
if (candidate != null &&
(unwrapLazilyEvaluated || ExpressionUtils.isSimpleExpression(createExpression(candidate.getFalseBranch())))) {
myPlaceholder = parent;
myStreamExpression = parent;
return candidate;
}
}
@@ -679,13 +684,13 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
@NotNull
private ConditionalExpression tryUnwrapOptional(ConditionalExpression.Optional condition, boolean unwrapLazilyEvaluated) {
if (myPlaceholder instanceof PsiExpression) {
PsiMethodCallExpression call = ExpressionUtils.getCallForQualifier((PsiExpression)myPlaceholder);
if (myStreamExpression instanceof PsiExpression) {
PsiMethodCallExpression call = ExpressionUtils.getCallForQualifier((PsiExpression)myStreamExpression);
if (call != null && !(call.getParent() instanceof PsiExpressionStatement)) {
String name = call.getMethodExpression().getReferenceName();
PsiExpression[] args = call.getArgumentList().getExpressions();
if (args.length == 0 && "isPresent".equals(name)) {
myPlaceholder = call;
myStreamExpression = call;
return new ConditionalExpression.Boolean(condition.getCondition(), false);
}
if (args.length == 1) {
@@ -701,7 +706,7 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
}
}
if (absentExpression != null) {
myPlaceholder = call;
myStreamExpression = call;
return condition.unwrap(absentExpression);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -40,6 +40,8 @@ import org.jetbrains.annotations.Nullable;
import java.beans.Introspector;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* @author max
@@ -677,12 +679,7 @@ public class JavaCodeStyleManagerImpl extends JavaCodeStyleManager {
@NotNull
private static String constantValueToConstantName(@NotNull String[] names) {
final StringBuilder result = new StringBuilder();
for (int i = 0; i < names.length; i++) {
if (i > 0) result.append("_");
result.append(names[i]);
}
return result.toString();
return Arrays.stream(names).collect(Collectors.joining("_"));
}
@NotNull
@@ -749,13 +746,7 @@ public class JavaCodeStyleManagerImpl extends JavaCodeStyleManager {
method = (PsiMethod)navElement;
}
PsiExpression[] expressions = list.getExpressions();
int index = -1;
for (int i = 0; i < expressions.length; i++) {
if (expressions[i] == expr) {
index = i;
break;
}
}
int index = ArrayUtil.indexOf(expressions, expr);
PsiParameter[] parameters = method.getParameterList().getParameters();
if (index < parameters.length) {
String name = parameters[index].getName();
@@ -899,62 +890,13 @@ public class JavaCodeStyleManagerImpl extends JavaCodeStyleManager {
@NotNull
@Override
public String suggestUniqueVariableName(@NotNull String baseName, PsiElement place, boolean lookForward) {
return suggestUniqueVariableName(baseName, place, lookForward, false);
return suggestUniqueVariableName(baseName, place, lookForward, false, v -> false);
}
@NotNull
private static String suggestUniqueVariableName(@NotNull String baseName, PsiElement place, boolean lookForward, boolean allowShadowing) {
PsiElement scope = PsiTreeUtil.getNonStrictParentOfType(place, PsiStatement.class, PsiCodeBlock.class, PsiMethod.class);
for (int index = 0; ; index++) {
String name = index > 0 ? baseName + index : baseName;
if (hasConflictingVariable(place, name, allowShadowing) || lookForward && hasConflictingVariableAfterwards(scope, name)) {
continue;
}
return name;
}
}
private static boolean hasConflictingVariableAfterwards(@Nullable PsiElement scope, @NotNull final String name) {
PsiElement run = scope;
while (run != null) {
class CancelException extends RuntimeException {
}
try {
run.accept(new JavaRecursiveElementWalkingVisitor() {
@Override
public void visitClass(final PsiClass aClass) {}
@Override public void visitVariable(PsiVariable variable) {
if (name.equals(variable.getName())) {
throw new CancelException();
}
}
});
}
catch (CancelException e) {
return true;
}
run = run.getNextSibling();
if (scope instanceof PsiMethod || scope instanceof PsiForeachStatement) {//do not check next member for param name conflict
break;
}
}
return false;
}
private static boolean hasConflictingVariable(@Nullable PsiElement place, @NotNull String name, boolean allowShadowing) {
if (place == null) {
return false;
}
PsiResolveHelper helper = JavaPsiFacade.getInstance(place.getProject()).getResolveHelper();
PsiVariable existingVariable = helper.resolveAccessibleReferencedVariable(name, place);
if (existingVariable == null) return false;
if (allowShadowing && existingVariable instanceof PsiField && PsiTreeUtil.getNonStrictParentOfType(place, PsiMethod.class) != null) {
return false;
}
return true;
@NotNull
@Override
public String suggestUniqueVariableName(@NotNull String baseName, PsiElement place, Predicate<PsiVariable> canBeReused) {
return suggestUniqueVariableName(baseName, place, true, false, canBeReused);
}
@Override
@@ -975,7 +917,7 @@ public class JavaCodeStyleManagerImpl extends JavaCodeStyleManager {
}
String unique = suggestUniqueVariableName(name, place, lookForward);
if (!unique.equals(name)) {
String withShadowing = suggestUniqueVariableName(name, place, lookForward, true);
String withShadowing = suggestUniqueVariableName(name, place, lookForward, true, v -> false);
if (withShadowing.equals(name)) {
uniqueNames.add(name);
}
@@ -991,6 +933,68 @@ public class JavaCodeStyleManagerImpl extends JavaCodeStyleManager {
};
}
@NotNull
private static String suggestUniqueVariableName(@NotNull String baseName,
PsiElement place,
boolean lookForward,
boolean allowShadowing,
Predicate<PsiVariable> canBeReused) {
PsiElement scope = PsiTreeUtil.getNonStrictParentOfType(place, PsiStatement.class, PsiCodeBlock.class, PsiMethod.class);
for (int index = 0; ; index++) {
String name = index > 0 ? baseName + index : baseName;
if (hasConflictingVariable(place, name, allowShadowing) ||
lookForward && hasConflictingVariableAfterwards(scope, name, canBeReused)) {
continue;
}
return name;
}
}
private static boolean hasConflictingVariable(@Nullable PsiElement place, @NotNull String name, boolean allowShadowing) {
if (place == null) {
return false;
}
PsiResolveHelper helper = JavaPsiFacade.getInstance(place.getProject()).getResolveHelper();
PsiVariable existingVariable = helper.resolveAccessibleReferencedVariable(name, place);
if (existingVariable == null) return false;
if (allowShadowing && existingVariable instanceof PsiField && PsiTreeUtil.getNonStrictParentOfType(place, PsiMethod.class) != null) {
return false;
}
return true;
}
private static boolean hasConflictingVariableAfterwards(@Nullable PsiElement scope,
@NotNull final String name,
@NotNull Predicate<PsiVariable> canBeReused) {
PsiElement run = scope;
while (run != null) {
class CancelException extends RuntimeException {
}
try {
run.accept(new JavaRecursiveElementWalkingVisitor() {
@Override
public void visitClass(final PsiClass aClass) {}
@Override public void visitVariable(PsiVariable variable) {
if (name.equals(variable.getName()) && !canBeReused.test(variable)) {
throw new CancelException();
}
}
});
}
catch (CancelException e) {
return true;
}
run = run.getNextSibling();
if (scope instanceof PsiMethod || scope instanceof PsiForeachStatement) {//do not check next member for param name conflict
break;
}
}
return false;
}
private static void sortVariableNameSuggestions(@NotNull String[] names,
@NotNull final VariableKind variableKind,
@Nullable final String propertyName,
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.function.Predicate;
/**
* @author max
@@ -203,6 +204,18 @@ public abstract class JavaCodeStyleManager {
return suggestUniqueVariableName(baseNameInfo, place, false, lookForward);
}
/**
* Suggests a unique name for the variable used at the specified location looking forward with possible filtering.
*
* @param baseName the base name info for the variable.
* @param place the location where the variable will be used.
* @param canBeReused a predicate which returns true for variables which names still could be reused (e.g. a variable will be deleted
* during the ongoing refactoring)
* @return the generated unique name
*/
@NotNull
public abstract String suggestUniqueVariableName(@NotNull String baseName, PsiElement place, Predicate<PsiVariable> canBeReused);
/**
* Suggests a unique name for the variable used at the specified location.
*
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,6 +31,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.function.Predicate;
public class CoreJavaCodeStyleManager extends JavaCodeStyleManager {
@Override
@@ -63,6 +64,7 @@ public class CoreJavaCodeStyleManager extends JavaCodeStyleManager {
return 0;
}
@NotNull
@Override
public PsiElement shortenClassReferences(@NotNull PsiElement element) throws IncorrectOperationException {
return element;
@@ -106,6 +108,14 @@ public class CoreJavaCodeStyleManager extends JavaCodeStyleManager {
@NotNull
@Override
public String suggestUniqueVariableName(@NotNull @NonNls String baseName, PsiElement place, boolean lookForward) {
return suggestUniqueVariableName(baseName, place, lookForward, v -> false);
}
@NotNull
private static String suggestUniqueVariableName(@NotNull @NonNls String baseName,
PsiElement place,
boolean lookForward,
Predicate<PsiVariable> canBeReused) {
int index = 0;
PsiElement scope = PsiTreeUtil.getNonStrictParentOfType(place, PsiStatement.class, PsiCodeBlock.class, PsiMethod.class);
NextName:
@@ -129,7 +139,7 @@ public class CoreJavaCodeStyleManager extends JavaCodeStyleManager {
}
@Override public void visitVariable(PsiVariable variable) {
if (name1.equals(variable.getName())) {
if (name1.equals(variable.getName()) && !canBeReused.test(variable)) {
throw new CancelException();
}
}
@@ -150,6 +160,12 @@ public class CoreJavaCodeStyleManager extends JavaCodeStyleManager {
}
}
@NotNull
@Override
public String suggestUniqueVariableName(@NotNull String baseName, PsiElement place, Predicate<PsiVariable> canBeReused) {
return suggestUniqueVariableName(baseName, place, true, canBeReused);
}
@NotNull
@Override
public SuggestedNameInfo suggestUniqueVariableName(@NotNull final SuggestedNameInfo baseNameInfo,