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}