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/09 14:36:44 UTC
[commons-io] branch master updated: [IO-615] Add classes TeeWriter,
FilterCollectionWriter, ProxyCollectionWriter, IOExceptionList,
IOIndexedException.
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 1ee660d [IO-615] Add classes TeeWriter, FilterCollectionWriter, ProxyCollectionWriter, IOExceptionList, IOIndexedException.
1ee660d is described below
commit 1ee660d9f1f2af298b7ebe49ddf8191592267aca
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Fri Aug 9 10:36:39 2019 -0400
[IO-615] Add classes TeeWriter, FilterCollectionWriter,
ProxyCollectionWriter, IOExceptionList, IOIndexedException.
Closes #88.
---
src/changes/changes.xml | 3 +
.../org/apache/commons/io/IOExceptionList.java | 89 ++++
.../org/apache/commons/io/IOIndexedException.java | 60 +++
.../commons/io/output/FilterCollectionWriter.java | 302 ++++++++++++++
.../commons/io/output/ProxyCollectionWriter.java | 280 +++++++++++++
.../org/apache/commons/io/output/TeeWriter.java | 51 +++
.../apache/commons/io/IOExceptionListTestCase.java | 58 +++
.../commons/io/IOIndexedExceptionTestCase.java | 48 +++
.../io/output/ProxyCollectionWriterTest.java | 449 +++++++++++++++++++++
.../apache/commons/io/output/TeeWriterTest.java | 449 +++++++++++++++++++++
10 files changed, 1789 insertions(+)
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 9d627c1..888aeb5 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -119,6 +119,9 @@ The <action> type attribute can be add,update,fix,remove.
<action issue="IO-614" dev="ggregory" type="add" due-to="Rob Spoor">
Add classes TaggedWriter, ClosedWriter and BrokenWriter. #86.
</action>
+ <action issue="IO-615" dev="ggregory" type="add" due-to="Gary Gregory, Rob Spoor">
+ Add classes TeeWriter, FilterCollectionWriter, ProxyCollectionWriter, IOExceptionList, IOIndexedException.
+ </action>
</release>
<release version="2.6" date="2017-10-15" description="Java 7 required, Java 9 supported.">
diff --git a/src/main/java/org/apache/commons/io/IOExceptionList.java b/src/main/java/org/apache/commons/io/IOExceptionList.java
new file mode 100644
index 0000000..f013f68
--- /dev/null
+++ b/src/main/java/org/apache/commons/io/IOExceptionList.java
@@ -0,0 +1,89 @@
+/*
+ * 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;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A IOException based on a list of Throwable causes.
+ * <p>
+ * The first exception in the list is used as this exception's cause and is accessible with the usual
+ * {@link #getCause()} while the complete list is accessible with {@link #getCauseList()}.
+ * </p>
+ *
+ * @since 2.7
+ */
+public class IOExceptionList extends IOException {
+
+ private static final long serialVersionUID = 1L;
+ private final List<? extends Throwable> causeList;
+
+ /**
+ * Creates a new exception caused by a list of exceptions.
+ *
+ * @param causeList a list of cause exceptions.
+ */
+ public IOExceptionList(List<? extends Throwable> causeList) {
+ super(String.format("%,d exceptions: %s", causeList == null ? 0 : causeList.size(), causeList),
+ causeList == null ? null : causeList.get(0));
+ this.causeList = causeList == null ? Collections.emptyList() : causeList;
+ }
+
+ /**
+ * Gets the cause list.
+ *
+ * @return The list of causes.
+ */
+ public <T extends Throwable> List<T> getCauseList() {
+ return (List<T>) causeList;
+ }
+
+ /**
+ * Gets the cause list.
+ *
+ * @param index index in the cause list.
+ * @return The list of causes.
+ */
+ public <T extends Throwable> T getCause(final int index) {
+ return (T) causeList.get(index);
+ }
+
+ /**
+ * Gets the cause list.
+ *
+ * @param index index in the cause list.
+ * @return The list of causes.
+ */
+ public <T extends Throwable> T getCause(final int index, Class<T> clazz) {
+ return (T) causeList.get(index);
+ }
+
+ /**
+ * Works around Throwable and Generics, may fail at runtime depending on the argument value.
+ *
+ * @param <T> the target type
+ * @param clazz the target type
+ * @return The list of causes.
+ */
+ public <T extends Throwable> List<T> getCauseList(Class<T> clazz) {
+ return (List<T>) causeList;
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/io/IOIndexedException.java b/src/main/java/org/apache/commons/io/IOIndexedException.java
new file mode 100644
index 0000000..635c9ad
--- /dev/null
+++ b/src/main/java/org/apache/commons/io/IOIndexedException.java
@@ -0,0 +1,60 @@
+/*
+ * 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;
+
+import java.io.IOException;
+
+/**
+ * A IOException associated with a source index.
+ *
+ * @since 2.7
+ */
+public class IOIndexedException extends IOException {
+
+ private static final long serialVersionUID = 1L;
+ private final int index;
+
+ /**
+ * Creates a new exception.
+ *
+ * @param index index of this exception.
+ * @param cause cause exceptions.
+ */
+ public IOIndexedException(final int index, final Throwable cause) {
+ super(toMessage(index, cause), cause);
+ this.index = index;
+ }
+
+ protected static String toMessage(final int index, final Throwable cause) {
+ // Letting index be any int
+ final String unspecified = "Null";
+ final String name = cause == null ? unspecified : cause.getClass().getSimpleName();
+ final String msg = cause == null ? unspecified : cause.getMessage();
+ return String.format("%s #%,d: %s", name, index, msg);
+ }
+
+ /**
+ * The index of this exception.
+ *
+ * @return index of this exception.
+ */
+ public int getIndex() {
+ return index;
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/io/output/FilterCollectionWriter.java b/src/main/java/org/apache/commons/io/output/FilterCollectionWriter.java
new file mode 100644
index 0000000..ed9114c
--- /dev/null
+++ b/src/main/java/org/apache/commons/io/output/FilterCollectionWriter.java
@@ -0,0 +1,302 @@
+/*
+ * 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.FilterWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.commons.io.IOExceptionList;
+import org.apache.commons.io.IOIndexedException;
+
+/**
+ * Abstract class for writing filtered character streams to a {@link Collection} of writers. This is in contrast to
+ * {@link FilterWriter} which is backed by a single {@link Writer}.
+ * <p>
+ * This abstract class provides default methods that pass all requests to the contained writers. Subclasses should
+ * likely override some of these methods.
+ * </p>
+ * <p>
+ * The class {@link Writer} defines method signatures with {@code throws} {@link IOException}, which in this class are
+ * actually {@link IOExceptionList} containing a list of {@link IOIndexedException}.
+ * </p>
+ *
+ * @since 2.7
+ */
+public class FilterCollectionWriter extends Writer {
+
+ /**
+ * Empty and immutable collection of writers.
+ */
+ protected final Collection<Writer> EMPTY_WRITERS = Collections.emptyList();
+
+ /**
+ * The underlying writers.
+ */
+ protected final Collection<Writer> writers;
+
+ /**
+ * Creates a new filtered collection writer.
+ *
+ * @param writers Writers to provide the underlying targets.
+ */
+ protected FilterCollectionWriter(final Collection<Writer> writers) {
+ this.writers = writers == null ? EMPTY_WRITERS : writers;
+ }
+
+ /**
+ * Creates a new filtered collection writer.
+ *
+ * @param writers Writers to provide the underlying targets.
+ */
+ protected FilterCollectionWriter(final Writer... writers) {
+ this.writers = writers == null ? EMPTY_WRITERS : Arrays.asList(writers);
+ }
+
+ @Override
+ public Writer append(final char c) throws IOException {
+ final List<Exception> causeList = new ArrayList<>();
+ int i = 0;
+ for (final Writer w : writers) {
+ if (w != null) {
+ try {
+ w.append(c);
+ } catch (final IOException e) {
+ causeList.add(new IOIndexedException(i, e));
+ }
+ }
+ i++;
+ }
+ if (!causeList.isEmpty()) {
+ throw new IOExceptionList(causeList);
+ }
+ return this;
+ }
+
+ @Override
+ public Writer append(final CharSequence csq) throws IOException {
+ final List<Exception> causeList = new ArrayList<>();
+ int i = 0;
+ for (final Writer w : writers) {
+ if (w != null) {
+ try {
+ w.append(csq);
+ } catch (final IOException e) {
+ causeList.add(new IOIndexedException(i, e));
+ }
+ }
+ i++;
+ }
+ if (!causeList.isEmpty()) {
+ throw new IOExceptionList(causeList);
+ }
+ return this;
+ }
+
+ @Override
+ public Writer append(final CharSequence csq, final int start, final int end) throws IOException {
+
+ final List<Exception> causeList = new ArrayList<>();
+ int i = 0;
+ for (final Writer w : writers) {
+ if (w != null) {
+ try {
+ w.append(csq, start, end);
+ } catch (final IOException e) {
+ causeList.add(new IOIndexedException(i, e));
+ }
+ }
+ i++;
+ }
+ if (!causeList.isEmpty()) {
+ throw new IOExceptionList(causeList);
+ }
+ return this;
+ }
+
+ @Override
+ public void close() throws IOException {
+ final List<Exception> causeList = new ArrayList<>();
+ int i = 0;
+ for (final Writer w : writers) {
+ if (w != null) {
+ try {
+ w.close();
+ } catch (final IOException e) {
+ causeList.add(new IOIndexedException(i, e));
+ }
+ }
+ i++;
+ }
+ if (!causeList.isEmpty()) {
+ throw new IOExceptionList(causeList);
+ }
+
+ }
+
+ /**
+ * Flushes the stream.
+ *
+ * @exception IOException If an I/O error occurs
+ */
+ @Override
+ public void flush() throws IOException {
+ final List<Exception> causeList = new ArrayList<>();
+ int i = 0;
+ for (final Writer w : writers) {
+ if (w != null) {
+ try {
+ w.flush();
+ } catch (final IOException e) {
+ causeList.add(new IOIndexedException(i, e));
+ }
+ }
+ i++;
+ }
+ if (!causeList.isEmpty()) {
+ throw new IOExceptionList(causeList);
+ }
+
+ }
+
+ /**
+ * Writes a portion of an array of characters.
+ *
+ * @param cbuf Buffer of characters to be written
+ * @param off Offset from which to start reading characters
+ * @param len Number of characters to be written
+ *
+ * @exception IOException If an I/O error occurs
+ */
+ @Override
+ public void write(final char cbuf[], final int off, final int len) throws IOException {
+ final List<Exception> causeList = new ArrayList<>();
+ int i = 0;
+ for (final Writer w : writers) {
+ if (w != null) {
+ try {
+ w.write(cbuf, off, len);
+ } catch (final IOException e) {
+ causeList.add(new IOIndexedException(i, e));
+ }
+ }
+ i++;
+ }
+ if (!causeList.isEmpty()) {
+ throw new IOExceptionList(causeList);
+ }
+ }
+
+ @Override
+ public void write(final char[] cbuf) throws IOException {
+ final List<Exception> causeList = new ArrayList<>();
+ int i = 0;
+ for (final Writer w : writers) {
+ if (w != null) {
+ try {
+ w.write(cbuf);
+ } catch (final IOException e) {
+ causeList.add(new IOIndexedException(i, e));
+ }
+ }
+ i++;
+ }
+ if (!causeList.isEmpty()) {
+ throw new IOExceptionList(causeList);
+ }
+ }
+
+ /**
+ * Writes a single character.
+ *
+ * @exception IOException If an I/O error occurs
+ */
+ @Override
+ public void write(final int c) throws IOException {
+ final List<Exception> causeList = new ArrayList<>();
+ int i = 0;
+ for (final Writer w : writers) {
+ if (w != null) {
+ try {
+ w.write(c);
+ } catch (final IOException e) {
+ causeList.add(new IOIndexedException(i, e));
+ }
+ }
+ i++;
+ }
+ if (!causeList.isEmpty()) {
+ throw new IOExceptionList(causeList);
+ }
+ }
+
+ @Override
+ public void write(final String str) throws IOException {
+ final List<Exception> causeList = new ArrayList<>();
+ int i = 0;
+ for (final Writer w : writers) {
+ if (w != null) {
+ try {
+ w.write(str);
+ } catch (final IOException e) {
+ causeList.add(new IOIndexedException(i, e));
+ }
+ }
+ i++;
+ }
+ if (!causeList.isEmpty()) {
+ throw new IOExceptionList(causeList);
+ }
+
+ }
+
+ /**
+ * Writes a portion of a string.
+ *
+ * @param str String to be written
+ * @param off Offset from which to start reading characters
+ * @param len Number of characters to be written
+ *
+ * @exception IOException If an I/O error occurs
+ */
+ @Override
+ public void write(final String str, final int off, final int len) throws IOException {
+ final List<Exception> causeList = new ArrayList<>();
+ int i = 0;
+ for (final Writer w : writers) {
+ if (w != null) {
+ try {
+ w.write(str, off, len);
+ } catch (final IOException e) {
+ causeList.add(new IOIndexedException(i, e));
+ }
+ }
+ i++;
+ }
+ if (!causeList.isEmpty()) {
+ throw new IOExceptionList(causeList);
+ }
+
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/io/output/ProxyCollectionWriter.java b/src/main/java/org/apache/commons/io/output/ProxyCollectionWriter.java
new file mode 100644
index 0000000..b5fdcfb
--- /dev/null
+++ b/src/main/java/org/apache/commons/io/output/ProxyCollectionWriter.java
@@ -0,0 +1,280 @@
+/*
+ * 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.FilterWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Collection;
+
+import org.apache.commons.io.IOUtils;
+
+/**
+ * A Proxy stream collection which acts as expected, that is it passes the method calls on to the proxied streams and
+ * doesn't change which methods are being called. It is an alternative base class to {@link FilterWriter} and
+ * {@link FilterCollectionWriter} to increase reusability, because FilterWriter changes the methods being called, such
+ * as {@code write(char[])} to {@code write(char[], int, int)} and {@code write(String)} to
+ * {@code write(String, int, int)}. This is in contrast to {@link ProxyWriter} which is backed by a single
+ * {@link Writer}.
+ *
+ * @since 2.7
+ */
+public class ProxyCollectionWriter extends FilterCollectionWriter {
+
+ /**
+ * Creates a new proxy collection writer.
+ *
+ * @param writers Writers object to provide the underlying targets.
+ */
+ public ProxyCollectionWriter(final Collection<Writer> writers) {
+ super(writers);
+ }
+
+ /**
+ * Creates a new proxy collection writer.
+ *
+ * @param writers Writers to provide the underlying targets.
+ */
+ public ProxyCollectionWriter(final Writer... writers) {
+ super(writers);
+ }
+
+ /**
+ * Invoked by the write methods after the proxied call has returned successfully. The number of chars written (1 for
+ * the {@link #write(int)} method, buffer length for {@link #write(char[])}, etc.) is given as an argument.
+ * <p>
+ * Subclasses can override this method to add common post-processing functionality without having to override all
+ * the write methods. The default implementation does nothing.
+ * </p>
+ *
+ * @param n number of chars written
+ * @throws IOException if the post-processing fails
+ */
+ protected void afterWrite(final int n) throws IOException {
+ // noop
+ }
+
+ /**
+ * Invokes the delegates' <code>append(char)</code> methods.
+ *
+ * @param c The character to write
+ * @return this writer
+ * @throws IOException if an I/O error occurs
+ * @since 2.0
+ */
+ @Override
+ public Writer append(final char c) throws IOException {
+ try {
+ beforeWrite(1);
+ super.append(c);
+ afterWrite(1);
+ } catch (final IOException e) {
+ handleIOException(e);
+ }
+ return this;
+ }
+
+ /**
+ * Invokes the delegates' <code>append(CharSequence)</code> methods.
+ *
+ * @param csq The character sequence to write
+ * @return this writer
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public Writer append(final CharSequence csq) throws IOException {
+ try {
+ final int len = IOUtils.length(csq);
+ beforeWrite(len);
+ super.append(csq);
+ afterWrite(len);
+ } catch (final IOException e) {
+ handleIOException(e);
+ }
+ return this;
+ }
+
+ /**
+ * Invokes the delegates' <code>append(CharSequence, int, int)</code> methods.
+ *
+ * @param csq The character sequence to write
+ * @param start The index of the first character to write
+ * @param end The index of the first character to write (exclusive)
+ * @return this writer
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public Writer append(final CharSequence csq, final int start, final int end) throws IOException {
+ try {
+ beforeWrite(end - start);
+ super.append(csq, start, end);
+ afterWrite(end - start);
+ } catch (final IOException e) {
+ handleIOException(e);
+ }
+ return this;
+ }
+
+ /**
+ * Invoked by the write methods before the call is proxied. The number of chars to be written (1 for the
+ * {@link #write(int)} method, buffer length for {@link #write(char[])}, etc.) is given as an argument.
+ * <p>
+ * Subclasses can override this method to add common pre-processing functionality without having to override all the
+ * write methods. The default implementation does nothing.
+ * </p>
+ *
+ * @param n number of chars to be written
+ * @throws IOException if the pre-processing fails
+ */
+ protected void beforeWrite(final int n) throws IOException {
+ // noop
+ }
+
+ /**
+ * Invokes the delegate's <code>close()</code> method.
+ *
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public void close() throws IOException {
+ try {
+ super.close();
+ } catch (final IOException e) {
+ handleIOException(e);
+ }
+ }
+
+ /**
+ * Invokes the delegate's <code>flush()</code> method.
+ *
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public void flush() throws IOException {
+ try {
+ super.flush();
+ } catch (final IOException e) {
+ handleIOException(e);
+ }
+ }
+
+ /**
+ * Handle any IOExceptions thrown.
+ * <p>
+ * This method provides a point to implement custom exception handling. The default behaviour is to re-throw the
+ * exception.
+ * </p>
+ *
+ * @param e The IOException thrown
+ * @throws IOException if an I/O error occurs
+ */
+ protected void handleIOException(final IOException e) throws IOException {
+ throw e;
+ }
+
+ /**
+ * Invokes the delegate's <code>write(char[])</code> method.
+ *
+ * @param cbuf the characters to write
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public void write(final char[] cbuf) throws IOException {
+ try {
+ final int len = IOUtils.length(cbuf);
+ beforeWrite(len);
+ super.write(cbuf);
+ afterWrite(len);
+ } catch (final IOException e) {
+ handleIOException(e);
+ }
+ }
+
+ /**
+ * Invokes the delegate's <code>write(char[], int, int)</code> method.
+ *
+ * @param cbuf the characters to write
+ * @param off The start offset
+ * @param len The number of characters to write
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public void write(final char[] cbuf, final int off, final int len) throws IOException {
+ try {
+ beforeWrite(len);
+ super.write(cbuf, off, len);
+ afterWrite(len);
+ } catch (final IOException e) {
+ handleIOException(e);
+ }
+ }
+
+ /**
+ * Invokes the delegate's <code>write(int)</code> method.
+ *
+ * @param c the character to write
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public void write(final int c) throws IOException {
+ try {
+ beforeWrite(1);
+ super.write(c);
+ afterWrite(1);
+ } catch (final IOException e) {
+ handleIOException(e);
+ }
+ }
+
+ /**
+ * Invokes the delegate's <code>write(String)</code> method.
+ *
+ * @param str the string to write
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public void write(final String str) throws IOException {
+ try {
+ final int len = IOUtils.length(str);
+ beforeWrite(len);
+ super.write(str);
+ afterWrite(len);
+ } catch (final IOException e) {
+ handleIOException(e);
+ }
+ }
+
+ /**
+ * Invokes the delegate's <code>write(String)</code> method.
+ *
+ * @param str the string to write
+ * @param off The start offset
+ * @param len The number of characters to write
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public void write(final String str, final int off, final int len) throws IOException {
+ try {
+ beforeWrite(len);
+ super.write(str, off, len);
+ afterWrite(len);
+ } catch (final IOException e) {
+ handleIOException(e);
+ }
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/io/output/TeeWriter.java b/src/main/java/org/apache/commons/io/output/TeeWriter.java
new file mode 100644
index 0000000..3fc0168
--- /dev/null
+++ b/src/main/java/org/apache/commons/io/output/TeeWriter.java
@@ -0,0 +1,51 @@
+/*
+ * 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.Writer;
+import java.util.Collection;
+
+/**
+ * Classic splitter of {@link Writer}. Named after the Unix 'tee' command. It allows a stream to be branched off so
+ * there are now two streams.
+ * <p>
+ * This currently a only convenience class with the proper name "TeeWriter".
+ * </p>
+ *
+ * @since 2.7
+ */
+public class TeeWriter extends ProxyCollectionWriter {
+
+ /**
+ * Creates a new filtered collection writer.
+ *
+ * @param writers Writers to provide the underlying targets.
+ */
+ public TeeWriter(final Collection<Writer> writers) {
+ super(writers);
+ }
+
+ /**
+ * Creates a new filtered collection writer.
+ *
+ * @param writers Writers to provide the underlying targets.
+ */
+ public TeeWriter(final Writer... writers) {
+ super(writers);
+ }
+}
diff --git a/src/test/java/org/apache/commons/io/IOExceptionListTestCase.java b/src/test/java/org/apache/commons/io/IOExceptionListTestCase.java
new file mode 100644
index 0000000..7289129
--- /dev/null
+++ b/src/test/java/org/apache/commons/io/IOExceptionListTestCase.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;
+
+import java.io.EOFException;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class IOExceptionListTestCase {
+
+ @Test
+ public void testCause() {
+ final EOFException cause = new EOFException();
+ final List<EOFException> list = Collections.singletonList(cause);
+ final IOExceptionList sqlExceptionList = new IOExceptionList(list);
+ Assert.assertEquals(cause, sqlExceptionList.getCause());
+ Assert.assertEquals(cause, sqlExceptionList.getCause(0));
+ Assert.assertEquals(list, sqlExceptionList.getCauseList());
+ Assert.assertEquals(list, sqlExceptionList.getCauseList(EOFException.class));
+ Assert.assertEquals(cause, sqlExceptionList.getCause(0, EOFException.class));
+ // No CCE:
+ final List<EOFException> causeList = sqlExceptionList.getCauseList();
+ Assert.assertEquals(list, causeList);
+ }
+
+ @Test
+ public void testNullCause() {
+ final IOExceptionList sqlExceptionList = new IOExceptionList(null);
+ Assert.assertNull(sqlExceptionList.getCause());
+ Assert.assertTrue(sqlExceptionList.getCauseList().isEmpty());
+ }
+
+ @Test
+ public void testPrintStackTrace() {
+ final EOFException cause = new EOFException();
+ final List<EOFException> list = Collections.singletonList(cause);
+ final IOExceptionList sqlExceptionList = new IOExceptionList(list);
+ sqlExceptionList.printStackTrace();
+ }
+}
diff --git a/src/test/java/org/apache/commons/io/IOIndexedExceptionTestCase.java b/src/test/java/org/apache/commons/io/IOIndexedExceptionTestCase.java
new file mode 100644
index 0000000..bed9157
--- /dev/null
+++ b/src/test/java/org/apache/commons/io/IOIndexedExceptionTestCase.java
@@ -0,0 +1,48 @@
+/*
+ * 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;
+
+import java.io.EOFException;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Tests {@link IOIndexedException}.
+ *
+ * @since 2.7
+ */
+public class IOIndexedExceptionTestCase {
+
+ @Test
+ public void testEdge() {
+ final IOIndexedException exception = new IOIndexedException(-1, null);
+ Assert.assertEquals(-1, exception.getIndex());
+ Assert.assertEquals(null, exception.getCause());
+ Assert.assertNotNull(exception.getMessage());
+ }
+
+ @Test
+ public void testPlain() {
+ final EOFException e = new EOFException("end");
+ final IOIndexedException exception = new IOIndexedException(0, e);
+ Assert.assertEquals(0, exception.getIndex());
+ Assert.assertEquals(e, exception.getCause());
+ Assert.assertNotNull(exception.getMessage());
+ }
+}
diff --git a/src/test/java/org/apache/commons/io/output/ProxyCollectionWriterTest.java b/src/test/java/org/apache/commons/io/output/ProxyCollectionWriterTest.java
new file mode 100644
index 0000000..94fc4c4
--- /dev/null
+++ b/src/test/java/org/apache/commons/io/output/ProxyCollectionWriterTest.java
@@ -0,0 +1,449 @@
+/*
+ * 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 static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.apache.commons.io.IOExceptionList;
+import org.apache.commons.io.IOIndexedException;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * JUnit Test Case for {@link ProxyCollectionWriter}.
+ */
+public class ProxyCollectionWriterTest {
+
+ @Test
+ public void testArrayIOExceptionOnAppendChar1() throws IOException {
+ final Writer badW = new BrokenWriter();
+ final StringWriter goodW = mock(StringWriter.class);
+ final ProxyCollectionWriter tw = new ProxyCollectionWriter(badW, goodW, null);
+ final char data = 'A';
+ try {
+ tw.append(data);
+ fail("Expected " + IOException.class.getName());
+ } catch (final IOExceptionList e) {
+ verify(goodW).append(data);
+ Assert.assertEquals(1, e.getCauseList().size());
+ Assert.assertEquals(0, e.getCause(0, IOIndexedException.class).getIndex());
+ }
+ }
+
+ @Test
+ public void testArrayIOExceptionOnAppendChar2() throws IOException {
+ final Writer badW = new BrokenWriter();
+ final StringWriter goodW = mock(StringWriter.class);
+ final ProxyCollectionWriter tw = new ProxyCollectionWriter(goodW, badW, null);
+ final char data = 'A';
+ try {
+ tw.append(data);
+ fail("Expected " + IOException.class.getName());
+ } catch (final IOExceptionList e) {
+ verify(goodW).append(data);
+ Assert.assertEquals(1, e.getCauseList().size());
+ Assert.assertEquals(1, e.getCause(0, IOIndexedException.class).getIndex());
+ }
+ }
+
+ @Test
+ public void testArrayIOExceptionOnAppendCharSequence1() throws IOException {
+ final Writer badW = new BrokenWriter();
+ final StringWriter goodW = mock(StringWriter.class);
+ final ProxyCollectionWriter tw = new ProxyCollectionWriter(badW, goodW, null);
+ final CharSequence data = "A";
+ try {
+ tw.append(data);
+ fail("Expected " + IOException.class.getName());
+ } catch (final IOExceptionList e) {
+ verify(goodW).append(data);
+ Assert.assertEquals(1, e.getCauseList().size());
+ Assert.assertEquals(0, e.getCause(0, IOIndexedException.class).getIndex());
+ }
+ }
+
+ @Test
+ public void testArrayIOExceptionOnAppendCharSequence2() throws IOException {
+ final Writer badW = new BrokenWriter();
+ final StringWriter goodW = mock(StringWriter.class);
+ final ProxyCollectionWriter tw = new ProxyCollectionWriter(goodW, badW, null);
+ final CharSequence data = "A";
+ try {
+ tw.append(data);
+ fail("Expected " + IOException.class.getName());
+ } catch (final IOExceptionList e) {
+ verify(goodW).append(data);
+ Assert.assertEquals(1, e.getCauseList().size());
+ Assert.assertEquals(1, e.getCause(0, IOIndexedException.class).getIndex());
+ }
+ }
+
+ @Test
+ public void testArrayIOExceptionOnAppendCharSequenceIntInt1() throws IOException {
+ final Writer badW = new BrokenWriter();
+ final StringWriter goodW = mock(StringWriter.class);
+ final ProxyCollectionWriter tw = new ProxyCollectionWriter(badW, goodW, null);
+ final CharSequence data = "A";
+ try {
+ tw.append(data, 0, 0);
+ fail("Expected " + IOException.class.getName());
+ } catch (final IOExceptionList e) {
+ verify(goodW).append(data, 0, 0);
+ Assert.assertEquals(1, e.getCauseList().size());
+ Assert.assertEquals(0, e.getCause(0, IOIndexedException.class).getIndex());
+ }
+ }
+
+ @Test
+ public void testArrayIOExceptionOnAppendCharSequenceIntInt2() throws IOException {
+ final Writer badW = new BrokenWriter();
+ final StringWriter goodW = mock(StringWriter.class);
+ final ProxyCollectionWriter tw = new ProxyCollectionWriter(goodW, badW, null);
+ final CharSequence data = "A";
+ try {
+ tw.append(data, 0, 0);
+ fail("Expected " + IOException.class.getName());
+ } catch (final IOExceptionList e) {
+ verify(goodW).append(data, 0, 0);
+ Assert.assertEquals(1, e.getCauseList().size());
+ Assert.assertEquals(1, e.getCause(0, IOIndexedException.class).getIndex());
+ }
+ }
+
+ @Test
+ public void testArrayIOExceptionOnClose1() throws IOException {
+ final Writer badW = new BrokenWriter();
+ final StringWriter goodW = mock(StringWriter.class);
+ final ProxyCollectionWriter tw = new ProxyCollectionWriter(badW, goodW, null);
+ try {
+ tw.close();
+ fail("Expected " + IOException.class.getName());
+ } catch (final IOExceptionList e) {
+ verify(goodW).close();
+ Assert.assertEquals(1, e.getCauseList().size());
+ Assert.assertEquals(0, e.getCause(0, IOIndexedException.class).getIndex());
+ }
+ }
+
+ @Test
+ public void testArrayIOExceptionOnClose2() throws IOException {
+ final Writer badW = new BrokenWriter();
+ final StringWriter goodW = mock(StringWriter.class);
+ final ProxyCollectionWriter tw = new ProxyCollectionWriter(goodW, badW, null);
+ try {
+ tw.close();
+ fail("Expected " + IOException.class.getName());
+ } catch (final IOExceptionList e) {
+ verify(goodW).close();
+ Assert.assertEquals(1, e.getCauseList().size());
+ Assert.assertEquals(1, e.getCause(0, IOIndexedException.class).getIndex());
+ }
+ }
+
+ @Test
+ public void testArrayIOExceptionOnFlush1() throws IOException {
+ final Writer badW = new BrokenWriter();
+ final StringWriter goodW = mock(StringWriter.class);
+ final ProxyCollectionWriter tw = new ProxyCollectionWriter(badW, goodW, null);
+ try {
+ tw.flush();
+ fail("Expected " + IOException.class.getName());
+ } catch (final IOExceptionList e) {
+ verify(goodW).flush();
+ Assert.assertEquals(1, e.getCauseList().size());
+ Assert.assertEquals(0, e.getCause(0, IOIndexedException.class).getIndex());
+ }
+ }
+
+ @Test
+ public void testArrayIOExceptionOnFlush2() throws IOException {
+ final Writer badW = new BrokenWriter();
+ final StringWriter goodW = mock(StringWriter.class);
+ final ProxyCollectionWriter tw = new ProxyCollectionWriter(goodW, badW, null);
+ try {
+ tw.flush();
+ fail("Expected " + IOException.class.getName());
+ } catch (final IOExceptionList e) {
+ verify(goodW).flush();
+ Assert.assertEquals(1, e.getCauseList().size());
+ Assert.assertEquals(1, e.getCause(0, IOIndexedException.class).getIndex());
+ }
+ }
+
+ @Test
+ public void testArrayIOExceptionOnWriteCharArray1() throws IOException {
+ final Writer badW = new BrokenWriter();
+ final StringWriter goodW = mock(StringWriter.class);
+ final ProxyCollectionWriter tw = new ProxyCollectionWriter(badW, goodW, null);
+ final char[] data = new char[] { 'a' };
+ try {
+ tw.write(data);
+ fail("Expected " + IOException.class.getName());
+ } catch (final IOExceptionList e) {
+ verify(goodW).write(data);
+ Assert.assertEquals(1, e.getCauseList().size());
+ Assert.assertEquals(0, e.getCause(0, IOIndexedException.class).getIndex());
+ }
+ }
+
+ @Test
+ public void testArrayIOExceptionOnWriteCharArray2() throws IOException {
+ final Writer badW = new BrokenWriter();
+ final StringWriter goodW = mock(StringWriter.class);
+ final ProxyCollectionWriter tw = new ProxyCollectionWriter(goodW, badW, null);
+ final char[] data = new char[] { 'a' };
+ try {
+ tw.write(data);
+ fail("Expected " + IOException.class.getName());
+ } catch (final IOExceptionList e) {
+ verify(goodW).write(data);
+ Assert.assertEquals(1, e.getCauseList().size());
+ Assert.assertEquals(1, e.getCause(0, IOIndexedException.class).getIndex());
+ }
+ }
+
+ @Test
+ public void testArrayIOExceptionOnWriteCharArrayIntInt1() throws IOException {
+ final Writer badW = new BrokenWriter();
+ final StringWriter goodW = mock(StringWriter.class);
+ final ProxyCollectionWriter tw = new ProxyCollectionWriter(badW, goodW, null);
+ final char[] data = new char[] { 'a' };
+ try {
+ tw.write(data, 0, 0);
+ fail("Expected " + IOException.class.getName());
+ } catch (final IOExceptionList e) {
+ verify(goodW).write(data, 0, 0);
+ Assert.assertEquals(1, e.getCauseList().size());
+ Assert.assertEquals(0, e.getCause(0, IOIndexedException.class).getIndex());
+ }
+ }
+
+ @Test
+ public void testArrayIOExceptionOnWriteCharArrayIntInt2() throws IOException {
+ final Writer badW = new BrokenWriter();
+ final StringWriter goodW = mock(StringWriter.class);
+ final ProxyCollectionWriter tw = new ProxyCollectionWriter(goodW, badW, null);
+ final char[] data = new char[] { 'a' };
+ try {
+ tw.write(data, 0, 0);
+ fail("Expected " + IOException.class.getName());
+ } catch (final IOExceptionList e) {
+ verify(goodW).write(data, 0, 0);
+ Assert.assertEquals(1, e.getCauseList().size());
+ Assert.assertEquals(1, e.getCause(0, IOIndexedException.class).getIndex());
+ }
+ }
+
+ @Test
+ public void testArrayIOExceptionOnWriteInt1() throws IOException {
+ final Writer badW = new BrokenWriter();
+ final StringWriter goodW = mock(StringWriter.class);
+ final ProxyCollectionWriter tw = new ProxyCollectionWriter(badW, goodW, null);
+ final int data = 32;
+ try {
+ tw.write(data);
+ fail("Expected " + IOException.class.getName());
+ } catch (final IOExceptionList e) {
+ verify(goodW).write(data);
+ Assert.assertEquals(1, e.getCauseList().size());
+ Assert.assertEquals(0, e.getCause(0, IOIndexedException.class).getIndex());
+ }
+ }
+
+ @Test
+ public void testArrayIOExceptionOnWriteInt2() throws IOException {
+ final Writer badW = new BrokenWriter();
+ final StringWriter goodW = mock(StringWriter.class);
+ final ProxyCollectionWriter tw = new ProxyCollectionWriter(goodW, badW, null);
+ try {
+ tw.write(32);
+ fail("Expected " + IOException.class.getName());
+ } catch (final IOExceptionList e) {
+ verify(goodW).write(32);
+ Assert.assertEquals(1, e.getCauseList().size());
+ Assert.assertEquals(1, e.getCause(0, IOIndexedException.class).getIndex());
+
+ }
+ }
+
+ @Test
+ public void testArrayIOExceptionOnWriteString1() throws IOException {
+ final Writer badW = new BrokenWriter();
+ final StringWriter goodW = mock(StringWriter.class);
+ final ProxyCollectionWriter tw = new ProxyCollectionWriter(badW, goodW, null);
+ final String data = "A";
+ try {
+ tw.write(data);
+ fail("Expected " + IOException.class.getName());
+ } catch (final IOExceptionList e) {
+ verify(goodW).write(data);
+ Assert.assertEquals(1, e.getCauseList().size());
+ Assert.assertEquals(0, e.getCause(0, IOIndexedException.class).getIndex());
+ }
+ }
+
+ @Test
+ public void testArrayIOExceptionOnWriteString2() throws IOException {
+ final Writer badW = new BrokenWriter();
+ final StringWriter goodW = mock(StringWriter.class);
+ final ProxyCollectionWriter tw = new ProxyCollectionWriter(goodW, badW, null);
+ final String data = "A";
+ try {
+ tw.write(data);
+ fail("Expected " + IOException.class.getName());
+ } catch (final IOExceptionList e) {
+ verify(goodW).write(data);
+ Assert.assertEquals(1, e.getCauseList().size());
+ Assert.assertEquals(1, e.getCause(0, IOIndexedException.class).getIndex());
+
+ }
+ }
+
+ @Test
+ public void testArrayIOExceptionOnWriteStringIntInt1() throws IOException {
+ final Writer badW = new BrokenWriter();
+ final StringWriter goodW = mock(StringWriter.class);
+ final ProxyCollectionWriter tw = new ProxyCollectionWriter(badW, goodW, null);
+ final String data = "A";
+ try {
+ tw.write(data, 0, 0);
+ fail("Expected " + IOException.class.getName());
+ } catch (final IOExceptionList e) {
+ verify(goodW).write(data, 0, 0);
+ Assert.assertEquals(1, e.getCauseList().size());
+ Assert.assertEquals(0, e.getCause(0, IOIndexedException.class).getIndex());
+ }
+ }
+
+ @Test
+ public void testArrayIOExceptionOnWriteStringIntInt2() throws IOException {
+ final Writer badW = new BrokenWriter();
+ final StringWriter goodW = mock(StringWriter.class);
+ final ProxyCollectionWriter tw = new ProxyCollectionWriter(goodW, badW, null);
+ final String data = "A";
+ try {
+ tw.write(data, 0, 0);
+ fail("Expected " + IOException.class.getName());
+ } catch (final IOExceptionList e) {
+ verify(goodW).write(data, 0, 0);
+ Assert.assertEquals(1, e.getCauseList().size());
+ Assert.assertEquals(1, e.getCause(0, IOIndexedException.class).getIndex());
+
+ }
+ }
+
+ @Test
+ public void testCollectionCloseBranchIOException() throws IOException {
+ final Writer badW = new BrokenWriter();
+ final StringWriter goodW = mock(StringWriter.class);
+ final ProxyCollectionWriter tw = new ProxyCollectionWriter(Arrays.asList(goodW, badW, null));
+ try {
+ tw.close();
+ fail("Expected " + IOException.class.getName());
+ } catch (final IOExceptionList e) {
+ verify(goodW).close();
+ Assert.assertEquals(1, e.getCauseList().size());
+ Assert.assertEquals(1, e.getCause(0, IOIndexedException.class).getIndex());
+
+ }
+ }
+
+ @Test
+ public void testConstructorsNull() throws IOException {
+ try (final ProxyCollectionWriter teeWriter = new ProxyCollectionWriter((Writer[]) null)) {
+ // Call any method, should not throw
+ teeWriter.append('a');
+ teeWriter.flush();
+ }
+ try (final ProxyCollectionWriter teeWriter = new ProxyCollectionWriter((Collection<Writer>) null)) {
+ // Call any method, should not throw
+ teeWriter.append('a');
+ teeWriter.flush();
+ }
+ }
+
+ @Test
+ public void testTee() throws IOException {
+ final StringBuilderWriter sbw1 = new StringBuilderWriter();
+ final StringBuilderWriter sbw2 = new StringBuilderWriter();
+ final StringBuilderWriter expected = new StringBuilderWriter();
+
+ try (final ProxyCollectionWriter tw = new ProxyCollectionWriter(sbw1, sbw2, null)) {
+ for (int i = 0; i < 20; i++) {
+ tw.write(i);
+ expected.write(i);
+ }
+ assertEquals("ProxyCollectionWriter.write(int)", expected.toString(), sbw1.toString());
+ assertEquals("ProxyCollectionWriter.write(int)", expected.toString(), sbw2.toString());
+
+ final char[] array = new char[10];
+ for (int i = 20; i < 30; i++) {
+ array[i - 20] = (char) i;
+ }
+ tw.write(array);
+ expected.write(array);
+ assertEquals("ProxyCollectionWriter.write(char[])", expected.toString(), sbw1.toString());
+ assertEquals("ProxyCollectionWriter.write(char[])", expected.toString(), sbw2.toString());
+
+ for (int i = 25; i < 35; i++) {
+ array[i - 25] = (char) i;
+ }
+ tw.write(array, 5, 5);
+ expected.write(array, 5, 5);
+ assertEquals("TeeOutputStream.write(byte[], int, int)", expected.toString(), sbw1.toString());
+ assertEquals("TeeOutputStream.write(byte[], int, int)", expected.toString(), sbw2.toString());
+
+ for (int i = 0; i < 20; i++) {
+ tw.append((char) i);
+ expected.append((char) i);
+ }
+ assertEquals("ProxyCollectionWriter.append(char)", expected.toString(), sbw1.toString());
+ assertEquals("ProxyCollectionWriter.append(char)", expected.toString(), sbw2.toString());
+
+ for (int i = 20; i < 30; i++) {
+ array[i - 20] = (char) i;
+ }
+ tw.append(new String(array));
+ expected.append(new String(array));
+ assertEquals("ProxyCollectionWriter.append(CharSequence)", expected.toString(), sbw1.toString());
+ assertEquals("ProxyCollectionWriter.write(CharSequence)", expected.toString(), sbw2.toString());
+
+ for (int i = 25; i < 35; i++) {
+ array[i - 25] = (char) i;
+ }
+ tw.append(new String(array), 5, 5);
+ expected.append(new String(array), 5, 5);
+ assertEquals("ProxyCollectionWriter.append(CharSequence, int, int)", expected.toString(), sbw1.toString());
+ assertEquals("ProxyCollectionWriter.append(CharSequence, int, int)", expected.toString(), sbw2.toString());
+
+ expected.flush();
+ expected.close();
+
+ tw.flush();
+ }
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/io/output/TeeWriterTest.java b/src/test/java/org/apache/commons/io/output/TeeWriterTest.java
new file mode 100644
index 0000000..90e028a
--- /dev/null
+++ b/src/test/java/org/apache/commons/io/output/TeeWriterTest.java
@@ -0,0 +1,449 @@
+/*
+ * 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 static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.apache.commons.io.IOExceptionList;
+import org.apache.commons.io.IOIndexedException;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * JUnit Test Case for {@link TeeWriter}.
+ */
+public class TeeWriterTest {
+
+ @Test
+ public void testArrayIOExceptionOnAppendChar1() throws IOException {
+ final Writer badW = new BrokenWriter();
+ final StringWriter goodW = mock(StringWriter.class);
+ final ProxyCollectionWriter tw = new ProxyCollectionWriter(badW, goodW, null);
+ final char data = 'A';
+ try {
+ tw.append(data);
+ fail("Expected " + IOException.class.getName());
+ } catch (final IOExceptionList e) {
+ verify(goodW).append(data);
+ Assert.assertEquals(1, e.getCauseList().size());
+ Assert.assertEquals(0, e.getCause(0, IOIndexedException.class).getIndex());
+ }
+ }
+
+ @Test
+ public void testArrayIOExceptionOnAppendChar2() throws IOException {
+ final Writer badW = new BrokenWriter();
+ final StringWriter goodW = mock(StringWriter.class);
+ final ProxyCollectionWriter tw = new ProxyCollectionWriter(goodW, badW, null);
+ final char data = 'A';
+ try {
+ tw.append(data);
+ fail("Expected " + IOException.class.getName());
+ } catch (final IOExceptionList e) {
+ verify(goodW).append(data);
+ Assert.assertEquals(1, e.getCauseList().size());
+ Assert.assertEquals(1, e.getCause(0, IOIndexedException.class).getIndex());
+ }
+ }
+
+ @Test
+ public void testArrayIOExceptionOnAppendCharSequence1() throws IOException {
+ final Writer badW = new BrokenWriter();
+ final StringWriter goodW = mock(StringWriter.class);
+ final TeeWriter tw = new TeeWriter(badW, goodW, null);
+ final String data = "A";
+ try {
+ tw.append(data);
+ fail("Expected " + IOException.class.getName());
+ } catch (final IOExceptionList e) {
+ verify(goodW).append(data);
+ Assert.assertEquals(1, e.getCauseList().size());
+ Assert.assertEquals(0, e.getCause(0, IOIndexedException.class).getIndex());
+ }
+ }
+
+ @Test
+ public void testArrayIOExceptionOnAppendCharSequence2() throws IOException {
+ final Writer badW = new BrokenWriter();
+ final StringWriter goodW = mock(StringWriter.class);
+ final TeeWriter tw = new TeeWriter(goodW, badW, null);
+ final String data = "A";
+ try {
+ tw.append(data);
+ fail("Expected " + IOException.class.getName());
+ } catch (final IOExceptionList e) {
+ verify(goodW).append(data);
+ Assert.assertEquals(1, e.getCauseList().size());
+ Assert.assertEquals(1, e.getCause(0, IOIndexedException.class).getIndex());
+ }
+ }
+
+ @Test
+ public void testArrayIOExceptionOnAppendCharSequenceIntInt1() throws IOException {
+ final Writer badW = new BrokenWriter();
+ final StringWriter goodW = mock(StringWriter.class);
+ final TeeWriter tw = new TeeWriter(badW, goodW, null);
+ final String data = "A";
+ try {
+ tw.append(data, 0, 0);
+ fail("Expected " + IOException.class.getName());
+ } catch (final IOExceptionList e) {
+ verify(goodW).append(data, 0, 0);
+ Assert.assertEquals(1, e.getCauseList().size());
+ Assert.assertEquals(0, e.getCause(0, IOIndexedException.class).getIndex());
+ }
+ }
+
+ @Test
+ public void testArrayIOExceptionOnAppendCharSequenceIntInt2() throws IOException {
+ final Writer badW = new BrokenWriter();
+ final StringWriter goodW = mock(StringWriter.class);
+ final TeeWriter tw = new TeeWriter(goodW, badW, null);
+ final String data = "A";
+ try {
+ tw.append(data, 0, 0);
+ fail("Expected " + IOException.class.getName());
+ } catch (final IOExceptionList e) {
+ verify(goodW).append(data, 0, 0);
+ Assert.assertEquals(1, e.getCauseList().size());
+ Assert.assertEquals(1, e.getCause(0, IOIndexedException.class).getIndex());
+ }
+ }
+
+ @Test
+ public void testArrayIOExceptionOnClose1() throws IOException {
+ final Writer badW = new BrokenWriter();
+ final StringWriter goodW = mock(StringWriter.class);
+ final TeeWriter tw = new TeeWriter(badW, goodW, null);
+ try {
+ tw.close();
+ fail("Expected " + IOException.class.getName());
+ } catch (final IOExceptionList e) {
+ verify(goodW).close();
+ Assert.assertEquals(1, e.getCauseList().size());
+ Assert.assertEquals(0, e.getCause(0, IOIndexedException.class).getIndex());
+ }
+ }
+
+ @Test
+ public void testArrayIOExceptionOnClose2() throws IOException {
+ final Writer badW = new BrokenWriter();
+ final StringWriter goodW = mock(StringWriter.class);
+ final TeeWriter tw = new TeeWriter(goodW, badW, null);
+ try {
+ tw.close();
+ fail("Expected " + IOException.class.getName());
+ } catch (final IOExceptionList e) {
+ verify(goodW).close();
+ Assert.assertEquals(1, e.getCauseList().size());
+ Assert.assertEquals(1, e.getCause(0, IOIndexedException.class).getIndex());
+ }
+ }
+
+ @Test
+ public void testArrayIOExceptionOnFlush1() throws IOException {
+ final Writer badW = new BrokenWriter();
+ final StringWriter goodW = mock(StringWriter.class);
+ final TeeWriter tw = new TeeWriter(badW, goodW, null);
+ try {
+ tw.flush();
+ fail("Expected " + IOException.class.getName());
+ } catch (final IOExceptionList e) {
+ verify(goodW).flush();
+ Assert.assertEquals(1, e.getCauseList().size());
+ Assert.assertEquals(0, e.getCause(0, IOIndexedException.class).getIndex());
+ }
+ }
+
+ @Test
+ public void testArrayIOExceptionOnFlush2() throws IOException {
+ final Writer badW = new BrokenWriter();
+ final StringWriter goodW = mock(StringWriter.class);
+ final TeeWriter tw = new TeeWriter(goodW, badW, null);
+ try {
+ tw.flush();
+ fail("Expected " + IOException.class.getName());
+ } catch (final IOExceptionList e) {
+ verify(goodW).flush();
+ Assert.assertEquals(1, e.getCauseList().size());
+ Assert.assertEquals(1, e.getCause(0, IOIndexedException.class).getIndex());
+ }
+ }
+
+ @Test
+ public void testArrayIOExceptionOnWriteCharArray1() throws IOException {
+ final Writer badW = new BrokenWriter();
+ final StringWriter goodW = mock(StringWriter.class);
+ final TeeWriter tw = new TeeWriter(badW, goodW, null);
+ final char[] data = new char[] { 'a' };
+ try {
+ tw.write(data);
+ fail("Expected " + IOException.class.getName());
+ } catch (final IOExceptionList e) {
+ verify(goodW).write(data);
+ Assert.assertEquals(1, e.getCauseList().size());
+ Assert.assertEquals(0, e.getCause(0, IOIndexedException.class).getIndex());
+ }
+ }
+
+ @Test
+ public void testArrayIOExceptionOnWriteCharArray2() throws IOException {
+ final Writer badW = new BrokenWriter();
+ final StringWriter goodW = mock(StringWriter.class);
+ final TeeWriter tw = new TeeWriter(goodW, badW, null);
+ final char[] data = new char[] { 'a' };
+ try {
+ tw.write(data);
+ fail("Expected " + IOException.class.getName());
+ } catch (final IOExceptionList e) {
+ verify(goodW).write(data);
+ Assert.assertEquals(1, e.getCauseList().size());
+ Assert.assertEquals(1, e.getCause(0, IOIndexedException.class).getIndex());
+ }
+ }
+
+ @Test
+ public void testArrayIOExceptionOnWriteCharArrayIntInt1() throws IOException {
+ final Writer badW = new BrokenWriter();
+ final StringWriter goodW = mock(StringWriter.class);
+ final TeeWriter tw = new TeeWriter(badW, goodW, null);
+ final char[] data = new char[] { 'a' };
+ try {
+ tw.write(data, 0, 0);
+ fail("Expected " + IOException.class.getName());
+ } catch (final IOExceptionList e) {
+ verify(goodW).write(data, 0, 0);
+ Assert.assertEquals(1, e.getCauseList().size());
+ Assert.assertEquals(0, e.getCause(0, IOIndexedException.class).getIndex());
+ }
+ }
+
+ @Test
+ public void testArrayIOExceptionOnWriteCharArrayIntInt2() throws IOException {
+ final Writer badW = new BrokenWriter();
+ final StringWriter goodW = mock(StringWriter.class);
+ final TeeWriter tw = new TeeWriter(goodW, badW, null);
+ final char[] data = new char[] { 'a' };
+ try {
+ tw.write(data, 0, 0);
+ fail("Expected " + IOException.class.getName());
+ } catch (final IOExceptionList e) {
+ verify(goodW).write(data, 0, 0);
+ Assert.assertEquals(1, e.getCauseList().size());
+ Assert.assertEquals(1, e.getCause(0, IOIndexedException.class).getIndex());
+ }
+ }
+
+ @Test
+ public void testArrayIOExceptionOnWriteInt1() throws IOException {
+ final Writer badW = new BrokenWriter();
+ final StringWriter goodW = mock(StringWriter.class);
+ final TeeWriter tw = new TeeWriter(badW, goodW, null);
+ final int data = 32;
+ try {
+ tw.write(data);
+ fail("Expected " + IOException.class.getName());
+ } catch (final IOExceptionList e) {
+ verify(goodW).write(data);
+ Assert.assertEquals(1, e.getCauseList().size());
+ Assert.assertEquals(0, e.getCause(0, IOIndexedException.class).getIndex());
+ }
+ }
+
+ @Test
+ public void testArrayIOExceptionOnWriteInt2() throws IOException {
+ final Writer badW = new BrokenWriter();
+ final StringWriter goodW = mock(StringWriter.class);
+ final TeeWriter tw = new TeeWriter(goodW, badW, null);
+ try {
+ tw.write(32);
+ fail("Expected " + IOException.class.getName());
+ } catch (final IOExceptionList e) {
+ verify(goodW).write(32);
+ Assert.assertEquals(1, e.getCauseList().size());
+ Assert.assertEquals(1, e.getCause(0, IOIndexedException.class).getIndex());
+
+ }
+ }
+
+ @Test
+ public void testArrayIOExceptionOnWriteString1() throws IOException {
+ final Writer badW = new BrokenWriter();
+ final StringWriter goodW = mock(StringWriter.class);
+ final TeeWriter tw = new TeeWriter(badW, goodW, null);
+ final String data = "A";
+ try {
+ tw.write(data);
+ fail("Expected " + IOException.class.getName());
+ } catch (final IOExceptionList e) {
+ verify(goodW).write(data);
+ Assert.assertEquals(1, e.getCauseList().size());
+ Assert.assertEquals(0, e.getCause(0, IOIndexedException.class).getIndex());
+ }
+ }
+
+ @Test
+ public void testArrayIOExceptionOnWriteString2() throws IOException {
+ final Writer badW = new BrokenWriter();
+ final StringWriter goodW = mock(StringWriter.class);
+ final TeeWriter tw = new TeeWriter(goodW, badW, null);
+ final String data = "A";
+ try {
+ tw.write(data);
+ fail("Expected " + IOException.class.getName());
+ } catch (final IOExceptionList e) {
+ verify(goodW).write(data);
+ Assert.assertEquals(1, e.getCauseList().size());
+ Assert.assertEquals(1, e.getCause(0, IOIndexedException.class).getIndex());
+
+ }
+ }
+
+ @Test
+ public void testArrayIOExceptionOnWriteStringIntInt1() throws IOException {
+ final Writer badW = new BrokenWriter();
+ final StringWriter goodW = mock(StringWriter.class);
+ final TeeWriter tw = new TeeWriter(badW, goodW, null);
+ final String data = "A";
+ try {
+ tw.write(data, 0, 0);
+ fail("Expected " + IOException.class.getName());
+ } catch (final IOExceptionList e) {
+ verify(goodW).write(data, 0, 0);
+ Assert.assertEquals(1, e.getCauseList().size());
+ Assert.assertEquals(0, e.getCause(0, IOIndexedException.class).getIndex());
+ }
+ }
+
+ @Test
+ public void testArrayIOExceptionOnWriteStringIntInt2() throws IOException {
+ final Writer badW = new BrokenWriter();
+ final StringWriter goodW = mock(StringWriter.class);
+ final TeeWriter tw = new TeeWriter(goodW, badW, null);
+ final String data = "A";
+ try {
+ tw.write(data, 0, 0);
+ fail("Expected " + IOException.class.getName());
+ } catch (final IOExceptionList e) {
+ verify(goodW).write(data, 0, 0);
+ Assert.assertEquals(1, e.getCauseList().size());
+ Assert.assertEquals(1, e.getCause(0, IOIndexedException.class).getIndex());
+
+ }
+ }
+
+ @Test
+ public void testCollectionCloseBranchIOException() throws IOException {
+ final Writer badW = new BrokenWriter();
+ final StringWriter goodW = mock(StringWriter.class);
+ final TeeWriter tw = new TeeWriter(Arrays.asList(goodW, badW, null));
+ try {
+ tw.close();
+ fail("Expected " + IOException.class.getName());
+ } catch (final IOExceptionList e) {
+ verify(goodW).close();
+ Assert.assertEquals(1, e.getCauseList().size());
+ Assert.assertEquals(1, e.getCause(0, IOIndexedException.class).getIndex());
+
+ }
+ }
+
+ @Test
+ public void testConstructorsNull() throws IOException {
+ try (final TeeWriter teeWriter = new TeeWriter((Writer[]) null)) {
+ // Call any method, should not throw
+ teeWriter.append('a');
+ teeWriter.flush();
+ }
+ try (final TeeWriter teeWriter = new TeeWriter((Collection<Writer>) null)) {
+ // Call any method, should not throw
+ teeWriter.append('a');
+ teeWriter.flush();
+ }
+ }
+
+ @Test
+ public void testTee() throws IOException {
+ final StringBuilderWriter sbw1 = new StringBuilderWriter();
+ final StringBuilderWriter sbw2 = new StringBuilderWriter();
+ final StringBuilderWriter expected = new StringBuilderWriter();
+
+ try (final TeeWriter tw = new TeeWriter(sbw1, sbw2, null)) {
+ for (int i = 0; i < 20; i++) {
+ tw.write(i);
+ expected.write(i);
+ }
+ assertEquals("TeeWriter.write(int)", expected.toString(), sbw1.toString());
+ assertEquals("TeeWriter.write(int)", expected.toString(), sbw2.toString());
+
+ final char[] array = new char[10];
+ for (int i = 20; i < 30; i++) {
+ array[i - 20] = (char) i;
+ }
+ tw.write(array);
+ expected.write(array);
+ assertEquals("TeeWriter.write(char[])", expected.toString(), sbw1.toString());
+ assertEquals("TeeWriter.write(char[])", expected.toString(), sbw2.toString());
+
+ for (int i = 25; i < 35; i++) {
+ array[i - 25] = (char) i;
+ }
+ tw.write(array, 5, 5);
+ expected.write(array, 5, 5);
+ assertEquals("TeeOutputStream.write(byte[], int, int)", expected.toString(), sbw1.toString());
+ assertEquals("TeeOutputStream.write(byte[], int, int)", expected.toString(), sbw2.toString());
+
+ for (int i = 0; i < 20; i++) {
+ tw.append((char) i);
+ expected.append((char) i);
+ }
+ assertEquals("TeeWriter.append(char)", expected.toString(), sbw1.toString());
+ assertEquals("TeeWriter.append(char)", expected.toString(), sbw2.toString());
+
+ for (int i = 20; i < 30; i++) {
+ array[i - 20] = (char) i;
+ }
+ tw.append(new String(array));
+ expected.append(new String(array));
+ assertEquals("TeeWriter.append(CharSequence)", expected.toString(), sbw1.toString());
+ assertEquals("TeeWriter.write(CharSequence)", expected.toString(), sbw2.toString());
+
+ for (int i = 25; i < 35; i++) {
+ array[i - 25] = (char) i;
+ }
+ tw.append(new String(array), 5, 5);
+ expected.append(new String(array), 5, 5);
+ assertEquals("TeeWriter.append(CharSequence, int, int)", expected.toString(), sbw1.toString());
+ assertEquals("TeeWriter.append(CharSequence, int, int)", expected.toString(), sbw2.toString());
+
+ expected.flush();
+ expected.close();
+
+ tw.flush();
+ }
+ }
+
+}