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