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    package org.apache.commons.io.output;
018    
019    import java.io.IOException;
020    import java.io.OutputStream;
021    import java.io.Writer;
022    import java.nio.ByteBuffer;
023    import java.nio.CharBuffer;
024    import java.nio.charset.Charset;
025    import java.nio.charset.CharsetDecoder;
026    import java.nio.charset.CoderResult;
027    import java.nio.charset.CodingErrorAction;
028    
029    /**
030     * {@link OutputStream} implementation that transforms a byte stream to a
031     * character stream using a specified charset encoding and writes the resulting
032     * stream to a {@link Writer}. The stream is transformed using a
033     * {@link CharsetDecoder} object, guaranteeing that all charset
034     * encodings supported by the JRE are handled correctly.
035     * <p>
036     * The output of the {@link CharsetDecoder} is buffered using a fixed size buffer.
037     * This implies that the data is written to the underlying {@link Writer} in chunks
038     * that are no larger than the size of this buffer. By default, the buffer is
039     * flushed only when it overflows or when {@link #flush()} or {@link #close()}
040     * is called. In general there is therefore no need to wrap the underlying {@link Writer}
041     * in a {@link java.io.BufferedWriter}. {@link WriterOutputStream} can also
042     * be instructed to flush the buffer after each write operation. In this case, all
043     * available data is written immediately to the underlying {@link Writer}, implying that
044     * the current position of the {@link Writer} is correlated to the current position
045     * of the {@link WriterOutputStream}.
046     * <p>
047     * {@link WriterOutputStream} implements the inverse transformation of {@link java.io.OutputStreamWriter};
048     * in the following example, writing to <tt>out2</tt> would have the same result as writing to
049     * <tt>out</tt> directly (provided that the byte sequence is legal with respect to the
050     * charset encoding):
051     * <pre>
052     * OutputStream out = ...
053     * Charset cs = ...
054     * OutputStreamWriter writer = new OutputStreamWriter(out, cs);
055     * WriterOutputStream out2 = new WriterOutputStream(writer, cs);</pre>
056     * {@link WriterOutputStream} implements the same transformation as {@link java.io.InputStreamReader},
057     * except that the control flow is reversed: both classes transform a byte stream
058     * into a character stream, but {@link java.io.InputStreamReader} pulls data from the underlying stream,
059     * while {@link WriterOutputStream} pushes it to the underlying stream.
060     * <p>
061     * Note that while there are use cases where there is no alternative to using
062     * this class, very often the need to use this class is an indication of a flaw
063     * in the design of the code. This class is typically used in situations where an existing
064     * API only accepts an {@link OutputStream} object, but where the stream is known to represent
065     * character data that must be decoded for further use.
066     * <p>
067     * Instances of {@link WriterOutputStream} are not thread safe.
068     * 
069     * @see org.apache.commons.io.input.ReaderInputStream
070     * 
071     * @author <a href="mailto:veithen@apache.org">Andreas Veithen</a>
072     * @since Commons IO 2.0
073     */
074    public class WriterOutputStream extends OutputStream {
075        private static final int DEFAULT_BUFFER_SIZE = 1024;
076    
077        private final Writer writer;
078        private final CharsetDecoder decoder;
079        private final boolean writeImmediately;
080    
081        /**
082         * ByteBuffer used as input for the decoder. This buffer can be small
083         * as it is used only to transfer the received data to the
084         * decoder.
085         */
086        private final ByteBuffer decoderIn = ByteBuffer.allocate(128);
087    
088        /**
089         * CharBuffer used as output for the decoder. It should be
090         * somewhat larger as we write from this buffer to the
091         * underlying Writer.
092         */
093        private final CharBuffer decoderOut;
094    
095        /**
096         * Constructs a new {@link WriterOutputStream} with a default output buffer size of
097         * 1024 characters. The output buffer will only be flushed when it overflows or when
098         * {@link #flush()} or {@link #close()} is called.
099         * 
100         * @param writer the target {@link Writer}
101         * @param decoder the charset decoder
102         * @since Commons IO 2.1
103         */
104        public WriterOutputStream(Writer writer, CharsetDecoder decoder) {
105            this(writer, decoder, DEFAULT_BUFFER_SIZE, false);
106        }
107    
108        /**
109         * Constructs a new {@link WriterOutputStream}.
110         * 
111         * @param writer the target {@link Writer}
112         * @param decoder the charset decoder
113         * @param bufferSize the size of the output buffer in number of characters
114         * @param writeImmediately If <tt>true</tt> the output buffer will be flushed after each
115         *                         write operation, i.e. all available data will be written to the
116         *                         underlying {@link Writer} immediately. If <tt>false</tt>, the
117         *                         output buffer will only be flushed when it overflows or when
118         *                         {@link #flush()} or {@link #close()} is called.
119         * @since Commons IO 2.1
120         */
121        public WriterOutputStream(Writer writer, CharsetDecoder decoder, int bufferSize, boolean writeImmediately) {
122            this.writer = writer;
123            this.decoder = decoder;
124            this.writeImmediately = writeImmediately;
125            decoderOut = CharBuffer.allocate(bufferSize);
126        }
127    
128        /**
129         * Constructs a new {@link WriterOutputStream}.
130         * 
131         * @param writer the target {@link Writer}
132         * @param charset the charset encoding
133         * @param bufferSize the size of the output buffer in number of characters
134         * @param writeImmediately If <tt>true</tt> the output buffer will be flushed after each
135         *                         write operation, i.e. all available data will be written to the
136         *                         underlying {@link Writer} immediately. If <tt>false</tt>, the
137         *                         output buffer will only be flushed when it overflows or when
138         *                         {@link #flush()} or {@link #close()} is called.
139         */
140        public WriterOutputStream(Writer writer, Charset charset, int bufferSize, boolean writeImmediately) {
141            this(writer,
142                 charset.newDecoder()
143                        .onMalformedInput(CodingErrorAction.REPLACE)
144                        .onUnmappableCharacter(CodingErrorAction.REPLACE)
145                        .replaceWith("?"),
146                 bufferSize,
147                 writeImmediately);
148        }
149    
150        /**
151         * Constructs a new {@link WriterOutputStream} with a default output buffer size of
152         * 1024 characters. The output buffer will only be flushed when it overflows or when
153         * {@link #flush()} or {@link #close()} is called.
154         * 
155         * @param writer the target {@link Writer}
156         * @param charset the charset encoding
157         */
158        public WriterOutputStream(Writer writer, Charset charset) {
159            this(writer, charset, DEFAULT_BUFFER_SIZE, false);
160        }
161    
162        /**
163         * Constructs a new {@link WriterOutputStream}.
164         * 
165         * @param writer the target {@link Writer}
166         * @param charsetName the name of the charset encoding
167         * @param bufferSize the size of the output buffer in number of characters
168         * @param writeImmediately If <tt>true</tt> the output buffer will be flushed after each
169         *                         write operation, i.e. all available data will be written to the
170         *                         underlying {@link Writer} immediately. If <tt>false</tt>, the
171         *                         output buffer will only be flushed when it overflows or when
172         *                         {@link #flush()} or {@link #close()} is called.
173         */
174        public WriterOutputStream(Writer writer, String charsetName, int bufferSize, boolean writeImmediately) {
175            this(writer, Charset.forName(charsetName), bufferSize, writeImmediately);
176        }
177    
178        /**
179         * Constructs a new {@link WriterOutputStream} with a default output buffer size of
180         * 1024 characters. The output buffer will only be flushed when it overflows or when
181         * {@link #flush()} or {@link #close()} is called.
182         * 
183         * @param writer the target {@link Writer}
184         * @param charsetName the name of the charset encoding
185         */
186        public WriterOutputStream(Writer writer, String charsetName) {
187            this(writer, charsetName, DEFAULT_BUFFER_SIZE, false);
188        }
189    
190        /**
191         * Constructs a new {@link WriterOutputStream} that uses the default character encoding
192         * and with a default output buffer size of 1024 characters. The output buffer will only
193         * be flushed when it overflows or when {@link #flush()} or {@link #close()} is called.
194         * 
195         * @param writer the target {@link Writer}
196         */
197        public WriterOutputStream(Writer writer) {
198            this(writer, Charset.defaultCharset(), DEFAULT_BUFFER_SIZE, false);
199        }
200    
201        /**
202         * Write bytes from the specified byte array to the stream.
203         * 
204         * @param b the byte array containing the bytes to write
205         * @param off the start offset in the byte array
206         * @param len the number of bytes to write
207         * @throws IOException if an I/O error occurs
208         */
209        @Override
210        public void write(byte[] b, int off, int len) throws IOException {
211            while (len > 0) {
212                int c = Math.min(len, decoderIn.remaining());
213                decoderIn.put(b, off, c);
214                processInput(false);
215                len -= c;
216                off += c;
217            }
218            if (writeImmediately) {
219                flushOutput();
220            }
221        }
222    
223        /**
224         * Write bytes from the specified byte array to the stream.
225         * 
226         * @param b the byte array containing the bytes to write
227         * @throws IOException if an I/O error occurs
228         */
229        @Override
230        public void write(byte[] b) throws IOException {
231            write(b, 0, b.length);
232        }
233    
234        /**
235         * Write a single byte to the stream.
236         * 
237         * @param b the byte to write
238         * @throws IOException if an I/O error occurs
239         */
240        @Override
241        public void write(int b) throws IOException {
242            write(new byte[] { (byte)b }, 0, 1);
243        }
244    
245        /**
246         * Flush the stream. Any remaining content accumulated in the output buffer
247         * will be written to the underlying {@link Writer}. After that
248         * {@link Writer#flush()} will be called. 
249         * @throws IOException if an I/O error occurs
250         */
251        @Override
252        public void flush() throws IOException {
253            flushOutput();
254            writer.flush();
255        }
256    
257        /**
258         * Close the stream. Any remaining content accumulated in the output buffer
259         * will be written to the underlying {@link Writer}. After that
260         * {@link Writer#close()} will be called. 
261         * @throws IOException if an I/O error occurs
262         */
263        @Override
264        public void close() throws IOException {
265            processInput(true);
266            flushOutput();
267            writer.close();
268        }
269    
270        /**
271         * Decode the contents of the input ByteBuffer into a CharBuffer.
272         * 
273         * @param endOfInput indicates end of input
274         * @throws IOException if an I/O error occurs
275         */
276        private void processInput(boolean endOfInput) throws IOException {
277            // Prepare decoderIn for reading
278            decoderIn.flip();
279            CoderResult coderResult;
280            while (true) {
281                coderResult = decoder.decode(decoderIn, decoderOut, endOfInput);
282                if (coderResult.isOverflow()) {
283                    flushOutput();
284                } else if (coderResult.isUnderflow()) {
285                    break;
286                } else {
287                    // The decoder is configured to replace malformed input and unmappable characters,
288                    // so we should not get here.
289                    throw new IOException("Unexpected coder result");
290                }
291            }
292            // Discard the bytes that have been read
293            decoderIn.compact();
294        }
295    
296        /**
297         * Flush the output.
298         * 
299         * @throws IOException if an I/O error occurs
300         */
301        private void flushOutput() throws IOException {
302            if (decoderOut.position() > 0) {
303                writer.write(decoderOut.array(), 0, decoderOut.position());
304                decoderOut.rewind();
305            }
306        }
307    }