diff --git a/java/java-impl/src/com/intellij/codeInspection/ReadWriteStringCanBeUsedInspection.java b/java/java-impl/src/com/intellij/codeInspection/ReadWriteStringCanBeUsedInspection.java index a91bf45e17bb..e3e6cbde6976 100644 --- a/java/java-impl/src/com/intellij/codeInspection/ReadWriteStringCanBeUsedInspection.java +++ b/java/java-impl/src/com/intellij/codeInspection/ReadWriteStringCanBeUsedInspection.java @@ -2,6 +2,7 @@ package com.intellij.codeInspection; import com.intellij.openapi.project.Project; +import com.intellij.pom.java.LanguageLevel; import com.intellij.psi.*; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtil; @@ -22,11 +23,14 @@ public class ReadWriteStringCanBeUsedInspection extends AbstractBaseJavaLocalIns .parameterTypes("java.nio.file.Path"); private static final CallMatcher STRING_GET_BYTES = CallMatcher.exactInstanceCall(CommonClassNames.JAVA_LANG_STRING, "getBytes") .parameterTypes("java.nio.charset.Charset"); + private static final CallMatcher CHARSET_FOR_NAME = CallMatcher.staticCall("java.nio.charset.Charset", "forName") + .parameterTypes(CommonClassNames.JAVA_LANG_STRING); @NotNull @Override public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { - if (!PsiUtil.isLanguageLevel11OrHigher(holder.getFile())) { + LanguageLevel level = PsiUtil.getLanguageLevel(holder.getFile()); + if (level.isLessThan(LanguageLevel.JDK_11)) { return PsiElementVisitor.EMPTY_VISITOR; } return new JavaElementVisitor() { @@ -35,8 +39,15 @@ public class ReadWriteStringCanBeUsedInspection extends AbstractBaseJavaLocalIns if (FILES_WRITE.test(call)) { PsiMethodCallExpression bytesExpression = tryCast(ExpressionUtils.resolveExpression(call.getArgumentList().getExpressions()[1]), PsiMethodCallExpression.class); if (STRING_GET_BYTES.test(bytesExpression) && bytesExpression.getMethodExpression().getQualifierExpression() != null) { - holder.registerProblem(call, "Can be replaced with 'Files.writeString()'", - new ReplaceWithWriteStringFix()); + // Do not suggest for UTF-8 before Java 12 due to JDK-8209576 + ProblemHighlightType highlight; + String message = "Can be replaced with 'Files.writeString()'"; + if (level.isAtLeast(LanguageLevel.JDK_12) || isNonUtf8Charset(bytesExpression.getArgumentList().getExpressions()[0])) { + highlight = ProblemHighlightType.WARNING; + } else { + highlight = ProblemHighlightType.INFORMATION; + } + holder.registerProblem(call, message, highlight, new ReplaceWithWriteStringFix(highlight == ProblemHighlightType.INFORMATION)); } } else if (FILES_READ_ALL_BYTES.test(call)) { PsiExpressionList expressionList = tryCast(PsiUtil.skipParenthesizedExprUp(call.getParent()), PsiExpressionList.class); @@ -58,6 +69,7 @@ public class ReadWriteStringCanBeUsedInspection extends AbstractBaseJavaLocalIns } private static class ReplaceWithReadStringFix implements LocalQuickFix { + @Nls(capitalization = Nls.Capitalization.Sentence) @NotNull @Override @@ -87,6 +99,19 @@ public class ReadWriteStringCanBeUsedInspection extends AbstractBaseJavaLocalIns } private static class ReplaceWithWriteStringFix implements LocalQuickFix { + private final boolean myMayNotWork; + + private ReplaceWithWriteStringFix(boolean mayNotWork) { + myMayNotWork = mayNotWork; + } + + @Nls(capitalization = Nls.Capitalization.Sentence) + @NotNull + @Override + public String getName() { + return myMayNotWork ? getFamilyName() + " (may not work before JDK 11.0.2)" : getFamilyName(); + } + @Nls(capitalization = Nls.Capitalization.Sentence) @NotNull @Override @@ -122,16 +147,50 @@ public class ReadWriteStringCanBeUsedInspection extends AbstractBaseJavaLocalIns } } } - static boolean isUtf8Charset(PsiExpression expression) { - PsiReferenceExpression ref = tryCast(PsiUtil.skipParenthesizedExprDown(expression), PsiReferenceExpression.class); - if (ref == null) return false; - if (!"UTF_8".equals(ref.getReferenceName())) { - return false; + expression = PsiUtil.skipParenthesizedExprDown(expression); + if (expression instanceof PsiReferenceExpression) { + PsiReferenceExpression ref = (PsiReferenceExpression)expression; + if (!"UTF_8".equals(ref.getReferenceName())) { + return false; + } + PsiField target = tryCast(ref.resolve(), PsiField.class); + return target != null && + target.getContainingClass() != null && + "java.nio.charset.StandardCharsets".equals(target.getContainingClass().getQualifiedName()); } - PsiField target = tryCast(ref.resolve(), PsiField.class); - return target != null && - target.getContainingClass() != null && - "java.nio.charset.StandardCharsets".equals(target.getContainingClass().getQualifiedName()); + if (expression instanceof PsiMethodCallExpression && CHARSET_FOR_NAME.test((PsiMethodCallExpression)expression)) { + PsiExpression arg = ((PsiMethodCallExpression)expression).getArgumentList().getExpressions()[0]; + Object value = ExpressionUtils.computeConstantExpression(arg); + return value instanceof String && ((String)value).equalsIgnoreCase("utf-8"); + } + return false; + } + + static boolean isNonUtf8Charset(PsiExpression expression) { + for (int i = 0; i < 3; i++) { + expression = PsiUtil.skipParenthesizedExprDown(expression); + if (expression instanceof PsiReferenceExpression) { + PsiField target = tryCast(((PsiReferenceExpression)expression).resolve(), PsiField.class); + if (target != null) { + if ("UTF_8".equals(target.getName())) return false; + if (target.getContainingClass() != null && + "java.nio.charset.StandardCharsets".equals(target.getContainingClass().getQualifiedName())) { + return true; + } + if (target.hasModifierProperty(PsiModifier.FINAL)) { + expression = target.getInitializer(); + continue; + } + } + } + if (expression instanceof PsiMethodCallExpression && CHARSET_FOR_NAME.test((PsiMethodCallExpression)expression)) { + PsiExpression arg = ((PsiMethodCallExpression)expression).getArgumentList().getExpressions()[0]; + Object value = ExpressionUtils.computeConstantExpression(arg); + return value instanceof String && !((String)value).equalsIgnoreCase("utf-8"); + } + break; + } + return false; } } diff --git a/java/java-tests/testData/inspection/filesReadWriteString/afterWriteStringForNameUtf16.java b/java/java-tests/testData/inspection/filesReadWriteString/afterWriteStringForNameUtf16.java new file mode 100644 index 000000000000..f9fc1326d063 --- /dev/null +++ b/java/java-tests/testData/inspection/filesReadWriteString/afterWriteStringForNameUtf16.java @@ -0,0 +1,15 @@ +// "Replace with 'Files.writeString()'" "WARNING" +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; + +public class Example { + private static final Charset CHARSET = Charset.forName("UTF-16"); + + void test1(String str) throws IOException { + Files.writeString(Paths.get("/etc/passwd"), str, CHARSET, StandardOpenOption.CREATE); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/filesReadWriteString/afterWriteStringUtf16.java b/java/java-tests/testData/inspection/filesReadWriteString/afterWriteStringUtf16.java new file mode 100644 index 000000000000..c06360844d71 --- /dev/null +++ b/java/java-tests/testData/inspection/filesReadWriteString/afterWriteStringUtf16.java @@ -0,0 +1,15 @@ +// "Replace with 'Files.writeString()'" "WARNING" +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; + +public class Example { + private static final Charset CHARSET = StandardCharsets.UTF_16; + + void test1(String str) throws IOException { + Files.writeString(Paths.get("/etc/passwd"), str, CHARSET, StandardOpenOption.CREATE); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/filesReadWriteString/afterWriteStringUtf8.java b/java/java-tests/testData/inspection/filesReadWriteString/afterWriteStringUtf8.java new file mode 100644 index 000000000000..2717d3c04494 --- /dev/null +++ b/java/java-tests/testData/inspection/filesReadWriteString/afterWriteStringUtf8.java @@ -0,0 +1,15 @@ +// "Replace with 'Files.writeString()' (may not work before JDK 11.0.2)" "INFORMATION" +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; + +public class Example { + private static final Charset CHARSET = StandardCharsets.UTF_8; + + void test1(String str) throws IOException { + Files.writeString(Paths.get("/etc/passwd"), str, CHARSET, StandardOpenOption.CREATE); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/filesReadWriteString/beforeWriteStringForNameUtf16.java b/java/java-tests/testData/inspection/filesReadWriteString/beforeWriteStringForNameUtf16.java new file mode 100644 index 000000000000..1a02c8c96e0f --- /dev/null +++ b/java/java-tests/testData/inspection/filesReadWriteString/beforeWriteStringForNameUtf16.java @@ -0,0 +1,16 @@ +// "Replace with 'Files.writeString()'" "WARNING" +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; + +public class Example { + private static final Charset CHARSET = Charset.forName("UTF-16"); + + void test1(String str) throws IOException { + byte[] bytes = str.getBytes(CHARSET); + Files.write(Paths.get("/etc/passwd"), bytes, StandardOpenOption.CREATE); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/filesReadWriteString/beforeWriteStringUtf16.java b/java/java-tests/testData/inspection/filesReadWriteString/beforeWriteStringUtf16.java new file mode 100644 index 000000000000..fce72e2f8c98 --- /dev/null +++ b/java/java-tests/testData/inspection/filesReadWriteString/beforeWriteStringUtf16.java @@ -0,0 +1,16 @@ +// "Replace with 'Files.writeString()'" "WARNING" +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; + +public class Example { + private static final Charset CHARSET = StandardCharsets.UTF_16; + + void test1(String str) throws IOException { + byte[] bytes = str.getBytes(CHARSET); + Files.write(Paths.get("/etc/passwd"), bytes, StandardOpenOption.CREATE); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/filesReadWriteString/beforeWriteStringUtf8.java b/java/java-tests/testData/inspection/filesReadWriteString/beforeWriteStringUtf8.java new file mode 100644 index 000000000000..f32ff805c214 --- /dev/null +++ b/java/java-tests/testData/inspection/filesReadWriteString/beforeWriteStringUtf8.java @@ -0,0 +1,16 @@ +// "Replace with 'Files.writeString()' (may not work before JDK 11.0.2)" "INFORMATION" +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; + +public class Example { + private static final Charset CHARSET = StandardCharsets.UTF_8; + + void test1(String str) throws IOException { + byte[] bytes = str.getBytes(CHARSET); + Files.write(Paths.get("/etc/passwd"), bytes, StandardOpenOption.CREATE); + } +} \ No newline at end of file