Skip to content
89 changes: 89 additions & 0 deletions src/main/java/org/apache/commons/lang3/StringUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -1904,6 +1904,53 @@ public static <T extends CharSequence> T firstNonBlank(final T... values) {
return null;
}

/**
* Returns the first value supplied which is not empty (""), {@code null} or whitespace only.
*
* <p>
* Whitespace is defined by {@link Character#isWhitespace(char)}.
* </p>
*
* <p>
* 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.
* </p>
*
* <p>
* If all supplied values are blank, or the array of suppliers is {@code null} or empty,
* then {@code null} is returned.
* </p>
*
* <pre>
* 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
* </pre>
*
* @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 extends CharSequence> T firstNonBlankSupplier(final Supplier<T>... suppliers) {
if (suppliers != null) {
for (final Supplier<T> 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.
*
Expand Down Expand Up @@ -1939,6 +1986,48 @@ public static <T extends CharSequence> T firstNonEmpty(final T... values) {
return null;
}

/**
* Returns the first value supplied which is not empty ("") or {@code null}.
*
* <p>
* Whitespace is defined by {@link Character#isWhitespace(char)}.
* </p>
*
* <p>
* 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.
* </p>
*
* <pre>
* 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
* </pre>
*
* @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 extends CharSequence> T firstNonEmptySupplier(final Supplier<T>... suppliers) {
if (suppliers != null) {
for (final Supplier<T> 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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -56,6 +59,66 @@ void testFirstNonEmpty() {
assertEquals("xyz", StringUtils.firstNonEmpty(null, "xyz", "abc"));
}

@Test
void testFirstNonBlankSupplier() {
assertNull(StringUtils.firstNonBlankSupplier());
assertNull(StringUtils.firstNonBlankSupplier((Supplier<String>[]) 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<String>[]) 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));
Expand Down