2 changed files with 209 additions and 0 deletions
@ -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); |
||||||
|
} |
||||||
|
} |
||||||
@ -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…
Reference in new issue