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 }