Browse Source

Add ThrowingRunnable alongside ThrowingConsumer et al

Signed-off-by: Hosam Aly <hosam.aly@exequt.com>
pull/35992/head
Hosam Aly 3 weeks ago
parent
commit
ceb332c025
No known key found for this signature in database
  1. 123
      spring-core/src/main/java/org/springframework/util/function/ThrowingRunnable.java
  2. 86
      spring-core/src/test/java/org/springframework/util/function/ThrowingRunnableTests.java

123
spring-core/src/main/java/org/springframework/util/function/ThrowingRunnable.java

@ -0,0 +1,123 @@ @@ -0,0 +1,123 @@
/*
* Copyright 2002-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.util.function;
import java.util.function.BiFunction;
/**
* A {@link Runnable} that allows invocation of code that throws a checked exception.
*
* @author Hosam Aly
*/
@FunctionalInterface
public interface ThrowingRunnable extends Runnable {
/**
* Performs this operation, possibly throwing a checked exception.
* @throws Exception on error
*/
void runWithException() throws Exception;
/**
* Default {@link Runnable#run()} that wraps any thrown checked exceptions
* (by default in a {@link RuntimeException}).
* @see java.lang.Runnable#run()
*/
@Override
default void run() {
run(RuntimeException::new);
}
/**
* Performs this operation, wrapping any thrown checked exceptions using the given
* {@code exceptionWrapper}.
* @param exceptionWrapper {@link BiFunction} that wraps the given message
* and checked exception into a runtime exception
*/
default void run(BiFunction<String, Exception, RuntimeException> exceptionWrapper) {
try {
runWithException();
}
catch (RuntimeException ex) {
throw ex;
}
catch (Exception ex) {
throw exceptionWrapper.apply(ex.getMessage(), ex);
}
}
/**
* Return a new {@link ThrowingRunnable} where the {@link #run()} method
* wraps any thrown checked exceptions using the given
* {@code exceptionWrapper}.
* @param exceptionWrapper {@link BiFunction} that wraps the given message
* and checked exception into a runtime exception
* @return the replacement {@link ThrowingRunnable} instance
*/
default ThrowingRunnable throwing(BiFunction<String, Exception, RuntimeException> exceptionWrapper) {
return new ThrowingRunnable() {
@Override
public void runWithException() throws Exception {
ThrowingRunnable.this.runWithException();
}
@Override
public void run() {
run(exceptionWrapper);
}
};
}
/**
* Lambda friendly convenience method that can be used to create a
* {@link ThrowingRunnable} where the {@link #run()} method wraps any checked
* exception thrown by the supplied lambda expression or method reference.
* <p>This method can be especially useful when working with method references.
* It allows you to easily convert a method that throws a checked exception
* into an instance compatible with a regular {@link Runnable}.
* <p>For example:
* <pre class="code">
* new Thread(ThrowingRunnable.of(Example::methodThatCanThrowCheckedException));
* </pre>
* @param runnable the source runnable
* @return a new {@link ThrowingRunnable} instance
*/
static ThrowingRunnable of(ThrowingRunnable runnable) {
return runnable;
}
/**
* Lambda friendly convenience method that can be used to create
* {@link ThrowingRunnable} where the {@link #run()} method wraps any
* thrown checked exceptions using the given {@code exceptionWrapper}.
* <p>This method can be especially useful when working with method references.
* It allows you to easily convert a method that throws a checked exception
* into an instance compatible with a regular {@link Runnable}.
* <p>For example:
* <pre class="code">
* new Thread(ThrowingRunnable.of(Example::methodThatCanThrowCheckedException, IllegalStateException::new));
* </pre>
* @param runnable the source runnable
* @param exceptionWrapper the exception wrapper to use
* @return a new {@link ThrowingRunnable} instance
*/
static ThrowingRunnable of(
ThrowingRunnable runnable, BiFunction<String, Exception, RuntimeException> exceptionWrapper) {
return runnable.throwing(exceptionWrapper);
}
}

86
spring-core/src/test/java/org/springframework/util/function/ThrowingRunnableTests.java

@ -0,0 +1,86 @@ @@ -0,0 +1,86 @@
/*
* Copyright 2002-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.util.function;
import java.io.IOException;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/**
* Tests for {@link ThrowingRunnable}.
*
* @author Hosam Aly
*/
class ThrowingRunnableTests {
@Test
void applyWhenThrowingUncheckedExceptionThrowsOriginal() {
ThrowingRunnable runnable = this::throwIllegalArgumentException;
assertThatIllegalArgumentException().isThrownBy(() -> runnable.run());
}
@Test
void applyWhenThrowingCheckedExceptionThrowsWrapperRuntimeException() {
ThrowingRunnable runnable = this::throwIOException;
assertThatExceptionOfType(RuntimeException.class).isThrownBy(
() -> runnable.run()).withCauseInstanceOf(IOException.class);
}
@Test
void applyWithExceptionWrapperWhenThrowingUncheckedExceptionThrowsOriginal() {
ThrowingRunnable runnable = this::throwIllegalArgumentException;
assertThatIllegalArgumentException().isThrownBy(
() -> runnable.run(IllegalStateException::new));
}
@Test
void applyWithExceptionWrapperWhenThrowingCheckedExceptionThrowsWrapper() {
ThrowingRunnable runnable = this::throwIOException;
assertThatIllegalStateException().isThrownBy(() -> runnable.run(
IllegalStateException::new)).withCauseInstanceOf(IOException.class);
}
@Test
void throwingModifiesThrownException() {
ThrowingRunnable runnable = this::throwIOException;
ThrowingRunnable modified = runnable.throwing(
IllegalStateException::new);
assertThatIllegalStateException().isThrownBy(
() -> modified.run()).withCauseInstanceOf(IOException.class);
}
@Test
void ofModifiesThrownException() {
ThrowingRunnable runnable = ThrowingRunnable.of(this::throwIOException,
IllegalStateException::new);
assertThatIllegalStateException().isThrownBy(
() -> runnable.run()).withCauseInstanceOf(IOException.class);
}
private void throwIOException() throws IOException {
throw new IOException();
}
private void throwIllegalArgumentException() {
throw new IllegalArgumentException();
}
}
Loading…
Cancel
Save