    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.Arrays;
    024import java.util.Collection;
    025import java.util.Collections;
    026import java.util.Objects;
    027import java.util.stream.Stream;
    028
    029import org.apache.commons.io.IOExceptionList;
    030import org.apache.commons.io.IOIndexedException;
    031import org.apache.commons.io.function.IOConsumer;
    032
    033/**
    034 * Abstract class for writing filtered character streams to a {@link Collection} of writers. This is in contrast to
    035 * {@link FilterWriter} which is backed by a single {@link Writer}.
    036 * <p>
    037 * This abstract class provides default methods that pass all requests to the contained writers. Subclasses should
    038 * likely override some of these methods.
    039 * </p>
    040 * <p>
    041 * The class {@link Writer} defines method signatures with {@code throws} {@link IOException}, which in this class are
    042 * actually {@link IOExceptionList} containing a list of {@link IOIndexedException}.
    043 * </p>
    044 *
    045 * @since 2.7
    046 */
    047public class FilterCollectionWriter extends Writer {
    048
    049    /**
    050     * Empty and immutable collection of writers.
    051     */
    052    protected final Collection<Writer> EMPTY_WRITERS = Collections.emptyList();
    053
    054    /**
    055     * The underlying writers.
    056     */
    057    protected final Collection<Writer> writers;
    058
    059    /**
    060     * Creates a new filtered collection writer.
    061     *
    062     * @param writers Writers to provide the underlying targets.
    063     */
    064    protected FilterCollectionWriter(final Collection<Writer> writers) {
    065        this.writers = writers == null ? EMPTY_WRITERS : writers;
    066    }
    067
    068    /**
    069     * Creates a new filtered collection writer.
    070     *
    071     * @param writers Writers to provide the underlying targets.
    072     */
    073    protected FilterCollectionWriter(final Writer... writers) {
    074        this.writers = writers == null ? EMPTY_WRITERS : Arrays.asList(writers);
    075    }
    076
    077    @Override
    078    public Writer append(final char c) throws IOException {
    079        return forAllWriters(w -> w.append(c));
    080    }
    081
    082    @Override
    083    public Writer append(final CharSequence csq) throws IOException {
    084        return forAllWriters(w -> w.append(csq));
    085    }
    086
    087    @Override
    088    public Writer append(final CharSequence csq, final int start, final int end) throws IOException {
    089        return forAllWriters(w -> w.append(csq, start, end));
    090    }
    091
    092    @SuppressWarnings("resource") // no allocation
    093    @Override
    094    public void close() throws IOException {
    095        forAllWriters(Writer::close);
    096    }
    097
    098    /**
    099     * Flushes the stream.
    100     *
    101     * @throws IOException If an I/O error occurs
    102     */
    103    @SuppressWarnings("resource") // no allocation
    104    @Override
    105    public void flush() throws IOException {
    106        forAllWriters(Writer::flush);
    107    }
    108
    109    private FilterCollectionWriter forAllWriters(final IOConsumer<Writer> action) throws IOExceptionList {
    110        IOConsumer.forAll(action, writers());
    111        return this;
    112    }
    113
    114    @SuppressWarnings("resource") // no allocation
    115    @Override
    116    public void write(final char[] cbuf) throws IOException {
    117        forAllWriters(w -> w.write(cbuf));
    118    }
    119
    120    /**
    121     * Writes a portion of an array of characters.
    122     *
    123     * @param cbuf Buffer of characters to be written
    124     * @param off  Offset from which to start reading characters
    125     * @param len  Number of characters to be written
    126     * @throws IOException If an I/O error occurs
    127     */
    128    @SuppressWarnings("resource") // no allocation
    129    @Override
    130    public void write(final char[] cbuf, final int off, final int len) throws IOException {
    131        forAllWriters(w -> w.write(cbuf, off, len));
    132    }
    133
    134    /**
    135     * Writes a single character.
    136     *
    137     * @throws IOException If an I/O error occurs
    138     */
    139    @SuppressWarnings("resource") // no allocation
    140    @Override
    141    public void write(final int c) throws IOException {
    142        forAllWriters(w -> w.write(c));
    143    }
    144
    145    @SuppressWarnings("resource") // no allocation
    146    @Override
    147    public void write(final String str) throws IOException {
    148        forAllWriters(w -> w.write(str));
    149    }
    150
    151    /**
    152     * Writes a portion of a string.
    153     *
    154     * @param str String to be written
    155     * @param off Offset from which to start reading characters
    156     * @param len Number of characters to be written
    157     * @throws IOException If an I/O error occurs
    158     */
    159    @SuppressWarnings("resource") // no allocation
    160    @Override
    161    public void write(final String str, final int off, final int len) throws IOException {
    162        forAllWriters(w -> w.write(str, off, len));
    163    }
    164
    165    private Stream<Writer> writers() {
    166        return writers.stream().filter(Objects::nonNull);
    167    }
    168
    169}