commons-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From c...@apache.org
Subject [lang] LANG-1149 Ability to throw checked exceptions without declaring them
Date Sat, 11 Jul 2015 22:42:33 GMT
Repository: commons-lang
Updated Branches:
  refs/heads/master 1b066eb4f -> 59022fb87


LANG-1149
Ability to throw checked exceptions without declaring them


Project: http://git-wip-us.apache.org/repos/asf/commons-lang/repo
Commit: http://git-wip-us.apache.org/repos/asf/commons-lang/commit/59022fb8
Tree: http://git-wip-us.apache.org/repos/asf/commons-lang/tree/59022fb8
Diff: http://git-wip-us.apache.org/repos/asf/commons-lang/diff/59022fb8

Branch: refs/heads/master
Commit: 59022fb870c2c45a27e00943003c5acdeddaeec3
Parents: 1b066eb
Author: Chas Honton <chas@apache.org>
Authored: Sat Jul 11 15:41:56 2015 -0700
Committer: Chas Honton <chas@apache.org>
Committed: Sat Jul 11 15:42:27 2015 -0700

----------------------------------------------------------------------
 src/changes/changes.xml                         |  1 +
 .../commons/lang3/exception/ExceptionUtils.java | 66 ++++++++++++++++++++
 .../lang3/exception/ExceptionUtilsTest.java     | 60 ++++++++++++++++--
 3 files changed, 123 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/commons-lang/blob/59022fb8/src/changes/changes.xml
----------------------------------------------------------------------
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 19e7c11..cffa0b3 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -22,6 +22,7 @@
   <body>
 
   <release version="3.5" date="tba" description="tba">
+    <action issue="LANG-1149" type="add" dev="chas" due-to="Gregory Zak">Ability to
throw checked exceptions without declaring them</action>
     <action issue="LANG-1002" type="fix" dev="chas" due-to="Michael Osipov">Several
predefined ISO FastDateFormats in DateFormatUtils are incorrect</action>
     <action issue="LANG-1152" type="fix" dev="chas" due-to="Pas Filip">StringIndexOutOfBoundsException
or field over-write for large year fields in FastDateParser</action>
     <action issue="LANG-1153" type="add" dev="chas">Implement ParsePosition api for
FastDateParser</action>

http://git-wip-us.apache.org/repos/asf/commons-lang/blob/59022fb8/src/main/java/org/apache/commons/lang3/exception/ExceptionUtils.java
----------------------------------------------------------------------
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 a64357c..8f0898f 100644
--- a/src/main/java/org/apache/commons/lang3/exception/ExceptionUtils.java
+++ b/src/main/java/org/apache/commons/lang3/exception/ExceptionUtils.java
@@ -693,4 +693,70 @@ public class ExceptionUtils {
         return getMessage(root);
     }
 
+    /**
+     * 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
+     * stacktrace.
+     * <p>
+     * The use of this technique may be controversial, but exceedingly useful to
+     * library developers.
+     * <code>
+     *  public int propagateExample { // note that there is no throws clause
+     *      try {
+     *          return invocation(); // throws IOException
+     *      } catch (Exception e) {
+     *          return ExceptionUtils.rethrow(e);  // propagates a checked exception
+     *      }
+     *  }
+     * </code>
+     * <p>
+     * This is an alternative to the more conservative approach of wrapping the
+     * checked exception in a RuntimeException:
+     * <code>
+     *  public int wrapExample { // note that there is no throws clause
+     *      try {
+     *          return invocation(); // throws IOException
+     *      } catch (Exception e) {
+     *          throw new UndeclaredThrowableException(e);  // wraps a checked exception
+     *      }
+     *  }
+     * </code>
+     * <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
+     * Jyton, Scala, or Groovy, since these languages do not consider any
+     * exceptions as checked.
+     * 
+     * @since 3.5
+     *
+     * @param throwable
+     *            The throwable to rethrow.
+     * @return R Never actually returns, 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 that all code paths return a value.
+     * @throws throwable
+     */
+    public static <R> R rethrow(Throwable throwable) {
+        // claim that the typeErasure invocation throws a RuntimeException
+        return ExceptionUtils.<R, RuntimeException> typeErasure(throwable);
+    }
+
+    /**
+     * Claim a Throwable is another Exception type using type erasure. This
+     * hides a checked exception from the java compiler, allowing a checked
+     * exception to be thrown without having the exception in the method's throw
+     * clause.
+     */
+    @SuppressWarnings("unchecked")
+    private static <R, T extends Throwable> R typeErasure(Throwable throwable) throws
T {
+        throw (T) throwable;
+    }
 }

http://git-wip-us.apache.org/repos/asf/commons-lang/blob/59022fb8/src/test/java/org/apache/commons/lang3/exception/ExceptionUtilsTest.java
----------------------------------------------------------------------
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 c8ef1b6..e7d264d 100644
--- a/src/test/java/org/apache/commons/lang3/exception/ExceptionUtilsTest.java
+++ b/src/test/java/org/apache/commons/lang3/exception/ExceptionUtilsTest.java
@@ -16,11 +16,15 @@
  */
 package org.apache.commons.lang3.exception;
 
-import org.junit.After;
-import org.junit.Test;
-import org.junit.Before;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.io.PrintStream;
 import java.io.PrintWriter;
 import java.io.StringWriter;
@@ -28,6 +32,11 @@ import java.lang.reflect.Constructor;
 import java.lang.reflect.Modifier;
 import java.util.List;
 
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
 /**
  * Tests {@link org.apache.commons.lang3.exception.ExceptionUtils}.
  * 
@@ -535,4 +544,47 @@ public class ExceptionUtilsTest {
         public NestableException(final Throwable t) { super(t); }
     }
 
+    @Test
+    public void testThrow() {
+        Exception expected = new InterruptedException();
+        try {
+            ExceptionUtils.rethrow(expected);
+            Assert.fail("Exception not thrown");
+        }
+        catch(Exception actual) {
+            Assert.assertSame(expected, actual);
+        }
+    }
+
+    @Test
+    public void testCatchTechniques() {
+        try {
+            throwsCheckedException();
+            Assert.fail("Exception not thrown");
+        }
+        catch(Exception ioe) {
+            assertTrue(ioe instanceof IOException);
+            assertEquals(1, ExceptionUtils.getThrowableCount(ioe));
+        }
+        
+        try {
+            redeclareCheckedException();
+            Assert.fail("Exception not thrown");
+        }
+        catch(IOException ioe) {
+            assertEquals(1, ExceptionUtils.getThrowableCount(ioe));
+        }
+    }
+
+    private static int redeclareCheckedException() throws IOException {
+        return throwsCheckedException();
+    }
+
+    private static int throwsCheckedException() {
+        try {
+            throw new IOException();
+        } catch (Exception e) {
+            return ExceptionUtils.rethrow(e);
+        }
+    }
 }


Mime
View raw message