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     * @since 2.0
072     */
073    public class WriterOutputStream extends OutputStream {
074        private static final int DEFAULT_BUFFER_SIZE = 1024;
075    
076        private final Writer writer;
077        private final CharsetDecoder decoder;
078        private final boolean writeImmediately;
079    
080        /**
081         * ByteBuffer used as input for the decoder. This buffer can be small
082         * as it is used only to transfer the received data to the
083         * decoder.
084         */
085        private final ByteBuffer decoderIn = ByteBuffer.allocate(128);
086    
087        /**
088         * CharBuffer used as output for the decoder. It should be
089         * somewhat larger as we write from this buffer to the
090         * underlying Writer.
091         */
092        private final CharBuffer decoderOut;
093    
094        /**
095         * Constructs a new {@link WriterOutputStream} with a default output buffer size of
096         * 1024 characters. The output buffer will only be flushed when it overflows or when
097         * {@link #flush()} or {@link #close()} is called.
098         * 
099         * @param writer the target {@link Writer}
100         * @param decoder the charset decoder
101         * @since 2.1
102         */
103        public WriterOutputStream(Writer writer, CharsetDecoder decoder) {
104            this(writer, decoder, DEFAULT_BUFFER_SIZE, false);
105        }
106    
107        /**
108         * Constructs a new {@link WriterOutputStream}.
109         * 
110         * @param writer the target {@link Writer}
111         * @param decoder the charset decoder
112         * @param bufferSize the size of the output buffer in number of characters
113         * @param writeImmediately If <tt>true</tt> the output buffer will be flushed after each
114         *                         write operation, i.e. all available data will be written to the
115         *                         underlying {@link Writer} immediately. If <tt>false</tt>, the
116         *                         output buffer will only be flushed when it overflows or when
117         *                         {@link #flush()} or {@link #close()} is called.
118         * @since 2.1
119         */
120        public WriterOutputStream(Writer writer, CharsetDecoder decoder, int bufferSize, boolean writeImmediately) {
121            this.writer = writer;
122            this.decoder = decoder;
123            this.writeImmediately = writeImmediately;
124            decoderOut = CharBuffer.allocate(bufferSize);
125        }
126    
127        /**
128         * Constructs a new {@link WriterOutputStream}.
129         * 
130         * @param writer the target {@link Writer}
131         * @param charset the charset encoding
132         * @param bufferSize the size of the output buffer in number of characters
133         * @param writeImmediately If <tt>true</tt> the output buffer will be flushed after each
134         *                         write operation, i.e. all available data will be written to the
135         *                         underlying {@link Writer} immediately. If <tt>false</tt>, the
136         *                         output buffer will only be flushed when it overflows or when
137         *                         {@link #flush()} or {@link #close()} is called.
138         */
139        public WriterOutputStream(Writer writer, Charset charset, int bufferSize, boolean writeImmediately) {
140            this(writer,
141                 charset.newDecoder()
142                        .onMalformedInput(CodingErrorAction.REPLACE)
143                        .onUnmappableCharacter(CodingErrorAction.REPLACE)
144                        .replaceWith("?"),
145                 bufferSize,
146                 writeImmediately);
147        }
148    
149        /**
150         * Constructs a new {@link WriterOutputStream} with a default output buffer size of
151         * 1024 characters. The output buffer will only be flushed when it overflows or when
152         * {@link #flush()} or {@link #close()} is called.
153         * 
154         * @param writer the target {@link Writer}
155         * @param charset the charset encoding
156         */
157        public WriterOutputStream(Writer writer, Charset charset) {
158            this(writer, charset, DEFAULT_BUFFER_SIZE, false);
159        }
160    
161        /**
162         * Constructs a new {@link WriterOutputStream}.
163         * 
164         * @param writer the target {@link Writer}
165         * @param charsetName the name of the charset encoding
166         * @param bufferSize the size of the output buffer in number of characters
167         * @param writeImmediately If <tt>true</tt> the output buffer will be flushed after each
168         *                         write operation, i.e. all available data will be written to the
169         *                         underlying {@link Writer} immediately. If <tt>false</tt>, the
170         *                         output buffer will only be flushed when it overflows or when
171         *                         {@link #flush()} or {@link #close()} is called.
172         */
173        public WriterOutputStream(Writer writer, String charsetName, int bufferSize, boolean writeImmediately) {
174            this(writer, Charset.forName(charsetName), bufferSize, writeImmediately);
175        }
176    
177        /**
178         * Constructs a new {@link WriterOutputStream} with a default output buffer size of
179         * 1024 characters. The output buffer will only be flushed when it overflows or when
180         * {@link #flush()} or {@link #close()} is called.
181         * 
182         * @param writer the target {@link Writer}
183         * @param charsetName the name of the charset encoding
184         */
185        public WriterOutputStream(Writer writer, String charsetName) {
186            this(writer, charsetName, DEFAULT_BUFFER_SIZE, false);
187        }
188    
189        /**
190         * Constructs a new {@link WriterOutputStream} that uses the default character encoding
191         * and with a default output buffer size of 1024 characters. The output buffer will only
192         * be flushed when it overflows or when {@link #flush()} or {@link #close()} is called.
193         * 
194         * @param writer the target {@link Writer}
195         */
196        public WriterOutputStream(Writer writer) {
197            this(writer, Charset.defaultCharset(), DEFAULT_BUFFER_SIZE, false);
198        }
199    
200        /**
201         * Write bytes from the specified byte array to the stream.
202         * 
203         * @param b the byte array containing the bytes to write
204         * @param off the start offset in the byte array
205         * @param len the number of bytes to write
206         * @throws IOException if an I/O error occurs
207         */
208        @Override
209        public void write(byte[] b, int off, int len) throws IOException {
210            while (len > 0) {
211                int c = Math.min(len, decoderIn.remaining());
212                decoderIn.put(b, off, c);
213                processInput(false);
214                len -= c;
215                off += c;
216            }
217            if (writeImmediately) {
218                flushOutput();
219            }
220        }
221    
222        /**
223         * Write bytes from the specified byte array to the stream.
224         * 
225         * @param b the byte array containing the bytes to write
226         * @throws IOException if an I/O error occurs
227         */
228        @Override
229        public void write(byte[] b) throws IOException {
230            write(b, 0, b.length);
231        }
232    
233        /**
234         * Write a single byte to the stream.
235         * 
236         * @param b the byte to write
237         * @throws IOException if an I/O error occurs
238         */
239        @Override
240        public void write(int b) throws IOException {
241            write(new byte[] { (byte)b }, 0, 1);
242        }
243    
244        /**
245         * Flush the stream. Any remaining content accumulated in the output buffer
246         * will be written to the underlying {@link Writer}. After that
247         * {@link Writer#flush()} will be called. 
248         * @throws IOException if an I/O error occurs
249         */
250        @Override
251        public void flush() throws IOException {
252            flushOutput();
253            writer.flush();
254        }
255    
256        /**
257         * Close the stream. Any remaining content accumulated in the output buffer
258         * will be written to the underlying {@link Writer}. After that
259         * {@link Writer#close()} will be called. 
260         * @throws IOException if an I/O error occurs
261         */
262        @Override
263        public void close() throws IOException {
264            processInput(true);
265            flushOutput();
266            writer.close();
267        }
268    
269        /**
270         * Decode the contents of the input ByteBuffer into a CharBuffer.
271         * 
272         * @param endOfInput indicates end of input
273         * @throws IOException if an I/O error occurs
274         */
275        private void processInput(boolean endOfInput) throws IOException {
276            // Prepare decoderIn for reading
277            decoderIn.flip();
278            CoderResult coderResult;
279            while (true) {
280                coderResult = decoder.decode(decoderIn, decoderOut, endOfInput);
281                if (coderResult.isOverflow()) {
282                    flushOutput();
283                } else if (coderResult.isUnderflow()) {
284                    break;
285                } else {
286                    // The decoder is configured to replace malformed input and unmappable characters,
287                    // so we should not get here.
288                    throw new IOException("Unexpected coder result");
289                }
290            }
291            // Discard the bytes that have been read
292            decoderIn.compact();
293        }
294    
295        /**
296         * Flush the output.
297         * 
298         * @throws IOException if an I/O error occurs
299         */
300        private void flushOutput() throws IOException {
301            if (decoderOut.position() > 0) {
302                writer.write(decoderOut.array(), 0, decoderOut.position());
303                decoderOut.rewind();
304            }
305        }
306    }