001package io.jstach.jstachio;
002
003import java.util.Objects;
004import java.util.function.Function;
005
006import org.eclipse.jdt.annotation.Nullable;
007
008import io.jstach.jstache.JStacheConfig;
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 *
023 * <h2>Implementing</h2>
024 *
025 * An alternative to implementing this complicated interface is to simply make a
026 * {@code Function<@Nullable Object, String>} and call {@link #of(Function)} to create a
027 * formatter.
028 * <p>
029 * To implement a custom formatter:
030 *
031 * <ol>
032 * <li>Implement this interface or use {@link #of(Function)}.</li>
033 * <li>Register the custom formatter with {@link JStacheFormatter}.</li>
034 * <li>Add additional allowed types with {@link JStacheFormatterTypes} on to the class
035 * that is annotated with {@link JStacheFormatter}</li>
036 * <li>Set {@link JStacheConfig#formatter()} to the class that has the
037 * {@link JStacheFormatter}.</li>
038 * </ol>
039 * <em>It is the formatters responsibility to handle <code>null</code> for
040 * {@linkplain Formatter#format(Appender, Output, String, Class, Object) nullable format
041 * calls} as the downstream appender do not allow <code>null</code>. </em>
042 *
043 * @apiNote Although the formatter has access to the raw {@link Output} the formatter
044 * should never use it directly and simply pass it on to the downstream appender.
045 * @author agentgt
046 * @see JStacheFormatterTypes
047 * @see JStacheFormatter
048 *
049 */
050public interface Formatter extends Function<@Nullable Object, String> {
051
052        /**
053         * Formats an object by using {@link StringBuilder} and calling
054         * {@link #format(Appender, Output, String, Class, Object)}.
055         * @param t the object to be formatted. Maybe <code>null</code>.
056         * @return the formatted results as a String.
057         */
058        @Override
059        default String apply(@Nullable Object t) {
060                var sb = new Output.StringOutput(new StringBuilder());
061                format(Appender.defaultAppender(), sb, "", Object.class, t);
062                return sb.toString();
063        }
064
065        /**
066         * Formats the object and then sends the results to the downstream appender.
067         *
068         *
069         * @apiNote Although the formatter has access to the raw {@link Appendable} the
070         * formatter should never use it directly and simply pass it on to the downstream
071         * appender.
072         * @param <A> the appendable type
073         * @param <E> the appender exception type
074         * @param downstream the downstream appender to be used instead of the appendable
075         * directly
076         * @param a the appendable to be passed to the appender
077         * @param path the dotted mustache like path
078         * @param c the object class but is not guaranteed to be accurate. If it is not known
079         * Object.class will be used.
080         * @param o the object which maybe null
081         * @throws E if the appender or appendable throws an exception
082         */
083
084        <A extends Output<E>, E extends Exception> void format(Appender downstream, A a, String path, Class<?> c,
085                        @Nullable Object o) throws E;
086
087        /**
088         * Formats the object and then sends the results to the downstream appender. The
089         * default implementation passes natives through to the downstream appender.
090         *
091         * @apiNote Although the formatter has access to the raw {@link Appendable} the
092         * formatter should never use it directly and simply pass it on to the downstream
093         * appender.
094         * @param <A> the appendable type
095         * @param <E> the appender exception type
096         * @param downstream the downstream appender to be used instead of the appendable
097         * directly
098         * @param a the appendable to be passed to the appender
099         * @param path the dotted mustache like path
100         * @param c character
101         * @throws E if the appender or appendable throws an exception
102         */
103        default <A extends Output<E>, E extends Exception> void format(Appender downstream, A a, String path, char c)
104                        throws E {
105                downstream.append(a, c);
106        }
107
108        /**
109         * Formats the object and then sends the results to the downstream appender. The
110         * default implementation passes natives through to the downstream appender.
111         *
112         * @apiNote Although the formatter has access to the raw {@link Appendable} the
113         * formatter should never use it directly and simply pass it on to the downstream
114         * appender.
115         * @param <A> the appendable type
116         * @param <E> the appender exception type
117         * @param downstream the downstream appender to be used instead of the appendable
118         * directly
119         * @param a the appendable to be passed to the appender
120         * @param path the dotted mustache like path
121         * @param s short
122         * @throws E if the appender or appendable throws an exception
123         */
124        default <A extends Output<E>, E extends Exception> void format(Appender downstream, A a, String path, short s)
125                        throws E {
126                downstream.append(a, s);
127        }
128
129        /**
130         * Formats the object and then sends the results to the downstream appender. The
131         * default implementation passes natives through to the downstream appender.
132         *
133         * @apiNote Although the formatter has access to the raw {@link Appendable} the
134         * formatter should never use it directly and simply pass it on to the downstream
135         * appender.
136         * @param <A> the appendable type
137         * @param <E> the appender exception type
138         * @param downstream the downstream appender to be used instead of the appendable
139         * directly
140         * @param a the appendable to be passed to the appender
141         * @param path the dotted mustache like path
142         * @param i integer
143         * @throws E if the appender or appendable throws an exception
144         */
145        default <A extends Output<E>, E extends Exception> void format(Appender downstream, A a, String path, int i)
146                        throws E {
147                downstream.append(a, i);
148        }
149
150        /**
151         * Formats the object and then sends the results to the downstream appender. The
152         * default implementation passes natives through to the downstream appender.
153         *
154         * @apiNote Although the formatter has access to the raw {@link Appendable} the
155         * formatter should never use it directly and simply pass it on to the downstream
156         * appender.
157         * @param <A> the appendable type
158         * @param <E> the appender exception type
159         * @param downstream the downstream appender to be used instead of the appendable
160         * directly
161         * @param a the appendable to be passed to the appender
162         * @param path the dotted mustache like path
163         * @param l long
164         * @throws E if the appender or appendable throws an exception
165         */
166        default <A extends Output<E>, E extends Exception> void format(Appender downstream, A a, String path, long l)
167                        throws E {
168                downstream.append(a, l);
169        }
170
171        /**
172         * Formats the object and then sends the results to the downstream appender. The
173         * default implementation passes natives through to the downstream appender.
174         *
175         * @apiNote Although the formatter has access to the raw {@link Appendable} the
176         * formatter should never use it directly and simply pass it on to the downstream
177         * appender.
178         * @param <A> the appendable type
179         * @param <E> the appender exception type
180         * @param downstream the downstream appender to be used instead of the appendable
181         * directly
182         * @param a the appendable to be passed to the appender
183         * @param path the dotted mustache like path
184         * @param d double
185         * @throws E if the appender or appendable throws an exception
186         */
187        default <A extends Output<E>, E extends Exception> void format(Appender downstream, A a, String path, double d)
188                        throws E {
189                downstream.append(a, d);
190        }
191
192        /**
193         * Formats the object and then sends the results to the downstream appender. The
194         * default implementation passes natives through to the downstream appender.
195         *
196         * @apiNote Although the formatter has access to the raw {@link Appendable} the
197         * formatter should never use it directly and simply pass it on to the downstream
198         * appender.
199         * @param <A> the appendable type
200         * @param <E> the appender exception type
201         * @param downstream the downstream appender to be used instead of the appendable
202         * directly
203         * @param a the appendable to be passed to the appender
204         * @param path the dotted mustache like path
205         * @param b boolean
206         * @throws E if the appender or appendable throws an exception
207         */
208        default <A extends Output<E>, E extends Exception> void format(Appender downstream, A a, String path, boolean b)
209                        throws E {
210                downstream.append(a, b);
211        }
212
213        /**
214         * Formats the object and then sends the results to the downstream appender. The
215         * default implementation calls
216         * {@link #format(Appender, Output, String, Class, Object)} and it is generally
217         * recommend you override for performance.
218         *
219         * @apiNote Although the formatter has access to the raw {@link Appendable} the
220         * formatter should never use it directly and simply pass it on to the downstream
221         * appender. Also take note that the string value maybe null!
222         * @param <A> the appendable type
223         * @param <E> the appender exception type
224         * @param downstream the downstream appender to be used instead of the appendable
225         * directly
226         * @param a the appendable to be passed to the appender
227         * @param path the dotted mustache like path
228         * @param s String value which maybe <code>null</code>.
229         * @throws E if the appender or appendable throws an exception
230         */
231        default <A extends Output<E>, E extends Exception> void format(Appender downstream, A a, String path,
232                        @Nullable String s) throws E {
233                format(downstream, a, path, String.class, s);
234        }
235
236        /**
237         * Formats the formattable object and then sends the results to the downstream
238         * appender. The default implementation will call the supplied
239         * {@linkplain Formattable} if it is not null otherwise it will call
240         * {@link #format(Appender, Output, String, Class, Object)} to handle the null case.
241         *
242         * @apiNote Although the formatter has access to the raw {@link Appendable} the
243         * formatter should never use it directly and simply pass it on to the downstream
244         * appender. Also take note that the string value maybe null!
245         * @param <A> the appendable type
246         * @param <E> the appender exception type
247         * @param downstream the downstream appender to be used instead of the appendable
248         * directly
249         * @param a the appendable to be passed to the appender
250         * @param path the dotted mustache like path
251         * @param f Formattable which maybe <code>null</code>.
252         * @throws E if the appender or appendable throws an exception
253         */
254        default <A extends Output<E>, E extends Exception> void format(Appender downstream, A a, String path,
255                        @Nullable Formattable f) throws E {
256                if (f != null) {
257                        f.format(this, downstream, path, a);
258                }
259                else {
260                        format(downstream, a, path, Formattable.class, null);
261
262                }
263        }
264
265        /**
266         * Adapts a function to a formatter.
267         *
268         * If the function is already a formatter then it is simply returned (noop). Thus it
269         * is safe to repeatedly call this on formatters. If the function is adapted the
270         * returned adapted formatter does not pass native types to the inputted function.
271         * @param formatterFunction if it is already an escaper
272         * @return adapted formattter
273         */
274        public static Formatter of(@SuppressWarnings("exports") Function<@Nullable Object, String> formatterFunction) {
275                if (formatterFunction instanceof Formatter f) {
276                        return f;
277                }
278                return new ObjectFunctionFormatter(formatterFunction);
279        }
280
281        /**
282         * Implement to allow formatting of custom objects you want to output. This is often
283         * easier than registering custom {@link JStacheFormatterTypes} but couples model
284         * objects to JStachio.
285         */
286        public interface Formattable {
287
288                /**
289                 * Called by the formatter to format. Implementations can decide if they want to
290                 * use the passed in formatter to perhaps format other types.
291                 * @param <A> the appendable type
292                 * @param <E> the appender exception type
293                 * @param formatter the calling formatter
294                 * @param downstream the downstream appender to be used instead of the appendable
295                 * directly.
296                 * @param path the dotted mustache like path
297                 * @param a the appendable to be passed to the appender
298                 * @throws E if the appender or appendable throws an exception
299                 */
300                <A extends Output<E>, E extends Exception> void format(Formatter formatter, Appender downstream, String path,
301                                A a) throws E;
302
303        }
304
305}
306
307class ObjectFunctionFormatter implements Formatter {
308
309        private final Function<@Nullable Object, String> function;
310
311        public ObjectFunctionFormatter(Function<@Nullable Object, String> function) {
312                super();
313                this.function = function;
314        }
315
316        @Override
317        public <A extends Output<E>, E extends Exception> void format(Appender downstream, A a, String path, Class<?> c,
318                        @Nullable Object o) throws E {
319                String result = Objects.requireNonNull(function.apply(o));
320                downstream.append(a, result);
321        }
322
323}