From 3935fa2f193a0cc652300677beeb61b535eedf4c Mon Sep 17 00:00:00 2001 From: Gary Gregory Date: Tue, 17 Oct 2023 11:16:29 -0400 Subject: [PATCH] Add ExceptionUtils.asRuntimeException(T), and deprecate rethrow(T) --- src/changes/changes.xml | 1 + .../lang3/exception/ExceptionUtils.java | 82 ++++++++++++++++--- .../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 type attribute can be add,update,fix,remove. Add FailableSupplier.nul(). Add Suppliers.nul(). Add ExceptionUtils.throwUnchecked(T) where T extends Throwable, and deprecate Object version. + Add ExceptionUtils.rethrowRuntimeException(T), and deprecate rethrow(T). Bump commons-parent from 58 to 64. Bump org.easymock:easymock from 5.1.0 to 5.2.0 #1104. 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. + *

+ * The use of this technique may be controversial, but exceedingly useful to + * library developers. + *

+ *
+     *  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
+     *      }
+     *  }
+     * 
+ *

+ * This is an alternative to the more conservative approach of wrapping the + * checked exception in a RuntimeException: + *

+ *
+     *  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
+     *      }
+     *  }
+     * 
+ *

+ * 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. + *

+ * + * @param throwable + * The throwable to rethrow. + * @param 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 asRuntimeException(final Throwable throwable) { + // claim that the typeErasure invocation throws a RuntimeException + return ExceptionUtils.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 The type of the returned value. + * @param 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 rethrow(final Throwable throwable) { + @Deprecated + public static T rethrow(final Throwable throwable) { // claim that the typeErasure invocation throws a RuntimeException - return ExceptionUtils.eraseType(throwable); + return ExceptionUtils.eraseType(throwable); } /** @@ -993,11 +1058,8 @@ public class ExceptionUtils { * @since 3.14.0 */ public static 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 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.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);