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    @Override
077    public Writer append(final char c) throws IOException {
078        final List<Exception> causeList = new ArrayList<>();
079        int i = 0;
080        for (final Writer w : writers) {
081            if (w != null) {
082                try {
083                    w.append(c);
084                } catch (final IOException e) {
085                    causeList.add(new IOIndexedException(i, e));
086                }
087            }
088            i++;
089        }
090        if (!causeList.isEmpty()) {
091            throw new IOExceptionList(causeList);
092        }
093        return this;
094    }
095
096    @Override
097    public Writer append(final CharSequence csq) throws IOException {
098        final List<Exception> causeList = new ArrayList<>();
099        int i = 0;
100        for (final Writer w : writers) {
101            if (w != null) {
102                try {
103                    w.append(csq);
104                } catch (final IOException e) {
105                    causeList.add(new IOIndexedException(i, e));
106                }
107            }
108            i++;
109        }
110        if (!causeList.isEmpty()) {
111            throw new IOExceptionList(causeList);
112        }
113        return this;
114    }
115
116    @Override
117    public Writer append(final CharSequence csq, final int start, final int end) throws IOException {
118
119        final List<Exception> causeList = new ArrayList<>();
120        int i = 0;
121        for (final Writer w : writers) {
122            if (w != null) {
123                try {
124                    w.append(csq, start, end);
125                } catch (final IOException e) {
126                    causeList.add(new IOIndexedException(i, e));
127                }
128            }
129            i++;
130        }
131        if (!causeList.isEmpty()) {
132            throw new IOExceptionList(causeList);
133        }
134        return this;
135    }
136
137    @Override
138    public void close() throws IOException {
139        final List<Exception> causeList = new ArrayList<>();
140        int i = 0;
141        for (final Writer w : writers) {
142            if (w != null) {
143                try {
144                    w.close();
145                } catch (final IOException e) {
146                    causeList.add(new IOIndexedException(i, e));
147                }
148            }
149            i++;
150        }
151        if (!causeList.isEmpty()) {
152            throw new IOExceptionList(causeList);
153        }
154
155    }
156
157    /**
158     * Flushes the stream.
159     *
160     * @exception IOException If an I/O error occurs
161     */
162    @Override
163    public void flush() throws IOException {
164        final List<Exception> causeList = new ArrayList<>();
165        int i = 0;
166        for (final Writer w : writers) {
167            if (w != null) {
168                try {
169                    w.flush();
170                } catch (final IOException e) {
171                    causeList.add(new IOIndexedException(i, e));
172                }
173            }
174            i++;
175        }
176        if (!causeList.isEmpty()) {
177            throw new IOExceptionList(causeList);
178        }
179
180    }
181
182    /**
183     * Writes a portion of an array of characters.
184     *
185     * @param cbuf Buffer of characters to be written
186     * @param off  Offset from which to start reading characters
187     * @param len  Number of characters to be written
188     *
189     * @exception IOException If an I/O error occurs
190     */
191    @Override
192    public void write(final char cbuf[], final int off, final int len) throws IOException {
193        final List<Exception> causeList = new ArrayList<>();
194        int i = 0;
195        for (final Writer w : writers) {
196            if (w != null) {
197                try {
198                    w.write(cbuf, off, len);
199                } catch (final IOException e) {
200                    causeList.add(new IOIndexedException(i, e));
201                }
202            }
203            i++;
204        }
205        if (!causeList.isEmpty()) {
206            throw new IOExceptionList(causeList);
207        }
208    }
209
210    @Override
211    public void write(final char[] cbuf) throws IOException {
212        final List<Exception> causeList = new ArrayList<>();
213        int i = 0;
214        for (final Writer w : writers) {
215            if (w != null) {
216                try {
217                    w.write(cbuf);
218                } catch (final IOException e) {
219                    causeList.add(new IOIndexedException(i, e));
220                }
221            }
222            i++;
223        }
224        if (!causeList.isEmpty()) {
225            throw new IOExceptionList(causeList);
226        }
227    }
228
229    /**
230     * Writes a single character.
231     *
232     * @exception IOException If an I/O error occurs
233     */
234    @Override
235    public void write(final int c) throws IOException {
236        final List<Exception> causeList = new ArrayList<>();
237        int i = 0;
238        for (final Writer w : writers) {
239            if (w != null) {
240                try {
241                    w.write(c);
242                } catch (final IOException e) {
243                    causeList.add(new IOIndexedException(i, e));
244                }
245            }
246            i++;
247        }
248        if (!causeList.isEmpty()) {
249            throw new IOExceptionList(causeList);
250        }
251    }
252
253    @Override
254    public void write(final String str) throws IOException {
255        final List<Exception> causeList = new ArrayList<>();
256        int i = 0;
257        for (final Writer w : writers) {
258            if (w != null) {
259                try {
260                    w.write(str);
261                } catch (final IOException e) {
262                    causeList.add(new IOIndexedException(i, e));
263                }
264            }
265            i++;
266        }
267        if (!causeList.isEmpty()) {
268            throw new IOExceptionList(causeList);
269        }
270
271    }
272
273    /**
274     * Writes a portion of a string.
275     *
276     * @param str String to be written
277     * @param off Offset from which to start reading characters
278     * @param len Number of characters to be written
279     *
280     * @exception IOException If an I/O error occurs
281     */
282    @Override
283    public void write(final String str, final int off, final int len) throws IOException {
284        final List<Exception> causeList = new ArrayList<>();
285        int i = 0;
286        for (final Writer w : writers) {
287            if (w != null) {
288                try {
289                    w.write(str, off, len);
290                } catch (final IOException e) {
291                    causeList.add(new IOIndexedException(i, e));
292                }
293            }
294            i++;
295        }
296        if (!causeList.isEmpty()) {
297            throw new IOExceptionList(causeList);
298        }
299
300    }
301
302}