001package io.jstach.rainbowgum;
002
003import java.io.UncheckedIOException;
004import java.net.URI;
005
006import io.jstach.rainbowgum.LogEncoder.AbstractEncoder;
007import io.jstach.rainbowgum.LogEncoder.Buffer.StringBuilderBuffer;
008import io.jstach.rainbowgum.LogOutput.WriteMethod;
009
010/**
011 * Encodes a {@link LogEvent} into a buffer of its choosing. While the {@link Buffer} does
012 * not need to be thread-safe the encoder itself should be.
013 * <p>
014 * An appender typically calls an encoder by first {@linkplain #buffer(BufferHints)
015 * creating a buffer} that the encoder knows about or reusing an existing {@link Buffer}
016 * the encoder knows about.
017 * <p>
018 * The {@linkplain #encode(LogEvent, Buffer) encoding into a buffer} typically happens
019 * outside of lock to minimize lock contention. Thus the appender promises not to share a
020 * buffer at the same time with other threads as well as only use a buffer the log encoder
021 * created at some point.
022 * <p>
023 * Once encoding is done the appender than typically enters into a lock (appenders
024 * attached to an async publisher may not need to use a lock) where the appender will ask
025 * the buffer to {@linkplain Buffer#drain(LogOutput, LogEvent) drain} its contents into
026 * the output.
027 * <p>
028 * Because {@link Buffer} is not a specific implementation the Encoder typically casts the
029 * buffer to the expected concrete implementation. {@link AbstractEncoder} can make this
030 * logic easier and is recommended to extend it.
031 * <p>
032 * Given the complexity of encoders it is recommend use the much easier to implement
033 * interface of {@link LogFormatter} and convert it to an encoder with
034 * {@link #of(LogFormatter)}.
035 *
036 * @see LogFormatter
037 * @see Buffer
038 * @see LogAppender
039 * @see StringBuilderBuffer
040 */
041public interface LogEncoder {
042
043        /**
044         * Creates a <strong>new</strong> buffer. The encoder should not try to reuse buffers
045         * as that is the responsibility of the {@linkplain LogAppender appender} (and
046         * possibly {@link LogOutput} but usually not). Hints can be retrieved by call
047         * {@link LogOutput#bufferHints()}.
048         * @param hints hints are like size and storage type etc.
049         * @return a new buffer.
050         * @apiNote hints can be retrieved by calling {@link LogOutput#bufferHints()} the
051         * reason the output itself is not passed is to prevent the buffer from using the
052         * output directly at an inappropriate time as well as the rare possibility of the
053         * buffer being used by multiple outputs.
054         */
055        public Buffer buffer(BufferHints hints);
056
057        /**
058         * Encodes an event to the buffer. It is recommended that the encoder call
059         * {@link Buffer#clear()} before using.
060         * @param event log event.
061         * @param buffer buffer created from {@link #buffer(BufferHints)}.
062         */
063        public void encode(LogEvent event, Buffer buffer);
064
065        /**
066         * Creates an encoder from a formatter.
067         * @param formatter formatter.
068         * @return encoder.
069         */
070        public static LogEncoder of(LogFormatter formatter) {
071                return new FormatterEncoder(formatter);
072        }
073
074        /**
075         * Provides a lazy loaded encoder from a URI.
076         * @param uri uri.
077         * @return provider of encoder.
078         */
079        public static LogProvider<LogEncoder> of(URI uri) {
080                return of(LogProviderRef.of(uri));
081        }
082
083        /**
084         * Provides a lazy loaded encoder from a provider ref.
085         * @param ref uri.
086         * @return provider of output.
087         * @apiNote the provider may throw an {@link UncheckedIOException}.
088         */
089        public static LogProvider<LogEncoder> of(LogProviderRef ref) {
090                return (s, c) -> {
091                        return c.encoderRegistry().provide(ref).provide(s, c);
092                };
093        }
094
095        /**
096         * Finds output based on URI.
097         */
098        public interface EncoderProvider {
099
100                /**
101                 * Loads an encoder from a URI.
102                 * @param ref reference to provider usually just a uri.
103                 * @return output.
104                 * @throws LogProviderRef.NotFoundException if there is no registered provider.
105                 */
106                LogProvider<LogEncoder> provide(LogProviderRef ref) throws LogProviderRef.NotFoundException;
107
108        }
109
110        /**
111         * Encoders buffer.
112         */
113        public interface Buffer extends AutoCloseable {
114
115                /**
116                 * The appender will call this usually within a lock to transfer content from the
117                 * buffer to the output.
118                 * @param output output to receive content.
119                 * @param event log event.
120                 */
121                public void drain(LogOutput output, LogEvent event);
122
123                /**
124                 * Prepare the buffer for reuse.
125                 * <p>
126                 * An appender may not call clear before being passed to the encoder so the
127                 * encoder should do its own clearing.
128                 */
129                public void clear();
130
131                /**
132                 * Convenience that will call clear.
133                 */
134                @Override
135                default void close() {
136                        clear();
137                }
138
139                /**
140                 * A buffer that simply wraps a {@link StringBuilder}. Direct access to the
141                 * {@link StringBuilder} is available as the field {@link #stringBuilder}.
142                 *
143                 * @see AbstractEncoder
144                 */
145                public final class StringBuilderBuffer implements Buffer {
146
147                        /**
148                         * Underlying StringBuilder.
149                         */
150                        public final StringBuilder stringBuilder;
151
152                        /**
153                         * Creates a StringBuilder based buffer.
154                         * @param sb string builder.
155                         * @return buffer.
156                         */
157                        public static StringBuilderBuffer of(StringBuilder sb) {
158                                return new StringBuilderBuffer(sb);
159                        }
160
161                        private StringBuilderBuffer(StringBuilder stringBuilder) {
162                                super();
163                                this.stringBuilder = stringBuilder;
164                        }
165
166                        @Override
167                        public void drain(LogOutput output, LogEvent event) {
168                                output.write(event, stringBuilder.toString());
169                        }
170
171                        @Override
172                        public void clear() {
173                                stringBuilder.setLength(0);
174                        }
175
176                }
177
178        }
179
180        /**
181         * Hints the output can pass to the encoder for creating buffers like max size and
182         * storage style of the buffer etc.
183         *
184         * @apiNote There is no guarantees the encoder/buffer will honor these hints.
185         */
186        public interface BufferHints {
187
188                /*
189                 * TODO should we seal this?
190                 */
191
192                /**
193                 * The preferred write style of the output.
194                 * @return write method.
195                 * @apiNote {@link WriteMethod} implements this interface for convenience.
196                 */
197                LogOutput.WriteMethod writeMethod();
198
199                /**
200                 * Maximum size of the buffer. This is a way for the encoder to say it can only
201                 * handle so much data per event.
202                 * @return a negative number indicates size is not important.
203                 */
204                default int maximumSize() {
205                        return -1;
206                }
207
208        }
209
210        /**
211         * Abstract encoder that will cast the buffer to the desired implementation. Extend to
212         * make creating encoders easier.
213         *
214         * @param <T> buffer type.
215         */
216        abstract class AbstractEncoder<T extends Buffer> implements LogEncoder {
217
218                /**
219                 * Do nothing constructor.
220                 */
221                protected AbstractEncoder() {
222
223                }
224
225                /**
226                 * Create a specific buffer implementation.
227                 * @param hints buffer creation hints.
228                 * @return buffer
229                 */
230                protected abstract T doBuffer(BufferHints hints);
231
232                /**
233                 * A type safe version of {@link #encode(LogEvent, Buffer)}.
234                 * @param event event.
235                 * @param buffer casted buffer.
236                 */
237                protected abstract void doEncode(LogEvent event, T buffer);
238
239                @Override
240                public final Buffer buffer(BufferHints hints) {
241                        return doBuffer(hints);
242                }
243
244                @SuppressWarnings("unchecked")
245                @Override
246                public final void encode(LogEvent event, Buffer buffer) {
247                        doEncode(event, (T) buffer);
248
249                }
250
251        }
252
253}
254
255final class FormatterEncoder extends AbstractEncoder<StringBuilderBuffer> {
256
257        private final LogFormatter formatter;
258
259        public FormatterEncoder(LogFormatter formatter) {
260                super();
261                this.formatter = formatter;
262        }
263
264        @Override
265        protected void doEncode(LogEvent event, StringBuilderBuffer buffer) {
266                buffer.clear();
267                StringBuilder sb = buffer.stringBuilder;
268                formatter.format(sb, event);
269        }
270
271        @Override
272        protected StringBuilderBuffer doBuffer(BufferHints hints) {
273                return StringBuilderBuffer.of(new StringBuilder());
274        }
275
276}