Bug 573289 - [cleanup] String.strip().isEmpty() => String.isBlank()

String.isBlank() rather than String.strip().isEmpty()

String.isBlank() is shorter than checking the stripped string or
checking the length of the stripped string and it avoids the creation of
an intermediate trimmed string.

To apply the cleanup also to String.trim().isEmpty() (which is probably
more common than String.strip().isEmpty(), since strip() exists only
since Java 11), trim() must first be replaced by strip() which may
change the runtime behavior.

Given:
if (input.strip().isEmpty()) {
    System.err.println("Input must not be blank");
}
boolean hasComment = comment.strip().length() > 0;

When:
Clean up the code enabling "String.isBlank() rather than
String.strip().isEmpty()" and using Java 11 or higher

Then:
if (input.isBlank()) {
    System.err.println("Input must not be blank");
}
boolean hasComment = !comment.isBlank();

Also use Java 12 Runtime jar for the Java 11 tests instead of Java 10
Runtime jar

Change-Id: I072b6e18fa951ab9901e3363b55be2c18654888f
Signed-off-by: Holger Voormann <eclipse@voormann.de>
Reviewed-on: https://git.eclipse.org/r/c/jdt/eclipse.jdt.ui/+/180067
Tested-by: JDT Bot <jdt-bot@eclipse.org>
Reviewed-by: Fabrice Tiercelin <fabrice.tiercelin@yahoo.fr>
diff --git a/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstants.java b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstants.java
index 23b05ba..7111d69 100644
--- a/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstants.java
+++ b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstants.java
@@ -1211,6 +1211,21 @@
 	public static final String PLAIN_REPLACEMENT= "cleanup.plain_replacement"; //$NON-NLS-1$
 
 	/**
+	 * Replace
+	 * <code>s.strip().length() == 0</code>
+	 * by
+	 * <code>s.isBlank()</code> (Java 11).
+	 * <p>
+	 * Possible values: {TRUE, FALSE}
+	 * <p>
+	 *
+	 * @see CleanUpOptionsCore#TRUE
+	 * @see CleanUpOptionsCore#FALSE
+	 * @since 4.20
+	 */
+	public static final String USE_STRING_IS_BLANK= "cleanup.use_string_is_blank"; //$NON-NLS-1$
+
+	/**
 	 * Removes unused imports. <br>
 	 * <br>
 	 * Possible values: {TRUE, FALSE}<br>
diff --git a/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/FixMessages.java b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/FixMessages.java
index af8830b..ee02c5a 100644
--- a/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/FixMessages.java
+++ b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/FixMessages.java
@@ -136,8 +136,6 @@
 	public static String VariableDeclarationFix_changeModifierOfUnknownToFinal_description;
 	public static String VariableDeclarationFix_ChangeMidifiersToFinalWherPossible_description;
 
-	public static String PlainReplacementFix_use_plain_text;
-
 	public static String NullAnnotationsFix_add_annotation_change_name;
 	public static String NullAnnotationsRewriteOperations_change_method_parameter_nullness;
 	public static String NullAnnotationsRewriteOperations_change_target_method_parameter_nullness;
@@ -167,6 +165,8 @@
 	public static String TypeParametersFix_remove_redundant_type_arguments_description;
 	public static String TypeParametersFix_remove_redundant_type_arguments_name;
 	public static String BooleanValueRatherThanComparisonFix_description;
+	public static String PlainReplacementFix_use_plain_text;
+	public static String UseStringIsBlankCleanUp_description;
 	public static String RedundantComparatorFix_remove_comparator;
 	public static String ArrayWithCurlyFix_description;
 	public static String ReturnExpressionFix_description;
diff --git a/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/FixMessages.properties b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/FixMessages.properties
index 1868189..0bf5094 100644
--- a/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/FixMessages.properties
+++ b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/FixMessages.properties
@@ -123,8 +123,6 @@
 SortMembersFix_Change_description=Sort Members
 SortMembersFix_Fix_description=Sort Members
 
-PlainReplacementFix_use_plain_text=Use String.replace() instead of String.replaceAll() when possible
-
 NullAnnotationsFix_add_annotation_change_name=Add Annotations
 NullAnnotationsRewriteOperations_change_method_parameter_nullness=Change parameter ''{0}'' to ''@{1}''
 NullAnnotationsRewriteOperations_change_target_method_parameter_nullness=Change parameter of ''{0}(..)'' to ''@{1}''
@@ -154,6 +152,8 @@
 TypeParametersFix_remove_redundant_type_arguments_description=Remove redundant type arguments
 TypeParametersFix_remove_redundant_type_arguments_name=Remove redundant type arguments
 BooleanValueRatherThanComparisonFix_description=Boolean value rather than comparison
+PlainReplacementFix_use_plain_text=Use String.replace() instead of String.replaceAll() when possible
+UseStringIsBlankCleanUp_description=String.isBlank() rather than String.strip().isEmpty()
 RedundantComparatorFix_remove_comparator=Implicit comparator
 ArrayWithCurlyFix_description=Create array with curly
 ReturnExpressionFix_description=Remove variable assignment before return
diff --git a/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/core/rules/Java11ProjectTestSetup.java b/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/core/rules/Java11ProjectTestSetup.java
index 3e658c5..8eabeca 100644
--- a/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/core/rules/Java11ProjectTestSetup.java
+++ b/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/core/rules/Java11ProjectTestSetup.java
@@ -33,12 +33,12 @@
 public class Java11ProjectTestSetup extends ProjectTestSetup {
 
 	public Java11ProjectTestSetup() {
-		super("TestSetupProject11", JavaProjectHelper.RT_STUBS_10);
+		super("TestSetupProject11", JavaProjectHelper.RT_STUBS_12);
 	}
 
 	@Override
 	public IClasspathEntry[] getDefaultClasspath() throws CoreException {
-		IPath[] rtJarPath= JavaProjectHelper.findRtJar(ipath); // use stubs 10 for now
+		IPath[] rtJarPath= JavaProjectHelper.findRtJar(ipath); // use stubs 12 for now
 		IClasspathAttribute[] extraAttributes= { JavaCore.newClasspathAttribute(IClasspathAttribute.MODULE, Boolean.TRUE.toString()) };
 		return new IClasspathEntry[] { JavaCore.newLibraryEntry(rtJarPath[0], rtJarPath[1], rtJarPath[2], ClasspathEntry.NO_ACCESS_RULES, extraAttributes, true) };
 	}
diff --git a/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/CleanUpTest11.java b/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/CleanUpTest11.java
index 60004ab..863a0aa 100644
--- a/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/CleanUpTest11.java
+++ b/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/CleanUpTest11.java
@@ -13,6 +13,11 @@
  *******************************************************************************/
 package org.eclipse.jdt.ui.tests.quickfix;
 
