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}