All Downloads are FREE. Search and download functionalities are using the official Maven repository.

gg.jte.runtime.Template Maven / Gradle / Ivy

There is a newer version: 3.1.12
Show newest version
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-"); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy