How to convert a Reader to InputStream and a Writer to OutputStream?

Viewed 157707

Is there an easy way to avoid dealing with text encoding problems?

13 Answers

You can't really avoid dealing with the text encoding issues, but there are existing solutions in Apache Commons:

You 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);
                    }
                }
            }
        }
    }

Related