Add ExceptionUtils.asRuntimeException(T), and deprecate rethrow(T)

This commit is contained in:
Gary Gregory 2023-10-17 11:16:29 -04:00
parent ce5522f60d
commit 3935fa2f19
4 changed files with 84 additions and 13 deletions

View File

@ -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>

View File

@ -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) {

View File

@ -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;
}

View File

@ -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);