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 }