001package io.jstach.jstachio.spi; 002 003import static io.jstach.jstachio.spi.Templates.sneakyThrow; 004import static io.jstach.jstachio.spi.Templates.validateEncoding; 005 006import io.jstach.jstachio.JStachio; 007import io.jstach.jstachio.Output; 008import io.jstach.jstachio.Output.EncodedOutput; 009import io.jstach.jstachio.Template; 010import io.jstach.jstachio.TemplateInfo; 011import io.jstach.jstachio.TemplateModel; 012import io.jstach.jstachio.context.ContextJStachio; 013import io.jstach.jstachio.context.ContextNode; 014import io.jstach.jstachio.context.ContextTemplate; 015import io.jstach.jstachio.spi.JStachioFilter.FilterChain; 016 017/** 018 * An abstract jstachio that just needs a {@link JStachioExtensions} container. 019 * <p> 020 * To extend just override {@link #extensions()}. 021 * 022 * @see JStachioExtensions 023 * @author agentgt 024 */ 025public abstract class AbstractJStachio implements JStachio, JStachioExtensions.Provider, ContextJStachio { 026 027 /** 028 * Do nothing constructor 029 */ 030 public AbstractJStachio() { 031 032 } 033 034 @Override 035 public <A extends Output<E>, E extends Exception> A execute(Object model, A appendable) throws E { 036 var t = _findTemplate(model); 037 return t.execute(_resolveModel(model), appendable); 038 } 039 040 @Override 041 public <A extends EncodedOutput<E>, E extends Exception> A write(Object model, A appendable) throws E { 042 var t = _findTemplate(model); 043 validateEncoding(t, appendable); 044 return t.write(_resolveModel(model), appendable); 045 } 046 047 /* 048 * IF YOU want this method to be not final please file a bug. 049 */ 050 @Override 051 public final <A extends EncodedOutput<E>, E extends Exception> A write(Object model, ContextNode context, A output) 052 throws E { 053 var t = _findTemplate(model); 054 validateEncoding(t, output); 055 var ct = ContextTemplate.of(t); 056 return ct.write(_resolveModel(model), context, output); 057 } 058 059 /* 060 * IF YOU want this method to be not final please file a bug. 061 */ 062 @Override 063 public final <A extends Output<E>, E extends Exception> A execute(Object model, ContextNode context, A appendable) 064 throws E { 065 var t = _findTemplate(model); 066 var ct = ContextTemplate.of(t); 067 return ct.execute(_resolveModel(model), context, appendable); 068 } 069 070 /* 071 * IF YOU want this method to be not final please file a bug. 072 */ 073 @Override 074 public final Template<Object> findTemplate(Object model) { 075 return _findTemplate(model); 076 } 077 078 /* 079 * IF YOU want this method protected (thus overrideable) please file a bug. 080 */ 081 @SuppressWarnings({ "unchecked" }) 082 private Template<Object> _findTemplate(Object model) { 083 TemplateInfo template; 084 if (model instanceof TemplateModel tm) { 085 /* 086 * TemplateModel has the template and model combined 087 */ 088 template = tm.template(); 089 model = tm.model(); 090 } 091 else { 092 template = template(model.getClass()); 093 } 094 var filter = loadFilter(model, template); 095 Template<Object> t = (Template<Object>) FilterChain.toTemplate(filter, template); 096 return t; 097 } 098 099 private static Object _resolveModel(Object model) { 100 if (model instanceof TemplateModel tm) { 101 return tm.model(); 102 } 103 return model; 104 } 105 106 /** 107 * Loads the filter and checks if it can process the model and template. 108 * @param model to render 109 * @param template loaded by {@link #template(Class)} 110 * @return filter chain that can process model 111 */ 112 protected final FilterChain loadFilter(Object model, TemplateInfo template) { 113 var jstachioFilter = extensions().getFilter(); 114 var filter = FilterChain.of(jstachioFilter, template); 115 if (filter.isBroken(model)) { 116 boolean isReflectiveTemplate = Templates.isReflectionTemplate(template); 117 final String ind = "\n\t"; 118 String reason = ""; 119 if (isReflectiveTemplate) { 120 reason = " This is usually because the template " 121 + "has not been compiled and reflection based rendering is not available."; 122 } 123 throw new BrokenFilterException( // 124 "Filter chain unable to process template/model." + reason // 125 + ind + "template: \"" + template.description() + "\"" // 126 + ind + "model type: \"" + model.getClass() + "\"" // 127 + ind + "reflection used: \"" + isReflectiveTemplate + "\""); 128 } 129 return filter; 130 } 131 132 /* 133 * IF YOU want this method to be not final please file a bug. 134 */ 135 @Override 136 public final boolean supportsType(Class<?> modelType) { 137 if (TemplateModel.class.isAssignableFrom(modelType)) { 138 return true; 139 } 140 if (Templates.isIgnoredType(modelType)) { 141 return false; 142 } 143 return extensions().getTemplateFinder().supportsType(modelType); 144 } 145 146 /** 147 * Finds the template by model class wrapping any exceptions. 148 * @param modelType the class of the model. 149 * @return found template never <code>null</code>. 150 */ 151 protected TemplateInfo template(Class<?> modelType) { 152 try { 153 return extensions().getTemplateFinder().findTemplate(modelType); 154 } 155 catch (Exception e) { 156 sneakyThrow(e); 157 throw new RuntimeException(e); 158 } 159 } 160 161} 162 163class DefaultJStachio extends AbstractJStachio { 164 165 private final JStachioExtensions extensions; 166 167 public DefaultJStachio(JStachioExtensions extensions) { 168 super(); 169 this.extensions = extensions; 170 } 171 172 @Override 173 public JStachioExtensions extensions() { 174 return this.extensions; 175 } 176 177}