/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.commons.io.output;

import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;
import java.nio.charset.CodingErrorAction;

OutputStream implementation that transforms a byte stream to a character stream using a specified charset encoding and writes the resulting stream to a Writer. The stream is transformed using a CharsetDecoder object, guaranteeing that all charset encodings supported by the JRE are handled correctly.

The output of the CharsetDecoder is buffered using a fixed size buffer. This implies that the data is written to the underlying Writer in chunks that are no larger than the size of this buffer. By default, the buffer is flushed only when it overflows or when flush() or close() is called. In general there is therefore no need to wrap the underlying Writer in a BufferedWriter. WriterOutputStream can also be instructed to flush the buffer after each write operation. In this case, all available data is written immediately to the underlying Writer, implying that the current position of the Writer is correlated to the current position of the WriterOutputStream.

WriterOutputStream implements the inverse transformation of OutputStreamWriter; in the following example, writing to out2 would have the same result as writing to out directly (provided that the byte sequence is legal with respect to the charset encoding):

OutputStream out = ...
Charset cs = ...
OutputStreamWriter writer = new OutputStreamWriter(out, cs);
WriterOutputStream out2 = new WriterOutputStream(writer, cs);
WriterOutputStream implements the same transformation as InputStreamReader, except that the control flow is reversed: both classes transform a byte stream into a character stream, but InputStreamReader pulls data from the underlying stream, while WriterOutputStream pushes it to the underlying stream.

Note that while there are use cases where there is no alternative to using this class, very often the need to use this class is an indication of a flaw in the design of the code. This class is typically used in situations where an existing API only accepts an OutputStream object, but where the stream is known to represent character data that must be decoded for further use.

Instances of WriterOutputStream are not thread safe.

