blob: 3ad63a8f38133b1d0293fe91619d522fa6b73ea7 [file] [log] [blame]
/*
* Copyright (c) 2002-2017, the original author or authors.
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
*
* https://opensource.org/licenses/BSD-3-Clause
*/
package jdk.internal.org.jline.utils;
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;
/**
* Redirects an {@link OutputStream} to a {@link Writer} by decoding the data
* using the specified {@link Charset}.
*
* <p><b>Note:</b> This class should only be used if it is necessary to
* redirect an {@link OutputStream} to a {@link Writer} for compatibility
* purposes. It is much more efficient to write to the {@link Writer}
* directly.</p>
*/
public class WriterOutputStream extends OutputStream {
private final Writer out;
private final CharsetDecoder decoder;
private final ByteBuffer decoderIn = ByteBuffer.allocate(256);
private final CharBuffer decoderOut = CharBuffer.allocate(128);
public WriterOutputStream(Writer out, Charset charset) {
this(out, charset.newDecoder()
.onMalformedInput(CodingErrorAction.REPLACE)
.onUnmappableCharacter(CodingErrorAction.REPLACE));
}
public WriterOutputStream(Writer out, CharsetDecoder decoder) {
this.out = out;
this.decoder = decoder;
}
@Override
public void write(int b) throws IOException {
write(new byte[] { (byte)b }, 0, 1);
}
@Override
public void write(byte[] b) throws IOException {
write(b, 0, b.length);
}
@Override
public void write(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;
}
flush();
}
@Override
public void flush() throws IOException {
flushOutput();
out.flush();
}
@Override
public void close() throws IOException {
processInput(true);
flush();
out.close();
}
/**
* 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
*/
private void flushOutput() throws IOException {
if (decoderOut.position() > 0) {
out.write(decoderOut.array(), 0, decoderOut.position());
decoderOut.rewind();
}
}
}