You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2019/08/08 23:15:07 UTC

[commons-io] branch master updated: Added TaggedWriter, ClosedWriter and BrokenWriter. (#86)

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-io.git


The following commit(s) were added to refs/heads/master by this push:
     new b358a63  Added TaggedWriter, ClosedWriter and BrokenWriter. (#86)
b358a63 is described below

commit b358a63b22b9cb614bdc8830a3c88ae256e77387
Author: Rob Spoor <ro...@users.noreply.github.com>
AuthorDate: Fri Aug 9 01:15:01 2019 +0200

    Added TaggedWriter, ClosedWriter and BrokenWriter. (#86)
---
 .../org/apache/commons/io/output/BrokenWriter.java |  87 ++++++++++++++
 .../org/apache/commons/io/output/ClosedWriter.java |  65 +++++++++++
 .../org/apache/commons/io/output/TaggedWriter.java | 116 +++++++++++++++++++
 .../apache/commons/io/output/BrokenWriterTest.java |  87 ++++++++++++++
 .../apache/commons/io/output/ClosedWriterTest.java |  58 ++++++++++
 .../apache/commons/io/output/TaggedWriterTest.java | 126 +++++++++++++++++++++
 6 files changed, 539 insertions(+)

diff --git a/src/main/java/org/apache/commons/io/output/BrokenWriter.java b/src/main/java/org/apache/commons/io/output/BrokenWriter.java
new file mode 100644
index 0000000..15737e1
--- /dev/null
+++ b/src/main/java/org/apache/commons/io/output/BrokenWriter.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.io.output;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * Broken writer. This writer always throws an {@link IOException} from
+ * all {@link Writer} methods.
+ * <p>
+ * This class is mostly useful for testing error handling in code that uses a
+ * writer.
+ *
+ * @since 2.0
+ */
+public class BrokenWriter extends Writer {
+
+    /**
+     * The exception that is thrown by all methods of this class.
+     */
+    private final IOException exception;
+
+    /**
+     * Creates a new writer that always throws the given exception.
+     *
+     * @param exception the exception to be thrown
+     */
+    public BrokenWriter(final IOException exception) {
+        this.exception = exception;
+    }
+
+    /**
+     * Creates a new writer that always throws an {@link IOException}
+     */
+    public BrokenWriter() {
+        this(new IOException("Broken writer"));
+    }
+
+    /**
+     * Throws the configured exception.
+     *
+     * @param cbuf ignored
+     * @param off ignored
+     * @param len ignored
+     * @throws IOException always thrown
+     */
+    @Override
+    public void write(final char[] cbuf, final int off, final int len) throws IOException {
+        throw exception;
+    }
+
+    /**
+     * Throws the configured exception.
+     *
+     * @throws IOException always thrown
+     */
+    @Override
+    public void flush() throws IOException {
+        throw exception;
+    }
+
+    /**
+     * Throws the configured exception.
+     *
+     * @throws IOException always thrown
+     */
+    @Override
+    public void close() throws IOException {
+        throw exception;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/io/output/ClosedWriter.java b/src/main/java/org/apache/commons/io/output/ClosedWriter.java
new file mode 100644
index 0000000..a596c12
--- /dev/null
+++ b/src/main/java/org/apache/commons/io/output/ClosedWriter.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.io.output;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * Closed writer. This writer throws an exception on all attempts to
+ * write something to it.
+ * <p>
+ * Typically uses of this class include testing for corner cases in methods
+ * that accept a writer and acting as a sentinel value instead of
+ * a {@code null} writer.
+ *
+ * @since 2.7
+ */
+public class ClosedWriter extends Writer {
+
+    /**
+     * A singleton.
+     */
+    public static final ClosedWriter CLOSED_WRITER = new ClosedWriter();
+
+    /**
+     * Throws an {@link IOException} to indicate that the writer is closed.
+     *
+     * @param cbuf ignored
+     * @param off ignored
+     * @param len ignored
+     * @throws IOException always thrown
+     */
+    @Override
+    public void write(final char[] cbuf, final int off, final int len) throws IOException {
+        throw new IOException("write(" + new String(cbuf) + ", " + off + ", " + len + ") failed: stream is closed");
+    }
+
+    /**
+     * Throws an {@link IOException} to indicate that the stream is closed.
+     *
+     * @throws IOException always thrown
+     */
+    @Override
+    public void flush() throws IOException {
+        throw new IOException("flush() failed: stream is closed");
+    }
+
+    @Override
+    public void close() throws IOException {
+    }
+}
diff --git a/src/main/java/org/apache/commons/io/output/TaggedWriter.java b/src/main/java/org/apache/commons/io/output/TaggedWriter.java
new file mode 100644
index 0000000..c086d26
--- /dev/null
+++ b/src/main/java/org/apache/commons/io/output/TaggedWriter.java
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.io.output;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.io.Writer;
+import java.util.UUID;
+
+import org.apache.commons.io.TaggedIOException;
+
+/**
+ * A writer decorator that tags potential exceptions so that the
+ * reader that caused the exception can easily be identified. This is
+ * done by using the {@link TaggedIOException} class to wrap all thrown
+ * {@link IOException}s. See below for an example of using this class.
+ * <pre>
+ * TaggedReader reader = new TaggedReader(...);
+ * try {
+ *     // Processing that may throw an IOException either from this reader
+ *     // or from some other IO activity like temporary files, etc.
+ *     writeToWriter(writer);
+ * } catch (IOException e) {
+ *     if (writer.isCauseOf(e)) {
+ *         // The exception was caused by this writer.
+ *         // Use e.getCause() to get the original exception.
+ *     } else {
+ *         // The exception was caused by something else.
+ *     }
+ * }
+ * </pre>
+ * <p>
+ * Alternatively, the {@link #throwIfCauseOf(Exception)} method can be
+ * used to let higher levels of code handle the exception caused by this
+ * writer while other processing errors are being taken care of at this
+ * lower level.
+ * <pre>
+ * TaggedWriter writer = new TaggedWriter(...);
+ * try {
+ *     writeToWriter(writer);
+ * } catch (IOException e) {
+ *     writer.throwIfCauseOf(e);
+ *     // ... or process the exception that was caused by something else
+ * }
+ * </pre>
+ *
+ * @see TaggedIOException
+ * @since 2.0
+ */
+public class TaggedWriter extends ProxyWriter {
+
+    /**
+     * The unique tag associated with exceptions from writer.
+     */
+    private final Serializable tag = UUID.randomUUID();
+
+    /**
+     * Creates a tagging decorator for the given writer.
+     *
+     * @param proxy writer to be decorated
+     */
+    public TaggedWriter(final Writer proxy) {
+        super(proxy);
+    }
+
+    /**
+     * Tests if the given exception was caused by this writer.
+     *
+     * @param exception an exception
+     * @return {@code true} if the exception was thrown by this writer,
+     *         {@code false} otherwise
+     */
+    public boolean isCauseOf(final Exception exception) {
+        return TaggedIOException.isTaggedWith(exception, tag);
+    }
+
+    /**
+     * Re-throws the original exception thrown by this writer. This method
+     * first checks whether the given exception is a {@link TaggedIOException}
+     * wrapper created by this decorator, and then unwraps and throws the
+     * original wrapped exception. Returns normally if the exception was
+     * not thrown by this writer.
+     *
+     * @param exception an exception
+     * @throws IOException original exception, if any, thrown by this writer
+     */
+    public void throwIfCauseOf(final Exception exception) throws IOException {
+        TaggedIOException.throwCauseIfTaggedWith(exception, tag);
+    }
+
+    /**
+     * Tags any IOExceptions thrown, wrapping and re-throwing.
+     *
+     * @param e The IOException thrown
+     * @throws IOException if an I/O error occurs
+     */
+    @Override
+    protected void handleIOException(final IOException e) throws IOException {
+        throw new TaggedIOException(e, tag);
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/io/output/BrokenWriterTest.java b/src/test/java/org/apache/commons/io/output/BrokenWriterTest.java
new file mode 100644
index 0000000..a6d978a
--- /dev/null
+++ b/src/test/java/org/apache/commons/io/output/BrokenWriterTest.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.io.output;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * JUnit Test Case for {@link BrokenWriter}.
+ */
+public class BrokenWriterTest {
+
+    private IOException exception;
+
+    private Writer writer;
+
+    @Before
+    public void setUp() {
+        exception = new IOException("test exception");
+        writer = new BrokenWriter(exception);
+    }
+
+    @Test
+    public void testWrite() {
+        try {
+            writer.write(1);
+            fail("Expected exception not thrown.");
+        } catch (final IOException e) {
+            assertEquals(exception, e);
+        }
+
+        try {
+            writer.write(new char[1]);
+            fail("Expected exception not thrown.");
+        } catch (final IOException e) {
+            assertEquals(exception, e);
+        }
+
+        try {
+            writer.write(new char[1], 0, 1);
+            fail("Expected exception not thrown.");
+        } catch (final IOException e) {
+            assertEquals(exception, e);
+        }
+    }
+
+    @Test
+    public void testFlush() {
+        try {
+            writer.flush();
+            fail("Expected exception not thrown.");
+        } catch (final IOException e) {
+            assertEquals(exception, e);
+        }
+    }
+
+    @Test
+    public void testClose() {
+        try {
+            writer.close();
+            fail("Expected exception not thrown.");
+        } catch (final IOException e) {
+            assertEquals(exception, e);
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/io/output/ClosedWriterTest.java b/src/test/java/org/apache/commons/io/output/ClosedWriterTest.java
new file mode 100644
index 0000000..2089307
--- /dev/null
+++ b/src/test/java/org/apache/commons/io/output/ClosedWriterTest.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.io.output;
+
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+
+import org.junit.Test;
+
+/**
+ * JUnit Test Case for {@link ClosedWriter}.
+ */
+public class ClosedWriterTest {
+
+    /**
+     * Test the <code>write(cbuf, off, len)</code> method.
+     * @throws Exception
+     */
+    @Test
+    public void testWrite() throws Exception {
+        try (ClosedWriter cw = new ClosedWriter()) {
+            cw.write(new char[0], 0, 0);
+            fail("write(cbuf, off, len)");
+        } catch (final IOException e) {
+            // expected
+        }
+    }
+
+    /**
+     * Test the <code>flush()</code> method.
+     * @throws Exception
+     */
+    @Test
+    public void testFlush() throws Exception {
+        try (ClosedWriter cw = new ClosedWriter()) {
+            cw.flush();
+            fail("flush()");
+        } catch (final IOException e) {
+            // expected
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/io/output/TaggedWriterTest.java b/src/test/java/org/apache/commons/io/output/TaggedWriterTest.java
new file mode 100644
index 0000000..13340ff
--- /dev/null
+++ b/src/test/java/org/apache/commons/io/output/TaggedWriterTest.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.io.output;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.UUID;
+
+import org.apache.commons.io.TaggedIOException;
+import org.junit.Test;
+
+/**
+ * JUnit Test Case for {@link TaggedWriter}.
+ */
+public class TaggedWriterTest  {
+
+    @Test
+    public void testNormalWriter() {
+        try (final StringBuilderWriter buffer = new StringBuilderWriter()) {
+            try (final Writer writer = new TaggedWriter(buffer)) {
+                writer.write('a');
+                writer.write(new char[] { 'b' });
+                writer.write(new char[] { 'c' }, 0, 1);
+                writer.flush();
+            }
+            assertEquals(3, buffer.getBuilder().length());
+            assertEquals('a', buffer.getBuilder().charAt(0));
+            assertEquals('b', buffer.getBuilder().charAt(1));
+            assertEquals('c', buffer.getBuilder().charAt(2));
+        } catch (final IOException e) {
+            fail("Unexpected exception thrown");
+        }
+    }
+
+    @Test
+    public void testBrokenWriter() {
+        final IOException exception = new IOException("test exception");
+        final TaggedWriter writer =
+            new TaggedWriter(new BrokenWriter(exception));
+
+        // Test the write() method
+        try {
+            writer.write(new char[] { 'x' }, 0, 1);
+            fail("Expected exception not thrown.");
+        } catch (final IOException e) {
+            assertTrue(writer.isCauseOf(e));
+            try {
+                writer.throwIfCauseOf(e);
+                fail("Expected exception not thrown.");
+            } catch (final IOException e2) {
+                assertEquals(exception, e2);
+            }
+        }
+
+        // Test the flush() method
+        try {
+            writer.flush();
+            fail("Expected exception not thrown.");
+        } catch (final IOException e) {
+            assertTrue(writer.isCauseOf(e));
+            try {
+                writer.throwIfCauseOf(e);
+                fail("Expected exception not thrown.");
+            } catch (final IOException e2) {
+                assertEquals(exception, e2);
+            }
+        }
+
+        // Test the close() method
+        try {
+            writer.close();
+            fail("Expected exception not thrown.");
+        } catch (final IOException e) {
+            assertTrue(writer.isCauseOf(e));
+            try {
+                writer.throwIfCauseOf(e);
+                fail("Expected exception not thrown.");
+            } catch (final IOException e2) {
+                assertEquals(exception, e2);
+            }
+        }
+    }
+
+    @Test
+    public void testOtherException() throws Exception {
+        final IOException exception = new IOException("test exception");
+        try (final Writer closed = new ClosedWriter();
+                final TaggedWriter writer = new TaggedWriter(closed)) {
+
+            assertFalse(writer.isCauseOf(exception));
+            assertFalse(writer.isCauseOf(new TaggedIOException(exception, UUID.randomUUID())));
+
+            try {
+                writer.throwIfCauseOf(exception);
+            } catch (final IOException e) {
+                fail("Unexpected exception thrown");
+            }
+
+            try {
+                writer.throwIfCauseOf(new TaggedIOException(exception, UUID.randomUUID()));
+            } catch (final IOException e) {
+                fail("Unexpected exception thrown");
+            }
+        }
+    }
+
+}