class ThrottledTemplateWriter extends Writer implements IThrottledTemplateWriterControl {

    private final String templateName;
    private final TemplateFlowController flowController;

    private IThrottledTemplateWriterAdapter adapter;
    private Writer writer;

    private boolean flushable;


    ThrottledTemplateWriter(final String templateName, final TemplateFlowController flowController) {
        super();
        this.templateName = templateName;
        this.flowController = flowController;
        this.adapter = null;
        this.writer = null;
        this.flushable = false;
    }




    void setOutput(final Writer writer) {
        if (this.adapter != null && this.adapter instanceof ThrottledTemplateWriterOutputStreamAdapter) {
            throw new TemplateOutputException(
                    "The throttled processor has already been initialized to use byte-based output (OutputStream), " +
                    "but a Writer has been specified.", this.templateName, -1, -1, null);
        }
        if (this.adapter == null) {
            this.adapter = new ThrottledTemplateWriterWriterAdapter(this.templateName, this.flowController);
            this.writer = ((ThrottledTemplateWriterWriterAdapter)this.adapter);
        }
        ((ThrottledTemplateWriterWriterAdapter)this.adapter).setWriter(writer);
    }


    void setOutput(final OutputStream outputStream, final Charset charset, final int maxOutputInBytes) {<FILL_FUNCTION_BODY>}




    public boolean isOverflown() throws IOException {
        if (this.flushable) {
            // We need this flushing because OutputStreamWriter bufferizes, and given we might be taking account of
            // the output bytes at an OutputStream implementation in a level below this OutputStreamWriter, we could
            // have the wrong figures until we flush contents.
            this.flush();
            this.flushable = false;
        }
        return this.adapter.isOverflown();
    }

    public boolean isStopped() throws IOException {
        if (this.flushable) {
            // We need this flushing because OutputStreamWriter bufferizes, and given we might be taking account of
            // the output bytes at an OutputStream implementation in a level below this OutputStreamWriter, we could
            // have the wrong figures until we flush contents.
            this.flush();
            this.flushable = false;
        }
        return this.adapter.isStopped();
    }


    public int getWrittenCount() {
        return this.adapter.getWrittenCount();
    }


    public int getMaxOverflowSize() {
        return this.adapter.getMaxOverflowSize();
    }


    public int getOverflowGrowCount() {
        return this.adapter.getOverflowGrowCount();
    }


    void allow(final int limit) {
        this.adapter.allow(limit);
    }



    @Override
    public void write(final int c) throws IOException {
        this.flushable = true;
        this.writer.write(c);
    }


    @Override
    public void write(final String str) throws IOException {
        this.flushable = true;
        this.writer.write(str);
    }


    @Override
    public void write(final String str, final int off, final int len) throws IOException {
        this.flushable = true;
        this.writer.write(str, off, len);
    }


    @Override
    public void write(final char[] cbuf) throws IOException {
        this.flushable = true;
        this.writer.write(cbuf);
    }


    @Override
    public void write(final char[] cbuf, final int off, final int len) throws IOException {
        this.flushable = true;
        this.writer.write(cbuf, off, len);
    }


    @Override
    public void flush() throws IOException {
        this.writer.flush();
    }


    @Override
    public void close() throws IOException {
        this.writer.close();
    }




    interface IThrottledTemplateWriterAdapter {

        boolean isOverflown();
        boolean isStopped();
        int getWrittenCount();
        int getMaxOverflowSize();
        int getOverflowGrowCount();
        void allow(final int limit);

    }


}

        if (this.adapter != null && this.adapter instanceof ThrottledTemplateWriterWriterAdapter) {
            throw new TemplateOutputException(
                    "The throttled processor has already been initialized to use char-based output (Writer), " +
                    "but an OutputStream has been specified.", this.templateName, -1, -1, null);
        }
        if (this.adapter == null) {
            final int adapterOverflowBufferIncrementBytes =
                    (maxOutputInBytes == Integer.MAX_VALUE?
                            128 :
                            // output size could be too small, so we will set a minimum of 16b, and max of 128b
                            Math.min(128, Math.max(16, maxOutputInBytes / 8)));
            this.adapter = new ThrottledTemplateWriterOutputStreamAdapter(this.templateName, this.flowController, adapterOverflowBufferIncrementBytes);
            // We cannot directly use a java.io.OutputStreamWriter here because that class uses a CharsetEncoder
            // underneath that always creates a 8192byte (8KB) buffer, and there is no way to configure that.
            //
            // The problem with such buffer is that we are counting the number of output bytes at the OutputStream
            // wrapper (the adapter we just created), which is set as the output of the OutputStreamWriter, and which
            // does not receive any bytes until the OutputStreamWriter flushes its 8KB buffer. But in a scenario in
            // which, for instance, we only need 100 bytes to complete our output chunk, this would mean we would still
            // have an overflow of more than 8,000 bytes. And that basically renders this whole template throttling
            // mechanism useless.
            //
            // So we will use an alternative construct to OutputStreamWriter, based on a WritableByteChannel. This
            // will basically work in the same way as an OutputStreamWriter, but by building it manually we will be
            // able to specify the size of the buffer to be used.
            //
            // And we do not want the buffer at the Writer -> OutputStream converter to completely disappear, because
            // it actually improves the performance of the converter. So we will use the maxOutputInBytes (the size
            // of the output to be obtained from the throttled template the first time) as an approximate measure
            // of what we will need in subsequent calls, and we will to try to adjust the size of the buffer so
            // that we make the most use of it without needing to flush too often, nor 'losing' chars in the buffer.
            //
            // Last, note that in order to avoid this 'loss of chars' we will combine this with 'flush' calls at the
            // 'isOverflown()' and 'isStopped()' calls.
            final CharsetEncoder charsetEncoder = charset.newEncoder();
            int channelBufferSize =
                    (maxOutputInBytes == Integer.MAX_VALUE?
                            1024 :
                            // Buffers of CharsetEncoders behave strangely (even hanging) when the buffers being
                            // set are too small to house the encoding of some elements (e.g. 1 or 2 bytes). So we
                            // will set a minimum of 64b and a max of 512b.
                            Math.min(512, Math.max(64, adapterOverflowBufferIncrementBytes * 2)));
            final WritableByteChannel channel = Channels.newChannel((ThrottledTemplateWriterOutputStreamAdapter)this.adapter);
            this.writer = Channels.newWriter(channel, charsetEncoder, channelBufferSize);
            // Use of a wrapping BufferedWriter is recommended by OutputStreamWriter javadoc for improving efficiency,
            // avoiding frequent converter invocations (note that the character converter also has its own buffer).
            //this.writer = new BufferedWriter(new OutputStreamWriter((ThrottledTemplateWriterOutputStreamAdapter)this.adapter, charset));
        }
        ((ThrottledTemplateWriterOutputStreamAdapter)this.adapter).setOutputStream(outputStream);
    
<methods>public java.io.Writer append(java.lang.CharSequence) throws java.io.IOException,public java.io.Writer append(char) throws java.io.IOException,public java.io.Writer append(java.lang.CharSequence, int, int) throws java.io.IOException,public abstract void close() throws java.io.IOException,public abstract void flush() throws java.io.IOException,public static java.io.Writer nullWriter() ,public void write(int) throws java.io.IOException,public void write(char[]) throws java.io.IOException,public void write(java.lang.String) throws java.io.IOException,public abstract void write(char[], int, int) throws java.io.IOException,public void write(java.lang.String, int, int) throws java.io.IOException<variables>private static final int WRITE_BUFFER_SIZE,protected java.lang.Object lock,private char[] writeBuffer