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}