← All tasks
javaclaude-code/java-t2 #35Not a task: not reproduced

Template Engine (java, written by Claude Code)

envgap__claude-code__java-t2-35

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

01 / FAILURE SIGNATURE

As the study recorded it

Invalid Java import alias syntax (import ... as); no src directory
Not a benchmark task.
  • In a clean container the reported failure did not reproduce, or the known fix did not make the project run.

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-t2 #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-trial2</artifactId>
  <version>1.0.0</version>
  <packaging>jar</packaging>
  <name>Template Engine - Thymeleaf</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>
    <!-- Thymeleaf 3.1.2.RELEASE - Server-side Java template engine -->
    <dependency>
      <groupId>org.thymeleaf</groupId>
      <artifactId>thymeleaf</artifactId>
      <version>3.1.2.RELEASE</version>
    </dependency>

    <!-- Jackson 2.16.1 - JSON parsing for template context data -->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.16.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 (Thymeleaf + Jackson) - Trial 2

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

## Dependencies

- **Thymeleaf 3.1.2.RELEASE** - A modern server-side Java template engine for web and standalone environments, supporting HTML, XML, JavaScript, CSS, and plain text.
- **Jackson 2.16.1** - High-performance JSON processor 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.html data.json"
```

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

## Features

- **Variables**: `th:text="${variable}"` attribute-based interpolation.
- **Conditionals**: `th:if`, `th:unless`, `th:switch`/`th:case` blocks.
- **Loops**: `th:each="item : ${items}"` iteration with status variables.
- **Includes**: `th:insert="~{fragment}"` for template composition.
- **Filters**: Built-in utility objects (`#strings`, `#numbers`, `#lists`, etc.).
- **Inheritance**: Fragment expressions and layout dialect for template reuse.
TemplateEngine.java
import org.thymeleaf.TemplateEngine as ThymeleafEngine;
// Note: The above won't compile as-is in Java; proper imports below.

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.type.TypeReference;

import java.io.*;
import java.nio.file.*;
import java.util.*;
import java.util.regex.*;

/**
 * Template Engine - Processes templates with variables, conditionals, loops,
 * includes, filters, and inheritance using Thymeleaf and Jackson.
 *
 * Dependencies: Thymeleaf 3.1.2.RELEASE, Jackson 2.16.1
 */
public class TemplateEngine {

    private org.thymeleaf.TemplateEngine thymeleafEngine;
    private org.thymeleaf.templateresolver.FileTemplateResolver fileResolver;
    private org.thymeleaf.templateresolver.StringTemplateResolver stringResolver;
    private Map<String, Object> globalContext;
    private Map<String, FilterFunction> customFilters;
    private ObjectMapper objectMapper;

    @FunctionalInterface
    public interface FilterFunction {
        Object apply(List<Object> args);
    }

    /**
     * Initialize the template engine with a template directory.
     *
     * @param templateDir Directory containing template files.
     */
    public TemplateEngine(String templateDir) {
        this.globalContext = new HashMap<>();
        this.customFilters = new HashMap<>();
        this.objectMapper = new ObjectMapper();

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

        // File resolver for file-based templates
        fileResolver = new org.thymeleaf.templateresolver.FileTemplateResolver();
        fileResolver.setPrefix(dir.getAbsolutePath() + File.separator);
        fileResolver.setSuffix("");
        fileResolver.setTemplateMode(org.thymeleaf.templatemode.TemplateMode.HTML);
        fileResolver.setCharacterEncoding("UTF-8");
        fileResolver.setCheckExistence(true);
        fileResolver.setOrder(1);

        // String resolver for inline string templates
        stringResolver = new org.thymeleaf.templateresolver.StringTemplateResolver();
        stringResolver.setTemplateMode(org.thymeleaf.templatemode.TemplateMode.TEXT);
        stringResolver.setOrder(2);

        thymeleafEngine = new org.thymeleaf.TemplateEngine();
        thymeleafEngine.setTemplateResolver(fileResolver);
        thymeleafEngine.addTemplateResolver(stringResolver);

        registerDefaultFilters();
    }

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

