StreamApiMigration: support Java10 toUnmodifiableList/Set (part of IDEA-187213)

This commit is contained in:
Tagir Valeev
2018-03-13 11:14:23 +07:00
parent a9711af801
commit b2d5f75a92
8 changed files with 287 additions and 9 deletions

View File

@@ -5,6 +5,7 @@ import com.intellij.codeInsight.intention.impl.StreamRefactoringUtil;
import com.intellij.codeInspection.util.LambdaGenerationUtil;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.search.searches.ReferencesSearch;
@@ -135,7 +136,7 @@ class CollectMigration extends BaseStreamApiMigration {
static CollectTerminal includePostStatements(@NotNull CollectTerminal terminal, @Nullable PsiElement nextElement) {
if (nextElement == null) return terminal;
List<BiFunction<CollectTerminal, PsiElement, CollectTerminal>> wrappers =
Arrays.asList(SortingTerminal::tryWrap, ToArrayTerminal::tryWrap, NewListTerminal::tryWrap);
Arrays.asList(SortingTerminal::tryWrap, ToArrayTerminal::tryWrap, NewListTerminal::tryWrap, UnmodifiableTerminal::tryWrap);
while (true) {
CollectTerminal wrapped = null;
for (BiFunction<CollectTerminal, PsiElement, CollectTerminal> wrapper : wrappers) {
@@ -867,6 +868,107 @@ class CollectMigration extends BaseStreamApiMigration {
String intermediateSteps = terminal.getIntermediateStepsFromCollection();
if (intermediateSteps == null) return null;
WrapperCandidate candidate = WrapperCandidate.tryExtract(terminal, element);
if (candidate == null) return null;
if (!(candidate.myCandidate instanceof PsiNewExpression)) return null;
if (!InheritanceUtil.isInheritor(candidate.myType, CommonClassNames.JAVA_UTIL_COLLECTION)) return null;
PsiNewExpression newExpression = (PsiNewExpression)candidate.myCandidate;
PsiExpressionList argumentList = newExpression.getArgumentList();
if (argumentList == null) return null;
PsiExpression[] args = argumentList.getExpressions();
if (args.length != 1 || !terminal.isTargetReference(args[0])) return null;
return new NewListTerminal(terminal, candidate.myVar, intermediateSteps, newExpression, candidate.myType);
}
}
static class UnmodifiableTerminal extends RecreateTerminal {
private static final Map<String, String> TYPE_TO_UNMODIFIABLE_WRAPPER = EntryStream.of(
CommonClassNames.JAVA_UTIL_ARRAY_LIST, "toUnmodifiableList",
"java.util.LinkedList", "toUnmodifiableList",
CommonClassNames.JAVA_UTIL_HASH_SET, "toUnmodifiableSet"
).toMap();
private static final CallMatcher UNMODIFIABLE_WRAPPER =
CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_COLLECTIONS, "unmodifiableList", "unmodifiableSet", "unmodifiableCollection")
.parameterCount(1);
private static final CallMatcher STREAM_COLLECT =
CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_STREAM_STREAM, "collect").parameterTypes("java.util.stream.Collector");
private static final CallMatcher TO_LIST =
CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS, "toList").parameterCount(0);
private static final CallMatcher TO_SET =
CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS, "toSet").parameterCount(0);
private final String myUnmodifiableCollector;
UnmodifiableTerminal(CollectTerminal upstream,
PsiLocalVariable variable,
PsiMethodCallExpression unmodifiableExpression,
String unmodifiableCollector) {
super(upstream, variable, "", unmodifiableExpression);
myUnmodifiableCollector = unmodifiableCollector;
}
@Override
public String generateTerminal(CommentTracker ct, boolean strictMode) {
return ".collect(" + CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS + "." + myUnmodifiableCollector + "())";
}
@Override
StreamEx<String> fusedElements() {
String reference = ((PsiMethodCallExpression)myCreateExpression).getMethodExpression().getReferenceName();
return myUpstream.fusedElements().append(reference);
}
@Nullable
public static UnmodifiableTerminal tryWrap(CollectTerminal terminal, PsiElement element) {
if (PsiUtil.getLanguageLevel(element).isLessThan(LanguageLevel.JDK_10)) return null;
if (terminal.getStatus() == ControlFlowUtils.InitializerUsageStatus.UNKNOWN) return null;
WrapperCandidate candidate = WrapperCandidate.tryExtract(terminal, element);
if (candidate == null) return null;
if (!(candidate.myCandidate instanceof PsiMethodCallExpression)) return null;
PsiMethodCallExpression wrapCall = (PsiMethodCallExpression)candidate.myCandidate;
if (!UNMODIFIABLE_WRAPPER.test(wrapCall)) return null;
PsiExpression arg = wrapCall.getArgumentList().getExpressions()[0];
if (!terminal.isTargetReference(arg)) return null;
if (terminal.getTargetVariable() != null) {
arg = terminal.getTargetVariable().getInitializer();
}
if (arg == null) return null;
PsiClassType targetType = tryCast(arg.getType(), PsiClassType.class);
if (targetType == null) return null;
String collector = TYPE_TO_UNMODIFIABLE_WRAPPER.get(targetType.rawType().getCanonicalText());
if (collector == null) {
if (!(arg instanceof PsiMethodCallExpression)) return null;
PsiMethodCallExpression argCall = (PsiMethodCallExpression)arg;
if (!STREAM_COLLECT.test(argCall)) return null;
PsiExpression previousCollector = argCall.getArgumentList().getExpressions()[0];
if (TO_LIST.matches(previousCollector)) {
collector = "toUnmodifiableList";
} else if (TO_SET.matches(previousCollector)) {
collector = "toUnmodifiableSet";
} else {
return null;
}
}
return new UnmodifiableTerminal(terminal, candidate.myVar, wrapCall, collector);
}
}
private static class WrapperCandidate {
private final PsiExpression myCandidate;
private final PsiType myType;
private final PsiLocalVariable myVar;
public WrapperCandidate(PsiExpression candidate, PsiType type, PsiLocalVariable var) {
myCandidate = candidate;
myType = type;
myVar = var;
}
@Nullable
static WrapperCandidate tryExtract(CollectTerminal terminal, PsiElement element) {
PsiExpression candidate;
PsiType type;
PsiLocalVariable var = null;
@@ -894,14 +996,7 @@ class CollectMigration extends BaseStreamApiMigration {
return null;
}
}
if (!(candidate instanceof PsiNewExpression)) return null;
if (!InheritanceUtil.isInheritor(type, CommonClassNames.JAVA_UTIL_COLLECTION)) return null;
PsiNewExpression newExpression = (PsiNewExpression)candidate;
PsiExpressionList argumentList = newExpression.getArgumentList();
if (argumentList == null) return null;
PsiExpression[] args = argumentList.getExpressions();
if (args.length != 1 || !terminal.isTargetReference(args[0])) return null;
return new NewListTerminal(terminal, var, intermediateSteps, newExpression, type);
return new WrapperCandidate(candidate, type, var);
}
}
}