See Also:
Since:2.0
/** * {@link OutputStream} implementation that transforms a byte stream to a * character stream using a specified charset encoding and writes the resulting * stream to a {@link Writer}. The stream is transformed using a * {@link CharsetDecoder} object, guaranteeing that all charset * encodings supported by the JRE are handled correctly. * <p> * The output of the {@link CharsetDecoder} is buffered using a fixed size buffer. * This implies that the data is written to the underlying {@link Writer} in chunks * that are no larger than the size of this buffer. By default, the buffer is * flushed only when it overflows or when {@link #flush()} or {@link #close()} * is called. In general there is therefore no need to wrap the underlying {@link Writer} * in a {@link java.io.BufferedWriter}. {@link WriterOutputStream} can also * be instructed to flush the buffer after each write operation. In this case, all * available data is written immediately to the underlying {@link Writer}, implying that * the current position of the {@link Writer} is correlated to the current position * of the {@link WriterOutputStream}. * <p> * {@link WriterOutputStream} implements the inverse transformation of {@link java.io.OutputStreamWriter}; * in the following example, writing to {@code out2} would have the same result as writing to * {@code out} directly (provided that the byte sequence is legal with respect to the * charset encoding): * <pre> * OutputStream out = ... * Charset cs = ... * OutputStreamWriter writer = new OutputStreamWriter(out, cs); * WriterOutputStream out2 = new WriterOutputStream(writer, cs);</pre> * {@link WriterOutputStream} implements the same transformation as {@link java.io.InputStreamReader}, * except that the control flow is reversed: both classes transform a byte stream * into a character stream, but {@link java.io.InputStreamReader} pulls data from the underlying stream, * while {@link WriterOutputStream} pushes it to the underlying stream. * <p> * Note that while there are use cases where there is no alternative to using * this class, very often the need to use this class is an indication of a flaw * in the design of the code. This class is typically used in situations where an existing * API only accepts an {@link OutputStream} object, but where the stream is known to represent * character data that must be decoded for further use. * <p> * Instances of {@link WriterOutputStream} are not thread safe. * * @see org.apache.commons.io.input.ReaderInputStream * * @since 2.0 */
public class WriterOutputStream extends OutputStream { private static final int DEFAULT_BUFFER_SIZE = 1024; private final Writer writer; private final CharsetDecoder decoder; private final boolean writeImmediately;
ByteBuffer used as input for the decoder. This buffer can be small as it is used only to transfer the received data to the decoder.
/** * ByteBuffer used as input for the decoder. This buffer can be small * as it is used only to transfer the received data to the * decoder. */
private final ByteBuffer decoderIn = ByteBuffer.allocate(128);
CharBuffer used as output for the decoder. It should be somewhat larger as we write from this buffer to the underlying Writer.
/** * CharBuffer used as output for the decoder. It should be * somewhat larger as we write from this buffer to the * underlying Writer. */
private final CharBuffer decoderOut;
Constructs a new WriterOutputStream with a default output buffer size of 1024 characters. The output buffer will only be flushed when it overflows or when flush() or close() is called.
Params:
  • writer – the target Writer
  • decoder – the charset decoder
Since:2.1
/** * Constructs a new {@link WriterOutputStream} with a default output buffer size of * 1024 characters. The output buffer will only be flushed when it overflows or when * {@link #flush()} or {@link #close()} is called. * * @param writer the target {@link Writer} * @param decoder the charset decoder * @since 2.1 */
public WriterOutputStream(final Writer writer, final CharsetDecoder decoder) { this(writer, decoder, DEFAULT_BUFFER_SIZE, false); }
Constructs a new WriterOutputStream.
Params:
  • writer – the target Writer
  • decoder – the charset decoder
  • bufferSize – the size of the output buffer in number of characters
  • writeImmediately – If true the output buffer will be flushed after each write operation, i.e. all available data will be written to the underlying Writer immediately. If false, the output buffer will only be flushed when it overflows or when flush() or close() is called.
Since:2.1
/** * Constructs a new {@link WriterOutputStream}. * * @param writer the target {@link Writer} * @param decoder the charset decoder * @param bufferSize the size of the output buffer in number of characters * @param writeImmediately If {@code true} the output buffer will be flushed after each * write operation, i.e. all available data will be written to the * underlying {@link Writer} immediately. If {@code false}, the * output buffer will only be flushed when it overflows or when * {@link #flush()} or {@link #close()} is called. * @since 2.1 */
public WriterOutputStream(final Writer writer, final CharsetDecoder decoder, final int bufferSize, final boolean writeImmediately) { checkIbmJdkWithBrokenUTF16( decoder.charset()); this.writer = writer; this.decoder = decoder; this.writeImmediately = writeImmediately; decoderOut = CharBuffer.allocate(bufferSize); }
Constructs a new WriterOutputStream.
Params:
  • writer – the target Writer
  • charset – the charset encoding
  • bufferSize – the size of the output buffer in number of characters
  • writeImmediately – If true the output buffer will be flushed after each write operation, i.e. all available data will be written to the underlying Writer immediately. If false, the output buffer will only be flushed when it overflows or when flush() or close() is called.
/** * Constructs a new {@link WriterOutputStream}. * * @param writer the target {@link Writer} * @param charset the charset encoding * @param bufferSize the size of the output buffer in number of characters * @param writeImmediately If {@code true} the output buffer will be flushed after each * write operation, i.e. all available data will be written to the * underlying {@link Writer} immediately. If {@code false}, the * output buffer will only be flushed when it overflows or when * {@link #flush()} or {@link #close()} is called. */
public WriterOutputStream(final Writer writer, final Charset charset, final int bufferSize, final boolean writeImmediately) { this(writer, charset.newDecoder() .onMalformedInput(CodingErrorAction.REPLACE) .onUnmappableCharacter(CodingErrorAction.REPLACE) .replaceWith("?"), bufferSize, writeImmediately); }
Constructs a new WriterOutputStream with a default output buffer size of 1024 characters. The output buffer will only be flushed when it overflows or when flush() or close() is called.
Params:
  • writer – the target Writer
  • charset – the charset encoding
/** * Constructs a new {@link WriterOutputStream} with a default output buffer size of * 1024 characters. The output buffer will only be flushed when it overflows or when * {@link #flush()} or {@link #close()} is called. * * @param writer the target {@link Writer} * @param charset the charset encoding */
public WriterOutputStream(final Writer writer, final Charset charset) { this(writer, charset, DEFAULT_BUFFER_SIZE, false); }
Constructs a new WriterOutputStream.
Params:
  • writer – the target Writer
  • charsetName – the name of the charset encoding
  • bufferSize – the size of the output buffer in number of characters
  • writeImmediately – If true the output buffer will be flushed after each write operation, i.e. all available data will be written to the underlying Writer immediately. If false, the output buffer will only be flushed when it overflows or when flush() or close() is called.
/** * Constructs a new {@link WriterOutputStream}. * * @param writer the target {@link Writer} * @param charsetName the name of the charset encoding * @param bufferSize the size of the output buffer in number of characters * @param writeImmediately If {@code true} the output buffer will be flushed after each * write operation, i.e. all available data will be written to the * underlying {@link Writer} immediately. If {@code false}, the * output buffer will only be flushed when it overflows or when * {@link #flush()} or {@link #close()} is called. */
public WriterOutputStream(final Writer writer, final String charsetName, final int bufferSize, final boolean writeImmediately) { this(writer, Charset.forName(charsetName), bufferSize, writeImmediately); }
Constructs a new WriterOutputStream with a default output buffer size of 1024 characters. The output buffer will only be flushed when it overflows or when flush() or close() is called.
Params:
  • writer – the target Writer
  • charsetName – the name of the charset encoding
/** * Constructs a new {@link WriterOutputStream} with a default output buffer size of * 1024 characters. The output buffer will only be flushed when it overflows or when * {@link #flush()} or {@link #close()} is called. * * @param writer the target {@link Writer} * @param charsetName the name of the charset encoding */
public WriterOutputStream(final Writer writer, final String charsetName) { this(writer, charsetName, DEFAULT_BUFFER_SIZE, false); }
Constructs a new WriterOutputStream that uses the default character encoding and with a default output buffer size of 1024 characters. The output buffer will only be flushed when it overflows or when flush() or close() is called.
Params:
Deprecated:2.5 use WriterOutputStream(Writer, Charset) instead
/** * Constructs a new {@link WriterOutputStream} that uses the default character encoding * and with a default output buffer size of 1024 characters. The output buffer will only * be flushed when it overflows or when {@link #flush()} or {@link #close()} is called. * * @param writer the target {@link Writer} * @deprecated 2.5 use {@link #WriterOutputStream(Writer, Charset)} instead */
@Deprecated public WriterOutputStream(final Writer writer) { this(writer, Charset.defaultCharset(), DEFAULT_BUFFER_SIZE, false); }
Write bytes from the specified byte array to the stream.
Params:
  • b – the byte array containing the bytes to write
  • off – the start offset in the byte array
  • len – the number of bytes to write
Throws:
/** * Write bytes from the specified byte array to the stream. * * @param b the byte array containing the bytes to write * @param off the start offset in the byte array * @param len the number of bytes to write * @throws IOException if an I/O error occurs */
@Override public void write(final byte[] b, int off, int len) throws IOException { while (len > 0) { final int c = Math.min(len, decoderIn.remaining()); decoderIn.put(b, off, c); processInput(false); len -= c; off += c; } if (writeImmediately) { flushOutput(); } }
Write bytes from the specified byte array to the stream.
Params:
  • b – the byte array containing the bytes to write
Throws:
/** * Write bytes from the specified byte array to the stream. * * @param b the byte array containing the bytes to write * @throws IOException if an I/O error occurs */
@Override public void write(final byte[] b) throws IOException { write(b, 0, b.length); }
Write a single byte to the stream.
Params:
  • b – the byte to write
Throws:
/** * Write a single byte to the stream. * * @param b the byte to write * @throws IOException if an I/O error occurs */
@Override public void write(final int b) throws IOException { write(new byte[] { (byte)b }, 0, 1); }
Flush the stream. Any remaining content accumulated in the output buffer will be written to the underlying Writer. After that Writer.flush() will be called.
Throws:
/** * Flush the stream. Any remaining content accumulated in the output buffer * will be written to the underlying {@link Writer}. After that * {@link Writer#flush()} will be called. * @throws IOException if an I/O error occurs */
@Override public void flush() throws IOException { flushOutput(); writer.flush(); }
Close the stream. Any remaining content accumulated in the output buffer will be written to the underlying Writer. After that Writer.close() will be called.
Throws:
/** * Close the stream. Any remaining content accumulated in the output buffer * will be written to the underlying {@link Writer}. After that * {@link Writer#close()} will be called. * @throws IOException if an I/O error occurs */
@Override public void close() throws IOException { processInput(true); flushOutput(); writer.close(); }
Decode the contents of the input ByteBuffer into a CharBuffer.
Params:
  • endOfInput – indicates end of input
Throws:
/** * Decode the contents of the input ByteBuffer into a CharBuffer. * * @param endOfInput indicates end of input * @throws IOException if an I/O error occurs */
private void processInput(final boolean endOfInput) throws IOException { // Prepare decoderIn for reading decoderIn.flip(); CoderResult coderResult; while (true) { coderResult = decoder.decode(decoderIn, decoderOut, endOfInput); if (coderResult.isOverflow()) { flushOutput(); } else if (coderResult.isUnderflow()) { break; } else { // The decoder is configured to replace malformed input and unmappable characters, // so we should not get here. throw new IOException("Unexpected coder result"); } } // Discard the bytes that have been read decoderIn.compact(); }
Flush the output.
Throws:
  • IOException – if an I/O error occurs
/** * Flush the output. * * @throws IOException if an I/O error occurs */
private void flushOutput() throws IOException { if (decoderOut.position() > 0) { writer.write(decoderOut.array(), 0, decoderOut.position()); decoderOut.rewind(); } }
Check if the JDK in use properly supports the given charset.
Params:
  • charset – the charset to check the support for
/** * Check if the JDK in use properly supports the given charset. * * @param charset the charset to check the support for */
private static void checkIbmJdkWithBrokenUTF16(final Charset charset){ if (!"UTF-16".equals(charset.name())) { return; } final String TEST_STRING_2 = "v\u00e9s"; final byte[] bytes = TEST_STRING_2.getBytes(charset); final CharsetDecoder charsetDecoder2 = charset.newDecoder(); final ByteBuffer bb2 = ByteBuffer.allocate(16); final CharBuffer cb2 = CharBuffer.allocate(TEST_STRING_2.length()); final int len = bytes.length; for (int i = 0; i < len; i++) { bb2.put(bytes[i]); bb2.flip(); try { charsetDecoder2.decode(bb2, cb2, i == (len - 1)); } catch ( final IllegalArgumentException e){ throw new UnsupportedOperationException("UTF-16 requested when runninng on an IBM JDK with broken UTF-16 support. " + "Please find a JDK that supports UTF-16 if you intend to use UF-16 with WriterOutputStream"); } bb2.compact(); } cb2.rewind(); if (!TEST_STRING_2.equals(cb2.toString())){ throw new UnsupportedOperationException("UTF-16 requested when runninng on an IBM JDK with broken UTF-16 support. " + "Please find a JDK that supports UTF-16 if you intend to use UF-16 with WriterOutputStream"); }; } }