gg.jte.runtime.Template Maven / Gradle / Ivy
package gg.jte.runtime;
import gg.jte.TemplateException;
import gg.jte.TemplateOutput;
import gg.jte.html.HtmlInterceptor;
import gg.jte.html.HtmlTemplateOutput;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public final class Template {
private final String name;
private final Class> clazz;
private final int parameterCount;
private Method render;
private Method renderMap;
private Map> parameterInfo;
public Template(String name, Class> clazz) {
this.name = name;
this.clazz = clazz;
findRenderMethods(clazz);
parameterCount = resolveParameterCount();
}
public void render(TemplateOutput output, HtmlInterceptor htmlInterceptor, Object param) throws Throwable {
try {
if (parameterCount == 0) {
render.invoke(null, output, htmlInterceptor);
} else {
render.invoke(null, output, htmlInterceptor, param);
}
} catch (InvocationTargetException e) {
throw e.getCause();
} catch (IllegalArgumentException e) {
if (render.getParameterTypes()[0] == HtmlTemplateOutput.class && !(output instanceof HtmlTemplateOutput)) {
throw new TemplateException("The template " + name + " was compiled with ContentType.Html, but the template engine was initialized with ContentType.Plain. Please initialize the template engine with ContentType.Html.", e);
} else {
String expectedType = render.getParameterTypes()[2].getName();
String actualType = param != null ? param.getClass().getName() : null;
String message = "Failed to render " + name + ", type mismatch for parameter: Expected " + expectedType + ", got " + actualType;
if (isMangledKotlinRenderMethod(render)) {
message += "\nIt looks like you're rendering a template with a Kotlin value class parameter. To make this work, pass the value class parameter in a map.\nExample: templateEngine.render(\"" + name + "\", mapOf(\"myValue\" to MyValueClass(), output)";
}
throw new TemplateException(message, e);
}
}
}
public void renderMap(TemplateOutput output, HtmlInterceptor htmlInterceptor, Map params) throws Throwable {
try {
renderMap.invoke(null, output, htmlInterceptor, params);
} catch (InvocationTargetException e) {
throw e.getCause();
}
}
public ClassLoader getClassLoader() {
return clazz.getClassLoader();
}
private void findRenderMethods(Class> clazz) {
for (Method declaredMethod : clazz.getDeclaredMethods()) {
if ("render".equals(declaredMethod.getName())) {
render = declaredMethod;
} else if ("renderMap".equals(declaredMethod.getName())) {
renderMap = declaredMethod;
} else if (isMangledKotlinRenderMethod(declaredMethod)) {
render = declaredMethod;
}
}
if (render == null) {
throw new IllegalStateException("Failed to init template " + name + ", no method named 'render' found in " + clazz);
}
if (renderMap == null) {
throw new IllegalStateException("Failed to init template " + name + ", no method named 'renderMap' found in " + clazz);
}
}
private int resolveParameterCount() {
return render.getParameterCount() - 2;
}
public Map> getParamInfo() {
if (parameterInfo == null) {
parameterInfo = calculateParameterInfo();
}
return parameterInfo;
}
private Map> calculateParameterInfo() {
Map> result = new HashMap<>();
Parameter[] parameters = render.getParameters();
for (int i = 2; i < parameters.length; ++i) {
if (!parameters[i].isNamePresent()) {
throw new TemplateException("No parameter information is available for " + name + ", compile templates with -parameters flag, to use this method.");
}
result.put(parameters[i].getName(), parameters[i].getType());
}
return Collections.unmodifiableMap(result);
}
/**
* The Kotlin compiler mangles methods value class parameters, for example render-JMhnnco
* See related GitHub issue.
*/
private boolean isMangledKotlinRenderMethod(Method method) {
return name.endsWith(".kte") && method.getName().startsWith("render-");
}
}