View File

@@ -0,0 +1,13 @@
// "Fix all 'Subsequent steps can be fused into Stream API chain' problems in file" "true"
import java.util.*;
import java.util.stream.*;
public class Test {
List<String> testFuse(String[] list) {
return Arrays.stream(list).filter(s -> !s.isEmpty()).collect(Collectors.toUnmodifiableList());
}
Set<String> testFuse2(String[] list) {
return Arrays.stream(list).filter(s -> !s.isEmpty()).collect(Collectors.toUnmodifiableSet());
}
}

View File

@@ -0,0 +1,14 @@
// "Fix all 'Subsequent steps can be fused into Stream API chain' problems in file" "true"
import java.util.*;
import java.util.stream.*;
public class Test {
List<String> testFuse(String[] list) {
List<String> result = Arrays.stream(list).filter(s -> !s.isEmpty()).co<caret>llect(Collectors.toCollection(LinkedList::new));
return Collections.unmodifiableList(result);
}
Set<String> testFuse2(String[] list) {
return Collections.unmodifiableSet(Arrays.stream(list).filter(s -> !s.isEmpty()).collect(Collectors.toSet()));
}
}

View File

@@ -0,0 +1,26 @@
// "Fix all 'Loop can be collapsed with Stream API' problems in file" "true"
import java.util.*;
import java.util.stream.Collectors;
// Java 8 language level used: no toUnmodifiable suggestions
class Test {
List<String> test(String[] list) {
List<String> result = Arrays.stream(list).filter(s -> !s.isEmpty()).collect(Collectors.toCollection(LinkedList::new));
return Collections.unmodifiableList(result);
}
Collection<String> test2(String[] list) {
Set<String> result = Arrays.stream(list).filter(s -> !s.isEmpty()).collect(Collectors.toSet());
return Collections.unmodifiableCollection(result);
}
List<String> test3(String[] array) {
List<String> list = Arrays.stream(array).filter(s -> !s.isEmpty()).distinct().collect(Collectors.toList());
return Collections.unmodifiableList(list);
}
Set<String> test4(String[] array) {
Set<String> result = Arrays.stream(array).filter(s -> !s.isEmpty()).collect(Collectors.toCollection(TreeSet::new));
return Collections.unmodifiableSet(result);
}
}

