diff --git a/src/main/java/org/apache/commons/lang3/StringUtils.java b/src/main/java/org/apache/commons/lang3/StringUtils.java index 751896e1213..32f8c61efe6 100644 --- a/src/main/java/org/apache/commons/lang3/StringUtils.java +++ b/src/main/java/org/apache/commons/lang3/StringUtils.java @@ -1904,6 +1904,54 @@ 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 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 + */ + @SafeVarargs + public static T firstNonBlankSupplier(final Supplier... suppliers) { + if (suppliers != null) { + for (final Supplier supplier : suppliers) { + if (supplier != null) { + final T value = supplier.get(); + if (isNotBlank(value)) { + return value; + } + } + } + } + return null; + } + + /** * Returns the first value in the array which is not empty. * @@ -1939,6 +1987,49 @@ 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 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 + */ + @SafeVarargs + public static T firstNonEmptySupplier(final Supplier... suppliers) { + if (suppliers != null) { + for (final Supplier supplier : suppliers) { + if (supplier != null) { + final T value = supplier.get(); + if (isNotEmpty(value)) { + return value; + } + } + } + } + return null; + } + /** * Calls {@link String#getBytes(Charset)} in a null-safe manner. * diff --git a/src/test/java/org/apache/commons/lang3/StringUtilsEmptyBlankTest.java b/src/test/java/org/apache/commons/lang3/StringUtilsEmptyBlankTest.java index 642949f690d..ae35cdecbce 100644 --- a/src/test/java/org/apache/commons/lang3/StringUtilsEmptyBlankTest.java +++ b/src/test/java/org/apache/commons/lang3/StringUtilsEmptyBlankTest.java @@ -21,6 +21,9 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; + import org.junit.jupiter.api.Test; /** @@ -56,6 +59,66 @@ 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() { + final AtomicBoolean secondCalled = new AtomicBoolean(false); + final AtomicBoolean thirdCalled = new AtomicBoolean(false); + + final 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() { + final AtomicBoolean secondCalled = new AtomicBoolean(false); + + final 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));