+import static org.junit.Assert.assertNotEquals;
+
+import java.util.Arrays;
+import java.util.HashSet;
+
 import org.junit.Rule;
 import org.junit.Test;
 
@@ -24,6 +29,7 @@
 import org.eclipse.jdt.core.IPackageFragment;
 
 import org.eclipse.jdt.internal.corext.fix.CleanUpConstants;
+import org.eclipse.jdt.internal.corext.fix.FixMessages;
 
 import org.eclipse.jdt.ui.tests.core.rules.Java11ProjectTestSetup;
 import org.eclipse.jdt.ui.tests.core.rules.ProjectTestSetup;
@@ -342,4 +348,222 @@
 		assertRefactoringHasNoChange(new ICompilationUnit[] { cu1 });
 	}
 
+
+	@Test
+	public void testUseStringIsBlank() throws Exception {
+		// Given
+		IPackageFragment pack= fSourceFolder.createPackageFragment("test1", false, null);
+		String given= "" //
+				+ "package test1;\n" //
+				+ "\n" //
+				+ "import java.util.List;\n" //
+				+ "\n" //
+				+ "public class E {\n" //
+				+ "    private static final int ZERO = 0;\n" //
+				+ "    private static final int THREE = 3;\n" //
+				+ "    private static final String EMPTY_STRING = \"\";\n" //
+				+ "\n" //
+				+ "    void isBlank(String text) {\n" //
+				+ "        if (text.strip().isEmpty()) {\n" //
+				+ "            System.err.println(\"Text must not be blank\");\n" //
+				+ "        }\n" //
+				+ "        if (text.stripLeading().isEmpty()) {\n" //
+				+ "            System.err.println(\"Text must not be blank\");\n" //
+				+ "        }\n" //
+				+ "        if (text.stripTrailing().isEmpty()) {\n" //
+				+ "            System.err.println(\"Text must not be blank\");\n" //
+				+ "        }\n" //
+				+ "        if (text.strip().length() == 0) {\n" //
+				+ "            System.err.println(\"The text must not be blank\");\n" //
+				+ "        } else if (text.strip().length() <= 0) {\n" //
+				+ "            System.err.println(\"Text must not be blank\");\n" //
+				+ "        }\n" //
+				+ "        if (text.strip().length() < 1) {\n" //
+				+ "            System.err.println(\"This text must not be blank\");\n" //
+				+ "        }\n" //
+				+ "        if (0 == text.strip().length()) {\n" //
+				+ "            System.err.println(\"Text must not be blank\");\n" //
+				+ "        } else if (0 >= text.strip().length()) {\n" //
+				+ "            System.err.println(\"Text must not be blank\");\n" //
+				+ "        }\n" //
+				+ "        if (1 > text.strip().length()) {\n" //
+				+ "            System.err.println(\"Text must not be blank\");\n" //
+				+ "        }\n" //
+				+ "        if (text.strip().length() == ZERO) {\n" //
+				+ "            System.err.println(\"Text must not be blank\");\n" //
+				+ "        }\n" //
+				+ "        if (text.strip().equals(\"\")) {\n" //
+				+ "            System.err.println(\"Text must not be blank\");\n" //
+				+ "        }\n" //
+				+ "        if (\"\".equals(text.stripLeading())) {\n" //
+				+ "            System.err.println(\"Text must not be blank\");\n" //
+				+ "        }\n" //
+				+ "        if (EMPTY_STRING.equals(text.stripTrailing())) {\n" //
+				+ "            System.err.println(\"Text must not be blank\");\n" //
+				+ "        }\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    void isNotBlank(String text, StringBuilder builder) {\n" //
+				+ "        if (!text.strip().isEmpty()) {\n" //
+				+ "            System.out.println(text)\n" //
+				+ "        }\n" //
+				+ "        if (text.strip().length() != 0) {\n" //
+				+ "            System.out.println(text)\n" //
+				+ "        } else if (text.strip().length() > 0) {\n" //
+				+ "            System.out.println(text)\n" //
+				+ "        }\n" //
+				+ "        if (text.strip().length() >= 1) {\n" //
+				+ "            System.out.println(text)\n" //
+				+ "        }\n" //
+				+ "        if (0 != text.strip().length()) {\n" //
+				+ "            System.out.println(text)\n" //
+				+ "        } else if (0 < text.strip().length()) {\n" //
+				+ "            System.out.println(text)\n" //
+				+ "        }\n" //
+				+ "        if (1 <= text.strip().length()) {\n" //
+				+ "            System.out.println(text)\n" //
+				+ "        }\n" //
+				+ "        if (4 - THREE <= builder.toString().strip().length()) {\n" //
+				+ "            System.out.println(text)\n" //
+				+ "        }\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    void printList(List<String> list) {\n" //
+				+ "        list.stream().filter(s -> !s.strip().isEmpty()).map(String::strip);\n" //
+				+ "        list.stream().filter(s -> s.strip().length() != 0).map(String::strip);\n" //
+				+ "    }\n" //
+				+ "}\n";
+
+		String expected= "" //
+				+ "package test1;\n" //
+				+ "\n" //
+				+ "import java.util.List;\n" //
+				+ "\n" //
+				+ "public class E {\n" //
+				+ "    private static final int ZERO = 0;\n" //
+				+ "    private static final int THREE = 3;\n" //
+				+ "    private static final String EMPTY_STRING = \"\";\n" //
+				+ "\n" //
+				+ "    void isBlank(String text) {\n" //
+				+ "        if (text.isBlank()) {\n" //
+				+ "            System.err.println(\"Text must not be blank\");\n" //
+				+ "        }\n" //
+				+ "        if (text.isBlank()) {\n" //
+				+ "            System.err.println(\"Text must not be blank\");\n" //
+				+ "        }\n" //
+				+ "        if (text.isBlank()) {\n" //
+				+ "            System.err.println(\"Text must not be blank\");\n" //
+				+ "        }\n" //
+				+ "        if (text.isBlank()) {\n" //
+				+ "            System.err.println(\"The text must not be blank\");\n" //
+				+ "        } else if (text.isBlank()) {\n" //
+				+ "            System.err.println(\"Text must not be blank\");\n" //
+				+ "        }\n" //
+				+ "        if (text.isBlank()) {\n" //
+				+ "            System.err.println(\"This text must not be blank\");\n" //
+				+ "        }\n" //
+				+ "        if (text.isBlank()) {\n" //
+				+ "            System.err.println(\"Text must not be blank\");\n" //
+				+ "        } else if (text.isBlank()) {\n" //
+				+ "            System.err.println(\"Text must not be blank\");\n" //
+				+ "        }\n" //
+				+ "        if (text.isBlank()) {\n" //
+				+ "            System.err.println(\"Text must not be blank\");\n" //
+				+ "        }\n" //
+				+ "        if (text.isBlank()) {\n" //
+				+ "            System.err.println(\"Text must not be blank\");\n" //
+				+ "        }\n" //
+				+ "        if (text.isBlank()) {\n" //
+				+ "            System.err.println(\"Text must not be blank\");\n" //
+				+ "        }\n" //
+				+ "        if (text.isBlank()) {\n" //
+				+ "            System.err.println(\"Text must not be blank\");\n" //
+				+ "        }\n" //
+				+ "        if (text.isBlank()) {\n" //
+				+ "            System.err.println(\"Text must not be blank\");\n" //
+				+ "        }\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    void isNotBlank(String text, StringBuilder builder) {\n" //
+				+ "        if (!text.isBlank()) {\n" //
+				+ "            System.out.println(text)\n" //
+				+ "        }\n" //
+				+ "        if (!text.isBlank()) {\n" //
+				+ "            System.out.println(text)\n" //
+				+ "        } else if (!text.isBlank()) {\n" //
+				+ "            System.out.println(text)\n" //
+				+ "        }\n" //
+				+ "        if (!text.isBlank()) {\n" //
+				+ "            System.out.println(text)\n" //
+				+ "        }\n" //
+				+ "        if (!text.isBlank()) {\n" //
+				+ "            System.out.println(text)\n" //
+				+ "        } else if (!text.isBlank()) {\n" //
+				+ "            System.out.println(text)\n" //
+				+ "        }\n" //
+				+ "        if (!text.isBlank()) {\n" //
+				+ "            System.out.println(text)\n" //
+				+ "        }\n" //
+				+ "        if (!builder.toString().isBlank()) {\n" //
+				+ "            System.out.println(text)\n" //
+				+ "        }\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    void printList(List<String> list) {\n" //
+				+ "        list.stream().filter(s -> !s.isBlank()).map(String::strip);\n" //
+				+ "        list.stream().filter(s -> !s.isBlank()).map(String::strip);\n" //
+				+ "    }\n" //
+				+ "}\n";
+
+		// When
+		ICompilationUnit cu= pack.createCompilationUnit("E.java", given, false, null);
+		enable(CleanUpConstants.USE_STRING_IS_BLANK);
+
+		// Then
+		assertNotEquals("The class must be changed", given, expected);
+		assertRefactoringResultAsExpected(new ICompilationUnit[] { cu }, new String[] { expected }, new HashSet<>(Arrays.asList(FixMessages.UseStringIsBlankCleanUp_description)));
+	}
+
+	@Test
+	public void testDoNotUseStringIsBlank() throws Exception {
+		IPackageFragment pack= fSourceFolder.createPackageFragment("test1", false, null);
+		String sample= "" //
+				+ "package test1;\n" //
+				+ "\n" //
+				+ "public class NotAString {\n" //
+				+ "    int mostlyZero= 0;\n" //
+				+ "    private static int NON_FINAL_ZERO = 0;\n" //
+				+ "\n" //
+				+ "    public String strip() {\n" //
+				+ "        return \"\";\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    void doNotUseStringIsBlank(NotAString noString, String text) {\n" //
+				+ "        if (noString.strip().length() == 0) {\n" //
+				+ "            System.err.println(\"Text must not be blank\");\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if (text.strip().length() == mostlyZero) {\n" //
+				+ "            System.err.println(\"Text must not be blank\");\n" //
+				+ "        } else if (text.strip().length() <= NON_FINAL_ZERO) {\n" //
+				+ "            System.err.println(\"Text must not be blank\");\n" //
+				+ "        }\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    void doNotUseStringIsBlankWithUnknownString(String text, String emptyString) {\n" //
+				+ "        if (text.strip().equals(emptyString)) {\n" //
+				+ "            System.err.println(\"Text must not be blank\");\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if (emptyString.equals(text.strip())) {\n" //
+				+ "            System.err.println(\"Text must not be blank\");\n" //
+				+ "        }\n" //
+				+ "    }\n" //
+				+ "}\n";
+		ICompilationUnit cu= pack.createCompilationUnit("NotAString.java", sample, false, null);
+
+		enable(CleanUpConstants.USE_STRING_IS_BLANK);
+
+		assertRefactoringHasNoChange(new ICompilationUnit[] { cu });
+	}
 }
