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