001package io.jstach.jstachio;
002
003import java.io.IOException;
004import java.io.UncheckedIOException;
005import java.util.function.Function;
006
007import org.eclipse.jdt.annotation.Nullable;
008
009import io.jstach.jstache.JStacheFormatter;
010import io.jstach.jstache.JStacheFormatterTypes;
011
012/**
013 * Formats and then sends the results to the downstream appender.
014 *
015 * Implementations should be singleton like and should not contain state. By default
016 * native types are passed straight through to the downstream appender. If this is not
017 * desired one can override those methods.
018 * <p>
019 * <em>Important: the formatter does not decide what types are allowed at compile time to
020 * be formatted.</em> To control what types are allowed to be formatted see
021 * {@link JStacheFormatterTypes}.
022 * <p>
023 * An alternative to implementing this complicated interface is to simply make a
024 * {@code Function<@Nullable Object, String>} and call {@link #of(Function)} to create a
025 * formatter.
026 *
027 * @apiNote Although the formatter has access to the raw {@link Appendable} the formatter
028 * should never use it directly and simply pass it on to the downstream appender.
029 * @author agentgt
030 * @see JStacheFormatterTypes
031 * @see JStacheFormatter
032 *
033 */
034public interface Formatter extends Function<@Nullable Object, String> {
035
036        /**
037         * Formats an object by using {@link StringBuilder} and calling
038         * {@link #format(Appender, Appendable, String, Class, Object)}.
039         * @param t the object to be formatted. Maybe <code>null</code>.
040         * @return the formatted results as a String.
041         */
042        @Override
043        default String apply(@Nullable Object t) {
044                StringBuilder sb = new StringBuilder();
045                try {
046                        format(Appender.stringAppender(), sb, "", Object.class, t);
047                }
048                catch (IOException e) {
049                        throw new UncheckedIOException(e);
050                }
051                return sb.toString();
052        }
053
054        /**
055         * Formats the object and then sends the results to the downstream appender.
056         *
057         *
058         * @apiNote Although the formatter has access to the raw {@link Appendable} the
059         * formatter should never use it directly and simply pass it on to the downstream
060         * appender.
061         * @param <A> the appendable type
062         * @param <APPENDER> the downstream appender type
063         * @param downstream the downstream appender to be used instead of the appendable
064         * directly
065         * @param a the appendable to be passed to the appender
066         * @param path the dotted mustache like path
067         * @param c the object class but is not guaranteed to be accurate. If it is not known
068         * Object.class will be used.
069         * @param o the object which maybe null
070         * @throws IOException if the appender or appendable throws an exception
071         */
072        <A extends Appendable, APPENDER extends Appender<A>> //
073        void format(APPENDER downstream, A a, String path, Class<?> c, @Nullable Object o) throws IOException;
074
075        /**
076         * Formats the object and then sends the results to the downstream appender. The
077         * default implementation passes natives through to the downstream appender.
078         *
079         * @apiNote Although the formatter has access to the raw {@link Appendable} the
080         * formatter should never use it directly and simply pass it on to the downstream
081         * appender.
082         * @param <A> the appendable type
083         * @param <APPENDER> the downstream appender type
084         * @param downstream the downstream appender to be used instead of the appendable
085         * directly
086         * @param a the appendable to be passed to the appender
087         * @param path the dotted mustache like path
088         * @param c character
089         * @throws IOException if the appender or appendable throws an exception
090         */
091        default <A extends Appendable, APPENDER extends Appender<A>> void format(APPENDER downstream, A a, String path,
092                        char c) throws IOException {
093                downstream.append(a, c);
094        }
095
096        /**
097         * Formats the object and then sends the results to the downstream appender. The
098         * default implementation passes natives through to the downstream appender.
099         *
100         * @apiNote Although the formatter has access to the raw {@link Appendable} the
101         * formatter should never use it directly and simply pass it on to the downstream
102         * appender.
103         * @param <A> the appendable type
104         * @param <APPENDER> the downstream appender type
105         * @param downstream the downstream appender to be used instead of the appendable
106         * directly
107         * @param a the appendable to be passed to the appender
108         * @param path the dotted mustache like path
109         * @param s short
110         * @throws IOException if the appender or appendable throws an exception
111         */
112        default <A extends Appendable, APPENDER extends Appender<A>> void format(APPENDER downstream, A a, String path,
113                        short s) throws IOException {
114                downstream.append(a, s);
115        }
116
117        /**
118         * Formats the object and then sends the results to the downstream appender. The
119         * default implementation passes natives through to the downstream appender.
120         *
121         * @apiNote Although the formatter has access to the raw {@link Appendable} the
122         * formatter should never use it directly and simply pass it on to the downstream
123         * appender.
124         * @param <A> the appendable type
125         * @param <APPENDER> the downstream appender type
126         * @param downstream the downstream appender to be used instead of the appendable
127         * directly
128         * @param a the appendable to be passed to the appender
129         * @param path the dotted mustache like path
130         * @param i integer
131         * @throws IOException if the appender or appendable throws an exception
132         */
133        default <A extends Appendable, APPENDER extends Appender<A>> void format(APPENDER downstream, A a, String path,
134                        int i) throws IOException {
135                downstream.append(a, i);
136        }
137
138        /**
139         * Formats the object and then sends the results to the downstream appender. The
140         * default implementation passes natives through to the downstream appender.
141         *
142         * @apiNote Although the formatter has access to the raw {@link Appendable} the
143         * formatter should never use it directly and simply pass it on to the downstream
144         * appender.
145         * @param <A> the appendable type
146         * @param <APPENDER> the downstream appender type
147         * @param downstream the downstream appender to be used instead of the appendable
148         * directly
149         * @param a the appendable to be passed to the appender
150         * @param path the dotted mustache like path
151         * @param l long
152         * @throws IOException if the appender or appendable throws an exception
153         */
154        default <A extends Appendable, APPENDER extends Appender<A>> void format(APPENDER downstream, A a, String path,
155                        long l) throws IOException {
156                downstream.append(a, l);
157        }
158
159        /**
160         * Formats the object and then sends the results to the downstream appender. The
161         * default implementation passes natives through to the downstream appender.
162         *
163         * @apiNote Although the formatter has access to the raw {@link Appendable} the
164         * formatter should never use it directly and simply pass it on to the downstream
165         * appender.
166         * @param <A> the appendable type
167         * @param <APPENDER> the downstream appender type
168         * @param downstream the downstream appender to be used instead of the appendable
169         * directly
170         * @param a the appendable to be passed to the appender
171         * @param path the dotted mustache like path
172         * @param d double
173         * @throws IOException if the appender or appendable throws an exception
174         */
175        default <A extends Appendable, APPENDER extends Appender<A>> void format(APPENDER downstream, A a, String path,
176                        double d) throws IOException {
177                downstream.append(a, d);
178        }
179
180        /**
181         * Formats the object and then sends the results to the downstream appender. The
182         * default implementation passes natives through to the downstream appender.
183         *
184         * @apiNote Although the formatter has access to the raw {@link Appendable} the
185         * formatter should never use it directly and simply pass it on to the downstream
186         * appender.
187         * @param <A> the appendable type
188         * @param <APPENDER> the downstream appender type
189         * @param downstream the downstream appender to be used instead of the appendable
190         * directly
191         * @param a the appendable to be passed to the appender
192         * @param path the dotted mustache like path
193         * @param b boolean
194         * @throws IOException if the appender or appendable throws an exception
195         */
196        default <A extends Appendable, APPENDER extends Appender<A>> void format(APPENDER downstream, A a, String path,
197                        boolean b) throws IOException {
198                downstream.append(a, b);
199        }
200
201        /**
202         * Formats the object and then sends the results to the downstream appender. The
203         * default implementation passes natives through to the downstream appender.
204         *
205         * @apiNote Although the formatter has access to the raw {@link Appendable} the
206         * formatter should never use it directly and simply pass it on to the downstream
207         * appender.
208         * @param <A> the appendable type
209         * @param <APPENDER> the downstream appender type
210         * @param downstream the downstream appender to be used instead of the appendable
211         * directly
212         * @param a the appendable to be passed to the appender
213         * @param path the dotted mustache like path
214         * @param s String
215         * @throws IOException if the appender or appendable throws an exception
216         */
217        default <A extends Appendable, APPENDER extends Appender<A>> void format(APPENDER downstream, A a, String path,
218                        String s) throws IOException {
219                downstream.append(a, s);
220        }
221
222        /**
223         * Adapts a function to a formatter.
224         *
225         * If the function is already a formatter then it is simply returned (noop). Thus it
226         * is safe to repeatedly call this on formatters. If the function is adapted the
227         * returned adapted formatter does not pass native types to the inputted function.
228         * @param formatterFunction if it is already an escaper
229         * @return adapted formattter
230         */
231        public static Formatter of(@SuppressWarnings("exports") Function<@Nullable Object, String> formatterFunction) {
232                if (formatterFunction instanceof Formatter f) {
233                        return f;
234                }
235                return new ObjectFunctionFormatter(formatterFunction);
236        }
237
238}
239
240class ObjectFunctionFormatter implements Formatter {
241
242        private final Function<@Nullable Object, String> function;
243
244        public ObjectFunctionFormatter(Function<@Nullable Object, String> function) {
245                super();
246                this.function = function;
247        }
248
249        @Override
250        public <A extends Appendable, APPENDER extends Appender<A>> void format(APPENDER downstream, A a, String path,
251                        Class<?> c, @Nullable Object o) throws IOException {
252                String result = function.apply(o);
253                downstream.append(a, result);
254        }
255
256}