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 }