diff --git a/java/java-impl/src/com/intellij/codeInspection/java19api/Java9CollectionFactoryInspection.java b/java/java-impl/src/com/intellij/codeInspection/java19api/Java9CollectionFactoryInspection.java index ae6daeba43fc..256bd8f24d6c 100644 --- a/java/java-impl/src/com/intellij/codeInspection/java19api/Java9CollectionFactoryInspection.java +++ b/java/java-impl/src/com/intellij/codeInspection/java19api/Java9CollectionFactoryInspection.java @@ -46,12 +46,16 @@ import static com.intellij.util.ObjectUtils.tryCast; public class Java9CollectionFactoryInspection extends BaseLocalInspectionTool { private static final CallMatcher UNMODIFIABLE_SET = CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_COLLECTIONS, "unmodifiableSet").parameterCount(1); + private static final CallMatcher UNMODIFIABLE_MAP = + CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_COLLECTIONS, "unmodifiableMap").parameterCount(1); private static final CallMatcher UNMODIFIABLE_LIST = CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_COLLECTIONS, "unmodifiableList").parameterCount(1); private static final CallMatcher ARRAYS_AS_LIST = CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_ARRAYS, "asList"); private static final CallMatcher COLLECTION_ADD = CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_COLLECTION, "add").parameterCount(1); + private static final CallMatcher MAP_PUT = + CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_MAP, "put").parameterCount(2); private static final CallMatcher STREAM_COLLECT = CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_STREAM_STREAM, "collect").parameterCount(1); private static final CallMatcher STREAM_OF = @@ -63,6 +67,7 @@ public class Java9CollectionFactoryInspection extends BaseLocalInspectionTool { private static final CallMapper MAPPER = new CallMapper() .register(UNMODIFIABLE_SET, call -> PrepopulatedCollectionModel.fromSet(call.getArgumentList().getExpressions()[0])) + .register(UNMODIFIABLE_MAP, call -> PrepopulatedCollectionModel.fromMap(call.getArgumentList().getExpressions()[0])) .register(UNMODIFIABLE_LIST, call -> PrepopulatedCollectionModel.fromList(call.getArgumentList().getExpressions()[0])); public boolean IGNORE_NON_CONSTANT = false; @@ -122,7 +127,8 @@ public class Java9CollectionFactoryInspection extends BaseLocalInspectionTool { } public boolean isValid() { - return !myHasNulls && !myRepeatingKeys; + boolean mapOfTooManyParameters = myType.equals("Map") && myContent.size() > 20; + return !myHasNulls && !myRepeatingKeys && !mapOfTooManyParameters; } private StreamEx keyExpressions() { @@ -143,54 +149,56 @@ public class Java9CollectionFactoryInspection extends BaseLocalInspectionTool { if (ARRAYS_AS_LIST.test(call)) { return new PrepopulatedCollectionModel(Arrays.asList(call.getArgumentList().getExpressions()), Collections.emptyList(), "List"); } - if(STREAM_COLLECT.test(call) && COLLECTORS_TO_LIST.matches(call.getArgumentList().getExpressions()[0])) { - PsiMethodCallExpression qualifier = MethodCallUtils.getQualifierMethodCall(call); - if(STREAM_OF.matches(qualifier)) { - return new PrepopulatedCollectionModel(Arrays.asList(qualifier.getArgumentList().getExpressions()), Collections.emptyList(), - "List"); - } - } + return fromCollect(call, "List", COLLECTORS_TO_LIST); } if(listDefinition instanceof PsiNewExpression) { return fromNewExpression((PsiNewExpression)listDefinition, "List", CommonClassNames.JAVA_UTIL_ARRAY_LIST); } if (listDefinition instanceof PsiReferenceExpression) { - PsiLocalVariable variable = tryCast(((PsiReferenceExpression)listDefinition).resolve(), PsiLocalVariable.class); - if (variable != null) { - return fromVariable(variable, listDefinition, "List", CommonClassNames.JAVA_UTIL_ARRAY_LIST); - } + return fromVariable((PsiReferenceExpression)listDefinition, "List", CommonClassNames.JAVA_UTIL_ARRAY_LIST, COLLECTION_ADD); } return null; } public static PrepopulatedCollectionModel fromSet(PsiExpression setDefinition) { setDefinition = PsiUtil.skipParenthesizedExprDown(setDefinition); - if(setDefinition instanceof PsiNewExpression) { + if (setDefinition instanceof PsiMethodCallExpression) { + return fromCollect((PsiMethodCallExpression)setDefinition, "Set", COLLECTORS_TO_SET); + } + if (setDefinition instanceof PsiNewExpression) { return fromNewExpression((PsiNewExpression)setDefinition, "Set", CommonClassNames.JAVA_UTIL_HASH_SET); } - if(setDefinition instanceof PsiMethodCallExpression) { - PsiMethodCallExpression call = (PsiMethodCallExpression)setDefinition; - if(STREAM_COLLECT.test(call) && COLLECTORS_TO_SET.matches(call.getArgumentList().getExpressions()[0])) { - PsiMethodCallExpression qualifier = MethodCallUtils.getQualifierMethodCall(call); - if(STREAM_OF.matches(qualifier)) { - return new PrepopulatedCollectionModel(Arrays.asList(qualifier.getArgumentList().getExpressions()), Collections.emptyList(), - "Set"); - } - } - } if (setDefinition instanceof PsiReferenceExpression) { - PsiLocalVariable variable = tryCast(((PsiReferenceExpression)setDefinition).resolve(), PsiLocalVariable.class); - if (variable != null) { - return fromVariable(variable, setDefinition, "Set", CommonClassNames.JAVA_UTIL_HASH_SET); + return fromVariable((PsiReferenceExpression)setDefinition, "Set", CommonClassNames.JAVA_UTIL_HASH_SET, COLLECTION_ADD); + } + return null; + } + + public static PrepopulatedCollectionModel fromMap(PsiExpression mapDefinition) { + mapDefinition = PsiUtil.skipParenthesizedExprDown(mapDefinition); + if (mapDefinition instanceof PsiReferenceExpression) { + return fromVariable((PsiReferenceExpression)mapDefinition, "Map", CommonClassNames.JAVA_UTIL_HASH_MAP, MAP_PUT); + } + return null; + } + + @Nullable + private static PrepopulatedCollectionModel fromCollect(PsiMethodCallExpression call, String typeName, CallMatcher collector) { + if (STREAM_COLLECT.test(call) && collector.matches(call.getArgumentList().getExpressions()[0])) { + PsiMethodCallExpression qualifier = MethodCallUtils.getQualifierMethodCall(call); + if (STREAM_OF.matches(qualifier)) { + return new PrepopulatedCollectionModel(Arrays.asList(qualifier.getArgumentList().getExpressions()), Collections.emptyList(), + typeName); } } return null; } @Nullable - private static PrepopulatedCollectionModel fromVariable(PsiLocalVariable variable, - PsiExpression expression, - String typeName, String collectionClass) { + private static PrepopulatedCollectionModel fromVariable(PsiReferenceExpression expression, + String typeName, String collectionClass, CallMatcher addMethod) { + PsiLocalVariable variable = tryCast(expression.resolve(), PsiLocalVariable.class); + if (variable == null) return null; PsiCodeBlock block = PsiTreeUtil.getParentOfType(variable, PsiCodeBlock.class); PsiDeclarationStatement declaration = PsiTreeUtil.getParentOfType(variable, PsiDeclarationStatement.class); if (block == null || declaration == null) return null; @@ -211,9 +219,9 @@ public class Java9CollectionFactoryInspection extends BaseLocalInspectionTool { if (PsiTreeUtil.isAncestor(cur, expression, false)) break; if (!(cur instanceof PsiExpressionStatement)) return null; PsiMethodCallExpression call = tryCast(((PsiExpressionStatement)cur).getExpression(), PsiMethodCallExpression.class); - if (!COLLECTION_ADD.test(call)) return null; + if (!addMethod.test(call)) return null; if (!refs.remove(call.getMethodExpression().getQualifierExpression())) return null; - contents.add(call.getArgumentList().getExpressions()[0]); + contents.addAll(Arrays.asList(call.getArgumentList().getExpressions())); elementsToRemove.add(cur); } if (!refs.isEmpty()) return null; diff --git a/java/java-tests/testData/inspection/java9CollectionFactory/afterHashMap10.java b/java/java-tests/testData/inspection/java9CollectionFactory/afterHashMap10.java new file mode 100644 index 000000000000..8a084000a148 --- /dev/null +++ b/java/java-tests/testData/inspection/java9CollectionFactory/afterHashMap10.java @@ -0,0 +1,9 @@ +// "Replace with 'Map.of' call" "true" +import java.util.*; + +public class Test { + public void test() { + Map myMap; + myMap = Map.of("a", "1", "b", "1", "c", "1", "d", "1", "e", "1", "f", "1", "g", "1", "h", "1", "i", "1", "j", "1"); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/java9CollectionFactory/afterHashMapSimple.java b/java/java-tests/testData/inspection/java9CollectionFactory/afterHashMapSimple.java new file mode 100644 index 000000000000..a5dcd1da34d4 --- /dev/null +++ b/java/java-tests/testData/inspection/java9CollectionFactory/afterHashMapSimple.java @@ -0,0 +1,9 @@ +// "Replace with 'Map.of' call" "true" +import java.util.*; + +public class Test { + public void test() { + Map myMap; + myMap = Map.of("a", "b", "c", "b"); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/java9CollectionFactory/beforeHashMap10.java b/java/java-tests/testData/inspection/java9CollectionFactory/beforeHashMap10.java new file mode 100644 index 000000000000..42de56cd715f --- /dev/null +++ b/java/java-tests/testData/inspection/java9CollectionFactory/beforeHashMap10.java @@ -0,0 +1,19 @@ +// "Replace with 'Map.of' call" "true" +import java.util.*; + +public class Test { + public void test() { + Map myMap = new HashMap<>(); + myMap.put("a", "1"); + myMap.put("b", "1"); + myMap.put("c", "1"); + myMap.put("d", "1"); + myMap.put("e", "1"); + myMap.put("f", "1"); + myMap.put("g", "1"); + myMap.put("h", "1"); + myMap.put("i", "1"); + myMap.put("j", "1"); + myMap = Collections.unmodifiableMap(myMap); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/java9CollectionFactory/beforeHashMap11.java b/java/java-tests/testData/inspection/java9CollectionFactory/beforeHashMap11.java new file mode 100644 index 000000000000..2fb562b37e1d --- /dev/null +++ b/java/java-tests/testData/inspection/java9CollectionFactory/beforeHashMap11.java @@ -0,0 +1,20 @@ +// "Replace with 'Map.of' call" "false" +import java.util.*; + +public class Test { + public void test() { + Map myMap = new HashMap<>(); + myMap.put("a", "1"); + myMap.put("b", "1"); + myMap.put("c", "1"); + myMap.put("d", "1"); + myMap.put("e", "1"); + myMap.put("f", "1"); + myMap.put("g", "1"); + myMap.put("h", "1"); + myMap.put("i", "1"); + myMap.put("j", "1"); + myMap.put("k", "1"); + myMap = Collections.unmodifiableMap(myMap); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/java9CollectionFactory/beforeHashMapRepeatingKey.java b/java/java-tests/testData/inspection/java9CollectionFactory/beforeHashMapRepeatingKey.java new file mode 100644 index 000000000000..be80f59cf89e --- /dev/null +++ b/java/java-tests/testData/inspection/java9CollectionFactory/beforeHashMapRepeatingKey.java @@ -0,0 +1,11 @@ +// "Replace with 'Map.of' call" "false" +import java.util.*; + +public class Test { + public void test() { + Map myMap = new HashMap<>(); + myMap.put("a", "b"); + myMap.put("a", "c"); + myMap = Collections.unmodifiableMap(myMap); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/java9CollectionFactory/beforeHashMapSimple.java b/java/java-tests/testData/inspection/java9CollectionFactory/beforeHashMapSimple.java new file mode 100644 index 000000000000..0abc6109d594 --- /dev/null +++ b/java/java-tests/testData/inspection/java9CollectionFactory/beforeHashMapSimple.java @@ -0,0 +1,11 @@ +// "Replace with 'Map.of' call" "true" +import java.util.*; + +public class Test { + public void test() { + Map myMap = new HashMap<>(); + myMap.put("a", "b"); + myMap.put("c", "b"); + myMap = Collections.unmodifiableMap(myMap); + } +} \ No newline at end of file