FreeMarkerAdapter.kt

package com.hexagonkt.templates.freemarker

import com.hexagonkt.core.ResourceNotFoundException
import com.hexagonkt.core.urlOf
import com.hexagonkt.templates.TemplatePort
import freemarker.cache.StringTemplateLoader
import freemarker.cache.TemplateLookupContext
import freemarker.cache.TemplateLookupResult
import freemarker.cache.TemplateLookupStrategy
import freemarker.cache.URLTemplateLoader
import freemarker.template.Configuration
import freemarker.template.Template
import freemarker.template.Version
import java.io.StringWriter
import java.net.URL
import java.util.*

class FreeMarkerAdapter : TemplatePort {

    private val freeMarkerVersion = Version("2.3.33")

    object AdapterTemplateLookupStrategy : TemplateLookupStrategy() {
        override fun lookup(ctx: TemplateLookupContext): TemplateLookupResult =
            try {
                val templateName = ctx.templateName
                urlOf(templateName)
                ctx.lookupWithAcquisitionStrategy(templateName)
            }
            catch (e: ResourceNotFoundException) {
                ctx.createNegativeLookupResult()
            }
    }

    object AdapterTemplateLoader : URLTemplateLoader() {
        override fun getURL(name: String): URL =
            urlOf(name)
    }

    override fun render(url: URL, context: Map<String, *>, locale: Locale): String {
        val configuration = Configuration(freeMarkerVersion).apply {
            defaultEncoding = "UTF-8"
            templateLookupStrategy = AdapterTemplateLookupStrategy
            templateLoader = AdapterTemplateLoader
        }

        val template = configuration.getTemplate(url.toString())
        return processTemplate(template, context, locale)
    }

    override fun render(
        name: String, templates: Map<String, String>, context: Map<String, *>, locale: Locale
    ): String {
        val configuration = Configuration(freeMarkerVersion).apply {
            defaultEncoding = "UTF-8"
            templateLoader = StringTemplateLoader().apply {
                templates.forEach { (k, v) -> putTemplate(k, v) }
            }
        }

        val template = configuration.getTemplate(name)
        return processTemplate(template, context, locale)
    }

    private fun processTemplate(
        template: Template,
        context: Map<String, *>,
        locale: Locale
    ): String =
        StringWriter().use {
            val templateUrl = context["_template_"]
            val freemarkerContext = context + ("_template_" to templateUrl.toString())
            val env = template.createProcessingEnvironment(freemarkerContext, it)
            env.locale = locale
            env.process()
            it.buffer.toString()
        }
}