diff --git a/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstantsOptions.java b/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstantsOptions.java
index daf13d9..d7511b9 100644
--- a/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstantsOptions.java
+++ b/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstantsOptions.java
@@ -80,6 +80,7 @@
 		options.setOption(STATIC_INNER_CLASS, CleanUpOptions.FALSE);
 		options.setOption(STRINGBUILDER, CleanUpOptions.FALSE);
 		options.setOption(PLAIN_REPLACEMENT, CleanUpOptions.FALSE);
+		options.setOption(USE_STRING_IS_BLANK, CleanUpOptions.FALSE);
 		options.setOption(USE_LAZY_LOGICAL_OPERATOR, CleanUpOptions.FALSE);
 		options.setOption(VALUEOF_RATHER_THAN_INSTANTIATION, CleanUpOptions.FALSE);
 		options.setOption(PRIMITIVE_COMPARISON, CleanUpOptions.FALSE);
@@ -252,6 +253,7 @@
 		options.setOption(STATIC_INNER_CLASS, CleanUpOptions.FALSE);
 		options.setOption(STRINGBUILDER, CleanUpOptions.FALSE);
 		options.setOption(PLAIN_REPLACEMENT, CleanUpOptions.FALSE);
+		options.setOption(USE_STRING_IS_BLANK, CleanUpOptions.FALSE);
 		options.setOption(USE_LAZY_LOGICAL_OPERATOR, CleanUpOptions.FALSE);
 		options.setOption(VALUEOF_RATHER_THAN_INSTANTIATION, CleanUpOptions.FALSE);
 		options.setOption(PRIMITIVE_COMPARISON, CleanUpOptions.FALSE);
