Is there an easy way to avoid dealing with text encoding problems?
Is there an easy way to avoid dealing with text encoding problems?
You can't really avoid dealing with the text encoding issues, but there are existing solutions in Apache Commons:
Reader to InputStream: ReaderInputStreamWriter to OutputStream: WriterOutputStreamYou just need to pick the encoding of your choice.
Also note that, if you're starting off with a String, you can skip creating a StringReader and create an InputStream in one step using org.apache.commons.io.IOUtils from Commons IO like so:
InputStream myInputStream = IOUtils.toInputStream(reportContents, "UTF-8");
Of course you still need to think about the text encoding, but at least the conversion is happening in one step.
The obvious names for these classes are ReaderInputStream and WriterOutputStream. Unfortunately these are not included in the Java library. However, google is your friend.
I'm not sure that it is going to get around all text encoding problems, which are nightmarish.
There is an RFE, but it's Closed, will not fix.
This is the source code for a simple UTF-8 based encoding WriterOutputStream and ReaderInputStream. Tested at the end.
// https://www.woolha.com/tutorials/deno-utf-8-encoding-decoding-examples
public class WriterOutputStream extends OutputStream {
final Writer writer;
int count = 0;
int codepoint = 0;
public WriterOutputStream(Writer writer) {
this.writer = writer;
}
@Override
public void write(int b) throws IOException {
b &= 0xFF;
switch (b >> 4) {
case 0b0000:
case 0b0001:
case 0b0010:
case 0b0011:
case 0b0100:
case 0b0101:
case 0b0110:
case 0b0111:
count = 1;
codepoint = b;
break;
case 0b1000:
case 0b1001:
case 0b1010:
case 0b1011:
codepoint <<= 6;
codepoint |= b & 0b0011_1111;
break;
case 0b1100:
case 0b1101:
count = 2;
codepoint = b & 0b0001_1111;
break;
case 0b1110:
count = 3;
codepoint = b & 0b0000_1111;
break;
case 0b1111:
count = 4;
codepoint = b & 0b0000_0111;
break;
}
if (--count == 0) {
writer.write(codepoint);
}
}
}
public class ReaderInputStream extends InputStream {
final Reader reader;
int count = 0;
int codepoint;
public ReaderInputStream(Reader reader) {
this.reader = reader;
}
@Override
public int read() throws IOException {
if (count-- > 0) {
int r = codepoint >> (count * 6);
r &= 0b0011_1111;
r |= 0b1000_0000;
return r;
}
codepoint = reader.read();
if (codepoint < 0)
return -1;
if (codepoint > 0xFFFF)
return 0;
if (codepoint < 0x80)
return codepoint;
if (codepoint < 0x800) {
count = 1;
int v = (codepoint >> 6) | 0b1100_0000;
return v;
}
count = 2;
int v = (codepoint >> 12) | 0b1110_0000;
return v;
}
}
And the test case that verifies if each of the 65536 characters is properly encoded and decoded, as well as verifying it matches the Java encoding. The surrogates verification (2 character encoding) are ignored since this is handled in Java.
@Test
public void testAll() throws IOException {
for (char i = 0; i < 0xFFFF; i++) {
CharArrayReader car = new CharArrayReader(new char[] { i });
ReaderInputStream rtoi = new ReaderInputStream(car);
byte[] data = IO.read(rtoi);
CharArrayWriter caw = new CharArrayWriter();
try (WriterOutputStream wtoo = new WriterOutputStream(caw)) {
wtoo.write(data);
char[] translated = caw.toCharArray();
assertThat(translated.length).isEqualTo(1);
assertThat((int) translated[0]).isEqualTo(i);
if (!Character.isSurrogate((char) i)) {
try (InputStream stream = new ByteArrayInputStream(data)) {
caw = new CharArrayWriter();
IO.copy(data, caw);
translated = caw.toCharArray();
assertThat(translated.length).isEqualTo(1);
assertThat((int) translated[0]).isEqualTo(i);
}
}
}
}
}