IO-192: Tagged input and output streams

Added static checker methods on TaggedIOException. This simplified the tagged stream classes and should make it easier to reuse just the TaggedIOException class.

Made the tag object Serializable. Switched to using a unique id (UUID) object per tagged stream instead of the stream instance as the tag that binds thrown exceptions to the tagged stream.

git-svn-id: https://svn.apache.org/repos/asf/commons/proper/io/trunk@803310 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/java/org/apache/commons/io/TaggedIOException.java b/src/java/org/apache/commons/io/TaggedIOException.java
index 6dc02bc..1519582 100644
--- a/src/java/org/apache/commons/io/TaggedIOException.java
+++ b/src/java/org/apache/commons/io/TaggedIOException.java
@@ -17,38 +17,104 @@
 package org.apache.commons.io;
 
 import java.io.IOException;
+import java.io.Serializable;
 
 /**
- * An {@link IOException} wrapper that tags the wrapped exception with
- * a given object reference. Both the tag and the wrapped original exception
- * can be used to determine further processing when this exception is caught.
+ * An {@link IOException} decorator that adds a serializable tag to the
+ * wrapped exception. Both the tag and the original exception can be used
+ * to determine further processing when this exception is caught.
  *
- * @since Commons IO 1.5
+ * @since Commons IO 2.0
  */
 public class TaggedIOException extends IOExceptionWithCause {
 
     /**
-     * The object reference used to tag the exception.
+     * Generated serial version UID.
      */
-    private final Object tag;
+    private static final long serialVersionUID = -6994123481142850163L;
+
+    /**
+     * Checks whether the given throwable is tagged with the given tag.
+     * <p>
+     * This check can only succeed if the throwable is a
+     * {@link TaggedIOException} and the tag is {@link Serializable}, but
+     * the argument types are intentionally more generic to make it easier
+     * to use this method without type casts.
+     * <p>
+     * A typical use for this method is in a <code>catch</code> block to
+     * determine how a caught exception should be handled:
+     * <pre>
+     * Serializable tag = ...;
+     * try {
+     *     ...;
+     * } catch (Throwable t) {
+     *     if (TaggedIOExcepton.isTaggedWith(t, tag)) {
+     *         // special processing for tagged exception
+     *     } else {
+     *         // handling of other kinds of exceptions
+     *     }
+     * }
+     * </pre>
+     *
+     * @param tag tag object
+     */
+    public static boolean isTaggedWith(Throwable throwable, Object tag) {
+        return tag != null
+            && throwable instanceof TaggedIOException
+            && tag.equals(((TaggedIOException) throwable).tag);
+    }
+
+    /**
+     * Throws the original {@link IOException} if the given throwable is
+     * a {@link TaggedIOException} decorator the given tag. Does nothing
+     * if the given throwable is of a different type or if it is tagged
+     * with some other tag.
+     * <p>
+     * This method is typically used in a <code>catch</code> block to
+     * selectively rethrow tagged exceptions.
+     * <pre>
+     * Serializable tag = ...;
+     * try {
+     *     ...;
+     * } catch (Throwable t) {
+     *     TaggedIOExcepton.throwCauseIfTagged(t, tag);
+     *     // handle other kinds of exceptions
+     * }
+     * </pre>
+     *
+     * @param throwable an exception
+     * @param tag tag object
+     * @throws IOException original exception from the tagged decorator, if any
+     */
+    public static void throwCauseIfTaggedWith(Throwable throwable, Object tag)
+            throws IOException {
+        if (isTaggedWith(throwable, tag)) {
+            throw ((TaggedIOException) throwable).getCause();
+        }
+    }
+
+    /**
+     * The tag of this exception.
+     */
+    private final Serializable tag;
 
     /**
      * Creates a tagged wrapper for the given exception.
      *
      * @param original the exception to be tagged
-     * @param tag tag object
+     * @param tag tag of this exception
      */
-    public TaggedIOException(IOException original, Object tag) {
+    public TaggedIOException(IOException original, Serializable tag) {
         super(original.getMessage(), original);
         this.tag = tag;
     }
 
     /**
-     * Returns the object reference used as the tag this exception.
+     * Returns the serializable tag object.
      *
      * @return tag object
      */
-    public Object getTag() {
+    public Serializable getTag() {
         return tag;
     }
 
diff --git a/src/java/org/apache/commons/io/input/TaggedInputStream.java b/src/java/org/apache/commons/io/input/TaggedInputStream.java
index f846273..9c5cf12 100644
--- a/src/java/org/apache/commons/io/input/TaggedInputStream.java
+++ b/src/java/org/apache/commons/io/input/TaggedInputStream.java
@@ -18,6 +18,8 @@
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.Serializable;
+import java.util.UUID;
 
 import org.apache.commons.io.TaggedIOException;
 
@@ -57,11 +59,16 @@
  * </pre>
  *
  * @see TaggedIOException
- * @since Commons IO 1.5
+ * @since Commons IO 2.0
  */
 public class TaggedInputStream extends ProxyInputStream {
 
     /**
+     * The unique tag associated with exceptions from stream.
+     */
+    private final Serializable tag = UUID.randomUUID();
+
+    /**
      * Creates a tagging decorator for the given input stream.
      *
      * @param proxy input stream to be decorated
@@ -73,17 +80,12 @@
     /**
      * Tests if the given exception was caused by this stream.
      *
-     * @param exception an exception
+     * @param throwable an exception
      * @return <code>true</code> if the exception was thrown by this stream,
      *         <code>false</code> otherwise
      */
-    public boolean isCauseOf(IOException exception) {
-        if (exception instanceof TaggedIOException) {
-            TaggedIOException tagged = (TaggedIOException) exception;
-            return this == tagged.getTag();
-        } else {
-            return false;
-        }
+    public boolean isCauseOf(Throwable exception) {
+        return TaggedIOException.isTaggedWith(exception, tag);
     }
 
     /**
@@ -93,16 +95,11 @@
      * original wrapped exception. Returns normally if the exception was
      * not thrown by this stream.
      *
-     * @param exception an exception
+     * @param throwable an exception
      * @throws IOException original exception, if any, thrown by this stream
      */
-    public void throwIfCauseOf(Exception exception) throws IOException {
-        if (exception instanceof TaggedIOException) {
-            TaggedIOException tagged = (TaggedIOException) exception;
-            if (this == tagged.getTag()) {
-                throw tagged.getCause();
-            }
-        }
+    public void throwIfCauseOf(Throwable throwable) throws IOException {
+        TaggedIOException.throwCauseIfTaggedWith(throwable, tag);
     }
 
     /**
@@ -113,7 +110,7 @@
      */
     @Override
     protected void handleIOException(IOException e) throws IOException {
-        throw new TaggedIOException(e, this);
+        throw new TaggedIOException(e, tag);
     }
 
 }
diff --git a/src/java/org/apache/commons/io/output/TaggedOutputStream.java b/src/java/org/apache/commons/io/output/TaggedOutputStream.java
index 0ab5073..dfd6a85 100644
--- a/src/java/org/apache/commons/io/output/TaggedOutputStream.java
+++ b/src/java/org/apache/commons/io/output/TaggedOutputStream.java
@@ -18,6 +18,8 @@
 
 import java.io.IOException;
 import java.io.OutputStream;
+import java.io.Serializable;
+import java.util.UUID;
 
 import org.apache.commons.io.TaggedIOException;
 
@@ -57,11 +59,16 @@
  * </pre>
  *
  * @see TaggedIOException
- * @since Commons IO 1.5
+ * @since Commons IO 2.0
  */
 public class TaggedOutputStream extends ProxyOutputStream {
 
     /**
+     * The unique tag associated with exceptions from stream.
+     */
+    private final Serializable tag = UUID.randomUUID();
+
+    /**
      * Creates a tagging decorator for the given output stream.
      *
      * @param proxy output stream to be decorated
@@ -77,13 +84,8 @@
      * @return <code>true</code> if the exception was thrown by this stream,
      *         <code>false</code> otherwise
      */
-    public boolean isCauseOf(IOException exception) {
-        if (exception instanceof TaggedIOException) {
-            TaggedIOException tagged = (TaggedIOException) exception;
-            return this == tagged.getTag();
-        } else {
-            return false;
-        }
+    public boolean isCauseOf(Exception exception) {
+        return TaggedIOException.isTaggedWith(exception, tag);
     }
 
     /**
@@ -97,12 +99,7 @@
      * @throws IOException original exception, if any, thrown by this stream
      */
     public void throwIfCauseOf(Exception exception) throws IOException {
-        if (exception instanceof TaggedIOException) {
-            TaggedIOException tagged = (TaggedIOException) exception;
-            if (this == tagged.getTag()) {
-                throw tagged.getCause();
-            }
-        }
+        TaggedIOException.throwCauseIfTaggedWith(exception, tag);
     }
 
     /**
@@ -113,7 +110,7 @@
      */
     @Override
     protected void handleIOException(IOException e) throws IOException {
-        throw new TaggedIOException(e, this);
+        throw new TaggedIOException(e, tag);
     }
 
 }
diff --git a/src/test/org/apache/commons/io/TaggedIOExceptionTest.java b/src/test/org/apache/commons/io/TaggedIOExceptionTest.java
index 5774505..8b66e62 100644
--- a/src/test/org/apache/commons/io/TaggedIOExceptionTest.java
+++ b/src/test/org/apache/commons/io/TaggedIOExceptionTest.java
@@ -17,6 +17,8 @@
 package org.apache.commons.io;
 
 import java.io.IOException;
+import java.io.Serializable;
+import java.util.UUID;
 
 import junit.framework.TestCase;
 
@@ -26,10 +28,11 @@
 public class TaggedIOExceptionTest extends TestCase {
 
     public void testTaggedIOException() {
-        Object tag = new Object();
+        Serializable tag = UUID.randomUUID();
         IOException exception = new IOException("Test exception");
         TaggedIOException tagged = new TaggedIOException(exception, tag);
-        assertEquals(tag, tagged.getTag());
+        assertTrue(TaggedIOException.isTaggedWith(tagged, tag));
+        assertFalse(TaggedIOException.isTaggedWith(tagged, UUID.randomUUID()));
         assertEquals(exception, tagged.getCause());
         assertEquals(exception.getMessage(), tagged.getMessage());
     }
diff --git a/src/test/org/apache/commons/io/input/TaggedInputStreamTest.java b/src/test/org/apache/commons/io/input/TaggedInputStreamTest.java
index 70d6714..3d6b72d 100644
--- a/src/test/org/apache/commons/io/input/TaggedInputStreamTest.java
+++ b/src/test/org/apache/commons/io/input/TaggedInputStreamTest.java
@@ -19,6 +19,7 @@
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.UUID;
 
 import org.apache.commons.io.TaggedIOException;
 
@@ -114,7 +115,8 @@
         TaggedInputStream stream = new TaggedInputStream(closed);
 
         assertFalse(stream.isCauseOf(exception));
-        assertFalse(stream.isCauseOf(new TaggedIOException(exception, closed)));
+        assertFalse(stream.isCauseOf(
+                new TaggedIOException(exception, UUID.randomUUID())));
 
         try {
             stream.throwIfCauseOf(exception);
@@ -123,7 +125,8 @@
         }
 
         try {
-            stream.throwIfCauseOf(new TaggedIOException(exception, closed));
+            stream.throwIfCauseOf(
+                    new TaggedIOException(exception, UUID.randomUUID()));
         } catch (IOException e) {
             fail("Unexpected exception thrown");
         }
diff --git a/src/test/org/apache/commons/io/output/TaggedOutputStreamTest.java b/src/test/org/apache/commons/io/output/TaggedOutputStreamTest.java
index fcd2207..17ce80a 100644
--- a/src/test/org/apache/commons/io/output/TaggedOutputStreamTest.java
+++ b/src/test/org/apache/commons/io/output/TaggedOutputStreamTest.java
@@ -18,6 +18,7 @@
 
 import java.io.IOException;
 import java.io.OutputStream;
+import java.util.UUID;
 
 import org.apache.commons.io.TaggedIOException;
 
@@ -100,7 +101,8 @@
         TaggedOutputStream stream = new TaggedOutputStream(closed);
 
         assertFalse(stream.isCauseOf(exception));
-        assertFalse(stream.isCauseOf(new TaggedIOException(exception, closed)));
+        assertFalse(stream.isCauseOf(
+                new TaggedIOException(exception, UUID.randomUUID())));
 
         try {
             stream.throwIfCauseOf(exception);
@@ -109,7 +111,8 @@
         }
 
         try {
-            stream.throwIfCauseOf(new TaggedIOException(exception, closed));
+            stream.throwIfCauseOf(
+                    new TaggedIOException(exception, UUID.randomUUID()));
         } catch (IOException e) {
             fail("Unexpected exception thrown");
         }