diff --git a/org.eclipse.jdt.ui/plugin.xml b/org.eclipse.jdt.ui/plugin.xml
index b987955..95a41ea 100644
--- a/org.eclipse.jdt.ui/plugin.xml
+++ b/org.eclipse.jdt.ui/plugin.xml
@@ -7163,9 +7163,14 @@
             runAfter="org.eclipse.jdt.ui.cleanup.stringbuilder">
       </cleanUp>
       <cleanUp
+            class="org.eclipse.jdt.internal.ui.fix.UseStringIsBlankCleanUp"
+            id="org.eclipse.jdt.ui.cleanup.use_string_is_blank"
+            runAfter="org.eclipse.jdt.ui.cleanup.plain_replacement">
+      </cleanUp>
+      <cleanUp
             class="org.eclipse.jdt.internal.ui.fix.LazyLogicalCleanUp"
             id="org.eclipse.jdt.ui.cleanup.lazy_logical"
-            runAfter="org.eclipse.jdt.ui.cleanup.plain_replacement">
+            runAfter="org.eclipse.jdt.ui.cleanup.use_string_is_blank">
       </cleanUp>
       <cleanUp
             class="org.eclipse.jdt.internal.ui.fix.ValueOfRatherThanInstantiationCleanUp"
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/UseStringIsBlankCleanUp.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/UseStringIsBlankCleanUp.java
new file mode 100644
index 0000000..12d6204
--- /dev/null
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/UseStringIsBlankCleanUp.java
@@ -0,0 +1,243 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Holger VOORMANN and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package org.eclipse.jdt.internal.ui.fix;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.core.runtime.CoreException;
+
+import org.eclipse.text.edits.TextEditGroup;
+
+import org.eclipse.jdt.core.dom.AST;
+import org.eclipse.jdt.core.dom.ASTNode;
+import org.eclipse.jdt.core.dom.ASTVisitor;
+import org.eclipse.jdt.core.dom.CompilationUnit;
+import org.eclipse.jdt.core.dom.Expression;
+import org.eclipse.jdt.core.dom.InfixExpression;
+import org.eclipse.jdt.core.dom.MethodInvocation;
+import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
+import org.eclipse.jdt.core.dom.rewrite.TargetSourceRangeComputer;
+import org.eclipse.jdt.core.manipulation.CleanUpContextCore;
+import org.eclipse.jdt.core.manipulation.ICleanUpFixCore;
+
+import org.eclipse.jdt.internal.corext.dom.ASTNodeFactory;
+import org.eclipse.jdt.internal.corext.dom.ASTNodes;
+import org.eclipse.jdt.internal.corext.dom.OrderedInfixExpression;
+import org.eclipse.jdt.internal.corext.fix.CleanUpConstants;
+import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFixCore;
+import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFixCore.CompilationUnitRewriteOperation;
+import org.eclipse.jdt.internal.corext.fix.FixMessages;
+import org.eclipse.jdt.internal.corext.fix.LinkedProposalModelCore;
+import org.eclipse.jdt.internal.corext.refactoring.structure.CompilationUnitRewrite;
+import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
+
+import org.eclipse.jdt.ui.cleanup.CleanUpContext;
+import org.eclipse.jdt.ui.cleanup.CleanUpRequirements;
+import org.eclipse.jdt.ui.cleanup.ICleanUpFix;
+
+public class UseStringIsBlankCleanUp extends AbstractCleanUp {
+	public UseStringIsBlankCleanUp() {
+	}
+
+	public UseStringIsBlankCleanUp(final Map<String, String> options) {
+		super(options);
+	}
+
+	@Override
+	public CleanUpRequirements getRequirements() {
+		return new CleanUpRequirements(isEnabled(CleanUpConstants.USE_STRING_IS_BLANK), false, false, null);
+	}
+
+	@Override
+	public String[] getStepDescriptions() {
+		if (isEnabled(CleanUpConstants.USE_STRING_IS_BLANK)) {
+			return new String[] { FixMessages.UseStringIsBlankCleanUp_description };
+		}
+
+		return new String[0];
+	}
+
+	@Override
+	public String getPreview() {
+		boolean isEnabled= isEnabled(CleanUpConstants.USE_STRING_IS_BLANK);
+		return (isEnabled
+				? "if (input.isBlank()) {\n" //$NON-NLS-1$
+				: "if (\"\".equals(input.strip())) {\n") //$NON-NLS-1$
+
+				+ "    System.err.println(\"Input must not be blank\");\n" //$NON-NLS-1$
+				+ "};\n" //$NON-NLS-1$
+
+				+ "boolean hasComment = " //$NON-NLS-1$
+				+ (isEnabled
+						? "!comment.isBlank();\n" //$NON-NLS-1$
+						: "comment.strip().length() > 0;\n"); //$NON-NLS-1$
+	}
+
+	@Override
+	public ICleanUpFix createFix(CleanUpContext context) throws CoreException {
+		ICleanUpFixCore fixCore= createFixCore(context);
+		return fixCore != null ? new CleanUpFixWrapper(fixCore) : null;
+	}
+
+	private ICleanUpFixCore createFixCore(final CleanUpContextCore context) {
+		CompilationUnit compilationUnit= context.getAST();
+
+		if (compilationUnit == null
+				|| !isEnabled(CleanUpConstants.USE_STRING_IS_BLANK)
+				|| !JavaModelUtil.is11OrHigher(compilationUnit.getJavaElement().getJavaProject())) {
+			return null;
+		}
+
+		return createCleanUp(compilationUnit);
+	}
+
+	private static ICleanUpFixCore createCleanUp(final CompilationUnit compilationUnit) {
+		List<UseStringIsBlankFixOperation> operations= new ArrayList<>();
+		compilationUnit.accept(new UseStringIsBlankFinder(operations));
+
+		if (operations.isEmpty()) {
+			return null;
+		}
+
+		CompilationUnitRewriteOperation[] ops= operations.toArray(new CompilationUnitRewriteOperation[0]);
+		return new CompilationUnitRewriteOperationsFixCore(FixMessages.UseStringIsBlankCleanUp_description, compilationUnit, ops);
+	}
+
+
+	private static class UseStringIsBlankFinder extends ASTVisitor {
+		private List<UseStringIsBlankFixOperation> fResult;
+
+		public UseStringIsBlankFinder(List<UseStringIsBlankFixOperation> ops) {
+			fResult= ops;
+		}
+
+		@Override
+		public boolean visit(MethodInvocation visited) {
+			// s.strip().isEmpty()
+			if (isStringMethodInvocation(visited, "isEmpty") //$NON-NLS-1$
+					&& (isStringMethodInvocation(visited.getExpression(), "strip") //$NON-NLS-1$
+							|| isStringMethodInvocation(visited.getExpression(), "stripLeading") //$NON-NLS-1$
+							|| isStringMethodInvocation(visited.getExpression(), "stripTrailing"))) { //$NON-NLS-1$
+				fResult.add(new UseStringIsBlankFixOperation(visited, visited.getExpression(), true));
+				return false;
+			}
+
+			if (ASTNodes.usesGivenSignature(visited, Object.class.getCanonicalName(), "equals", Object.class.getCanonicalName())) { //$NON-NLS-1$
+				List<Expression> arguments= visited.arguments();
+
+				// s.strip().equals("")
+				if ("".equals(arguments.get(0).resolveConstantExpressionValue()) //$NON-NLS-1$
+						&& (isStringMethodInvocation(visited.getExpression(), "strip") //$NON-NLS-1$
+								|| isStringMethodInvocation(visited.getExpression(), "stripLeading") //$NON-NLS-1$
+								|| isStringMethodInvocation(visited.getExpression(), "stripTrailing"))) { //$NON-NLS-1$
+					fResult.add(new UseStringIsBlankFixOperation(visited, visited.getExpression(), true));
+					return false;
+				}
+
+				// "".equals(s.strip())
+				if ("".equals(visited.getExpression().resolveConstantExpressionValue()) //$NON-NLS-1$
+						&& (isStringMethodInvocation(arguments.get(0), "strip") //$NON-NLS-1$
+								|| isStringMethodInvocation(arguments.get(0), "stripLeading") //$NON-NLS-1$
+								|| isStringMethodInvocation(arguments.get(0), "stripTrailing"))) { //$NON-NLS-1$
+					fResult.add(new UseStringIsBlankFixOperation(visited, arguments.get(0), true));
+					return false;
+				}
+			}
+
+			return true;
+		}
+
+		@Override
+		public boolean visit(final InfixExpression visited) {
+			OrderedInfixExpression<MethodInvocation, Expression> orderedInfix= ASTNodes.orderedInfix(visited, MethodInvocation.class, Expression.class);
+
+			if (orderedInfix != null
+					&& isStringMethodInvocation(orderedInfix.getFirstOperand(), "length") //$NON-NLS-1$
+					&& (isStringMethodInvocation(orderedInfix.getFirstOperand().getExpression(), "strip") //$NON-NLS-1$
+							|| isStringMethodInvocation(orderedInfix.getFirstOperand().getExpression(), "stripLeading") //$NON-NLS-1$
+							|| isStringMethodInvocation(orderedInfix.getFirstOperand().getExpression(), "stripTrailing"))) { //$NON-NLS-1$
+				Long number= ASTNodes.getIntegerLiteral(orderedInfix.getSecondOperand());
+
+				if (Long.valueOf(0L).equals(number)) {
+					// s.strip().length() == 0
+					// s.strip().length() <= 0
+					if (Arrays.asList(InfixExpression.Operator.EQUALS, InfixExpression.Operator.LESS_EQUALS).contains(orderedInfix.getOperator())) {
+						fResult.add(new UseStringIsBlankFixOperation(visited, orderedInfix.getFirstOperand().getExpression(), true));
+						return false;
+					}
+
+					// s.strip().length() != 0
+					// s.strip().length() > 0
+					if (Arrays.asList(InfixExpression.Operator.NOT_EQUALS, InfixExpression.Operator.GREATER).contains(orderedInfix.getOperator())) {
+						fResult.add(new UseStringIsBlankFixOperation(visited, orderedInfix.getFirstOperand().getExpression(), false));
+						return false;
+					}
+				}
+
+				if (Long.valueOf(1L).equals(number)) {
+					// s.strip().length() < 1
+					if (InfixExpression.Operator.LESS.equals(orderedInfix.getOperator())) {
+						fResult.add(new UseStringIsBlankFixOperation(visited, orderedInfix.getFirstOperand().getExpression(), true));
+						return false;
+					}
+
+					// s.strip().length() >= 1
+					if (InfixExpression.Operator.GREATER_EQUALS.equals(orderedInfix.getOperator())) {
+						fResult.add(new UseStringIsBlankFixOperation(visited, orderedInfix.getFirstOperand().getExpression(), false));
+						return false;
+					}
+				}
+			}
+
+			return true;
+		}
+
+		private boolean isStringMethodInvocation(final Expression expression, final String methodName) {
+			return expression instanceof MethodInvocation && ASTNodes.usesGivenSignature((MethodInvocation) expression, String.class.getCanonicalName(), methodName);
+		}
+	}
+
+	private static class UseStringIsBlankFixOperation extends CompilationUnitRewriteOperation {
+		private final Expression visited;
+		private final Expression trim;
+		private final boolean isPositive;
+
+		public UseStringIsBlankFixOperation(final Expression visited, final Expression trim, final boolean isPositive) {
+			this.visited= visited;
+			this.trim= trim;
+			this.isPositive= isPositive;
+		}
+
+		@Override
+		public void rewriteAST(final CompilationUnitRewrite cuRewrite, final LinkedProposalModelCore linkedModel) throws CoreException {
+			ASTRewrite rewrite= cuRewrite.getASTRewrite();
+			AST ast= cuRewrite.getRoot().getAST();
+			TextEditGroup group= createTextEditGroup(FixMessages.UseStringIsBlankCleanUp_description, cuRewrite);
+			rewrite.setTargetSourceRangeComputer(new TargetSourceRangeComputer() {
+				@Override
+				public SourceRange computeSourceRange(final ASTNode nodeWithComment) {
+					if (Boolean.TRUE.equals(nodeWithComment.getProperty(ASTNodes.UNTOUCH_COMMENT))) {
+						return new SourceRange(nodeWithComment.getStartPosition(), nodeWithComment.getLength());
+					}
+
+					return super.computeSourceRange(nodeWithComment);
+				}
+			});
+
+			ASTNodes.replaceButKeepComment(rewrite, visited, isPositive ? trim : ASTNodeFactory.negate(ast, rewrite, trim, true), group);
+			rewrite.set(trim, MethodInvocation.NAME_PROPERTY, ast.newSimpleName("isBlank"), group); //$NON-NLS-1$
+		}
+	}
+
+}
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CleanUpMessages.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CleanUpMessages.java
index ec5d591..b6be52b 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CleanUpMessages.java
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CleanUpMessages.java
@@ -87,6 +87,7 @@
 	public static String OptimizationTabPage_CheckboxName_StaticInnerClass;
 	public static String OptimizationTabPage_CheckboxName_StringBuilder;
 	public static String OptimizationTabPage_CheckboxName_PlainReplacement;
