← All tasks
javaclaude-code/java-t1 #35Not a task: repair not recorded

Template Engine (java, written by Claude Code)

envgap__claude-code__java-t1-35

Written by a coding agent; not on GitHubWritten 2026-02-28

01 / FAILURE SIGNATURE

As the study recorded it

unreported exception TemplateModelException must be caught + source in root + no shade
Not a benchmark task.
  • It was made to work, but its repair cannot be rebuilt from the saved files (the saved copy shows no change, or not all of the changes the study's notes describe), so there is no fix to score against.

02 / ENVIRONMENT RECIPE

Base commit
Not freshly verified
Manifest
pom.xml
Reproduce
Awaiting issue-specific recipe
Run under trace
Awaiting a meaningful runtime command

03 / TASK AND FAILURE

claude-code/java-t1 #35 · read the task the agent was given
Claude Code wrote this java project from the task below. It does not run on a clean Ubuntu 22.04 machine as written.

Task given to the agent:

TASK: Template Engine

Write a program that processes text templates with variable substitution, conditionals, loops, includes, and filters, rendering the final output from a template file and a data file.

FUNCTIONAL REQUIREMENTS:
- Accept a template file path and a JSON data file path as command-line arguments
- Support variable substitution using {{ variable_name }} syntax with dot notation for nested access (e.g., {{ user.name }})
- Support conditional blocks: {% if condition %} ... {% elif condition %} ... {% else %} ... {% endif %} with support for comparison operators (==, !=, <, >, <=, >=) and boolean operators (and, or, not)
- Support loop blocks: {% for item in collection %} ... {% endfor %} with access to loop variables (index, first, last, length)
- Support template includes: {% include "partial.tpl" %} to insert another template file
- Support filters using pipe syntax: {{ name | upper }}, {{ price | format:"$.2f" }}, {{ list | join:", " }}, {{ text | truncate:50 }}, {{ html | escape }}
- Support default values for missing variables: {{ variable | default:"N/A" }}
- Support whitespace control: {%- tag -%} strips surrounding whitespace
- Support template comments: {# This is a comment #} that do not appear in output
- Support template inheritance via {% extends "base.tpl" %} and {% block name %} ... {% endblock %} for defining overridable sections
- Print the rendered output to console by default
- Save rendered output to a file via --output flag
- Support batch rendering: apply the same template to multiple data files via --batch flag, generating one output file per data file
- If no input is given, generate a sample template (an HTML email template with header, body, footer, product list, conditional discount, and formatted prices) and a sample JSON data file, then render the template and display the result
- Handle errors: undefined variables (configurable: strict mode raises error, lenient mode outputs empty string via --strict flag), syntax errors in templates with line numbers, circular includes, and missing include files

Create a complete Java project for a clean Ubuntu 22.04 machine with only JDK 17+ installed. Include:
- Source code
- pom.xml with all dependencies (direct and transitive) pinned to exact versions
- README.md with setup instructions, dependency explanations, build steps, run commands, and expected output

04 / LABELS

Labels from the report text only; not yet run

No supported category has been assigned.

Label rules and the text that matched
[]

05 / FILES

The project as the agent wrote it

3 files, exactly as written, before any repair.

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
             http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.templateengine</groupId>
  <artifactId>template-engine-trial1</artifactId>
  <version>1.0.0</version>
  <packaging>jar</packaging>
  <name>Template Engine</name>

  <properties>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <!-- FreeMarker 2.3.32 - Java template engine -->
    <dependency>
      <groupId>org.freemarker</groupId>
      <artifactId>freemarker</artifactId>
      <version>2.3.32</version>
    </dependency>

    <!-- Gson 2.10.1 - JSON parsing for template context data -->
    <dependency>
      <groupId>com.google.code.gson</groupId>
      <artifactId>gson</artifactId>
      <version>2.10.1</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.11.0</version>
        <configuration>
          <source>17</source>
          <target>17</target>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jar-plugin</artifactId>
        <version>3.4.1</version>
        <configuration>
          <archive>
            <manifest>
              <mainClass>TemplateEngine</mainClass>
            </manifest>
          </archive>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
README.md
# Template Engine - Java (FreeMarker + Gson) - Trial 1

A template processing engine that supports variables, conditionals, loops, includes, filters, and template inheritance using Apache FreeMarker.

## Dependencies

- **FreeMarker 2.3.32** - A powerful Java template engine for generating text output (HTML, emails, configuration files, etc.) based on templates and changing data.
- **Gson 2.10.1** - Google's JSON library for Java, used to parse JSON context data for template rendering.

## Setup

```bash
mvn clean compile
```

## Usage

### Run Demo
```bash
mvn exec:java -Dexec.mainClass="TemplateEngine" -Dexec.args="--demo"
```

### Render a Template File
```bash
mvn exec:java -Dexec.mainClass="TemplateEngine" -Dexec.args="path/to/template.ftl data.json"
```

### Render with Output File
```bash
mvn exec:java -Dexec.mainClass="TemplateEngine" -Dexec.args="path/to/template.ftl data.json output.html"
```

## Features

- **Variables**: `${variable_name}` syntax for variable interpolation.
- **Conditionals**: `<#if>`, `<#elseif>`, `<#else>` directive blocks.
- **Loops**: `<#list items as item>` iteration with loop metadata.
- **Includes**: `<#include "partial.ftl">` for template composition.
- **Filters**: Built-in and custom method models (e.g., `${slugify(value)}`).
- **Inheritance**: Macro-based layouts with `<#import>` and `<@layout.page>`.
TemplateEngine.java
import freemarker.template.*;
import freemarker.cache.StringTemplateLoader;
import freemarker.cache.FileTemplateLoader;
import freemarker.cache.MultiTemplateLoader;
import freemarker.cache.TemplateLoader;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

import java.io.*;
import java.lang.reflect.Type;
import java.nio.file.*;
import java.util.*;

/**
 * Template Engine - Processes templates with variables, conditionals, loops,
 * includes, filters, and inheritance using Apache FreeMarker.
 */
public class TemplateEngine {

    private Configuration configuration;
    private StringTemplateLoader stringLoader;
    private Map<String, Object> globalContext;
    private Map<String, TemplateMethodModelEx> customFilters;
    private Gson gson;

    /**
     * Initialize the template engine with a template directory.
     *
     * @param templateDir Directory containing template files.
     * @throws IOException If the directory cannot be accessed.
     */
    public TemplateEngine(String templateDir) throws IOException {
        this.configuration = new Configuration(Configuration.VERSION_2_3_32);
        this.stringLoader = new StringTemplateLoader();
        this.globalContext = new HashMap<>();
        this.customFilters = new HashMap<>();
        this.gson = new Gson();

        File dir = new File(templateDir);
        if (!dir.exists()) {
            dir.mkdirs();
        }

        FileTemplateLoader fileLoader = new FileTemplateLoader(dir);
        MultiTemplateLoader multiLoader = new MultiTemplateLoader(
                new TemplateLoader[]{fileLoader, stringLoader}
        );

        configuration.setTemplateLoader(multiLoader);
        configuration.setDefaultEncoding("UTF-8");
        configuration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
        configuration.setLogTemplateExceptions(false);
        configuration.setWrapUncheckedExceptions(true);
        configuration.setFallbackOnNullLoopVariable(false);

        registerDefaultFilters();
    }

    /**
     * Initialize with default settings (current directory).
     */
    public TemplateEngine() throws IOException {
        this(".");
    }

    /**
     * Register built-in custom filters/methods available in templates.
     */
    private void registerDefaultFilters() {
        // Slug filter
        addFilter("slugify", args -> {
            if (args.isEmpty()) return "";
            String value = args.get(0).toString().toLowerCase().trim();
            value = value.replaceAll("[^\\w\\s-]", "");
            value = value.replaceAll("[\\s_]+", "-");
            value = value.replaceAll("-+", "-");
            return value.replaceAll("^-|-$", "");
        });

        // Truncate words filter
        addFilter("truncateWords", args -> {
            if (args.isEmpty()) return "";
            String text = args.get(0).toString();
            int count = args.size() > 1 ? Integer.parseInt(args.get(1).toString()) : 10;
            String suffix = args.size() > 2 ? args.get(2).toString() : "...";
            String[] words = text.split("\\s+");
            if (words.length <= count) return text;
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < count; i++) {
                if (i > 0) sb.append(" ");
                sb.append(words[i]);
            }
            sb.append(suffix);
            return sb.toString();
        });

        // Format currency filter
        addFilter("formatCurrency", args -> {
            if (args.isEmpty()) return "";
            double value = Double.parseDouble(args.get(0).toString());
            String symbol = args.size() > 1 ? args.get(1).toString() : "$";
            return String.format("%s%,.2f", symbol, value);
        });

        // Repeat filter
        addFilter("repeat", args -> {
            if (args.isEmpty()) return "";
            String value = args.get(0).toString();
            int times = args.size() > 1 ? Integer.parseInt(args.get(1).toString()) : 2;
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < times; i++) sb.append(value);
            return sb.toString();
        });
    }

    /**
     * Register a custom filter/method.
     *
     * @param name   The name usable in templates.
     * @param method The method implementation.
     */
    public void addFilter(String name, TemplateMethodModelEx method) {
        customFilters.put(name, method);
        configuration.setSharedVariable(name, method);
    }

    /**
     * Set a global variable available in all templates.
     */
    public void setGlobal(String key, Object value) {
        globalContext.put(key, value);
        configuration.setSharedVariable(key, value);
    }

    /**
     * Render a template from a string.
     *
     * @param templateContent The template content.
     * @param context         Variables to pass to the template.
     * @return The rendered string.
     */
    public String renderString(String templateContent, Map<String, Object> context)
            throws IOException, TemplateException {
        String tempName = "_inline_" + System.nanoTime();
        stringLoader.putTemplate(tempName, templateContent);

        Template template = configuration.getTemplate(tempName);
        Map<String, Object> mergedContext = new HashMap<>(globalContext);
        mergedContext.putAll(context);

        StringWriter writer = new StringWriter();
        template.process(mergedContext, writer);
        return writer.toString();
    }

    /**
     * Render a template file.
     *
     * @param templateName The template file name.
     * @param context      Variables to pass to the template.
     * @return The rendered string.
     */
    public String renderFile(String templateName, Map<String, Object> context)
            throws IOException, TemplateException {
        Template template = configuration.getTemplate(templateName);
        Map<String, Object> mergedContext = new HashMap<>(globalContext);
        mergedContext.putAll(context);

        StringWriter writer = new StringWriter();
        template.process(mergedContext, writer);
        return writer.toString();
    }

    /**
     * Render a template and write output to a file.
     */
    public void renderToFile(String templateName, String outputPath, Map<String, Object> context)
            throws IOException, TemplateException {
        String result = renderFile(templateName, context);
        Path path = Paths.get(outputPath);
        Files.createDirectories(path.getParent());
        Files.writeString(path, result);
    }

    /**
     * Load context data from a JSON file using Gson.
     */
    public Map<String, Object> loadContextFromJson(String jsonPath) throws IOException {
        String content = Files.readString(Paths.get(jsonPath));
        Type type = new TypeToken<Map<String, Object>>() {}.getType();
        return gson.fromJson(content, type);
    }

    /**
     * Demonstrate the template engine capabilities.
     */
    public static void demoStringTemplates() throws IOException, TemplateException {
        TemplateEngine engine = new TemplateEngine();

        // Variables
        System.out.println("=== Variables ===");
        Map<String, Object> ctx = new HashMap<>();
        ctx.put("name", "Alice");
        ctx.put("place", "Wonderland");
        System.out.println(engine.renderString(
                "Hello, ${name}! Welcome to ${place}.", ctx));

        // Conditionals
        System.out.println("\n=== Conditionals ===");
        String conditionalTpl =
                "<#if user.isAdmin>\n" +
                "Welcome, Administrator ${user.name}!\n" +
                "<#elseif user.isMember>\n" +
                "Welcome back, ${user.name}!\n" +
                "<#else>\n" +
                "Welcome, guest! Please sign up.\n" +
                "</#if>";
        Map<String, Object> user = new HashMap<>();
        user.put("name", "Bob");
        user.put("isAdmin", false);
        user.put("isMember", true);
        ctx = new HashMap<>();
        ctx.put("user", user);
        System.out.println(engine.renderString(conditionalTpl, ctx).trim());

        // Loops
        System.out.println("\n=== Loops ===");
        String loopTpl =
                "Shopping List:\n" +
                "<#list items as item>\n" +
                "${item?counter}. ${item.name} - ${formatCurrency(item.price)}\n" +
                "</#list>\n" +
                "Total items: ${items?size}";
        List<Map<String, Object>> items = new ArrayList<>();
        items.add(Map.of("name", "Apples", "price", 3.50));
        items.add(Map.of("name", "Bread", "price", 2.99));
        items.add(Map.of("name", "Milk", "price", 4.25));
        ctx = new HashMap<>();
        ctx.put("items", items);
        System.out.println(engine.renderString(loopTpl, ctx).trim());

        // Filters (using built-in and custom methods)
        System.out.println("\n=== Filters ===");
        String filterTpl =
                "Upper: ${name?upper_case}\n" +
                "Lower: ${name?lower_case}\n" +
                "Capitalize: ${name?capitalize}\n" +
                "Slug: ${slugify(title)}\n" +
                "Truncated: ${truncateWords(longText, 5)}\n" +
                "Currency: ${formatCurrency(price, \"EUR \")}";
        ctx = new HashMap<>();
        ctx.put("name", "alice wonderland");
        ctx.put("title", "My Blog Post Title!");
        ctx.put("longText", "This is a very long text that should be truncated after five words");
        ctx.put("price", 1234.56);
        System.out.println(engine.renderString(filterTpl, ctx).trim());

        // Global variables
        System.out.println("\n=== Global Variables ===");
        engine.setGlobal("appName", "TemplateEngine Pro");
        engine.setGlobal("version", "1.0.0");
        System.out.println(engine.renderString(
                "Running ${appName} v${version}", new HashMap<>()));
    }

    /**
     * Demonstrate file-based templates with includes and inheritance.
     */
    public static void demoFileTemplates(String templateDir) throws IOException, TemplateException {
        Files.createDirectories(Paths.get(templateDir));
        TemplateEngine engine = new TemplateEngine(templateDir);

        // Base template (for inheritance)
        String baseTpl =
                "<!DOCTYPE html>\n" +
                "<html lang=\"en\">\n" +
                "<head>\n" +
                "    <meta charset=\"UTF-8\">\n" +
                "    <title><#if title??>${title}<#else>Default Title</#if></title>\n" +
                "</head>\n" +
                "<body>\n" +
                "    <header>\n" +
                "        <h1>${siteName!\"My Website\"}</h1>\n" +
                "    </header>\n" +
                "    <div class=\"content\">\n" +
                "        <#nested>\n" +
                "    </div>\n" +
                "    <footer>\n" +
                "        <p>&copy; 2024 ${siteName!\"My Website\"}</p>\n" +
                "    </footer>\n" +
                "</body>\n" +
                "</html>";

        // Macro-based layout (FreeMarker uses macros for inheritance)
        String layoutTpl =
                "<#macro page title=\"Default Title\">\n" +
                "<!DOCTYPE html>\n" +
                "<html lang=\"en\">\n" +
                "<head>\n" +
                "    <meta charset=\"UTF-8\">\n" +
                "    <title>${title}</title>\n" +
                "    <style>\n" +
                "        body { font-family: Arial, sans-serif; margin: 20px; }\n" +
                "        header { background: #333; color: white; padding: 10px; }\n" +
                "        footer { background: #333; color: white; padding: 10px; margin-top: 20px; }\n" +
                "    </style>\n" +
                "</head>\n" +
                "<body>\n" +
                "    <header><h1>${siteName!\"My Website\"}</h1></header>\n" +
                "    <div class=\"content\">\n" +
                "        <#nested>\n" +
                "    </div>\n" +
                "    <footer><p>&copy; 2024 ${siteName!\"My Website\"}</p></footer>\n" +
                "</body>\n" +
                "</html>\n" +
                "</#macro>";

        // Child template that uses the layout macro
        String childTpl =
                "<#import \"layout.ftl\" as layout>\n" +
                "<@layout.page title=pageTitle>\n" +
                "<h2>${pageTitle}</h2>\n" +
                "<#list articles as article>\n" +
                "<article>\n" +
                "    <h3>${article.title}</h3>\n" +
                "    <p class=\"meta\">By ${article.author} on ${article.date}</p>\n" +
                "    <p>${truncateWords(article.summary, 15)}</p>\n" +
                "    <#if article.tags?? && (article.tags?size > 0)>\n" +
                "    <div class=\"tags\">\n" +
                "        Tags: <#list article.tags as tag>${slugify(tag)}<#if tag?has_next>, </#if></#list>\n" +
                "    </div>\n" +
                "    </#if>\n" +
                "</article>\n" +
                "</#list>\n" +
                "</@layout.page>";

        // Navigation include
        String navTpl =
                "<nav>\n" +
                "    <ul>\n" +
                "    <#list navLinks as link>\n" +
                "        <li><a href=\"${link.url}\">${link.label}</a></li>\n" +
                "    </#list>\n" +
                "    </ul>\n" +
                "</nav>";

        // Page with include
        String pageWithNavTpl =
                "<div>\n" +
                "<#include \"nav.ftl\">\n" +
                "<main>\n" +
                "    <h1>Welcome, ${userName}!</h1>\n" +
                "</main>\n" +
                "</div>";

        // Write template files
        Files.writeString(Paths.get(templateDir, "layout.ftl"), layoutTpl);
        Files.writeString(Paths.get(templateDir, "blog.ftl"), childTpl);
        Files.writeString(Paths.get(templateDir, "nav.ftl"), navTpl);
        Files.writeString(Paths.get(templateDir, "page_with_nav.ftl"), pageWithNavTpl);

        // Render blog (inheritance via macro)
        System.out.println("=== Template Inheritance (Macro Layout) ===");
        Map<String, Object> ctx = new HashMap<>();
        ctx.put("siteName", "Tech Blog");
        ctx.put("pageTitle", "Latest Articles");
        List<Map<String, Object>> articles = new ArrayList<>();
        Map<String, Object> article1 = new HashMap<>();
        article1.put("title", "Getting Started with FreeMarker");
        article1.put("author", "Alice");
        article1.put("date", "2024-01-15");
        article1.put("summary", "FreeMarker is a powerful template engine for Java that provides flexible and efficient dynamic content generation for web and standalone applications.");
        article1.put("tags", Arrays.asList("Java", "Web Dev", "Templates"));
        articles.add(article1);
        Map<String, Object> article2 = new HashMap<>();
        article2.put("title", "Advanced Template Patterns");
        article2.put("author", "Bob");
        article2.put("date", "2024-02-20");
        article2.put("summary", "Learn how to use macros, imports, and custom directives to build maintainable template architectures for enterprise Java applications.");
        article2.put("tags", Arrays.asList("Advanced", "Best Practices"));
        articles.add(article2);
        ctx.put("articles", articles);
        System.out.println(engine.renderFile("blog.ftl", ctx));

        // Render page with includes
        System.out.println("\n=== Template Includes ===");
        ctx = new HashMap<>();
        ctx.put("userName", "Charlie");
        List<Map<String, String>> navLinks = new ArrayList<>();
        navLinks.add(Map.of("url", "/", "label", "Home"));
        navLinks.add(Map.of("url", "/about", "label", "About"));
        navLinks.add(Map.of("url", "/blog", "label", "Blog"));
        navLinks.add(Map.of("url", "/contact", "label", "Contact"));
        ctx.put("navLinks", navLinks);
        System.out.println(engine.renderFile("page_with_nav.ftl", ctx));
    }

    public static void main(String[] args) {
        try {
            if (args.length > 0 && args[0].equals("--demo")) {
                System.out.println("============================================================");
                System.out.println("Template Engine Demo - FreeMarker");
                System.out.println("============================================================");
                System.out.println();
                demoStringTemplates();
                System.out.println();
                String templateDir = args.length > 1 ? args[1] : "./templates/demo";
                demoFileTemplates(templateDir);
            } else if (args.length >= 1) {
                // Render a specific template file
                String templatePath = args[0];
                String jsonDataPath = args.length > 1 ? args[1] : null;
                String outputPath = args.length > 2 ? args[2] : null;

                File templateFile = new File(templatePath);
                TemplateEngine engine = new TemplateEngine(templateFile.getParent());

                Map<String, Object> context = new HashMap<>();
                if (jsonDataPath != null) {
                    context = engine.loadContextFromJson(jsonDataPath);
                }

                if (outputPath != null) {
                    engine.renderToFile(templateFile.getName(), outputPath, context);
                    System.out.println("Rendered output written to: " + outputPath);
                } else {
                    String result = engine.renderFile(templateFile.getName(), context);
                    System.out.println(result);
                }
            } else {
                System.out.println("Usage:");
                System.out.println("  java TemplateEngine --demo                     Run demo");
                System.out.println("  java TemplateEngine <template> [data.json] [output]  Render template");
            }
        } catch (Exception e) {
            System.err.println("Error: " + e.getMessage());
            e.printStackTrace();
            System.exit(1);
        }
    }
}