001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.io.output;
019
020import java.io.FilterWriter;
021import java.io.IOException;
022import java.io.Writer;
023import java.util.ArrayList;
024import java.util.Arrays;
025import java.util.Collection;
026import java.util.Collections;
027import java.util.List;
028
029import org.apache.commons.io.IOExceptionList;
030import org.apache.commons.io.IOIndexedException;
031
032/**
033 * Abstract class for writing filtered character streams to a {@link Collection} of writers. This is in contrast to
034 * {@link FilterWriter} which is backed by a single {@link Writer}.
035 * <p>
036 * This abstract class provides default methods that pass all requests to the contained writers. Subclasses should
037 * likely override some of these methods.
038 * </p>
039 * <p>
040 * The class {@link Writer} defines method signatures with {@code throws} {@link IOException}, which in this class are
041 * actually {@link IOExceptionList} containing a list of {@link IOIndexedException}.
042 * </p>
043 *
044 * @since 2.7
045 */
046public class FilterCollectionWriter extends Writer {
047
048    /**
049     * Empty and immutable collection of writers.
050     */
051    protected final Collection<Writer> EMPTY_WRITERS = Collections.emptyList();
052
053    /**
054     * The underlying writers.
055     */
056    protected final Collection<Writer> writers;
057
058    /**
059     * Creates a new filtered collection writer.
060     *
061     * @param writers Writers to provide the underlying targets.
062     */
063    protected FilterCollectionWriter(final Collection<Writer> writers) {
064        this.writers = writers == null ? EMPTY_WRITERS : writers;
065    }
066
067    /**
068     * Creates a new filtered collection writer.
069     *
070     * @param writers Writers to provide the underlying targets.
071     */
072    protected FilterCollectionWriter(final Writer... writers) {
073        this.writers = writers == null ? EMPTY_WRITERS : Arrays.asList(writers);
074    }
075
076    /**
077     * Adds an indexed exception to the list.
078     *
079     * @param causeList The target list.
080     * @param i The index.
081     * @param e The cause.
082     * @return the given list or a new list on null input.
083     */
084    private List<Exception> add(List<Exception> causeList, final int i, final IOException e) {
085        if (causeList == null) {
086            causeList = new ArrayList<>();
087        }
088        causeList.add(new IOIndexedException(i, e));
089        return causeList;
090    }
091
092    @Override
093    public Writer append(final char c) throws IOException {
094        List<Exception> causeList = null;
095        int i = 0;
096        for (final Writer w : writers) {
097            if (w != null) {
098                try {
099                    w.append(c);
100                } catch (final IOException e) {
101                    causeList = add(causeList, i, e);
102                }
103            }
104            i++;
105        }
106        if (notEmpty(causeList)) {
107            throw new IOExceptionList("append", causeList);
108        }
109        return this;
110    }
111
112    @Override
113    public Writer append(final CharSequence csq) throws IOException {
114        List<Exception> causeList = null;
115        int i = 0;
116        for (final Writer w : writers) {
117            if (w != null) {
118                try {
119                    w.append(csq);
120                } catch (final IOException e) {
121                    causeList = add(causeList, i, e);
122                }
123            }
124            i++;
125        }
126        if (notEmpty(causeList)) {
127            throw new IOExceptionList("append", causeList);
128        }
129        return this;
130    }
131
132    @Override
133    public Writer append(final CharSequence csq, final int start, final int end) throws IOException {
134
135        List<Exception> causeList = null;
136        int i = 0;
137        for (final Writer w : writers) {
138            if (w != null) {
139                try {
140                    w.append(csq, start, end);
141                } catch (final IOException e) {
142                    causeList = add(causeList, i, e);
143                }
144            }
145            i++;
146        }
147        if (notEmpty(causeList)) {
148            throw new IOExceptionList("append", causeList);
149        }
150        return this;
151    }
152
153    @Override
154    public void close() throws IOException {
155        List<Exception> causeList = null;
156        int i = 0;
157        for (final Writer w : writers) {
158            if (w != null) {
159                try {
160                    w.close();
161                } catch (final IOException e) {
162                    causeList = add(causeList, i, e);
163                }
164            }
165            i++;
166        }
167        if (notEmpty(causeList)) {
168            throw new IOExceptionList("close", causeList);
169        }
170
171    }
172
173    /**
174     * Flushes the stream.
175     *
176     * @exception IOException If an I/O error occurs
177     */
178    @Override
179    public void flush() throws IOException {
180        List<Exception> causeList = null;
181        int i = 0;
182        for (final Writer w : writers) {
183            if (w != null) {
184                try {
185                    w.flush();
186                } catch (final IOException e) {
187                    causeList = add(causeList, i, e);
188                }
189            }
190            i++;
191        }
192        if (notEmpty(causeList)) {
193            throw new IOExceptionList("flush", causeList);
194        }
195
196    }
197
198    /**
199     * Tests if the given list is empty in a null-safe manner.
200     *
201     * @param causeList the list to test.
202     * @return true if empty or null.
203     */
204    private boolean notEmpty(final List<Exception> causeList) {
205        return causeList != null && !causeList.isEmpty();
206    }
207
208    @Override
209    public void write(final char[] cbuf) throws IOException {
210        List<Exception> causeList = null;
211        int i = 0;
212        for (final Writer w : writers) {
213            if (w != null) {
214                try {
215                    w.write(cbuf);
216                } catch (final IOException e) {
217                    causeList = add(causeList, i, e);
218                }
219            }
220            i++;
221        }
222        if (notEmpty(causeList)) {
223            throw new IOExceptionList("write", causeList);
224        }
225    }
226
227    /**
228     * Writes a portion of an array of characters.
229     *
230     * @param cbuf Buffer of characters to be written
231     * @param off  Offset from which to start reading characters
232     * @param len  Number of characters to be written
233     *
234     * @exception IOException If an I/O error occurs
235     */
236    @Override
237    public void write(final char[] cbuf, final int off, final int len) throws IOException {
238        List<Exception> causeList = null;
239        int i = 0;
240        for (final Writer w : writers) {
241            if (w != null) {
242                try {
243                    w.write(cbuf, off, len);
244                } catch (final IOException e) {
245                    causeList = add(causeList, i, e);
246                }
247            }
248            i++;
249        }
250        if (notEmpty(causeList)) {
251            throw new IOExceptionList("write", causeList);
252        }
253    }
254
255    /**
256     * Writes a single character.
257     *
258     * @exception IOException If an I/O error occurs
259     */
260    @Override
261    public void write(final int c) throws IOException {
262        List<Exception> causeList = null;
263        int i = 0;
264        for (final Writer w : writers) {
265            if (w != null) {
266                try {
267                    w.write(c);
268                } catch (final IOException e) {
269                    causeList = add(causeList, i, e);
270                }
271            }
272            i++;
273        }
274        if (notEmpty(causeList)) {
275            throw new IOExceptionList("write", causeList);
276        }
277    }
278
279    @Override
280    public void write(final String str) throws IOException {
281        List<Exception> causeList = null;
282        int i = 0;
283        for (final Writer w : writers) {
284            if (w != null) {
285                try {
286                    w.write(str);
287                } catch (final IOException e) {
288                    causeList = add(causeList, i, e);
289                }
290            }
291            i++;
292        }
293        if (notEmpty(causeList)) {
294            throw new IOExceptionList("write", causeList);
295        }
296
297    }
298
299    /**
300     * Writes a portion of a string.
301     *
302     * @param str String to be written
303     * @param off Offset from which to start reading characters
304     * @param len Number of characters to be written
305     *
306     * @exception IOException If an I/O error occurs
307     */
308    @Override
309    public void write(final String str, final int off, final int len) throws IOException {
310        List<Exception> causeList = null;
311        int i = 0;
312        for (final Writer w : writers) {
313            if (w != null) {
314                try {
315                    w.write(str, off, len);
316                } catch (final IOException e) {
317                    causeList = add(causeList, i, e);
318                }
319            }
320            i++;
321        }
322        if (notEmpty(causeList)) {
323            throw new IOExceptionList("write", causeList);
324        }
325
326    }
327
328}