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     * Constructs 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     * Constructs 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}