Skip to content

Commit

Permalink
Fix possible encoding problems with wide chars if written separately
Browse files Browse the repository at this point in the history
  • Loading branch information
gnodet committed Sep 19, 2017
1 parent e689b20 commit 0fa03a9
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 7 deletions.
71 changes: 64 additions & 7 deletions terminal/src/main/java/org/jline/utils/WriterOutputStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@
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
Expand All @@ -25,39 +30,91 @@
public class WriterOutputStream extends OutputStream {

private final Writer out;
private final Charset charset;
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.charset = charset;
this.decoder = decoder;
}

@Override
public void write(int b) throws IOException {
out.write(b);
flush();
write(new byte[] { (byte)b }, 0, 1);
}

@Override
public void write(byte[] b) throws IOException {
out.write(new String(b, this.charset));
flush();
write(b, 0, b.length);
}

@Override
public void write(byte[] b, int off, int len) throws IOException {
out.write(new String(b, off, len, this.charset));
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();
}
}
}
31 changes: 31 additions & 0 deletions terminal/src/test/java/org/jline/utils/WriterOutputStreamTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* 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.
*
* http://www.opensource.org/licenses/bsd-license.php
*/
package org.jline.utils;

import org.junit.Test;

import java.io.StringWriter;
import java.nio.charset.Charset;

import static org.junit.Assert.assertEquals;

public class WriterOutputStreamTest {

@Test
public void testWideChar() throws Exception {
StringWriter sw = new StringWriter();
WriterOutputStream wos = new WriterOutputStream(sw, Charset.forName("UTF-8"));
byte[] bytes = "㐀".getBytes("UTF-8");
for (byte b : bytes) {
wos.write(b);
}
wos.flush();
assertEquals("㐀", sw.toString());
}
}

0 comments on commit 0fa03a9

Please sign in to comment.