src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/WriterOutputStream.java
changeset 52938 5ff7480c9e28
child 58903 eeb1c0da2126
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/utils/WriterOutputStream.java	Tue Dec 11 11:29:28 2018 +0100
@@ -0,0 +1,120 @@
+/*
+ * 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 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();
+        }
+    }
+}