+	public static String OptimizationTabPage_CheckboxName_UseStringIsBlank;
 	public static String OptimizationTabPage_CheckboxName_UseLazyLogicalOperator;
 	public static String OptimizationTabPage_CheckboxName_ValueOfRatherThanInstantiation;
 	public static String OptimizationTabPage_CheckboxName_PrimitiveComparison;
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CleanUpMessages.properties b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CleanUpMessages.properties
index 17a43e2..603dc13 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CleanUpMessages.properties
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CleanUpMessages.properties
@@ -66,6 +66,7 @@
 OptimizationTabPage_CheckboxName_StaticInnerClass=Make inner classes static where possible
 OptimizationTabPage_CheckboxName_StringBuilder=Replace &String concatenation by StringBuilder
 OptimizationTabPage_CheckboxName_PlainReplacement=Use String.replace() instead of String.replaceAll() when possible
+OptimizationTabPage_CheckboxName_UseStringIsBlank=String.isBlank() rather than String.strip().isEmpty()
 OptimizationTabPage_CheckboxName_UseLazyLogicalOperator=Use la&zy logical operator
 OptimizationTabPage_CheckboxName_ValueOfRatherThanInstantiation=valueOf() rather than instantiation
 OptimizationTabPage_CheckboxName_PrimitiveComparison=Primitive comparison
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/OptimizationTabPage.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/OptimizationTabPage.java
index 1d12eca..1a411e5 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/OptimizationTabPage.java
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/OptimizationTabPage.java
@@ -34,6 +34,7 @@
 import org.eclipse.jdt.internal.ui.fix.SingleUsedFieldCleanUp;
 import org.eclipse.jdt.internal.ui.fix.StaticInnerClassCleanUp;
 import org.eclipse.jdt.internal.ui.fix.StringBuilderCleanUp;
