Skip to content

Commit ceb332c

Browse files
committed
Add ThrowingRunnable alongside ThrowingConsumer et al
Signed-off-by: Hosam Aly <hosam.aly@exequt.com>
1 parent 75e3f44 commit ceb332c

File tree

2 files changed

+209
-0
lines changed

2 files changed

+209
-0
lines changed
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/*
2+
* Copyright 2002-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.util.function;
18+
19+
import java.util.function.BiFunction;
20+
21+
/**
22+
* A {@link Runnable} that allows invocation of code that throws a checked exception.
23+
*
24+
* @author Hosam Aly
25+
*/
26+
@FunctionalInterface
27+
public interface ThrowingRunnable extends Runnable {
28+
29+
/**
30+
* Performs this operation, possibly throwing a checked exception.
31+
* @throws Exception on error
32+
*/
33+
void runWithException() throws Exception;
34+
35+
/**
36+
* Default {@link Runnable#run()} that wraps any thrown checked exceptions
37+
* (by default in a {@link RuntimeException}).
38+
* @see java.lang.Runnable#run()
39+
*/
40+
@Override
41+
default void run() {
42+
run(RuntimeException::new);
43+
}
44+
45+
/**
46+
* Performs this operation, wrapping any thrown checked exceptions using the given
47+
* {@code exceptionWrapper}.
48+
* @param exceptionWrapper {@link BiFunction} that wraps the given message
49+
* and checked exception into a runtime exception
50+
*/
51+
default void run(BiFunction<String, Exception, RuntimeException> exceptionWrapper) {
52+
try {
53+
runWithException();
54+
}
55+
catch (RuntimeException ex) {
56+
throw ex;
57+
}
58+
catch (Exception ex) {
59+
throw exceptionWrapper.apply(ex.getMessage(), ex);
60+
}
61+
}
62+
63+
/**
64+
* Return a new {@link ThrowingRunnable} where the {@link #run()} method
65+
* wraps any thrown checked exceptions using the given
66+
* {@code exceptionWrapper}.
67+
* @param exceptionWrapper {@link BiFunction} that wraps the given message
68+
* and checked exception into a runtime exception
69+
* @return the replacement {@link ThrowingRunnable} instance
70+
*/
71+
default ThrowingRunnable throwing(BiFunction<String, Exception, RuntimeException> exceptionWrapper) {
72+
return new ThrowingRunnable() {
73+
@Override
74+
public void runWithException() throws Exception {
75+
ThrowingRunnable.this.runWithException();
76+
}
77+
78+
@Override
79+
public void run() {
80+
run(exceptionWrapper);
81+
}
82+
};
83+
}
84+
85+
/**
86+
* Lambda friendly convenience method that can be used to create a
87+
* {@link ThrowingRunnable} where the {@link #run()} method wraps any checked
88+
* exception thrown by the supplied lambda expression or method reference.
89+
* <p>This method can be especially useful when working with method references.
90+
* It allows you to easily convert a method that throws a checked exception
91+
* into an instance compatible with a regular {@link Runnable}.
92+
* <p>For example:
93+
* <pre class="code">
94+
* new Thread(ThrowingRunnable.of(Example::methodThatCanThrowCheckedException));
95+
* </pre>
96+
* @param runnable the source runnable
97+
* @return a new {@link ThrowingRunnable} instance
98+
*/
99+
static ThrowingRunnable of(ThrowingRunnable runnable) {
100+
return runnable;
101+
}
102+
103+
/**
104+
* Lambda friendly convenience method that can be used to create
105+
* {@link ThrowingRunnable} where the {@link #run()} method wraps any
106+
* thrown checked exceptions using the given {@code exceptionWrapper}.
107+
* <p>This method can be especially useful when working with method references.
108+
* It allows you to easily convert a method that throws a checked exception
109+
* into an instance compatible with a regular {@link Runnable}.
110+
* <p>For example:
111+
* <pre class="code">
112+
* new Thread(ThrowingRunnable.of(Example::methodThatCanThrowCheckedException, IllegalStateException::new));
113+
* </pre>
114+
* @param runnable the source runnable
115+
* @param exceptionWrapper the exception wrapper to use
116+
* @return a new {@link ThrowingRunnable} instance
117+
*/
118+
static ThrowingRunnable of(
119+
ThrowingRunnable runnable, BiFunction<String, Exception, RuntimeException> exceptionWrapper) {
120+
121+
return runnable.throwing(exceptionWrapper);
122+
}
123+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Copyright 2002-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.util.function;
18+
19+
import java.io.IOException;
20+
21+
import org.junit.jupiter.api.Test;
22+
23+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
24+
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
25+
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
26+
27+
/**
28+
* Tests for {@link ThrowingRunnable}.
29+
*
30+
* @author Hosam Aly
31+
*/
32+
class ThrowingRunnableTests {
33+
34+
@Test
35+
void applyWhenThrowingUncheckedExceptionThrowsOriginal() {
36+
ThrowingRunnable runnable = this::throwIllegalArgumentException;
37+
assertThatIllegalArgumentException().isThrownBy(() -> runnable.run());
38+
}
39+
40+
@Test
41+
void applyWhenThrowingCheckedExceptionThrowsWrapperRuntimeException() {
42+
ThrowingRunnable runnable = this::throwIOException;
43+
assertThatExceptionOfType(RuntimeException.class).isThrownBy(
44+
() -> runnable.run()).withCauseInstanceOf(IOException.class);
45+
}
46+
47+
@Test
48+
void applyWithExceptionWrapperWhenThrowingUncheckedExceptionThrowsOriginal() {
49+
ThrowingRunnable runnable = this::throwIllegalArgumentException;
50+
assertThatIllegalArgumentException().isThrownBy(
51+
() -> runnable.run(IllegalStateException::new));
52+
}
53+
54+
@Test
55+
void applyWithExceptionWrapperWhenThrowingCheckedExceptionThrowsWrapper() {
56+
ThrowingRunnable runnable = this::throwIOException;
57+
assertThatIllegalStateException().isThrownBy(() -> runnable.run(
58+
IllegalStateException::new)).withCauseInstanceOf(IOException.class);
59+
}
60+
61+
@Test
62+
void throwingModifiesThrownException() {
63+
ThrowingRunnable runnable = this::throwIOException;
64+
ThrowingRunnable modified = runnable.throwing(
65+
IllegalStateException::new);
66+
assertThatIllegalStateException().isThrownBy(
67+
() -> modified.run()).withCauseInstanceOf(IOException.class);
68+
}
69+
70+
@Test
71+
void ofModifiesThrownException() {
72+
ThrowingRunnable runnable = ThrowingRunnable.of(this::throwIOException,
73+
IllegalStateException::new);
74+
assertThatIllegalStateException().isThrownBy(
75+
() -> runnable.run()).withCauseInstanceOf(IOException.class);
76+
}
77+
78+
private void throwIOException() throws IOException {
79+
throw new IOException();
80+
}
81+
82+
private void throwIllegalArgumentException() {
83+
throw new IllegalArgumentException();
84+
}
85+
86+
}

0 commit comments

Comments
 (0)