001package io.jstach.jstachio; 002 003import java.io.IOException; 004import java.nio.charset.UnsupportedCharsetException; 005import java.util.NoSuchElementException; 006import java.util.Objects; 007import java.util.ServiceLoader; 008import java.util.function.Supplier; 009 010import io.jstach.jstache.JStache; 011import io.jstach.jstache.JStacheConfig; 012import io.jstach.jstachio.spi.AbstractJStachio; 013import io.jstach.jstachio.spi.JStachioConfig; 014import io.jstach.jstachio.spi.JStachioExtension; 015import io.jstach.jstachio.spi.JStachioFactory; 016 017/** 018 * Render models by using reflection or static catalog to lookup generated templates as 019 * well as apply filtering and fallback mechanisms. 020 * <h2>Example Usage</h2> <pre><code class="language-java"> 021 * @JStache(template = "Hello {{name}}!") 022 * public record HelloWorld(String name) {} 023 * 024 * public static String output(String name) { 025 * //Normally you would have to use generated class HelloWorldRenderer 026 * //but this JStachio allows you to render directly 027 * //from the model. 028 * return JStachio.render(new HelloWorld(name)); 029 * } 030 * </code> </pre> Not only is the above more convenient than using the raw generated code 031 * it also allows additional custom runtime behavior like filtering as well as allows 032 * easier integration with web frameworks. 033 * 034 * <h2>Customize</h2> 035 * 036 * The default {@link JStachio} uses the {@link ServiceLoader} to load 037 * {@link JStachioExtension}s. You can customize it by adding jars that have provided 038 * {@link JStachioExtension}s or by {@linkplain JStachioConfig adjusting config}. 039 * 040 * <p> 041 * 042 * If you would like to create your own {@link JStachio} instead of the default you can 043 * either extend {@link AbstractJStachio} or use {@link JStachioFactory#builder()}. If you 044 * want your custom {@link JStachio} to be set as the default such that the static render 045 * methods on this class call it you can do that with {@link #setStatic(Supplier)}. 046 * <strong> While this interface is not sealed it is strongly recommended that you do not 047 * implement this interface! </strong> It has been left unsealed for mocking and testing 048 * purposes. 049 * 050 * @apiNote The static <strong><code>render</code></strong> methods are convenience 051 * methods that will by default use the ServiceLoader based JStachio which loads all 052 * extensions via the {@link ServiceLoader}. 053 * @see JStachioExtension 054 * @see JStache 055 * @see JStachioFactory#builder() 056 */ 057public interface JStachio extends Renderer<Object> { 058 059 /** 060 * Finds a template by using the models class if possible and then applies filtering 061 * and then finally render the model by writing to the appendable. 062 * <p> 063 * {@inheritDoc} 064 */ 065 @Override 066 default void execute(Object model, Appendable appendable) throws IOException { 067 Renderer.super.execute(model, appendable); 068 } 069 070 /** 071 * Finds a template by using the models class if possible and then applies filtering 072 * and then finally render the model by writing to the {@link StringBuilder}. 073 * <p> 074 * {@inheritDoc} 075 */ 076 @Override 077 default StringBuilder execute(Object model, StringBuilder sb) { 078 return Renderer.super.execute(model, sb); 079 } 080 081 /** 082 * Finds a template by using the models class if possible and then applies filtering 083 * and then finally render the model to a String. 084 * <p> 085 * {@inheritDoc} 086 */ 087 @Override 088 default String execute(Object model) { 089 return Renderer.super.execute(model); 090 } 091 092 /** 093 * Renders the passed in model directly to a binary stream possibly leveraging 094 * pre-encoded parts of the template. This <em>may</em> improve performance when 095 * rendering UTF-8 to an OutputStream as some of the encoding is done in advance. 096 * Because the encoding is done statically you cannot pass the charset in. The chosen 097 * charset comes from {@link JStacheConfig#charset()}. 098 * @param <A> output type 099 * @param <E> error type 100 * @param model a model assumed never to be <code>null</code>. 101 * @param output to write to. 102 * @return the passed in output for convenience 103 * @throws UnsupportedCharsetException if the encoding of the output does not match 104 * the template. 105 * @throws E if an error occurs while writing to output 106 */ 107 public <A extends io.jstach.jstachio.Output.EncodedOutput<E>, E extends Exception> A write( // 108 Object model, // 109 A output) throws E; 110 111 /** 112 * Finds a template by model. This is useful if you need metadata before writing such 113 * as charset and media type for HTTP output which the template has. 114 * <p> 115 * The returned template is decorated if filtering is on and a filter that is not the 116 * template is applied. 117 * <p> 118 * Passing in a {@link TemplateModel} should work as well and the returned template 119 * will be able to execute the TemplateModel as though it were a regular model. 120 * @param model the actual model or a {@link TemplateModel} containing the model 121 * @return a filtered template 122 * @throws NoSuchElementException if a template is not found and no other lookup 123 * errors happen. 124 * @throws Exception if template cannot be found for unexpected reasons such as 125 * reflection errors. 126 * @apiNote implementations should handle {@link TemplateModel} passed in. 127 */ 128 Template<Object> findTemplate(Object model) throws Exception; 129 130 /** 131 * Determines if this jstachio can render the model type (the class annotated by 132 * JStache). 133 * @param modelType the models class (<em>the one annotated with {@link JStache} and 134 * not the Templates class</em>) 135 * @return true if this jstachio can render instances of modelType 136 */ 137 boolean supportsType(Class<?> modelType); 138 139 /** 140 * Executes the ServiceLoader instance of JStachio 141 * {@link #execute(Object, Appendable)}. 142 * @param model never <code>null</code> 143 * @param a appendable never <code>null</code> 144 * @throws IOException if there is an error using the appendable 145 * @see #execute(Object, Appendable) 146 */ 147 public static void render(Object model, Appendable a) throws IOException { 148 of().execute(model, a); 149 } 150 151 /** 152 * Executes the ServiceLoader instance of JStachio 153 * {@link #execute(Object, StringBuilder)}. 154 * @param model never <code>null</code> 155 * @param a appendable never <code>null</code> 156 * @return the passed in {@link StringBuilder} 157 * @see #execute(Object, StringBuilder) 158 */ 159 public static StringBuilder render(Object model, StringBuilder a) { 160 return of().execute(model, a); 161 } 162 163 /** 164 * Executes the ServiceLoader instance of JStachio {@link #execute(Object)}. 165 * @param model the root context model. Never <code>null</code>. 166 * @return the rendered string. 167 * @see #execute(Object) 168 */ 169 public static String render(Object model) { 170 return of().execute(model); 171 } 172 173 /** 174 * Gets the static singleton jstachio. 175 * @return the jstachio from {@link #setStatic(Supplier)} 176 * @throws NullPointerException if jstachio is not found 177 * @see #setStatic(Supplier) 178 */ 179 public static JStachio of() { 180 return JStachioHolder.get(); 181 } 182 183 /** 184 * Gets default singleton ServiceLoader based jstachio. 185 * @return service loaded jstachio 186 */ 187 public static JStachio defaults() { 188 return io.jstach.jstachio.spi.JStachioFactory.defaultJStachio(); 189 } 190 191 /** 192 * Set the static singleton of JStachio. 193 * <p> 194 * Useful if you would like to avoid using the default ServiceLoader mechanism. 195 * @param jstachioProvider if null a NPE will be thrown. 196 * @apiNote the provider will be called on every call of {@link #of()} and thus to 197 * avoid constant recreation it is recommend the supplier be memoized/cached. 198 */ 199 public static void setStatic(Supplier<JStachio> jstachioProvider) { 200 if (isNull(jstachioProvider)) { 201 throw new NullPointerException("JStachio provider cannot be null"); 202 } 203 JStachioHolder.provider = jstachioProvider; 204 } 205 206 private static boolean isNull(Object o) { 207 return o == null; 208 } 209 210} 211 212final class JStachioHolder { 213 214 static Supplier<JStachio> provider = JStachio::defaults; 215 216 private JStachioHolder() { 217 } 218 219 static JStachio get() { 220 return Objects.requireNonNull(provider.get(), "JStachio not found. This is probably a classloading issue."); 221 } 222 223}