View File

@@ -0,0 +1,23 @@
// "Fix all 'Loop can be collapsed with Stream API' problems in file" "true"
import java.util.*;
import java.util.stream.Collectors;
class Test {
List<String> test(String[] list) {
return Arrays.stream(list).filter(s -> !s.isEmpty()).collect(Collectors.toUnmodifiableList());
}
Collection<String> test2(String[] list) {
return Arrays.stream(list).filter(s -> !s.isEmpty()).collect(Collectors.toUnmodifiableSet());
}
List<String> test3(String[] array) {
return Arrays.stream(array).filter(s -> !s.isEmpty()).distinct().collect(Collectors.toUnmodifiableList());
}
Set<String> test4(String[] array) {
Set<String> result = Arrays.stream(array).filter(s -> !s.isEmpty()).collect(Collectors.toCollection(TreeSet::new));
// toUnmodifiableSet will not preserve order; not suggested here
return Collections.unmodifiableSet(result);
}
}

View File

@@ -0,0 +1,50 @@
// "Fix all 'Loop can be collapsed with Stream API' problems in file" "true"
import java.util.*;
// Java 8 language level used: no toUnmodifiable suggestions
class Test {
List<String> test(String[] list) {
List<String> result = new LinkedList<>();
f<caret>or (int i = 0; i < list.length; i++) {
String s = list[i];
if (!s.isEmpty()) {
result.add(s);
}
}
return Collections.unmodifiableList(result);
}
Collection<String> test2(String[] list) {
Set<String> result = new HashSet<>();
for (int i = 0; i < list.length; i++) {
String s = list[i];
if (!s.isEmpty()) {
result.add(s);
}
}
return Collections.unmodifiableCollection(result);
}
List<String> test3(String[] array) {
Set<String> result = new HashSet<>();
for (int i = 0; i < array.length; i++) {
String s = array[i];
if (!s.isEmpty()) {
result.add(s);
}
}
List<String> list = new ArrayList<>(result);
return Collections.unmodifiableList(list);
}
Set<String> test4(String[] array) {
Set<String> result = new TreeSet<>();
for (int i = 0; i < array.length; i++) {
String s = array[i];
if (!s.isEmpty()) {
result.add(s);
}
}
return Collections.unmodifiableSet(result);
}
}

View File

@@ -0,0 +1,50 @@
// "Fix all 'Loop can be collapsed with Stream API' problems in file" "true"
import java.util.*;
class Test {
List<String> test(String[] list) {
List<String> result = new LinkedList<>();
f<caret>or (int i = 0; i < list.length; i++) {
String s = list[i];
if (!s.isEmpty()) {
result.add(s);
}
}
return Collections.unmodifiableList(result);
}
Collection<String> test2(String[] list) {
Set<String> result = new HashSet<>();
for (int i = 0; i < list.length; i++) {
String s = list[i];
if (!s.isEmpty()) {
result.add(s);
}
}
return Collections.unmodifiableCollection(result);
}
List<String> test3(String[] array) {
Set<String> result = new HashSet<>();
for (int i = 0; i < array.length; i++) {
String s = array[i];
if (!s.isEmpty()) {
result.add(s);
}
}
List<String> list = new ArrayList<>(result);
return Collections.unmodifiableList(list);
}
Set<String> test4(String[] array) {
Set<String> result = new TreeSet<>();
for (int i = 0; i < array.length; i++) {
String s = array[i];
if (!s.isEmpty()) {
result.add(s);
}
}
// toUnmodifiableSet will not preserve order; not suggested here
return Collections.unmodifiableSet(result);
}
}

View File

@@ -227,4 +227,11 @@ public class StreamApiMigrationInspectionTest {
return LanguageLevel.JDK_1_9;
}
}
public static class Java10Test extends StreamApiMigrationInspectionBaseTest {
@Override
String getFolder() {
return "java10";
}
}
}