From 3f9718c3d9fe404327694cc37175bdf7032dfed7 Mon Sep 17 00:00:00 2001 From: Aidan Date: Fri, 17 Oct 2025 17:57:56 +1100 Subject: [PATCH 1/7] [LANG-1656] Short-circuit operation of firstNonBlank and firstNonEmpty. Introduced a new method: - StringUtils.firstNonBlankSupplier(Supplier... suppliers) This variant of firstNonBlank accepts suppliers evaluated lazily in order until a non-blank value is found. It enables short-circuit behavior to avoid unnecessary or expensive computations. --- .../org/apache/commons/lang3/StringUtils.java | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/main/java/org/apache/commons/lang3/StringUtils.java b/src/main/java/org/apache/commons/lang3/StringUtils.java index 751896e1213..eaa01a773f8 100644 --- a/src/main/java/org/apache/commons/lang3/StringUtils.java +++ b/src/main/java/org/apache/commons/lang3/StringUtils.java @@ -1904,6 +1904,53 @@ public static T firstNonBlank(final T... values) { return null; } + /** + * Returns the first value supplied which is not empty (""), {@code null} or whitespace only. + * + *

+ * Whitespace is defined by {@link Character#isWhitespace(char)}. + *

+ * + *

+ * Each supplier is evaluated lazily in order until a non-blank value is found. + * This allows the caller to defer expensive computations until they are needed. + *

+ * + *

+ * If all supplied values are blank, or the array of suppliers is {@code null} or empty, + * then {@code null} is returned. + *

+ * + *
+     * StringUtils.firstNonBlankSupplier(() -> null, () -> null, () -> null)     = null
+     * StringUtils.firstNonBlankSupplier(() -> null, () -> "", () -> " ")        = null
+     * StringUtils.firstNonBlankSupplier(() -> "abc")                            = "abc"
+     * StringUtils.firstNonBlankSupplier(() -> null, () -> "xyz")                = "xyz"
+     * StringUtils.firstNonBlankSupplier(() -> null, () -> "", () -> " ", () -> "xyz") = "xyz"
+     * StringUtils.firstNonBlankSupplier(() -> null, () -> "xyz", () -> "abc")   = "xyz"
+     * StringUtils.firstNonBlankSupplier()                                       = null
+     * 
+ * + * @param suppliers the suppliers providing String values, may be {@code null} or empty. + * @return the first non-blank value returned by a supplier, or {@code null} if there are none + * @since 3.19 + */ + @SafeVarargs + public static String firstNonBlankSupplier(final Supplier... suppliers) { + if (suppliers != null) { + for (final Supplier supplier : suppliers) { + if (supplier != null) { + final String value = supplier.get(); + if (isNotBlank(value)) { + return value; + } + } + } + } + return null; + } + + /** * Returns the first value in the array which is not empty. * From d0b8c4ddc23c24e7550917b2b0bac009776150a9 Mon Sep 17 00:00:00 2001 From: Aidan Ow Date: Sat, 18 Oct 2025 16:26:14 +1100 Subject: [PATCH 2/7] [LANG-1656] Short-circuit operation of firstNonBlank and firstNonEmpty. Introduced a new method: - StringUtils.firstNonEmptySupplier(Supplier... suppliers) This variant of firstNonEmpty accepts suppliers evaluated lazily in order until a non-empty value is found. It enables short-circuit behavior to avoid unnecessary or expensive computations. --- .../org/apache/commons/lang3/StringUtils.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/main/java/org/apache/commons/lang3/StringUtils.java b/src/main/java/org/apache/commons/lang3/StringUtils.java index eaa01a773f8..8eb19ee8e0a 100644 --- a/src/main/java/org/apache/commons/lang3/StringUtils.java +++ b/src/main/java/org/apache/commons/lang3/StringUtils.java @@ -1986,6 +1986,48 @@ public static T firstNonEmpty(final T... values) { return null; } + /** + * Returns the first value supplied which is not empty ("") or {@code null}. + * + *

+ * Whitespace is defined by {@link Character#isWhitespace(char)}. + *

+ * + *

+ * Each supplier is evaluated lazily in order until a non-empty value is found. + * This allows the caller to defer expensive computations until they are needed. + *

+ * + *
+     * StringUtils.firstNonEmptySuppler(() -> null, () -> null, () -> null)     = null
+     * StringUtils.firstNonEmptySuppler(() -> null, () -> null, () -> null)     = null
+     * StringUtils.firstNonEmptySuppler(() -> null, () -> "", () -> " ")        = " "
+     * StringUtils.firstNonEmptySuppler(() -> "abc")                            = "abc"
+     * StringUtils.firstNonEmptySuppler(() -> null, () -> "xyz")                = "xyz"
+     * StringUtils.firstNonEmptySuppler(() -> null, () -> "", () -> "xyz")      = "xyz"
+     * StringUtils.firstNonEmptySuppler(() -> null, () -> "xyz", () -> "abc")   = "xyz"
+     * StringUtils.firstNonEmptySuppler()                                       = null
+     * 
+ * + * @param suppliers the suppliers providing String values, may be {@code null} or empty. + * @return the first non-blank value returned by a supplier, or {@code null} if there are none + * @since 3.19 + */ + @SafeVarargs + public static String firstNonEmptySuppler(final Supplier... suppliers) { + if (suppliers != null) { + for (final Supplier supplier : suppliers) { + if (supplier != null) { + final String value = supplier.get(); + if (isNotEmpty(value)) { + return value; + } + } + } + } + return null; + } + /** * Calls {@link String#getBytes(Charset)} in a null-safe manner. * From 6727214d4ea6d336883387c0e33d8d47cf7d5901 Mon Sep 17 00:00:00 2001 From: Aidan Ow Date: Sat, 18 Oct 2025 16:37:35 +1100 Subject: [PATCH 3/7] [LANG-1656] Short-circuit operation of firstNonBlank and firstNonEmpty. Fixed a minor typo on method name: - StringUtils.firstNonEmptySuppler -> StringUtils.firstNonEmptySupplier --- src/main/java/org/apache/commons/lang3/StringUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/apache/commons/lang3/StringUtils.java b/src/main/java/org/apache/commons/lang3/StringUtils.java index 8eb19ee8e0a..dc656efbe8e 100644 --- a/src/main/java/org/apache/commons/lang3/StringUtils.java +++ b/src/main/java/org/apache/commons/lang3/StringUtils.java @@ -2014,7 +2014,7 @@ public static T firstNonEmpty(final T... values) { * @since 3.19 */ @SafeVarargs - public static String firstNonEmptySuppler(final Supplier... suppliers) { + public static String firstNonEmptySupplier(final Supplier... suppliers) { if (suppliers != null) { for (final Supplier supplier : suppliers) { if (supplier != null) { From b40ba2ef8971c6c128b184da5764015e869c0730 Mon Sep 17 00:00:00 2001 From: Aidan Ow Date: Sat, 18 Oct 2025 16:40:49 +1100 Subject: [PATCH 4/7] [LANG-1656] Short-circuit operation of firstNonBlank and firstNonEmpty. Added unit tests in StringUtilsEmptyBlankTest to verify: - StringUtils.firstNonBlankSupplier() - StringUtils.firstNonEmptySupplier() For each method, test: - Correct return values - null handling - Lazy evaluation / short-circuit behaviour --- .../lang3/StringUtilsEmptyBlankTest.java | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/src/test/java/org/apache/commons/lang3/StringUtilsEmptyBlankTest.java b/src/test/java/org/apache/commons/lang3/StringUtilsEmptyBlankTest.java index 642949f690d..3352e9730be 100644 --- a/src/test/java/org/apache/commons/lang3/StringUtilsEmptyBlankTest.java +++ b/src/test/java/org/apache/commons/lang3/StringUtilsEmptyBlankTest.java @@ -23,6 +23,9 @@ import org.junit.jupiter.api.Test; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; + /** * Tests {@link StringUtils} - Empty/Blank methods */ @@ -56,6 +59,60 @@ void testFirstNonEmpty() { assertEquals("xyz", StringUtils.firstNonEmpty(null, "xyz", "abc")); } + @Test + void testFirstNonBlankSupplier() { + assertNull(StringUtils.firstNonBlankSupplier()); + assertNull(StringUtils.firstNonBlankSupplier((Supplier[]) null)); + assertNull(StringUtils.firstNonBlankSupplier(() -> null, () -> "", () -> " ")); + assertEquals("a", StringUtils.firstNonBlankSupplier(() -> null, () -> "a")); + assertEquals("abc", StringUtils.firstNonBlankSupplier(() -> "abc")); + assertEquals("xyz", StringUtils.firstNonBlankSupplier(() -> null, () -> "xyz")); + assertEquals("xyz", StringUtils.firstNonBlankSupplier(() -> null, () -> "xyz", () -> "abc")); + } + + @Test + void testFirstNonEmptySupplier() { + assertNull(StringUtils.firstNonEmptySupplier()); + assertNull(StringUtils.firstNonEmptySupplier((Supplier[]) null)); + assertEquals(" ", StringUtils.firstNonEmptySupplier(() -> null, () -> "", () -> " ")); + assertEquals("a", StringUtils.firstNonEmptySupplier(() -> null, () -> "a")); + assertEquals("abc", StringUtils.firstNonEmptySupplier(() -> "abc")); + assertEquals("xyz", StringUtils.firstNonEmptySupplier(() -> null, () -> "xyz")); + assertEquals("xyz", StringUtils.firstNonEmptySupplier(() -> null, () -> "xyz", () -> "abc")); + } + + @Test + void testFirstNonBlankSupplierLazyEvaluation() { + AtomicBoolean secondCalled = new AtomicBoolean(false); + AtomicBoolean thirdCalled = new AtomicBoolean(false); + + String result = StringUtils.firstNonBlankSupplier( + () -> "first", + () -> { secondCalled.set(true); return "second"; }, + () -> { thirdCalled.set(true); return "third"; } + ); + + assertEquals("first", result); + assertFalse(secondCalled.get(), "Second supplier should not have been called"); + assertFalse(thirdCalled.get(), "Third supplier should not have been called"); + } + + @Test + void testFirstNonEmptySupplierLazyEvaluation() { + AtomicBoolean secondCalled = new AtomicBoolean(false); + + String result = StringUtils.firstNonEmptySupplier( + () -> "value", + () -> { + secondCalled.set(true); + return "should not run"; + } + ); + + assertEquals("value", result); + assertFalse(secondCalled.get(), "Second supplier should not have been called"); + } + @Test void testIsAllBlank() { assertTrue(StringUtils.isAllBlank((String) null)); From fed396afa1cee3d610b208a9556d277e42b7a748 Mon Sep 17 00:00:00 2001 From: Aidan Ow Date: Sat, 18 Oct 2025 17:23:52 +1100 Subject: [PATCH 5/7] [LANG-1656] Short-circuit operation of firstNonBlank and firstNonEmpty. Fixed Checkstyle issues: - Local variables are declared 'final' - Reordered imports (Static imports go first) - LeftCurly '{' fixes --- .../lang3/StringUtilsEmptyBlankTest.java | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/test/java/org/apache/commons/lang3/StringUtilsEmptyBlankTest.java b/src/test/java/org/apache/commons/lang3/StringUtilsEmptyBlankTest.java index 3352e9730be..ae35cdecbce 100644 --- a/src/test/java/org/apache/commons/lang3/StringUtilsEmptyBlankTest.java +++ b/src/test/java/org/apache/commons/lang3/StringUtilsEmptyBlankTest.java @@ -21,11 +21,11 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import org.junit.jupiter.api.Test; - import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; +import org.junit.jupiter.api.Test; + /** * Tests {@link StringUtils} - Empty/Blank methods */ @@ -83,13 +83,19 @@ void testFirstNonEmptySupplier() { @Test void testFirstNonBlankSupplierLazyEvaluation() { - AtomicBoolean secondCalled = new AtomicBoolean(false); - AtomicBoolean thirdCalled = new AtomicBoolean(false); + final AtomicBoolean secondCalled = new AtomicBoolean(false); + final AtomicBoolean thirdCalled = new AtomicBoolean(false); - String result = StringUtils.firstNonBlankSupplier( + final String result = StringUtils.firstNonBlankSupplier( () -> "first", - () -> { secondCalled.set(true); return "second"; }, - () -> { thirdCalled.set(true); return "third"; } + () -> { + secondCalled.set(true); + return "second"; + }, + () -> { + thirdCalled.set(true); + return "third"; + } ); assertEquals("first", result); @@ -99,9 +105,9 @@ void testFirstNonBlankSupplierLazyEvaluation() { @Test void testFirstNonEmptySupplierLazyEvaluation() { - AtomicBoolean secondCalled = new AtomicBoolean(false); + final AtomicBoolean secondCalled = new AtomicBoolean(false); - String result = StringUtils.firstNonEmptySupplier( + final String result = StringUtils.firstNonEmptySupplier( () -> "value", () -> { secondCalled.set(true); From 14422e82debcdd4ca364a00f92d8c98bcf1d70f3 Mon Sep 17 00:00:00 2001 From: Aidan Date: Sat, 25 Oct 2025 15:25:46 +1100 Subject: [PATCH 6/7] [LANG-1656] Short-circuit operation of firstNonBlank and firstNonEmpty. Added Generic type T to methods firstNonBlankSupplier() and firstNonEmptySupplier(). - Matches original function - Helps with scalability and backwards compatibility. --- .../java/org/apache/commons/lang3/StringUtils.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/apache/commons/lang3/StringUtils.java b/src/main/java/org/apache/commons/lang3/StringUtils.java index dc656efbe8e..6064e28c375 100644 --- a/src/main/java/org/apache/commons/lang3/StringUtils.java +++ b/src/main/java/org/apache/commons/lang3/StringUtils.java @@ -1936,11 +1936,11 @@ public static T firstNonBlank(final T... values) { * @since 3.19 */ @SafeVarargs - public static String firstNonBlankSupplier(final Supplier... suppliers) { + public static T firstNonBlankSupplier(final Supplier... suppliers) { if (suppliers != null) { - for (final Supplier supplier : suppliers) { + for (final Supplier supplier : suppliers) { if (supplier != null) { - final String value = supplier.get(); + final T value = supplier.get(); if (isNotBlank(value)) { return value; } @@ -2014,11 +2014,11 @@ public static T firstNonEmpty(final T... values) { * @since 3.19 */ @SafeVarargs - public static String firstNonEmptySupplier(final Supplier... suppliers) { + public static T firstNonEmptySupplier(final Supplier... suppliers) { if (suppliers != null) { - for (final Supplier supplier : suppliers) { + for (final Supplier supplier : suppliers) { if (supplier != null) { - final String value = supplier.get(); + final T value = supplier.get(); if (isNotEmpty(value)) { return value; } From 790cbbcc1ccd30a901d9db0f90491f0c0d30dd73 Mon Sep 17 00:00:00 2001 From: Aidan Ow Date: Sun, 26 Oct 2025 10:51:27 +1100 Subject: [PATCH 7/7] [LANG-1656] Short-circuit operation of firstNonBlank and firstNonEmpty. Fixes to javadoc warnings. - Added params for --- src/main/java/org/apache/commons/lang3/StringUtils.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/org/apache/commons/lang3/StringUtils.java b/src/main/java/org/apache/commons/lang3/StringUtils.java index 6064e28c375..32f8c61efe6 100644 --- a/src/main/java/org/apache/commons/lang3/StringUtils.java +++ b/src/main/java/org/apache/commons/lang3/StringUtils.java @@ -1931,6 +1931,7 @@ public static T firstNonBlank(final T... values) { * StringUtils.firstNonBlankSupplier() = null * * + * @param the specific kind of CharSequence. * @param suppliers the suppliers providing String values, may be {@code null} or empty. * @return the first non-blank value returned by a supplier, or {@code null} if there are none * @since 3.19 @@ -2009,6 +2010,7 @@ public static T firstNonEmpty(final T... values) { * StringUtils.firstNonEmptySuppler() = null * * + * @param the specific kind of CharSequence. * @param suppliers the suppliers providing String values, may be {@code null} or empty. * @return the first non-blank value returned by a supplier, or {@code null} if there are none * @since 3.19