package org.jolokia.util; import java.io.IOException; import java.io.OutputStream; import java.io.Writer; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.*; /** * Created by gnufied on 2/7/16. * Implement chunked writing of data. Part of chunking is actually already * done by OutputStream and doing so here again will result in double chunking. * We just ensure that, we are flushing and closing the stream properly. * * This code is very closely yanked from java.nio.StreamEncoder class. * The reason we couldn't simply extend StreamEncoder class is, that class marks certain * attributes private which are very important for overriding close and flush methods. */ public class ChunkedWriter extends Writer { private OutputStream out; private Charset cs; private CharsetEncoder encoder; private ByteBuffer bb; // Leftover first char in a surrogate pair private boolean haveLeftoverChar = false; private char leftoverChar; private CharBuffer lcb = null; private static final byte[] EMPTY = {}; public ChunkedWriter(OutputStream stream, String charset) { super(stream); this.out = stream; if (Charset.isSupported(charset)) { this.cs = Charset.forName(charset); this.encoder = cs.newEncoder().onMalformedInput(CodingErrorAction.REPLACE) .onUnmappableCharacter(CodingErrorAction.REPLACE); } else { throw new UnsupportedCharsetException(charset); } bb = ByteBuffer.allocate(DEFAULT_BYTE_BUFFER_SIZE); } private static final int DEFAULT_BYTE_BUFFER_SIZE = 4096; private volatile boolean isOpen = true; private void ensureOpen() throws IOException { if (!isOpen) throw new IOException("Stream closed"); } public boolean isOpen() { return isOpen; } @Override public void write(char[] cbuf, int off, int len) throws IOException { synchronized (lock) { ensureOpen(); if ((off < 0) || (off > cbuf.length) || (len < 0) || ((off + len) > cbuf.length) || ((off + len) < 0)) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return; } implWrite(cbuf, off, len); } } public void write(int c) throws IOException { char cbuf[] = new char[1]; cbuf[0] = (char) c; write(cbuf, 0, 1); } public void write(String str, int off, int len) throws IOException { /* Check the len before creating a char buffer */ if (len < 0) throw new IndexOutOfBoundsException(); char cbuf[] = new char[len]; str.getChars(off, off + len, cbuf, 0); write(cbuf, 0, len); } @Override public void flush() throws IOException { synchronized (lock) { ensureOpen(); implFlush(); } } void implFlushBuffer() throws IOException { if (bb.position() > 0) writeBytes(); flushLeftOverChar(null, true); try { for (;;) { CoderResult cr = encoder.flush(bb); if (cr.isUnderflow()) break; if (cr.isOverflow()) { assert bb.position() > 0; writeBytes(); continue; } cr.throwException(); } if (bb.position() > 0) writeBytes(); } catch (IOException x) { encoder.reset(); throw x; } out.write(EMPTY); } void implFlush() throws IOException { implFlushBuffer(); if (out != null) out.flush(); } @Override public void close() throws IOException { synchronized (lock) { if (!isOpen) return; out.close(); isOpen = false; } } void implWrite(char cbuf[], int off, int len) throws IOException{ CharBuffer cb = CharBuffer.wrap(cbuf,off, len); if(haveLeftoverChar) flushLeftOverChar(cb, false); while (cb.hasRemaining()) { CoderResult cr = encoder.encode(cb, bb, false); if (cr.isUnderflow()) { assert (cb.remaining() <= 1) : cb.remaining(); if(cb.remaining() == 1) { haveLeftoverChar = true; leftoverChar = cb.get(); } break; } if (cr.isOverflow()) { assert bb.position() > 0; writeBytes(); continue; } cr.throwException(); } } private void flushLeftOverChar(CharBuffer cb, boolean endOfInput) throws IOException{ if (!haveLeftoverChar && !endOfInput) return; if (lcb == null) lcb = CharBuffer.allocate(2); else lcb.clear(); if (haveLeftoverChar) lcb.put(leftoverChar); if ((cb != null) && cb.hasRemaining()) lcb.put(cb.get()); lcb.flip(); while (lcb.hasRemaining() || endOfInput) { CoderResult cr = encoder.encode(lcb, bb, endOfInput); if(cr.isUnderflow()) { if (lcb.hasRemaining()) { leftoverChar = lcb.get(); if (cb != null && cb.hasRemaining()) flushLeftOverChar(cb,endOfInput); return; } break; } if(cr.isOverflow()) { assert bb.position() > 0; writeBytes(); continue; } cr.throwException(); } haveLeftoverChar = false; } private void writeBytes() throws IOException{ bb.flip(); int lim = bb.limit(); int pos = bb.position(); assert (pos <= lim); int rem = (pos <= lim ? lim - pos : 0); if (rem > 0) { out.write(bb.array(), bb.arrayOffset() + pos, rem); } bb.clear(); } }