Coverage Report - org.apache.commons.io.output.WriterOutputStream
 
Classes in this File Line Coverage Branch Coverage Complexity
WriterOutputStream
86%
46/53
90%
9/10
1.5
 
 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  24
     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  0
         this(writer, decoder, DEFAULT_BUFFER_SIZE, false);
 105  0
     }
 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  24
     public WriterOutputStream(final Writer writer, final CharsetDecoder decoder, final int bufferSize, final boolean writeImmediately) {
 121  24
         this.writer = writer;
 122  24
         this.decoder = decoder;
 123  24
         this.writeImmediately = writeImmediately;
 124  24
         decoderOut = CharBuffer.allocate(bufferSize);
 125  24
     }
 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  24
         this(writer,
 141  
              charset.newDecoder()
 142  
                     .onMalformedInput(CodingErrorAction.REPLACE)
 143  
                     .onUnmappableCharacter(CodingErrorAction.REPLACE)
 144  
                     .replaceWith("?"),
 145  
              bufferSize,
 146  
              writeImmediately);
 147  24
     }
 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  0
         this(writer, charset, DEFAULT_BUFFER_SIZE, false);
 159  0
     }
 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  24
         this(writer, Charset.forName(charsetName), bufferSize, writeImmediately);
 175  24
     }
 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  20
         this(writer, charsetName, DEFAULT_BUFFER_SIZE, false);
 187  20
     }
 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  0
         this(writer, Charset.defaultCharset(), DEFAULT_BUFFER_SIZE, false);
 200  0
     }
 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  20819
         while (len > 0) {
 213  10409
             final int c = Math.min(len, decoderIn.remaining());
 214  10409
             decoderIn.put(b, off, c);
 215  10409
             processInput(false);
 216  10409
             len -= c;
 217  10409
             off += c;
 218  10409
         }
 219  10410
         if (writeImmediately) {
 220  2
             flushOutput();
 221  
         }
 222  10410
     }
 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  4
         write(b, 0, b.length);
 233  4
     }
 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  10240
         write(new byte[] { (byte)b }, 0, 1);
 244  10240
     }
 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  2
         flushOutput();
 255  2
         writer.flush();
 256  2
     }
 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  24
         processInput(true);
 267  24
         flushOutput();
 268  24
         writer.close();
 269  24
     }
 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  10433
         decoderIn.flip();
 280  
         CoderResult coderResult;
 281  
         while (true) {
 282  10449
             coderResult = decoder.decode(decoderIn, decoderOut, endOfInput);
 283  10449
             if (coderResult.isOverflow()) {
 284  16
                 flushOutput();
 285  10433
             } else if (coderResult.isUnderflow()) {
 286  10433
                 break;
 287  
             } else {
 288  
                 // The decoder is configured to replace malformed input and unmappable characters,
 289  
                 // so we should not get here.
 290  0
                 throw new IOException("Unexpected coder result");
 291  
             }
 292  
         }
 293  
         // Discard the bytes that have been read
 294  10433
         decoderIn.compact();
 295  10433
     }
 296  
 
 297  
     /**
 298  
      * Flush the output.
 299  
      * 
 300  
      * @throws IOException if an I/O error occurs
 301  
      */
 302  
     private void flushOutput() throws IOException {
 303  44
         if (decoderOut.position() > 0) {
 304  40
             writer.write(decoderOut.array(), 0, decoderOut.position());
 305  40
             decoderOut.rewind();
 306  
         }
 307  44
     }
 308  
 }