    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}