View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.io.output;
18  
19  import java.io.IOException;
20  import java.io.OutputStream;
21  import java.io.Writer;
22  import java.nio.ByteBuffer;
23  import java.nio.CharBuffer;
24  import java.nio.charset.Charset;
25  import java.nio.charset.CharsetDecoder;
26  import java.nio.charset.CoderResult;
27  import java.nio.charset.CodingErrorAction;
28  
29  /**
30   * {@link OutputStream} implementation that transforms a byte stream to a
31   * character stream using a specified charset encoding and writes the resulting
32   * stream to a {@link Writer}. The stream is transformed using a
33   * {@link CharsetDecoder} object, guaranteeing that all charset
34   * encodings supported by the JRE are handled correctly.
35   * <p>
36   * The output of the {@link CharsetDecoder} is buffered using a fixed size buffer.
37   * This implies that the data is written to the underlying {@link Writer} in chunks
38   * that are no larger than the size of this buffer. By default, the buffer is
39   * flushed only when it overflows or when {@link #flush()} or {@link #close()}
40   * is called. In general there is therefore no need to wrap the underlying {@link Writer}
41   * in a {@link java.io.BufferedWriter}. {@link WriterOutputStream} can also
42   * be instructed to flush the buffer after each write operation. In this case, all
43   * available data is written immediately to the underlying {@link Writer}, implying that
44   * the current position of the {@link Writer} is correlated to the current position
45   * of the {@link WriterOutputStream}.
46   * <p>
47   * {@link WriterOutputStream} implements the inverse transformation of {@link java.io.OutputStreamWriter};
48   * in the following example, writing to <tt>out2</tt> would have the same result as writing to
49   * <tt>out</tt> directly (provided that the byte sequence is legal with respect to the
50   * charset encoding):
51   * <pre>
52   * OutputStream out = ...
53   * Charset cs = ...
54   * OutputStreamWriter writer = new OutputStreamWriter(out, cs);
55   * WriterOutputStream out2 = new WriterOutputStream(writer, cs);</pre>
56   * {@link WriterOutputStream} implements the same transformation as {@link java.io.InputStreamReader},
57   * except that the control flow is reversed: both classes transform a byte stream
58   * into a character stream, but {@link java.io.InputStreamReader} pulls data from the underlying stream,
59   * while {@link WriterOutputStream} pushes it to the underlying stream.
60   * <p>
61   * Note that while there are use cases where there is no alternative to using
62   * this class, very often the need to use this class is an indication of a flaw
63   * in the design of the code. This class is typically used in situations where an existing
64   * API only accepts an {@link OutputStream} object, but where the stream is known to represent
65   * character data that must be decoded for further use.
66   * <p>
67   * Instances of {@link WriterOutputStream} are not thread safe.
68   * 
69   * @see org.apache.commons.io.input.ReaderInputStream
70   * 
71   * @since 2.0
72   */
73  public class WriterOutputStream extends OutputStream {
74      private static final int DEFAULT_BUFFER_SIZE = 1024;
75  
76      private final Writer writer;
77      private final CharsetDecoder decoder;
78      private final boolean writeImmediately;
79  
80      /**
81       * ByteBuffer used as input for the decoder. This buffer can be small
82       * as it is used only to transfer the received data to the
83       * decoder.
84       */
85      private final ByteBuffer decoderIn = ByteBuffer.allocate(128);
86  
87      /**
88       * CharBuffer used as output for the decoder. It should be
89       * somewhat larger as we write from this buffer to the
90       * underlying Writer.
91       */
92      private final CharBuffer decoderOut;
93  
94      /**
95       * Constructs a new {@link WriterOutputStream} with a default output buffer size of
96       * 1024 characters. The output buffer will only be flushed when it overflows or when
97       * {@link #flush()} or {@link #close()} is called.
98       * 
99       * @param writer the target {@link Writer}
100      * @param decoder the charset decoder
101      * @since 2.1
102      */
103     public WriterOutputStream(final Writer writer, final 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(final Writer writer, final CharsetDecoder decoder, final int bufferSize, final 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(final Writer writer, final Charset charset, final int bufferSize, final 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(final Writer writer, final 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(final Writer writer, final String charsetName, final int bufferSize, final 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(final Writer writer, final 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      * @deprecated 2.5 use {@link #WriterOutputStream(Writer, Charset)} instead
196      */
197     @Deprecated
198     public WriterOutputStream(final Writer writer) {
199         this(writer, Charset.defaultCharset(), DEFAULT_BUFFER_SIZE, false);
200     }
201 
202     /**
203      * Write bytes from the specified byte array to the stream.
204      * 
205      * @param b the byte array containing the bytes to write
206      * @param off the start offset in the byte array
207      * @param len the number of bytes to write
208      * @throws IOException if an I/O error occurs
209      */
210     @Override
211     public void write(final byte[] b, int off, int len) throws IOException {
212         while (len > 0) {
213             final int c = Math.min(len, decoderIn.remaining());
214             decoderIn.put(b, off, c);
215             processInput(false);
216             len -= c;
217             off += c;
218         }
219         if (writeImmediately) {
220             flushOutput();
221         }
222     }
223 
224     /**
225      * Write bytes from the specified byte array to the stream.
226      * 
227      * @param b the byte array containing the bytes to write
228      * @throws IOException if an I/O error occurs
229      */
230     @Override
231     public void write(final byte[] b) throws IOException {
232         write(b, 0, b.length);
233     }
234 
235     /**
236      * Write a single byte to the stream.
237      * 
238      * @param b the byte to write
239      * @throws IOException if an I/O error occurs
240      */
241     @Override
242     public void write(final int b) throws IOException {
243         write(new byte[] { (byte)b }, 0, 1);
244     }
245 
246     /**
247      * Flush the stream. Any remaining content accumulated in the output buffer
248      * will be written to the underlying {@link Writer}. After that
249      * {@link Writer#flush()} will be called. 
250      * @throws IOException if an I/O error occurs
251      */
252     @Override
253     public void flush() throws IOException {
254         flushOutput();
255         writer.flush();
256     }
257 
258     /**
259      * Close the stream. Any remaining content accumulated in the output buffer
260      * will be written to the underlying {@link Writer}. After that
261      * {@link Writer#close()} will be called. 
262      * @throws IOException if an I/O error occurs
263      */
264     @Override
265     public void close() throws IOException {
266         processInput(true);
267         flushOutput();
268         writer.close();
269     }
270 
271     /**
272      * Decode the contents of the input ByteBuffer into a CharBuffer.
273      * 
274      * @param endOfInput indicates end of input
275      * @throws IOException if an I/O error occurs
276      */
277     private void processInput(final boolean endOfInput) throws IOException {
278         // Prepare decoderIn for reading
279         decoderIn.flip();
280         CoderResult coderResult;
281         while (true) {
282             coderResult = decoder.decode(decoderIn, decoderOut, endOfInput);
283             if (coderResult.isOverflow()) {
284                 flushOutput();
285             } else if (coderResult.isUnderflow()) {
286                 break;
287             } else {
288                 // The decoder is configured to replace malformed input and unmappable characters,
289                 // so we should not get here.
290                 throw new IOException("Unexpected coder result");
291             }
292         }
293         // Discard the bytes that have been read
294         decoderIn.compact();
295     }
296 
297     /**
298      * Flush the output.
299      * 
300      * @throws IOException if an I/O error occurs
301      */
302     private void flushOutput() throws IOException {
303         if (decoderOut.position() > 0) {
304             writer.write(decoderOut.array(), 0, decoderOut.position());
305             decoderOut.rewind();
306         }
307     }
308 }