+import org.eclipse.jdt.internal.ui.fix.UseStringIsBlankCleanUp;
 import org.eclipse.jdt.internal.ui.fix.ValueOfRatherThanInstantiationCleanUp;
 
 public final class OptimizationTabPage extends AbstractCleanUpTabPage {
@@ -47,6 +48,7 @@
 				new StaticInnerClassCleanUp(values),
 				new StringBuilderCleanUp(values),
 				new PlainReplacementCleanUp(values),
+				new UseStringIsBlankCleanUp(values),
 				new LazyLogicalCleanUp(values),
 				new ValueOfRatherThanInstantiationCleanUp(values),
 				new PrimitiveComparisonCleanUp(values),
@@ -78,6 +80,10 @@
 		final CheckboxPreference plainReplacementPref= createCheckboxPref(optimizationGroup, numColumns, CleanUpMessages.OptimizationTabPage_CheckboxName_PlainReplacement, CleanUpConstants.PLAIN_REPLACEMENT, CleanUpModifyDialog.FALSE_TRUE);
 		registerPreference(plainReplacementPref);
 
+		final CheckboxPreference useStringIsBlankPref= createCheckboxPref(optimizationGroup, numColumns, CleanUpMessages.OptimizationTabPage_CheckboxName_UseStringIsBlank, CleanUpConstants.USE_STRING_IS_BLANK,
+				CleanUpModifyDialog.FALSE_TRUE);
+		registerPreference(useStringIsBlankPref);
+
 		final CheckboxPreference useLazyLogicalPref= createCheckboxPref(optimizationGroup, numColumns, CleanUpMessages.OptimizationTabPage_CheckboxName_UseLazyLogicalOperator,
 				CleanUpConstants.USE_LAZY_LOGICAL_OPERATOR, CleanUpModifyDialog.FALSE_TRUE);
 		registerPreference(useLazyLogicalPref);