diff --git a/java/java-impl/src/META-INF/JavaPlugin.xml b/java/java-impl/src/META-INF/JavaPlugin.xml
index 75e57e6ea70a..3994aefd5622 100644
--- a/java/java-impl/src/META-INF/JavaPlugin.xml
+++ b/java/java-impl/src/META-INF/JavaPlugin.xml
@@ -743,6 +743,10 @@
key="inspection.redundant.array.creation.display.name" groupKey="group.names.verbose.or.redundant.code.constructs"
enabledByDefault="true" level="WARNING" cleanupTool="true"
implementationClass="com.intellij.codeInspection.miscGenerics.RedundantArrayForVarargsCallInspection"/>
+
SUPPORTED_CHARSETS =
+ ContainerUtil.immutableSet("US-ASCII", "ISO-8859-1", "UTF-8", "UTF-16BE", "UTF-16LE", "UTF-16");
+
+ @NotNull
+ @Override
+ public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
+ if (!PsiUtil.isLanguageLevel7OrHigher(holder.getFile())) return PsiElementVisitor.EMPTY_VISITOR;
+ return new JavaElementVisitor() {
+ @Override
+ public void visitCallExpression(PsiCallExpression call) {
+ CharsetMatch match = StreamEx.of(MATCHERS)
+ .map(matcher -> matcher.extractCharsetMatch(call))
+ .nonNull()
+ .findFirst().orElse(null);
+ if (match == null) return;
+ String charsetString = getCharsetString(match.myStringCharset);
+ if (charsetString == null) return;
+ String constantName = "StandardCharsets." + charsetString.replace('-', '_');
+ holder
+ .registerProblem(match.myStringCharset, InspectionsBundle.message("inspection.charset.object.can.be.used.message", constantName),
+ new CharsetObjectCanBeUsedFix(constantName));
+ }
+
+ @Nullable
+ private String getCharsetString(PsiExpression charsetExpression) {
+ charsetExpression = PsiUtil.skipParenthesizedExprDown(charsetExpression);
+ String charsetString = ObjectUtils.tryCast(ExpressionUtils.computeConstantExpression(charsetExpression), String.class);
+ if (charsetString == null || !SUPPORTED_CHARSETS.contains(charsetString)) return null;
+ if (charsetExpression instanceof PsiLiteralExpression) return charsetString;
+ if (charsetExpression instanceof PsiReferenceExpression) {
+ String name = ((PsiReferenceExpression)charsetExpression).getReferenceName();
+ if (name == null) return null;
+ String baseName = name.replaceAll("[^A-Z0-9]", "").toLowerCase(Locale.ENGLISH);
+ String baseCharset = charsetString.replaceAll("[^A-Z0-9]", "").toLowerCase(Locale.ENGLISH);
+ // Do not report constants which name is not based on charset name (like "ENCODING", "DEFAULT_ENCODING", etc.)
+ // because replacement might not be well-suitable
+ if (!baseName.contains(baseCharset)) return null;
+ return charsetString;
+ }
+ return null;
+ }
+ };
+ }
+
+ abstract static class CharsetCallMatcher {
+ @NotNull final String myClassName;
+ @NotNull final String[] myParameters;
+ final int myCharsetParameterIndex;
+
+ CharsetCallMatcher(@NotNull String className, @NotNull String... parameters) {
+ myClassName = className;
+ myParameters = parameters;
+ int index = -1;
+ for (int i = 0; i < parameters.length; i++) {
+ if (parameters[i].isEmpty()) {
+ if (index == -1) {
+ index = i;
+ }
+ else {
+ throw new IllegalArgumentException("Empty parameter type must be specified exactly once");
+ }
+ }
+ }
+ if (index == -1) {
+ throw new IllegalArgumentException("No empty parameter type is specified");
+ }
+ myCharsetParameterIndex = index;
+ }
+
+ @Contract("null,_ -> false")
+ final boolean checkMethod(PsiMethod method, @NotNull String charsetType) {
+ if (method == null) return false;
+ PsiClass containingClass = method.getContainingClass();
+ if (containingClass == null || !myClassName.equals(containingClass.getQualifiedName())) return false;
+ PsiParameterList list = method.getParameterList();
+ if (list.getParametersCount() != myParameters.length) return false;
+ PsiParameter[] parameters = list.getParameters();
+ for (int i = 0; i < myParameters.length; i++) {
+ PsiType parameterType = parameters[i].getType();
+ if (!parameterType.equalsToText(myParameters[i].isEmpty() ? charsetType : myParameters[i])) return false;
+ }
+ return true;
+ }
+
+ @Nullable
+ final CharsetMatch createMatch(PsiMethod method, PsiExpressionList arguments) {
+ PsiExpression argument = arguments.getExpressions()[myCharsetParameterIndex];
+ PsiClass aClass = method.getContainingClass();
+ if (aClass == null) return null;
+
+ PsiMethod[] candidates = method.isConstructor() ? aClass.getConstructors() : aClass.findMethodsByName(method.getName(), false);
+ PsiMethod charsetMethod = Arrays.stream(candidates)
+ .filter(psiMethod -> checkMethod(psiMethod, "java.nio.charset.Charset"))
+ .findFirst().orElse(null);
+ if (charsetMethod == null) return null;
+ return new CharsetMatch(argument, method, charsetMethod);
+ }
+
+ @Nullable
+ abstract CharsetMatch extractCharsetMatch(PsiCallExpression call);
+ }
+
+ static class CharsetConstructorMatcher extends CharsetCallMatcher {
+ CharsetConstructorMatcher(@NotNull String className, @NotNull String... parameters) {
+ super(className, parameters);
+ }
+
+ @Override
+ CharsetMatch extractCharsetMatch(PsiCallExpression call) {
+ if (!(call instanceof PsiNewExpression)) return null;
+ PsiNewExpression newExpression = (PsiNewExpression)call;
+ PsiExpressionList argumentList = newExpression.getArgumentList();
+ if (argumentList == null || argumentList.getExpressionCount() != myParameters.length) return null;
+ PsiMethod method = call.resolveMethod();
+ if (!checkMethod(method, JAVA_LANG_STRING) || !method.isConstructor()) return null;
+ return createMatch(method, argumentList);
+ }
+ }
+
+ static class CharsetMethodMatcher extends CharsetCallMatcher {
+ @NotNull private final String myMethodName;
+
+ CharsetMethodMatcher(@NotNull String className, @NotNull String methodName, @NotNull String... parameters) {
+ super(className, parameters);
+ myMethodName = methodName;
+ }
+
+ @Override
+ CharsetMatch extractCharsetMatch(PsiCallExpression call) {
+ if (!(call instanceof PsiMethodCallExpression)) return null;
+ PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)call;
+ if (!myMethodName.equals(methodCallExpression.getMethodExpression().getReferenceName())) return null;
+ PsiExpressionList argumentList = methodCallExpression.getArgumentList();
+ if (argumentList.getExpressionCount() != myParameters.length) return null;
+ PsiMethod method = call.resolveMethod();
+ if (!checkMethod(method, JAVA_LANG_STRING)) return null;
+ return createMatch(method, argumentList);
+ }
+ }
+
+ static class CharsetMatch {
+ @NotNull final PsiExpression myStringCharset;
+ @NotNull final PsiMethod myStringMethod;
+ @NotNull final PsiMethod myCharsetMethod;
+
+ CharsetMatch(@NotNull PsiExpression charset, @NotNull PsiMethod stringMethod, @NotNull PsiMethod charsetMethod) {
+ myStringCharset = charset;
+ myStringMethod = stringMethod;
+ myCharsetMethod = charsetMethod;
+ }
+ }
+
+ static class CharsetObjectCanBeUsedFix implements LocalQuickFix {
+ private final String myConstantName;
+
+ public CharsetObjectCanBeUsedFix(String constantName) {
+ myConstantName = constantName;
+ }
+
+ @Nls
+ @NotNull
+ @Override
+ public String getName() {
+ return InspectionsBundle.message("inspection.charset.object.can.be.used.fix.name", myConstantName);
+ }
+
+ @Nls
+ @NotNull
+ @Override
+ public String getFamilyName() {
+ return InspectionsBundle.message("inspection.charset.object.can.be.used.fix.family.name");
+ }
+
+ @Override
+ public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
+ PsiExpression expression = ObjectUtils.tryCast(descriptor.getStartElement(), PsiExpression.class);
+ if (expression == null) return;
+ PsiElement anchor = PsiTreeUtil.getParentOfType(expression, PsiCallExpression.class);
+ if (anchor == null) return;
+ CommentTracker ct = new CommentTracker();
+ JavaCodeStyleManager.getInstance(project)
+ .shortenClassReferences(ct.replaceAndRestoreComments(expression, "java.nio.charset." + myConstantName));
+ while (true) {
+ PsiTryStatement tryStatement =
+ PsiTreeUtil.getParentOfType(anchor, PsiTryStatement.class, true, PsiMember.class, PsiLambdaExpression.class);
+ if (tryStatement == null) break;
+ PsiCodeBlock tryBlock = tryStatement.getTryBlock();
+ if (PsiTreeUtil.isAncestor(tryBlock, anchor, true)) {
+ for (PsiParameter parameter : tryStatement.getCatchBlockParameters()) {
+ List typeElements = PsiUtil.getParameterTypeElements(parameter);
+ for (PsiTypeElement element : typeElements) {
+ PsiType type = element.getType();
+ if (type.equalsToText("java.io.UnsupportedEncodingException") ||
+ type.equalsToText("java.io.IOException")) {
+ Collection unhandledExceptions = ExceptionUtil.collectUnhandledExceptions(tryBlock, tryBlock);
+ if(unhandledExceptions.stream().noneMatch(ue -> ue.isAssignableFrom(type) || type.isAssignableFrom(ue))) {
+ if(parameter.getType() instanceof PsiDisjunctionType) {
+ DeleteMultiCatchFix.deleteCaughtExceptionType(element);
+ } else {
+ DeleteCatchFix.deleteCatch(parameter);
+ }
+ }
+ return;
+ }
+ if (type.equalsToText(JAVA_LANG_EXCEPTION) || type.equalsToText(JAVA_LANG_THROWABLE)) {
+ return;
+ }
+ }
+ }
+ }
+ anchor = tryStatement;
+ }
+ }
+ }
+}
diff --git a/java/java-impl/src/inspectionDescriptions/CharsetObjectCanBeUsed.html b/java/java-impl/src/inspectionDescriptions/CharsetObjectCanBeUsed.html
new file mode 100644
index 000000000000..3ceb82126be1
--- /dev/null
+++ b/java/java-impl/src/inspectionDescriptions/CharsetObjectCanBeUsed.html
@@ -0,0 +1,10 @@
+
+
+Reports methods and constructors where constant charset String literal is used (like "UTF-8") which could be replaced with
+a predefined Charset object like StandardCharsets.UTF_8. This may work a little bit faster, because charset lookup becomes
+unnecessary. Also catching UnsupportedEncodingException may become unnecessary as well. In this case the catch block will
+be removed automatically.
+
+New in 2018.2
+
+
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/charsetObjectCanBeUsed/afterGetBytesMultiCatch.java b/java/java-tests/testData/inspection/charsetObjectCanBeUsed/afterGetBytesMultiCatch.java
new file mode 100644
index 000000000000..9fcd567eaeb7
--- /dev/null
+++ b/java/java-tests/testData/inspection/charsetObjectCanBeUsed/afterGetBytesMultiCatch.java
@@ -0,0 +1,17 @@
+// "Replace with 'StandardCharsets.US_ASCII'" "true"
+import java.io.*;
+
+class Test {
+ void test(String s) {
+ byte[] bytes = null;
+ try {
+ string = s.getBytes(java.nio.charset.StandardCharsets.US_ASCII);
+ }
+ catch (StackOverflowError exception) {
+ exception.printStackTrace();
+ }
+ if(bytes[0] == 'a') {
+ System.out.println("A-a-a!");
+ }
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/charsetObjectCanBeUsed/afterStringThrow.java b/java/java-tests/testData/inspection/charsetObjectCanBeUsed/afterStringThrow.java
new file mode 100644
index 000000000000..3eb67a5c60a3
--- /dev/null
+++ b/java/java-tests/testData/inspection/charsetObjectCanBeUsed/afterStringThrow.java
@@ -0,0 +1,11 @@
+// "Replace with 'StandardCharsets.ISO_8859_1'" "true"
+import java.io.UnsupportedEncodingException;
+
+class Test {
+ void test(byte[] bytes) throws UnsupportedEncodingException {
+ String string = new String(bytes, 0, 100, java.nio.charset.StandardCharsets.ISO_8859_1);
+ if(string.startsWith("Foo")) {
+ System.out.println("It's a foo!");
+ }
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/charsetObjectCanBeUsed/afterStringUEE.java b/java/java-tests/testData/inspection/charsetObjectCanBeUsed/afterStringUEE.java
new file mode 100644
index 000000000000..d06f27cda108
--- /dev/null
+++ b/java/java-tests/testData/inspection/charsetObjectCanBeUsed/afterStringUEE.java
@@ -0,0 +1,14 @@
+// "Replace with 'StandardCharsets.UTF_16'" "true"
+import java.io.UnsupportedEncodingException;
+
+class Test {
+ static final String UTF16 = "UTF-16";
+
+ void test(byte[] bytes) {
+ String string = null;
+ string = new String(bytes, java.nio.charset.StandardCharsets.UTF_16);
+ if(string.startsWith("Foo")) {
+ System.out.println("It's a foo!");
+ }
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/charsetObjectCanBeUsed/afterStringUEE2.java b/java/java-tests/testData/inspection/charsetObjectCanBeUsed/afterStringUEE2.java
new file mode 100644
index 000000000000..5815202cbb63
--- /dev/null
+++ b/java/java-tests/testData/inspection/charsetObjectCanBeUsed/afterStringUEE2.java
@@ -0,0 +1,22 @@
+// "Replace with 'StandardCharsets.UTF_16'" "true"
+import java.io.UnsupportedEncodingException;
+
+class Test {
+ static final String UTF16 = "UTF-16";
+
+ void test(byte[] bytes) {
+ String string = null;
+ String string2 = null;
+ try {
+ string = new String(bytes, java.nio.charset.StandardCharsets.UTF_16);
+ string2 = new String(bytes, "UTF-8");
+ }
+ // catch is still necessary after the single replacement
+ catch (UnsupportedEncodingException exception) {
+ exception.printStackTrace();
+ }
+ if(string.startsWith("Foo")) {
+ System.out.println("It's a foo!");
+ }
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/charsetObjectCanBeUsed/afterStringUEEDoubleCatch.java b/java/java-tests/testData/inspection/charsetObjectCanBeUsed/afterStringUEEDoubleCatch.java
new file mode 100644
index 000000000000..9d2a92d3f601
--- /dev/null
+++ b/java/java-tests/testData/inspection/charsetObjectCanBeUsed/afterStringUEEDoubleCatch.java
@@ -0,0 +1,16 @@
+// "Replace with 'StandardCharsets.UTF_16'" "true"
+import java.io.UnsupportedEncodingException;
+
+class Test {
+ void test(byte[] bytes) {
+ String string = null;
+ try {
+ string = new String(bytes, java.nio.charset.StandardCharsets.UTF_16);
+ } catch (Throwable t) {
+ return;
+ }
+ if(string.startsWith("Foo")) {
+ System.out.println("It's a foo!");
+ }
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/charsetObjectCanBeUsed/afterStringUEENestedTry.java b/java/java-tests/testData/inspection/charsetObjectCanBeUsed/afterStringUEENestedTry.java
new file mode 100644
index 000000000000..2fe65c60cd67
--- /dev/null
+++ b/java/java-tests/testData/inspection/charsetObjectCanBeUsed/afterStringUEENestedTry.java
@@ -0,0 +1,15 @@
+// "Replace with 'StandardCharsets.UTF_16'" "true"
+class Test {
+ void test(byte[] bytes) {
+ String string = null;
+ try {
+ string = new String(bytes, java.nio.charset.StandardCharsets.UTF_16);
+ }
+ catch (NullPointerException exception) {
+ System.out.println("ex1");
+ }
+ if(string.startsWith("Foo")) {
+ System.out.println("It's a foo!");
+ }
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/charsetObjectCanBeUsed/afterStringUEENestedTry2.java b/java/java-tests/testData/inspection/charsetObjectCanBeUsed/afterStringUEENestedTry2.java
new file mode 100644
index 000000000000..f421d4ac0aca
--- /dev/null
+++ b/java/java-tests/testData/inspection/charsetObjectCanBeUsed/afterStringUEENestedTry2.java
@@ -0,0 +1,21 @@
+// "Replace with 'StandardCharsets.UTF_16'" "true"
+class Test {
+ void test(byte[] bytes) {
+ String string = null;
+ try {
+ try {
+ string = new String(bytes, java.nio.charset.StandardCharsets.UTF_16);
+ }
+ catch (Exception exception) {
+ System.out.println("ex1");
+ }
+ }
+ // this catch is already unnecessary, so don't remove it
+ catch (java.io.UnsupportedEncodingException exception) {
+ exception.printStackTrace();
+ }
+ if(string.startsWith("Foo")) {
+ System.out.println("It's a foo!");
+ }
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/charsetObjectCanBeUsed/beforeGetBytesMultiCatch.java b/java/java-tests/testData/inspection/charsetObjectCanBeUsed/beforeGetBytesMultiCatch.java
new file mode 100644
index 000000000000..98cf59b51542
--- /dev/null
+++ b/java/java-tests/testData/inspection/charsetObjectCanBeUsed/beforeGetBytesMultiCatch.java
@@ -0,0 +1,17 @@
+// "Replace with 'StandardCharsets.US_ASCII'" "true"
+import java.io.*;
+
+class Test {
+ void test(String s) {
+ byte[] bytes = null;
+ try {
+ string = s.getBytes("US-ASCII");
+ }
+ catch (UnsupportedEncodingException | StackOverflowError exception) {
+ exception.printStackTrace();
+ }
+ if(bytes[0] == 'a') {
+ System.out.println("A-a-a!");
+ }
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/charsetObjectCanBeUsed/beforeStringThrow.java b/java/java-tests/testData/inspection/charsetObjectCanBeUsed/beforeStringThrow.java
new file mode 100644
index 000000000000..60571918c64e
--- /dev/null
+++ b/java/java-tests/testData/inspection/charsetObjectCanBeUsed/beforeStringThrow.java
@@ -0,0 +1,11 @@
+// "Replace with 'StandardCharsets.ISO_8859_1'" "true"
+import java.io.UnsupportedEncodingException;
+
+class Test {
+ void test(byte[] bytes) throws UnsupportedEncodingException {
+ String string = new String(bytes, 0, 100, "ISO-8859-1");
+ if(string.startsWith("Foo")) {
+ System.out.println("It's a foo!");
+ }
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/charsetObjectCanBeUsed/beforeStringUEE.java b/java/java-tests/testData/inspection/charsetObjectCanBeUsed/beforeStringUEE.java
new file mode 100644
index 000000000000..be8e8f4e0845
--- /dev/null
+++ b/java/java-tests/testData/inspection/charsetObjectCanBeUsed/beforeStringUEE.java
@@ -0,0 +1,19 @@
+// "Replace with 'StandardCharsets.UTF_16'" "true"
+import java.io.UnsupportedEncodingException;
+
+class Test {
+ static final String UTF16 = "UTF-16";
+
+ void test(byte[] bytes) {
+ String string = null;
+ try {
+ string = new String(bytes, UTF16);
+ }
+ catch (UnsupportedEncodingException exception) {
+ exception.printStackTrace();
+ }
+ if(string.startsWith("Foo")) {
+ System.out.println("It's a foo!");
+ }
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/charsetObjectCanBeUsed/beforeStringUEE2.java b/java/java-tests/testData/inspection/charsetObjectCanBeUsed/beforeStringUEE2.java
new file mode 100644
index 000000000000..e948828c0fd0
--- /dev/null
+++ b/java/java-tests/testData/inspection/charsetObjectCanBeUsed/beforeStringUEE2.java
@@ -0,0 +1,22 @@
+// "Replace with 'StandardCharsets.UTF_16'" "true"
+import java.io.UnsupportedEncodingException;
+
+class Test {
+ static final String UTF16 = "UTF-16";
+
+ void test(byte[] bytes) {
+ String string = null;
+ String string2 = null;
+ try {
+ string = new String(bytes, UTF16);
+ string2 = new String(bytes, "UTF-8");
+ }
+ // catch is still necessary after the single replacement
+ catch (UnsupportedEncodingException exception) {
+ exception.printStackTrace();
+ }
+ if(string.startsWith("Foo")) {
+ System.out.println("It's a foo!");
+ }
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/charsetObjectCanBeUsed/beforeStringUEECustomConstant.java b/java/java-tests/testData/inspection/charsetObjectCanBeUsed/beforeStringUEECustomConstant.java
new file mode 100644
index 000000000000..aa282a7154c3
--- /dev/null
+++ b/java/java-tests/testData/inspection/charsetObjectCanBeUsed/beforeStringUEECustomConstant.java
@@ -0,0 +1,20 @@
+// "Replace with 'StandardCharsets.UTF_16'" "false"
+import java.io.UnsupportedEncodingException;
+
+class Test {
+ static final String MY_ENCODING = "UTF-16";
+
+ void test(byte[] bytes) {
+ String string = null;
+ try {
+ // Do not suggest the replacement as "MY_ENCODING" could be a tuneable constant
+ string = new String(bytes, MY_ENCODING);
+ }
+ catch (UnsupportedEncodingException exception) {
+ exception.printStackTrace();
+ }
+ if(string.startsWith("Foo")) {
+ System.out.println("It's a foo!");
+ }
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/charsetObjectCanBeUsed/beforeStringUEEDoubleCatch.java b/java/java-tests/testData/inspection/charsetObjectCanBeUsed/beforeStringUEEDoubleCatch.java
new file mode 100644
index 000000000000..bd9713d732d9
--- /dev/null
+++ b/java/java-tests/testData/inspection/charsetObjectCanBeUsed/beforeStringUEEDoubleCatch.java
@@ -0,0 +1,20 @@
+// "Replace with 'StandardCharsets.UTF_16'" "true"
+import java.io.UnsupportedEncodingException;
+
+class Test {
+ void test(byte[] bytes) {
+ String string = null;
+ try {
+ string = new String(bytes, "UTF-16");
+ }
+ catch (UnsupportedEncodingException exception) {
+ exception.printStackTrace();
+ }
+ catch (Throwable t) {
+ return;
+ }
+ if(string.startsWith("Foo")) {
+ System.out.println("It's a foo!");
+ }
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/charsetObjectCanBeUsed/beforeStringUEENestedTry.java b/java/java-tests/testData/inspection/charsetObjectCanBeUsed/beforeStringUEENestedTry.java
new file mode 100644
index 000000000000..004125d55db4
--- /dev/null
+++ b/java/java-tests/testData/inspection/charsetObjectCanBeUsed/beforeStringUEENestedTry.java
@@ -0,0 +1,20 @@
+// "Replace with 'StandardCharsets.UTF_16'" "true"
+class Test {
+ void test(byte[] bytes) {
+ String string = null;
+ try {
+ try {
+ string = new String(bytes, "UTF-16");
+ }
+ catch (NullPointerException exception) {
+ System.out.println("ex1");
+ }
+ }
+ catch (java.io.UnsupportedEncodingException exception) {
+ exception.printStackTrace();
+ }
+ if(string.startsWith("Foo")) {
+ System.out.println("It's a foo!");
+ }
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/charsetObjectCanBeUsed/beforeStringUEENestedTry2.java b/java/java-tests/testData/inspection/charsetObjectCanBeUsed/beforeStringUEENestedTry2.java
new file mode 100644
index 000000000000..a1dab5763ac5
--- /dev/null
+++ b/java/java-tests/testData/inspection/charsetObjectCanBeUsed/beforeStringUEENestedTry2.java
@@ -0,0 +1,21 @@
+// "Replace with 'StandardCharsets.UTF_16'" "true"
+class Test {
+ void test(byte[] bytes) {
+ String string = null;
+ try {
+ try {
+ string = new String(bytes, "UTF-16");
+ }
+ catch (Exception exception) {
+ System.out.println("ex1");
+ }
+ }
+ // this catch is already unnecessary, so don't remove it
+ catch (java.io.UnsupportedEncodingException exception) {
+ exception.printStackTrace();
+ }
+ if(string.startsWith("Foo")) {
+ System.out.println("It's a foo!");
+ }
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/CharsetObjectCanBeUsedInspectionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/CharsetObjectCanBeUsedInspectionTest.java
new file mode 100644
index 000000000000..e8e76197dbb9
--- /dev/null
+++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/CharsetObjectCanBeUsedInspectionTest.java
@@ -0,0 +1,27 @@
+// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+package com.intellij.java.codeInspection;
+
+import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
+import com.intellij.codeInspection.CharsetObjectCanBeUsedInspection;
+import com.intellij.codeInspection.LocalInspectionTool;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author Tagir Valeev
+ */
+public class CharsetObjectCanBeUsedInspectionTest extends LightQuickFixParameterizedTestCase {
+ @NotNull
+ @Override
+ protected LocalInspectionTool[] configureLocalInspectionTools() {
+ return new LocalInspectionTool[]{new CharsetObjectCanBeUsedInspection()};
+ }
+
+ public void test() {
+ doAllTests();
+ }
+
+ @Override
+ protected String getBasePath() {
+ return "/inspection/charsetObjectCanBeUsed/";
+ }
+}
diff --git a/platform/platform-resources-en/src/messages/InspectionsBundle.properties b/platform/platform-resources-en/src/messages/InspectionsBundle.properties
index fa41e2cb71d9..ec81807052b7 100644
--- a/platform/platform-resources-en/src/messages/InspectionsBundle.properties
+++ b/platform/platform-resources-en/src/messages/InspectionsBundle.properties
@@ -956,3 +956,8 @@ inspection.redundant.explicit.close=Redundant close
inspection.redundant.explicit.close.fix.name=Remove redundant close
inspection.fold.expression.into.stream.display.name=Expression can be folded into Stream chain
inspection.fold.expression.into.stream.fix.name=Fold expression into Stream chain
+
+inspection.charset.object.can.be.used.display.name=Standard Charset object can be used
+inspection.charset.object.can.be.used.message={0} can be used instead
+inspection.charset.object.can.be.used.fix.family.name=Use Charset constant
+inspection.charset.object.can.be.used.fix.name=Replace with ''{0}''
\ No newline at end of file