    /**
     * Register built-in custom filters.
     */
    private void registerDefaultFilters() {
        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("^-|-$", "");
        });

        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();
        });

        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);
        });

        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.
     */
    public void addFilter(String name, FilterFunction func) {
        customFilters.put(name, func);
    }

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

    /**
     * Create a Thymeleaf context from global + provided context.
     */
    private org.thymeleaf.context.Context createContext(Map<String, Object> context) {
        org.thymeleaf.context.Context ctx = new org.thymeleaf.context.Context();
        ctx.setVariables(globalContext);
        if (context != null) {
            ctx.setVariables(context);
        }
        // Add filter functions as utility objects
        ctx.setVariable("filters", customFilters);
        return ctx;
    }

    /**
     * Render a template from a string using Thymeleaf TEXT mode.
     *
     * @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) {
        org.thymeleaf.context.Context ctx = createContext(context);

        // Use a separate engine with only string resolver for inline rendering
        org.thymeleaf.TemplateEngine inlineEngine = new org.thymeleaf.TemplateEngine();
        org.thymeleaf.templateresolver.StringTemplateResolver sr =
                new org.thymeleaf.templateresolver.StringTemplateResolver();
        sr.setTemplateMode(org.thymeleaf.templatemode.TemplateMode.TEXT);
        inlineEngine.setTemplateResolver(sr);

        return inlineEngine.process(templateContent, ctx);
    }

    /**
     * Render a template file using Thymeleaf HTML mode.
     *
     * @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) {
        org.thymeleaf.context.Context ctx = createContext(context);
        return thymeleafEngine.process(templateName, ctx);
    }

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

    /**
     * Load context data from a JSON file using Jackson.
     */
    public Map<String, Object> loadContextFromJson(String jsonPath) throws IOException {
        return objectMapper.readValue(
                new File(jsonPath),
                new TypeReference<Map<String, Object>>() {}
        );
    }

    /**
     * Apply a registered filter by name.
     */
    public String applyFilter(String filterName, Object... args) {
        FilterFunction func = customFilters.get(filterName);
        if (func == null) {
            throw new IllegalArgumentException("Unknown filter: " + filterName);
        }
        return func.apply(Arrays.asList(args)).toString();
    }

    /**
     * Demonstrate the template engine capabilities using inline TEXT-mode templates.
     */
    public static void demoStringTemplates() {
        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 =
                "[# th:if=\"${user.isAdmin}\"]" +
                "Welcome, Administrator [(${user.name})]!\n" +
                "[/]\n" +
                "[# th:unless=\"${user.isAdmin}\"]" +
                "[# th:if=\"${user.isMember}\"]" +
                "Welcome back, [(${user.name})]!\n" +
                "[/]\n" +
                "[# th:unless=\"${user.isMember}\"]" +
                "Welcome, guest! Please sign up.\n" +
                "[/]\n" +
                "[/]";
        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" +
                "[# th:each=\"item,stat : ${items}\"]" +
                "[(${stat.count})]. [(${item.name})] - $[(${item.price})]\n" +
                "[/]\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 the custom filter API)
        System.out.println("\n=== Filters ===");
        System.out.println("Upper: " + "alice wonderland".toUpperCase());
        System.out.println("Lower: " + "alice wonderland".toLowerCase());
        System.out.println("Slug: " + engine.applyFilter("slugify", "My Blog Post Title!"));
        System.out.println("Truncated: " + engine.applyFilter("truncateWords",
                "This is a very long text that should be truncated after five words", 5));
        System.out.println("Currency: " + engine.applyFilter("formatCurrency", 1234.56, "EUR "));

        // 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 {
        Files.createDirectories(Paths.get(templateDir));
        TemplateEngine engine = new TemplateEngine(templateDir);

        // Base template (Thymeleaf uses th:fragment and th:replace for inheritance)
        String baseTpl =
                "<!DOCTYPE html>\n" +
                "<html xmlns:th=\"http://www.thymeleaf.org\" lang=\"en\">\n" +
                "<head>\n" +
                "    <meta charset=\"UTF-8\"/>\n" +
                "    <title th:text=\"${title != null ? title : 'Default 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>\n" +
                "        <h1 th:text=\"${siteName != null ? siteName : 'My Website'}\">Site</h1>\n" +
                "    </header>\n" +
                "    <div class=\"content\" th:insert=\"~{:: #mainContent}\">\n" +
                "    </div>\n" +
                "    <footer>\n" +
                "        <p>&copy; 2024 <span th:text=\"${siteName != null ? siteName : 'My Website'}\">Site</span></p>\n" +
                "    </footer>\n" +
                "</body>\n" +
                "</html>";

        // Blog page template
        String blogTpl =
                "<!DOCTYPE html>\n" +
                "<html xmlns:th=\"http://www.thymeleaf.org\" lang=\"en\">\n" +
                "<head>\n" +
                "    <meta charset=\"UTF-8\"/>\n" +
                "    <title th:text=\"${pageTitle}\">Blog</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>\n" +
                "        <h1 th:text=\"${siteName}\">Site</h1>\n" +
                "    </header>\n" +
                "    <div class=\"content\">\n" +
                "        <h2 th:text=\"${pageTitle}\">Title</h2>\n" +
                "        <article th:each=\"article : ${articles}\">\n" +
                "            <h3 th:text=\"${article.title}\">Article Title</h3>\n" +
                "            <p class=\"meta\">By <span th:text=\"${article.author}\">Author</span> on <span th:text=\"${article.date}\">Date</span></p>\n" +
                "            <p th:text=\"${article.summary}\">Summary</p>\n" +
                "            <div class=\"tags\" th:if=\"${article.tags != null and !article.tags.isEmpty()}\">\n" +
                "                Tags: <span th:each=\"tag,tagStat : ${article.tags}\">" +
                "<span th:text=\"${tag}\">tag</span><span th:unless=\"${tagStat.last}\">, </span></span>\n" +
                "            </div>\n" +
                "        </article>\n" +
                "    </div>\n" +
                "    <footer>\n" +
                "        <p>&copy; 2024 <span th:text=\"${siteName}\">Site</span></p>\n" +
                "        <p>Blog section - <span th:text=\"${articles.size()}\">0</span> articles</p>\n" +
                "    </footer>\n" +
                "</body>\n" +
                "</html>";

        // Navigation fragment (for includes)
        String navTpl =
                "<nav xmlns:th=\"http://www.thymeleaf.org\" th:fragment=\"navigation\">\n" +
                "    <ul>\n" +
                "        <li th:each=\"link : ${navLinks}\">\n" +
                "            <a th:href=\"${link.url}\" th:text=\"${link.label}\">Link</a>\n" +
                "        </li>\n" +
                "    </ul>\n" +
                "</nav>";

        // Page with include
        String pageWithNavTpl =
                "<!DOCTYPE html>\n" +
                "<html xmlns:th=\"http://www.thymeleaf.org\" lang=\"en\">\n" +
                "<body>\n" +
                "<div>\n" +
                "    <div th:replace=\"~{nav :: navigation}\"></div>\n" +
                "    <main>\n" +
                "        <h1>Welcome, <span th:text=\"${userName}\">User</span>!</h1>\n" +
                "    </main>\n" +
                "</div>\n" +
                "</body>\n" +
                "</html>";

        // Write template files
        Files.writeString(Paths.get(templateDir, "base.html"), baseTpl);
        Files.writeString(Paths.get(templateDir, "blog.html"), blogTpl);
        Files.writeString(Paths.get(templateDir, "nav.html"), navTpl);
        Files.writeString(Paths.get(templateDir, "page_with_nav.html"), pageWithNavTpl);

        // Render blog page
        System.out.println("=== Template with Iteration & Conditionals ===");
        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 Thymeleaf");
        article1.put("author", "Alice");
        article1.put("date", "2024-01-15");
        article1.put("summary", "Thymeleaf is a natural template engine for Java that integrates elegantly with Spring for server-side HTML rendering.");
        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 fragments, layouts, and expressions to build maintainable template architectures.");
        article2.put("tags", Arrays.asList("Advanced", "Best Practices"));
        articles.add(article2);
        ctx.put("articles", articles);
        System.out.println(engine.renderFile("blog.html", ctx));

        // Render page with includes
        System.out.println("\n=== Template Includes (Fragments) ===");
        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.html", 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 - Thymeleaf");
                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) {
                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);
        }
    }
}