001package io.jstach.jstachio.output;
002
003import java.io.IOException;
004import java.io.OutputStream;
005import java.nio.ByteBuffer;
006import java.nio.channels.ReadableByteChannel;
007import java.nio.charset.Charset;
008
009import io.jstach.jstachio.Output.CloseableEncodedOutput;
010import io.jstach.jstachio.Template.EncodedTemplate;
011
012/**
013 * An encoded output that will store the output in its preferred memory structure and can
014 * then be copied to an {@link OutputStream} or read from a {@link ReadableByteChannel}.
015 * The total {@link #size()} of the output can also be retrieved before being copied which
016 * is useful for setting "<code>Content-Length</code>" HTTP header or for allocating
017 * buffers.
018 * <p>
019 * <em>The major impetus for this interface is needing the length of the output prior to
020 * outputing. If the length is not needed before writing then better performance and
021 * memory savings can probably be had by just using
022 * {@link EncodedOutput#of(OutputStream, Charset)}.</em>
023 * <p>
024 * In general instances will be empty and not have correct results till
025 * {@link EncodedTemplate#write(Object, EncodedOutput)} is called.
026 * <p>
027 * Currently there are two approaches to buffered output:
028 * <ul>
029 * <li>{@link ByteBufferEncodedOutput}: according to benchmarks on most platforms other
030 * than Windows it is more CPU friendly but less memory friendly. It is ideal for
031 * platforms where you can return {@link ByteBuffer} directly or need to return a
032 * <code>byte[]</code>. The default implementation uses a growing array and can be created
033 * with {@link ByteBufferEncodedOutput#ofByteArray(Charset, int)} and can be reused
034 * provided {@link #close()} is called. Reuse can be useful if using ThreadLocals or some
035 * other pooling mechanism.</li>
036 * <li>{@link ChunkEncodedOutput}: according to benchmarks is more optimized for memory
037 * savings as well as possible reduction of copying. This approach originated from
038 * Rocker's purported "near zero copy".</li>
039 * </ul>
040 * Regardless one should benchmark each approach to determine which one best fits for
041 * their platform. In many cases a custom {@link EncodedOutput} that uses the platform's
042 * underlying buffer pool will probably yield the best results.
043 *
044 * @author agentgt
045 * @see ByteBufferEncodedOutput
046 * @see ChunkEncodedOutput
047 * @apiNote Methods starting with "<code>as</code>" are a view where as methods starting
048 * with "<code>to</code>" (or ending in "To" for the case of transferTo) are a copy. If
049 * the output is to be reused "<code>as</code>" methods should be copied or fully used
050 * before the output is reused.
051 */
052public sealed interface BufferedEncodedOutput
053                extends CloseableEncodedOutput<RuntimeException>permits ChunkEncodedOutput, ByteBufferEncodedOutput {
054
055        /**
056         * Total size in number of bytes of the output.
057         * @return size
058         * @see #bufferSizeHint()
059         */
060        public int size();
061
062        /**
063         * Transfers the entire buffered output by writing to an OutputStream.
064         * @param stream not null and will not be closed or flushed.
065         * @throws IOException if the stream throws an IOException.
066         * @see #asReadableByteChannel()
067         * @apiNote For nonblocking {@link #asReadableByteChannel()} is generally accepted as
068         * the better aproach as it is a pull model.
069         */
070        default void transferTo(OutputStream stream) throws IOException {
071                accept(stream::write);
072        }
073
074        /**
075         * Decorates this buffer so that buffering is limited to certain amount and will
076         * eventually send all output to the OutputStream created by the factory. The factory
077         * will be passed <code>-1</code> if the limit is exceeded and
078         * {@linkplain OutputFactory#create(int) create} will only be called once and only
079         * once provided that the returned object is closed.
080         * <p>
081         * This method should be called before passed to JStachio and the result is the output
082         * that should be passed.
083         * @param limit the maximum amount of bytes to buffer.
084         * @param factory create the output stream on demand and will always be used before
085         * close is called.
086         * @return output that will need to be closed eventually.
087         * @see LimitEncodedOutput
088         */
089        default LimitEncodedOutput<OutputStream, IOException> limit(int limit,
090                        OutputFactory<OutputStream, IOException> factory) {
091                return new AbstractLimitEncodedOutput(this, limit) {
092                        @Override
093                        protected OutputStream createConsumer(int size) throws IOException {
094                                return factory.create(size);
095                        }
096                };
097        }
098
099        /**
100         * Transfers the entire buffered output to a consumer
101         * @param <E> the exception type
102         * @param consumer not null.
103         * @throws E if the consumer throws an exception
104         * @see #asReadableByteChannel()
105         * @apiNote For nonblocking {@link #asReadableByteChannel()} is generally accepted as
106         * the better aproach as it is a pull model.
107         */
108        public <E extends Exception> void accept(OutputConsumer<E> consumer) throws E;
109
110        /**
111         * The recommend buffer size to use for extracting with
112         * {@link #asReadableByteChannel()} or {@link #transferTo(OutputStream)}.
113         * @return buffer size to use which by default is {@link #size()}.
114         */
115        default int bufferSizeHint() {
116                return size();
117        }
118
119        /**
120         * Represents the encoded output as readable channel. <strong>The channel should be
121         * closed when finished to signal reuse or destruction of the buffered
122         * output!</strong>. To possibly help determine the buffer to use for
123         * {@link ReadableByteChannel#read(java.nio.ByteBuffer)} one can use
124         * {@link #bufferSizeHint()} or {@link #size()}.
125         * @return channel open and ready to read from at the start of the output.
126         * @see #bufferSizeHint()
127         */
128        public ReadableByteChannel asReadableByteChannel();
129
130        /**
131         * <strong>Copies</strong> the output to a byte array.
132         * @return a copied byte array of the output
133         */
134        default byte[] toByteArray() {
135                int size = size();
136                byte[] result = new byte[size];
137                OutputConsumer<RuntimeException> consumer = new OutputConsumer<>() {
138                        int index = 0;
139
140                        @Override
141                        public void accept(byte[] data, int offset, int length) throws RuntimeException {
142                                System.arraycopy(data, offset, result, index, length);
143                                index += length;
144                        }
145                };
146                accept(consumer);
147                return result;
148        }
149
150        @Override
151        default void append(String s) {
152                write(s.getBytes(charset()));
153        }
154
155        @Override
156        default void append(CharSequence s) {
157                append(s.toString());
158        }
159
160        /**
161         * Signals that the buffer should be reset for reuse or destroyed.
162         * @apiNote This does not throw an IOException on purpose since everything is in
163         * memory.
164         */
165        @Override
166        public void close();
167
168        /**
169         * If this instance can be reused after {@link #close()} is called.
170         * @return true if reuse is allowed by default false is returned.
171         */
172        default boolean isReusable() {
173                return false;
174        }
175
176        /**
177         * Create a buffered encoded output backed by a sequence of chunks.
178         * @param charset the expected encoding
179         * @return buffered output
180         */
181        public static BufferedEncodedOutput ofChunked(Charset charset) {
182                return ChunkEncodedOutput.ofByteArrays(charset);
183        }
184
185}