Added TaggedWriter, ClosedWriter and BrokenWriter.
ADD
src/main/java/org/apache/commons/io/output/BrokenWriter.java
+/*
+ * 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;
+ }
+
+}

ADD
src/main/java/org/apache/commons/io/output/ClosedWriter.java
+/*
+ * 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 {
+ }
+}

ADD
src/main/java/org/apache/commons/io/output/TaggedWriter.java
+/*
+ * 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);
+ }
+
+}

ADD
src/test/java/org/apache/commons/io/output/BrokenWriterTest.java
+/*
+ * 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);
+ }
+ }
+
+}

ADD
src/test/java/org/apache/commons/io/output/ClosedWriterTest.java
+/*
+ * 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
+ }
+ }
+
+}

ADD
src/test/java/org/apache/commons/io/output/TaggedWriterTest.java
+/*
+ * 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");
+ }
+ }
+ }
+
+}

Added TaggedWriter, ClosedWriter and BrokenWriter. (#86)