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 3615e14 Added TaggedReader, ClosedReader and BrokenReader. (#85) 3615e14 is described below commit 3615e14865491b792e64619a1584c2b026232253 Author: Rob Spoor <robti...@users.noreply.github.com> AuthorDate: Fri Aug 9 20:03:20 2019 +0200 Added TaggedReader, ClosedReader and BrokenReader. (#85) * Added TaggedReader, ClosedReader and BrokenReader. * Fixed the since version of TaggedReader * Marked parameters as final. * Replaced an incorrect tab with spaces. --- .../org/apache/commons/io/input/BrokenReader.java | 122 ++++++++++++++++++++ .../org/apache/commons/io/input/TaggedReader.java | 116 +++++++++++++++++++ .../apache/commons/io/input/BrokenReaderTest.java | 118 +++++++++++++++++++ .../apache/commons/io/input/TaggedReaderTest.java | 127 +++++++++++++++++++++ 4 files changed, 483 insertions(+) diff --git a/src/main/java/org/apache/commons/io/input/BrokenReader.java b/src/main/java/org/apache/commons/io/input/BrokenReader.java new file mode 100644 index 0000000..2b7c42e --- /dev/null +++ b/src/main/java/org/apache/commons/io/input/BrokenReader.java @@ -0,0 +1,122 @@ +/* + * 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.input; + +import java.io.IOException; +import java.io.Reader; + +/** + * Broken reader. This reader always throws an {@link IOException} from + * all the {@link Reader} methods where the exception is declared. + * <p> + * This class is mostly useful for testing error handling in code that uses a + * reader. + * + * @since 2.7 + */ +public class BrokenReader extends Reader { + + /** + * The exception that is thrown by all methods of this class. + */ + private final IOException exception; + + /** + * Creates a new reader that always throws the given exception. + * + * @param exception the exception to be thrown + */ + public BrokenReader(final IOException exception) { + this.exception = exception; + } + + /** + * Creates a new reader that always throws an {@link IOException} + */ + public BrokenReader() { + this(new IOException("Broken reader")); + } + + /** + * Throws the configured exception. + * + * @param cbuf ignored + * @param off ignored + * @param len ignored + * @return nothing + * @throws IOException always thrown + */ + @Override + public int read(final char[] cbuf, final int off, final int len) throws IOException { + throw exception; + } + + /** + * Throws the configured exception. + * + * @param n ignored + * @return nothing + * @throws IOException always thrown + */ + @Override + public long skip(final long n) throws IOException { + throw exception; + } + + /** + * Throws the configured exception. + * + * @return nothing + * @throws IOException always thrown + */ + @Override + public boolean ready() throws IOException { + throw exception; + } + + /** + * Throws the configured exception. + * + * @param readAheadLimit ignored + * @throws IOException always thrown + */ + @Override + public void mark(final int readAheadLimit) throws IOException { + throw exception; + } + + /** + * Throws the configured exception. + * + * @throws IOException always thrown + */ + @Override + public synchronized void reset() 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/input/TaggedReader.java b/src/main/java/org/apache/commons/io/input/TaggedReader.java new file mode 100644 index 0000000..b771792 --- /dev/null +++ b/src/main/java/org/apache/commons/io/input/TaggedReader.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.input; + +import java.io.IOException; +import java.io.Reader; +import java.io.Serializable; +import java.util.UUID; + +import org.apache.commons.io.TaggedIOException; + +/** + * A reader 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. + * processReader(reader); + * } catch (IOException e) { + * if (reader.isCauseOf(e)) { + * // The exception was caused by this reader. + * // Use e.getCause() to get the original exception. + * } else { + * // The exception was caused by something else. + * } + * } + * </pre> + * <p> + * Alternatively, the {@link #throwIfCauseOf(Throwable)} method can be + * used to let higher levels of code handle the exception caused by this + * reader while other processing errors are being taken care of at this + * lower level. + * <pre> + * TaggedReader reader = new TaggedReader(...); + * try { + * processReader(reader); + * } catch (IOException e) { + * reader.throwIfCauseOf(e); + * // ... or process the exception that was caused by something else + * } + * </pre> + * + * @see TaggedIOException + * @since 2.7 + */ +public class TaggedReader extends ProxyReader { + + /** + * The unique tag associated with exceptions from reader. + */ + private final Serializable tag = UUID.randomUUID(); + + /** + * Creates a tagging decorator for the given reader. + * + * @param proxy reader to be decorated + */ + public TaggedReader(final Reader proxy) { + super(proxy); + } + + /** + * Tests if the given exception was caused by this reader. + * + * @param exception an exception + * @return {@code true} if the exception was thrown by this reader, + * {@code false} otherwise + */ + public boolean isCauseOf(final Throwable exception) { + return TaggedIOException.isTaggedWith(exception, tag); + } + + /** + * Re-throws the original exception thrown by this reader. 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 reader. + * + * @param throwable an exception + * @throws IOException original exception, if any, thrown by this reader + */ + public void throwIfCauseOf(final Throwable throwable) throws IOException { + TaggedIOException.throwCauseIfTaggedWith(throwable, 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/input/BrokenReaderTest.java b/src/test/java/org/apache/commons/io/input/BrokenReaderTest.java new file mode 100644 index 0000000..c61b309 --- /dev/null +++ b/src/test/java/org/apache/commons/io/input/BrokenReaderTest.java @@ -0,0 +1,118 @@ +/* + * 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.input; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.io.Reader; + +import org.junit.Before; +import org.junit.Test; + +/** + * JUnit Test Case for {@link BrokenReader}. + */ +@SuppressWarnings("ResultOfMethodCallIgnored") +public class BrokenReaderTest { + + private IOException exception; + + private Reader reader; + + @Before + public void setUp() { + exception = new IOException("test exception"); + reader = new BrokenReader(exception); + } + + @Test + public void testRead() { + try { + reader.read(); + fail("Expected exception not thrown."); + } catch (final IOException e) { + assertEquals(exception, e); + } + + try { + reader.read(new char[1]); + fail("Expected exception not thrown."); + } catch (final IOException e) { + assertEquals(exception, e); + } + + try { + reader.read(new char[1], 0, 1); + fail("Expected exception not thrown."); + } catch (final IOException e) { + assertEquals(exception, e); + } + } + + @Test + public void testSkip() { + try { + reader.skip(1); + fail("Expected exception not thrown."); + } catch (final IOException e) { + assertEquals(exception, e); + } + } + + @Test + public void testReady() { + try { + reader.ready(); + fail("Expected exception not thrown."); + } catch (final IOException e) { + assertEquals(exception, e); + } + } + + @Test + public void testMark() { + try { + reader.mark(1); + fail("Expected exception not thrown."); + } catch (final IOException e) { + assertEquals(exception, e); + } + } + + @Test + public void testReset() { + try { + reader.reset(); + fail("Expected exception not thrown."); + } catch (final IOException e) { + assertEquals(exception, e); + } + } + + @Test + public void testClose() { + try { + reader.close(); + fail("Expected exception not thrown."); + } catch (final IOException e) { + assertEquals(exception, e); + } + } + +} diff --git a/src/test/java/org/apache/commons/io/input/TaggedReaderTest.java b/src/test/java/org/apache/commons/io/input/TaggedReaderTest.java new file mode 100644 index 0000000..6be26c8 --- /dev/null +++ b/src/test/java/org/apache/commons/io/input/TaggedReaderTest.java @@ -0,0 +1,127 @@ +/* + * 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.input; + +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.Reader; +import java.io.StringReader; +import java.util.UUID; + +import org.apache.commons.io.TaggedIOException; +import org.junit.Test; + +/** + * JUnit Test Case for {@link TaggedReader}. + */ +public class TaggedReaderTest { + + @Test + public void testEmptyReader() throws IOException { + final Reader reader = new TaggedReader(new ClosedReader()); + assertFalse(reader.ready()); + assertEquals(-1, reader.read()); + assertEquals(-1, reader.read(new char[1])); + assertEquals(-1, reader.read(new char[1], 0, 1)); + reader.close(); + } + + @Test + public void testNormalReader() throws IOException { + final Reader reader = new TaggedReader(new StringReader("abc")); + assertTrue(reader.ready()); + assertEquals('a', reader.read()); + final char[] buffer = new char[1]; + assertEquals(1, reader.read(buffer)); + assertEquals('b', buffer[0]); + assertEquals(1, reader.read(buffer, 0, 1)); + assertEquals('c', buffer[0]); + assertEquals(-1, reader.read()); + reader.close(); + } + + @Test + public void testBrokenReader() { + final IOException exception = new IOException("test exception"); + final TaggedReader reader = + new TaggedReader(new BrokenReader(exception)); + + // Test the ready() method + try { + reader.ready(); + fail("Expected exception not thrown."); + } catch (final IOException e) { + assertTrue(reader.isCauseOf(e)); + try { + reader.throwIfCauseOf(e); + fail("Expected exception not thrown."); + } catch (final IOException e2) { + assertEquals(exception, e2); + } + } + + // Test the read() method + try { + reader.read(); + fail("Expected exception not thrown."); + } catch (final IOException e) { + assertTrue(reader.isCauseOf(e)); + try { + reader.throwIfCauseOf(e); + fail("Expected exception not thrown."); + } catch (final IOException e2) { + assertEquals(exception, e2); + } + } + + // Test the close() method + try { + reader.close(); + fail("Expected exception not thrown."); + } catch (final IOException e) { + assertTrue(reader.isCauseOf(e)); + try { + reader.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"); + final Reader closed = new ClosedReader(); + final TaggedReader reader = new TaggedReader(closed); + + assertFalse(reader.isCauseOf(exception)); + assertFalse(reader.isCauseOf( + new TaggedIOException(exception, UUID.randomUUID()))); + + reader.throwIfCauseOf(exception); + + reader.throwIfCauseOf( + new TaggedIOException(exception, UUID.randomUUID())); + reader.close(); + } + +}