StreamApiMigration: support joining with prefix and suffix

This commit is contained in:
Tagir Valeev
2017-02-06 14:39:02 +07:00
parent 069f44aeff
commit 95baf652ae
8 changed files with 165 additions and 42 deletions

View File

@@ -28,6 +28,7 @@ import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiTypesUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.ArrayUtil;
import com.siyeh.ig.callMatcher.CallMatcher;
import com.siyeh.ig.psiutils.*;
import com.siyeh.ig.psiutils.ControlFlowUtils.InitializerUsageStatus;
import one.util.streamex.EntryStream;
@@ -523,18 +524,26 @@ class CollectMigration extends BaseStreamApiMigration {
}
static class StringBuilderTerminal extends CollectTerminal {
private static final CallMatcher APPEND = CallMatcher.anyOf(
CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING_BUILDER, "append").parameterCount(1),
CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING_BUFFER, "append").parameterCount(1)
);
private final PsiVariable myElement;
private final PsiMethodCallExpression myAppendCall;
private final PsiMethodCallExpression myFinalAppendCall;
private final PsiExpression myDelimiter;
StringBuilderTerminal(PsiLocalVariable variable,
PsiLoopStatement loop,
PsiVariable element,
PsiMethodCallExpression appendCall,
PsiExpression delimiter) {
PsiExpression delimiter,
PsiMethodCallExpression finalAppend) {
super(variable, loop, getInitializerUsageStatus(variable, loop));
myElement = element;
myAppendCall = appendCall;
myFinalAppendCall = finalAppend;
myDelimiter = delimiter;
}
@@ -563,7 +572,20 @@ class CollectMigration extends BaseStreamApiMigration {
@Override
String generateTerminal() {
String delimiter = myDelimiter == null ? "" : expressionToCharSequence(myDelimiter);
return ".collect(" + CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS + ".joining(" + delimiter + "))";
PsiExpression initializer = getTargetVariable().getInitializer();
String initialText = ConstructionUtils.getStringBuilderInitializerText(initializer);
String finalText = "\"\"";
if (myFinalAppendCall != null) {
finalText = expressionToCharSequence(myFinalAppendCall.getArgumentList().getExpressions()[0]);
}
String args;
if ("\"\"".equals(initialText) && "\"\"".equals(finalText)) {
args = delimiter;
}
else {
args = (delimiter.isEmpty() ? "\"\"" : delimiter) + "," + initialText + "," + finalText;
}
return ".collect(" + CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS + ".joining(" + args + "))";
}
@Override
@@ -578,8 +600,20 @@ class CollectMigration extends BaseStreamApiMigration {
target.getTypeElement().replace(factory.createTypeElementFromText(CommonClassNames.JAVA_LANG_STRING, target));
if (getStatus() == ControlFlowUtils.InitializerUsageStatus.AT_WANTED_PLACE) {
PsiExpression initializer = target.getInitializer();
if (initializer != null) {
initializer.replace(factory.createExpressionFromText("\"\"", target));
String initialText = ConstructionUtils.getStringBuilderInitializerText(initializer);
if (initialText != null) {
initializer.replace(factory.createExpressionFromText(initialText, target));
}
}
if (myFinalAppendCall != null) {
if (myFinalAppendCall.getParent() instanceof PsiExpressionStatement) {
myFinalAppendCall.delete();
} else {
PsiMethodCallExpression nextCall = ExpressionUtils.getCallForQualifier(myFinalAppendCall);
PsiExpression qualifier = myFinalAppendCall.getMethodExpression().getQualifierExpression();
if (nextCall != null && qualifier != null) {
nextCall.replace(qualifier);
}
}
}
Collection<PsiReference> usages = ReferencesSearch.search(target).findAll();
@@ -594,6 +628,16 @@ class CollectMigration extends BaseStreamApiMigration {
}
}
static PsiMethodCallExpression getAfterLoopAppend(PsiLoopStatement loop, PsiVariable target) {
PsiElement next = PsiTreeUtil.skipSiblingsForward(loop, PsiComment.class, PsiWhiteSpace.class);
if (!(next instanceof PsiExpressionStatement)) return null;
PsiExpression expression = ((PsiExpressionStatement)next).getExpression();
if (!(expression instanceof PsiMethodCallExpression)) return null;
PsiMethodCallExpression call = (PsiMethodCallExpression)expression;
if (APPEND.test(call) && ExpressionUtils.isReferenceTo(call.getMethodExpression().getQualifierExpression(), target)) return call;
return null;
}
static PsiExpression getExpressionComparedToZero(PsiBinaryExpression condition) {
if (condition == null) return null;
IElementType tokenType = condition.getOperationTokenType();
@@ -620,48 +664,63 @@ class CollectMigration extends BaseStreamApiMigration {
PsiLocalVariable builder = extractQualifierVariable(tb, maybeLength);
if (builder == null) return null;
PsiMethodCallExpression call = tryCast(thenBranch.getExpression(), PsiMethodCallExpression.class);
if (call == null || !MethodCallUtils.isCallToMethod(call, null, builder.getType(), "append", new PsiType[]{null})) return null;
if (!APPEND.test(call)) return null;
return extractQualifierVariable(tb, call) == builder ? call : null;
}
static StringBuilderTerminal tryExtract(TerminalBlock tb, PsiMethodCallExpression call, PsiMethodCallExpression delimiterAppend) {
if (tb.getCountExpression() != null) return null;
if (call == null || !MethodCallUtils.isCallToMethod(call, null, null, "append", new PsiType[]{null})) return null;
if (!APPEND.test(call)) return null;
PsiLocalVariable targetBuilder = extractQualifierVariable(tb, call);
if (targetBuilder == null) return null;
if (delimiterAppend != null &&
!ExpressionUtils.isReferenceTo(delimiterAppend.getMethodExpression().getQualifierExpression(), targetBuilder)) {
return null;
}
if (!ConstructionUtils.isEmptyStringBuilderInitializer(targetBuilder.getInitializer())) return null;
if (!ReferencesSearch.search(targetBuilder).forEach(ref -> {
PsiElement element = ref.getElement();
if (PsiTreeUtil.isAncestor(targetBuilder, element, false) || PsiTreeUtil.isAncestor(tb.getMainLoop(), element, false)) {
String initialText = ConstructionUtils.getStringBuilderInitializerText(targetBuilder.getInitializer());
if (initialText == null) return null;
PsiMethodCallExpression finalAppend = getAfterLoopAppend(tb.getMainLoop(), targetBuilder);
List<PsiElement> refs = StreamEx.of(ReferencesSearch.search(targetBuilder).findAll())
.map(PsiReference::getElement)
.remove(e -> PsiTreeUtil.isAncestor(targetBuilder, e, false) || PsiTreeUtil.isAncestor(tb.getMainLoop(), e, false))
.toList();
if (!refs.stream().allMatch(PsiExpression.class::isInstance)) return null;
boolean allowed = areReferencesAllowed(finalAppend, refs);
if (!allowed && refs.size() == 1 && finalAppend == null) {
PsiMethodCallExpression usage = ExpressionUtils.getCallForQualifier((PsiExpression)refs.get(0));
if (APPEND.test(usage)) {
PsiMethodCallExpression nextCall = ExpressionUtils.getCallForQualifier(usage);
if (nextCall != null && "toString".equals(nextCall.getMethodExpression().getReferenceName())) {
finalAppend = usage;
allowed = true;
}
}
}
if (!allowed) return null;
PsiExpression delimiter = delimiterAppend == null ? null : delimiterAppend.getArgumentList().getExpressions()[0];
return new StringBuilderTerminal(targetBuilder, tb.getMainLoop(), tb.getVariable(), call, delimiter, finalAppend);
}
private static boolean areReferencesAllowed(PsiMethodCallExpression finalAppend, List<PsiElement> refs) {
return StreamEx.of(refs).select(PsiExpression.class).allMatch(expression -> {
PsiMethodCallExpression usage = ExpressionUtils.getCallForQualifier(expression);
if (usage != null) {
if (usage == finalAppend) return true;
PsiExpression[] usageArgs = usage.getArgumentList().getExpressions();
String name = usage.getMethodExpression().getReferenceName();
if (usageArgs.length == 0 && ("toString".equals(name) || "length".equals(name))) return true;
}
PsiElement parent = PsiUtil.skipParenthesizedExprUp(expression.getParent());
if (parent instanceof PsiPolyadicExpression &&
((PsiPolyadicExpression)parent).getOperationTokenType().equals(JavaTokenType.PLUS)) {
return true;
}
if (element instanceof PsiExpression) {
PsiMethodCallExpression usage = ExpressionUtils.getCallForQualifier((PsiExpression)element);
if (usage != null) {
PsiExpression[] usageArgs = usage.getArgumentList().getExpressions();
String name = usage.getMethodExpression().getReferenceName();
if (usageArgs.length == 0 && ("toString".equals(name) || "length".equals(name))) return true;
}
PsiElement parent = PsiUtil.skipParenthesizedExprUp(element.getParent());
if (parent instanceof PsiPolyadicExpression &&
((PsiPolyadicExpression)parent).getOperationTokenType().equals(JavaTokenType.PLUS)) {
return true;
}
if (parent instanceof PsiAssignmentExpression &&
if (parent instanceof PsiAssignmentExpression &&
((PsiAssignmentExpression)parent).getOperationTokenType().equals(JavaTokenType.PLUSEQ)) {
return true;
}
return true;
}
return false;
})) {
return null;
}
PsiExpression delimiter = delimiterAppend == null ? null : delimiterAppend.getArgumentList().getExpressions()[0];
return new StringBuilderTerminal(targetBuilder, tb.getMainLoop(), tb.getVariable(), call, delimiter);
});
}
}

View File

@@ -0,0 +1,11 @@
// "Replace with collect" "true"
import java.util.List;
import java.util.stream.Collectors;
public class Test {
static String test(List<String> list) {
String sb = list.stream().filter(s -> !s.isEmpty()).collect(Collectors.joining(",", "[", "]"));
return sb.trim();
}
}

View File

@@ -0,0 +1,11 @@
// "Replace with collect" "true"
import java.util.List;
import java.util.stream.Collectors;
public class Test {
static String test(List<String> list) {
String sb = list.stream().filter(s -> !s.isEmpty()).collect(Collectors.joining("", "initial", ""));
return sb.trim();
}
}

View File

@@ -0,0 +1,13 @@
// "Replace with collect" "true"
import java.util.List;
import java.util.stream.Collectors;
public class Test {
static String test(List<String> list) {
String sb;
System.out.println("hello");
sb = list.stream().filter(s -> !s.isEmpty()).collect(Collectors.joining("", "", "xyz"));
return sb;
}
}

View File

@@ -0,0 +1,16 @@
// "Replace with collect" "true"
import java.util.List;
public class Test {
static String test(List<String> list) {
StringBuilder sb = new StringBuilder("[");
for (String s : li<caret>st) {
if (!s.isEmpty()) {
if(sb.length() > 0) sb.append(',');
sb.append(s);
}
}
return sb.append("]").toString().trim();
}
}

View File

@@ -1,10 +1,9 @@
// "Replace with collect" "false"
// "Replace with collect" "true"
import java.util.List;
public class Test {
static String test(List<String> list) {
// Not supported for now
StringBuilder sb = new StringBuilder("initial");
for (String s : li<caret>st) {
if (!s.isEmpty()) {

View File

@@ -1,4 +1,4 @@
// "Replace with collect" "false"
// "Replace with collect" "true"
import java.util.List;

View File

@@ -31,26 +31,40 @@ public class ConstructionUtils {
*/
@Contract("null -> false")
public static boolean isEmptyStringBuilderInitializer(PsiExpression initializer) {
initializer = PsiUtil.skipParenthesizedExprDown(initializer);
if (!(initializer instanceof PsiNewExpression)) return false;
final PsiNewExpression newExpression = (PsiNewExpression)initializer;
return "\"\"".equals(getStringBuilderInitializerText(initializer));
}
/**
* Returns a textual representation of an expression which is equivalent to the initial value of newly created StringBuilder or StringBuffer
*
* @param construction StringBuilder/StringBuffer construction expression
* @return a textual representation of an initial value CharSequence or null if supplied expression is not StringBuilder/StringBuffer
* construction expression
*/
@Contract("null -> null")
public static String getStringBuilderInitializerText(PsiExpression construction) {
construction = PsiUtil.skipParenthesizedExprDown(construction);
if (!(construction instanceof PsiNewExpression)) return null;
final PsiNewExpression newExpression = (PsiNewExpression)construction;
final PsiJavaCodeReferenceElement classReference = newExpression.getClassReference();
if (classReference == null) return false;
if (classReference == null) return null;
final PsiElement target = classReference.resolve();
if (!(target instanceof PsiClass)) return false;
if (!(target instanceof PsiClass)) return null;
final PsiClass aClass = (PsiClass)target;
final String qualifiedName = aClass.getQualifiedName();
if (!CommonClassNames.JAVA_LANG_STRING_BUILDER.equals(qualifiedName) &&
!CommonClassNames.JAVA_LANG_STRING_BUFFER.equals(qualifiedName)) {
return false;
return null;
}
final PsiExpressionList argumentList = newExpression.getArgumentList();
if (argumentList == null) return false;
if (argumentList == null) return null;
final PsiExpression[] arguments = argumentList.getExpressions();
if (arguments.length == 0) return true;
if (arguments.length == 0) return "\"\"";
if (arguments.length != 1) return null;
final PsiExpression argument = arguments[0];
final PsiType argumentType = argument.getType();
return PsiType.INT.equals(argumentType);
if (PsiType.INT.equals(argumentType)) return "\"\"";
return argument.getText();
}
/**