This is an automated email from the ASF dual-hosted git repository. ggregory pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-lang.git
commit 3935fa2f193a0cc652300677beeb61b535eedf4c Author: Gary Gregory <garydgreg...@gmail.com> AuthorDate: Tue Oct 17 11:16:29 2023 -0400 Add ExceptionUtils.asRuntimeException(T), and deprecate rethrow(T) --- src/changes/changes.xml | 1 + .../commons/lang3/exception/ExceptionUtils.java | 82 +++++++++++++++++++--- .../apache/commons/lang3/time/FastDatePrinter.java | 2 +- .../lang3/exception/ExceptionUtilsTest.java | 12 +++- 4 files changed, 84 insertions(+), 13 deletions(-) diff --git a/src/changes/changes.xml b/src/changes/changes.xml index acfd3a581..3dbc096f0 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -75,6 +75,7 @@ The <action> type attribute can be add,update,fix,remove. <action type="add" dev="ggregory" due-to="Gary Gregory">Add FailableSupplier.nul().</action> <action type="add" dev="ggregory" due-to="Gary Gregory">Add Suppliers.nul().</action> <action type="add" dev="ggregory" due-to="Gary Gregory">Add ExceptionUtils.throwUnchecked(T) where T extends Throwable, and deprecate Object version.</action> + <action type="add" dev="ggregory" due-to="Gary Gregory">Add ExceptionUtils.rethrowRuntimeException(T), and deprecate rethrow(T).</action> <!-- UPDATE --> <action type="update" dev="ggregory" due-to="Gary Gregory">Bump commons-parent from 58 to 64.</action> <action type="update" dev="ggregory" due-to="Gary Gregory">Bump org.easymock:easymock from 5.1.0 to 5.2.0 #1104.</action> diff --git a/src/main/java/org/apache/commons/lang3/exception/ExceptionUtils.java b/src/main/java/org/apache/commons/lang3/exception/ExceptionUtils.java index 57bfe286f..69f4f6ccf 100644 --- a/src/main/java/org/apache/commons/lang3/exception/ExceptionUtils.java +++ b/src/main/java/org/apache/commons/lang3/exception/ExceptionUtils.java @@ -71,6 +71,69 @@ public class ExceptionUtils { */ static final String WRAPPED_MARKER = " [wrapped] "; + /** + * Use to throws a checked exception without adding the exception to the throws + * clause of the calling method. This method prevents throws clause + * pollution and reduces the clutter of "Caused by" exceptions in the + * stack trace. + * <p> + * The use of this technique may be controversial, but exceedingly useful to + * library developers. + * </p> + * <pre> + * public int propagateExample { // note that there is no throws clause + * try { + * return invocation(); // throws IOException + * } catch (Exception e) { + * return ExceptionUtils.rethrowRuntimeException(e); // propagates a checked exception + * } + * } + * </pre> + * <p> + * This is an alternative to the more conservative approach of wrapping the + * checked exception in a RuntimeException: + * </p> + * <pre> + * public int wrapExample { // note that there is no throws clause + * try { + * return invocation(); // throws IOException + * } catch (Error e) { + * throw e; + * } catch (RuntimeException e) { + * throw e; // wraps a checked exception + * } catch (Exception e) { + * throw new UndeclaredThrowableException(e); // wraps a checked exception + * } + * } + * </pre> + * <p> + * One downside to using this approach is that the java compiler will not + * allow invoking code to specify a checked exception in a catch clause + * unless there is some code path within the try block that has invoked a + * method declared with that checked exception. If the invoking site wishes + * to catch the shaded checked exception, it must either invoke the shaded + * code through a method re-declaring the desired checked exception, or + * catch Exception and use the instanceof operator. Either of these + * techniques are required when interacting with non-java jvm code such as + * Jython, Scala, or Groovy, since these languages do not consider any + * exceptions as checked. + * </p> + * + * @param throwable + * The throwable to rethrow. + * @param <T> The type of the returned value. + * @return Never actually returned, this generic type matches any type + * which the calling site requires. "Returning" the results of this + * method, as done in the propagateExample above, will satisfy the + * java compiler requirement that all code paths return a value. + * @since 3.14.0 + * @see #wrapAndThrow(Throwable) + */ + public static <T extends RuntimeException> T asRuntimeException(final Throwable throwable) { + // claim that the typeErasure invocation throws a RuntimeException + return ExceptionUtils.<T, RuntimeException>eraseType(throwable); + } + /** * Claims a Throwable is another Throwable type using type erasure. This * hides a checked exception from the Java compiler, allowing a checked @@ -751,7 +814,7 @@ public class ExceptionUtils { } /** - * Throws a checked exception without adding the exception to the throws + * Use to throw a checked exception without adding the exception to the throws * clause of the calling method. This method prevents throws clause * pollution and reduces the clutter of "Caused by" exceptions in the * stack trace. @@ -800,17 +863,19 @@ public class ExceptionUtils { * * @param throwable * The throwable to rethrow. - * @param <R> The type of the returned value. + * @param <T> The type of the returned value. * @return Never actually returned, this generic type matches any type * which the calling site requires. "Returning" the results of this * method, as done in the propagateExample above, will satisfy the * java compiler requirement that all code paths return a value. * @since 3.5 * @see #wrapAndThrow(Throwable) + * @deprecated Use {@link #asRuntimeException(Throwable)}. */ - public static <R> R rethrow(final Throwable throwable) { + @Deprecated + public static <T> T rethrow(final Throwable throwable) { // claim that the typeErasure invocation throws a RuntimeException - return ExceptionUtils.<R, RuntimeException>eraseType(throwable); + return ExceptionUtils.<T, RuntimeException>eraseType(throwable); } /** @@ -993,11 +1058,8 @@ public class ExceptionUtils { * @since 3.14.0 */ public static <T extends Throwable> T throwUnchecked(final T throwable) { - if (throwable instanceof RuntimeException) { - throw (RuntimeException) throwable; - } - if (throwable instanceof Error) { - throw (Error) throwable; + if (isUnchecked(throwable)) { + throw asRuntimeException(throwable); } return throwable; } @@ -1021,7 +1083,7 @@ public class ExceptionUtils { * method will satisfy the java compiler requirement that all code * paths return a value. * @since 3.5 - * @see #rethrow(Throwable) + * @see #asRuntimeException(Throwable) * @see #hasCause(Throwable, Class) */ public static <R> R wrapAndThrow(final Throwable throwable) { diff --git a/src/main/java/org/apache/commons/lang3/time/FastDatePrinter.java b/src/main/java/org/apache/commons/lang3/time/FastDatePrinter.java index bcb112ce8..eafc6535a 100644 --- a/src/main/java/org/apache/commons/lang3/time/FastDatePrinter.java +++ b/src/main/java/org/apache/commons/lang3/time/FastDatePrinter.java @@ -585,7 +585,7 @@ public class FastDatePrinter implements DatePrinter, Serializable { rule.appendTo(buf, calendar); } } catch (final IOException ioe) { - ExceptionUtils.rethrow(ioe); + ExceptionUtils.asRuntimeException(ioe); } return buf; } diff --git a/src/test/java/org/apache/commons/lang3/exception/ExceptionUtilsTest.java b/src/test/java/org/apache/commons/lang3/exception/ExceptionUtilsTest.java index 4845df492..02273d566 100644 --- a/src/test/java/org/apache/commons/lang3/exception/ExceptionUtilsTest.java +++ b/src/test/java/org/apache/commons/lang3/exception/ExceptionUtilsTest.java @@ -119,7 +119,8 @@ public class ExceptionUtilsTest extends AbstractLangTest { try { throw new IOException(); } catch (final Exception e) { - return ExceptionUtils.<Integer>rethrow(e); + ExceptionUtils.asRuntimeException(e); + return -1; } } @@ -203,6 +204,13 @@ public class ExceptionUtilsTest extends AbstractLangTest { assertEquals("IllegalArgumentException: Base", ExceptionUtils.getRootCauseMessage(th)); } + @Test + public void testAsRuntimeException() { + final Exception expected = new InterruptedException(); + final Exception actual = assertThrows(Exception.class, () -> ExceptionUtils.asRuntimeException(expected)); + assertSame(expected, actual); + } + @Test public void testCatchTechniques() { IOException ioe = assertThrows(IOException.class, ExceptionUtilsTest::throwsCheckedException); @@ -765,7 +773,7 @@ public class ExceptionUtilsTest extends AbstractLangTest { } @Test - public void testThrow() { + public void testRethrow() { final Exception expected = new InterruptedException(); final Exception actual = assertThrows(Exception.class, () -> ExceptionUtils.rethrow(expected)); assertSame(expected, actual);