001package io.jstach.jstachio;
002
003import java.io.IOException;
004import java.util.ServiceLoader;
005import java.util.function.Supplier;
006
007import io.jstach.jstache.JStache;
008import io.jstach.jstachio.spi.JStachioExtension;
009
010/**
011 * Render models by using reflection to lookup generated templates as well as apply
012 * filtering and fallback mechanisms.
013 * <h2>Example Usage</h2> <pre><code class="language-java">
014 * &#64;JStache(template = "Hello {{name}}!")
015 * public record HelloWorld(String name) {}
016 *
017 * public static String output(String name) {
018 *   //Normally you would have to use generated class HelloWorldRenderer
019 *   //but this JStachio allows you to render directly
020 *   //from the model.
021 *   return JStachio.render(new HelloWorld(name));
022 * }
023 * </code> </pre> Not only is the above more convenient than using the raw generated code
024 * it also allows additional custom runtime behavior like filtering as well as allows
025 * easier integration with web frameworks.
026 *
027 * @apiNote The static <strong><code>render</code></strong> methods are convenience
028 * methods that will use the ServiceLoader based JStachio which loads all extensions via
029 * the {@link ServiceLoader}.
030 * @see JStachioExtension
031 * @see JStache
032 */
033public interface JStachio extends Renderer<Object> {
034
035        /**
036         * Finds a template by using the models class if possible and then applies filtering
037         * and then finally render the model by writing to the appendable.
038         * <p>
039         * {@inheritDoc}
040         */
041        void execute(Object model, Appendable appendable) throws IOException;
042
043        /**
044         * Finds a template by using the models class if possible and then applies filtering
045         * and then finally render the model by writing to the {@link StringBuilder}.
046         * <p>
047         * {@inheritDoc}
048         */
049        StringBuilder execute(Object model, StringBuilder sb);
050
051        /**
052         * Finds a template by using the models class if possible and then applies filtering
053         * and then finally render the model to a String.
054         * <p>
055         * {@inheritDoc}
056         */
057        String execute(Object model);
058
059        /**
060         * Determines if this jstachio can render the model type (the class annotated by
061         * JStache).
062         * @param modelType the models class (<em>the one annotated with {@link JStache} and
063         * not the Templates class</em>)
064         * @return true if this jstachio can render instances of modelType
065         */
066        boolean supportsType(Class<?> modelType);
067
068        /**
069         * Executes the ServiceLoader instance of JStachio
070         * {@link #execute(Object, Appendable)}.
071         * @param model never <code>null</code>
072         * @param a appendable never <code>null</code>
073         * @throws IOException if there is an error using the appendable
074         * @see #execute(Object, Appendable)
075         */
076        public static void render(Object model, Appendable a) throws IOException {
077                of().execute(model, a);
078        }
079
080        /**
081         * Executes the ServiceLoader instance of JStachio
082         * {@link #execute(Object, StringBuilder)}.
083         * @param model never <code>null</code>
084         * @param a appendable never <code>null</code>
085         * @return the passed in {@link StringBuilder}
086         * @see #execute(Object, StringBuilder)
087         */
088        public static StringBuilder render(Object model, StringBuilder a) {
089                return of().execute(model, a);
090        }
091
092        /**
093         * Executes the ServiceLoader instance of JStachio {@link #execute(Object)}.
094         * @param model the root context model. Never <code>null</code>.
095         * @return the rendered string.
096         * @see #execute(Object)
097         */
098        public static String render(Object model) {
099                return of().execute(model);
100        }
101
102        /**
103         * Gets the static singleton jstachio.
104         * @return the jstachio from {@link #setStaticJStachio(Supplier)}
105         * @throws NullPointerException if jstachio is not found
106         * @see #setStaticJStachio(Supplier)
107         */
108        public static JStachio of() {
109                JStachio jstachio = JStachioHolder.get();
110                if (jstachio == null) {
111                        throw new NullPointerException("JStachio not found. This is probably a classloading issue.");
112                }
113                return jstachio;
114        }
115
116        /**
117         * Gets default singleton ServiceLoader based jstachio.
118         * @return service loaded jstachio
119         */
120        public static JStachio defaults() {
121                return io.jstach.jstachio.spi.JStachioFactory.defaultJStachio();
122        }
123
124        /**
125         * Set the static singleton of JStachio.
126         * <p>
127         * Useful if you would like to avoid using the default ServiceLoader mechanism.
128         * @param jstachioProvider if null a NPE will be thrown.
129         * @apiNote the provider will be called on every call of {@link #of()} and thus to
130         * avoid constant recreation it is recommend the supplier be memoized/cached.
131         */
132        public static void setStaticJStachio(Supplier<JStachio> jstachioProvider) {
133                if (jstachioProvider == null) {
134                        throw new NullPointerException("JStachio provider cannot be null");
135                }
136                JStachioHolder.provider = jstachioProvider;
137        }
138
139}
140
141final class JStachioHolder {
142
143        static Supplier<JStachio> provider = JStachio::defaults;
144
145        static JStachio get() {
146                return provider.get();
147        }
148
149}