bjio - Bidji I/O Namespace

Version: 0.8+ Namespace: bjio Type: FreeMarker Custom Directive


Table of Contents


Overview

The bjio namespace (Bidji I/O) provides methods for manipulating file paths and directories in FreeMarker templates.

Replaces: file?api.* (deprecated syntax)

Benefits:

  • ✅ Shorter and clearer syntax
  • ✅ Native FreeMarker validation
  • ✅ Descriptive error messages
  • ✅ Consistent with FreeMarker standards

Syntax

New Syntax (bjio)

${bjio.method(file)}
${bjio.method(file, argument)}

Old Syntax (DEPRECATED)

${file?api.method()}          ❌ DEPRECATED
${file?api.method(argument)}  ❌ DEPRECATED

⚠️ Important: The file?api.* syntax is deprecated and throws exceptions with migration help messages.


Available Methods

Filename Operations

bjio.name(file)

Returns the complete filename (with extension).

Parameters:

  • file : Java File object

Returns: String - Filename

Example:

${bjio.name(file)}

Input: /home/user/project/src/MyFile.java Output: MyFile.java


bjio.base_name(file)

Returns the filename without extension.

⚠️ Important: Removes everything after the first dot!

Parameters:

  • file : Java File object

Returns: String - Name without extension

Example:

${bjio.base_name(file)}

Behavior examples:

Input Output
MyFile.java MyFile
NoExtension NoExtension
archive.tar.gz archive ⚠️
My.File.With.Dots.txt My ⚠️

File Path Operations

bjio.absolute_path(file)

Returns the complete absolute path of the file.

Parameters:

  • file : Java File object

Returns: String - Absolute path

Example:

${bjio.absolute_path(file)}

Input: src/MyFile.java (relative) Output: /home/user/project/src/MyFile.java


bjio.relative_path(file)

Returns the relative path of the file (truncated from project root).

Parameters:

  • file : Java File object

Returns: String - Relative path

Example:

${bjio.relative_path(file)}

Input: /home/user/project/src/MyFile.java Output: src/MyFile.java


bjio.path(file, mode?)

Returns the file path according to the specified mode.

Parameters:

  • file : Java File object
  • mode : (optional) 'relative' or 'absolute' (default: 'absolute')

Returns: String - Path according to mode

Examples:

[#-- Default mode (absolute) --]
${bjio.path(file)}

[#-- Relative mode --]
${bjio.path(file, 'relative')}

[#-- Explicit absolute mode --]
${bjio.path(file, 'absolute')}

Conditional usage:

[#assign useRelative = true]
${bjio.path(file, useRelative?string('relative', 'absolute'))}

Directory Operations

bjio.dir_name(file)

Returns the parent directory name (without path).

Parameters:

  • file : Java File object

Returns: String - Parent directory name

Example:

${bjio.dir_name(file)}

Input: /home/user/project/src/main/MyFile.java Output: main


bjio.absolute_dir_path(file)

Returns the absolute path of the parent directory.

Parameters:

  • file : Java File object

Returns: String - Absolute directory path

Example:

${bjio.absolute_dir_path(file)}

Input: src/main/MyFile.java Output: /home/user/project/src/main


bjio.relative_dir_path(file)

Returns the relative path of the parent directory.

Parameters:

  • file : Java File object

Returns: String - Relative directory path

Example:

${bjio.relative_dir_path(file)}

Input: /home/user/project/src/main/MyFile.java Output: src/main


bjio.dir_path(file, mode?)

Returns the parent directory path according to the specified mode.

Parameters:

  • file : Java File object
  • mode : (optional) 'relative' or 'absolute' (default: 'absolute')

Returns: String - Directory path according to mode

Examples:

[#-- Default mode (absolute) --]
${bjio.dir_path(file)}

[#-- Relative mode --]
${bjio.dir_path(file, 'relative')}

[#-- Explicit absolute mode --]
${bjio.dir_path(file, 'absolute')}

Line Operations

bjio.lines(helper)

Returns all lines from a helper (txt, csv, etc.).

Parameters:

  • helper : A BidjiBuiltIn object (TxtHelper, CsvHelper, etc.)

Returns: List - List of all lines

Example:

[#assign all_lines = bjio.lines(txt)]
[#foreach line in all_lines]
    ${line.content}
[/#foreach]

Works with:

  • txt (TxtHelper)
  • csv, csv1, csv2, csvt (CsvHelper)
  • Any subclass of BidjiBuiltIn

Old syntax:

[#assign lines = txt?api.lines()]        ❌ DEPRECATED
[#assign lines = csv?api.lines()]        ❌ DEPRECATED

New syntax:

[#assign lines = bjio.lines(txt)]        ✅ USE THIS
[#assign lines = bjio.lines(csv)]        ✅ USE THIS

bjio.line(helper, index)

Returns the line at the specified index (1-based indexing).

⚠️ Important: Index starts at 1 (not 0)!

Parameters:

  • helper : A BidjiBuiltIn object (TxtHelper, CsvHelper, etc.)
  • index : Line index (1 = first line)

Returns: Object - The line at the specified index

Example:

[#-- Access the 5th line --]
${bjio.line(txt, 5).content}

[#-- First and last line --]
First: ${bjio.line(txt, 1).content}
Last: ${bjio.line(txt, bjio.lines(txt)?size).content}

Old syntax:

${txt?api.line(5)}                       ❌ DEPRECATED
${csv?api.line(3)}                       ❌ DEPRECATED

New syntax:

${bjio.line(txt, 5)}                     ✅ USE THIS
${bjio.line(csv, 3)}                     ✅ USE THIS

Notes:

  • Index is 1-based (first line = 1)
  • Exception is thrown if index is invalid (< 1 or > number of lines)
  • Use bjio.lines(helper)?size to get the total number of lines

Examples

Example 1: Java Class Generation

package com.example.${bjio.dir_name(file)};

/**
 * Generated from: ${bjio.name(file)}
 * Path: ${bjio.relative_path(file)}
 */
public class ${bjio.base_name(file)} {
    private String name = "${bjio.name(file)}";
    private String path = "${bjio.absolute_path(file)}";

    // Implementation
}

Input: file = /home/user/project/src/main/MyService.java

Output:

package com.example.main;

/**
 * Generated from: MyService.java
 * Path: src/main/MyService.java
 */
public class MyService {
    private String name = "MyService.java";
    private String path = "/home/user/project/src/main/MyService.java";

    // Implementation
}

Example 2: Maven POM Processing with Conditional Line Insertion

From templates/dev/pom.ftl:

[#foreach line in bjio.lines(txt)]
 [#if line?contains('<!-- INCLUDE_ANT4X -->')]
<dependency>
<groupId>net.sourceforge.ant4x</groupId>
<artifactId>ant4x</artifactId>
<version>${net.sourceforge.ant4x-version}</version>
<scope>${project.deps.scope}</scope>
</dependency>
 [#else]
${line}
 [/#if]
[/#foreach]

This example shows how to:

  • Read all lines from a text file
  • Check for markers/comments in each line
  • Conditionally replace or inject content
  • Perfect for processing template configuration files

Example 3: File Header Generation

/**
 * ========================================
 * File: ${bjio.name(file)}
 * Location: ${bjio.relative_dir_path(file)}
 * ========================================
 *
 * Generated: ${.now?string("yyyy-MM-dd HH:mm:ss")}
 * Source: ${bjio.absolute_path(file)}
 */

Output:

/**
 * ========================================
 * File: UserController.java
 * Location: src/main/controllers
 * ========================================
 *
 * Generated: 2026-02-12 10:30:45
 * Source: /home/user/project/src/main/controllers/UserController.java
 */

Example 4: Build Script Generation

#!/bin/bash
# Auto-generated build script for ${bjio.name(file)}

SOURCE_FILE="${bjio.absolute_path(file)}"
SOURCE_DIR="${bjio.absolute_dir_path(file)}"
BASE_NAME="${bjio.base_name(file)}"
OUTPUT_DIR="build/${bjio.relative_dir_path(file)}"

echo "Building $BASE_NAME..."
mkdir -p "$OUTPUT_DIR"
cd "$SOURCE_DIR"
javac -d "$OUTPUT_DIR" "$SOURCE_FILE"
echo "Build complete: $OUTPUT_DIR/$BASE_NAME.class"

Example 5: Markdown Documentation Generator

# ${bjio.base_name(file)}

**File:** `${bjio.relative_path(file)}`
**Directory:** `${bjio.relative_dir_path(file)}`
**Full Path:** `${bjio.absolute_path(file)}`

## Overview

This file is located in the `${bjio.dir_name(file)}` directory.

## File Information

- **Filename:** ${bjio.name(file)}
- **Base name:** ${bjio.base_name(file)}
- **Parent folder:** ${bjio.dir_name(file)}

Output:

# UserService

**File:** `src/main/services/UserService.java`
**Directory:** `src/main/services`
**Full Path:** `/home/user/project/src/main/services/UserService.java`

## Overview

This file is located in the `services` directory.

## File Information

- **Filename:** UserService.java
- **Base name:** UserService
- **Parent folder:** services

Example 6: CSV to HTML Table Conversion

[#assign csv_lines = bjio.lines(csv)]
[#assign columns = csv.columns()]

<table class="data-table">
  <caption>Data from ${bjio.name(csv.file)}</caption>
  <thead>
    <tr>
      [#foreach col in columns]
      <th>${col?cap_first}</th>
      [/#foreach]
    </tr>
  </thead>
  <tbody>
    [#foreach line in csv_lines]
    <tr>
      [#foreach col in columns]
      <td>${line[col]!""}</td>
      [/#foreach]
    </tr>
    [/#foreach]
  </tbody>
  <tfoot>
    <tr>
      <td colspan="${columns?size}">
        Total records: ${csv_lines?size}
      </td>
    </tr>
  </tfoot>
</table>

Example 7: Text File Processing with Line Numbers

[#assign all_lines = bjio.lines(txt)]

File: ${bjio.relative_path(txt.file)}
Total lines: ${all_lines?size}

[#foreach line in all_lines]
${(line?index + 1)?string("000")}: ${line.content}
[/#foreach]

First line: ${bjio.line(txt, 1).content}
Last line: ${bjio.line(txt, all_lines?size).content}

Output:

File: src/config/settings.txt
Total lines: 15

001: # Application Settings
002: app.name=MyApp
003: app.version=1.0.0
...
015: # End of settings

First line: # Application Settings
Last line: # End of settings

Example 8: Multi-CSV Comparison Report

[#assign lines1 = bjio.lines(csv1)]
[#assign lines2 = bjio.lines(csv2)]

# CSV Comparison Report

## File 1: ${bjio.name(csv1.file)}
- Location: ${bjio.relative_path(csv1.file)}
- Lines: ${lines1?size}
- First record: ${bjio.line(csv1, 1).id!"N/A"}

## File 2: ${bjio.name(csv2.file)}
- Location: ${bjio.relative_path(csv2.file)}
- Lines: ${lines2?size}
- First record: ${bjio.line(csv2, 1).id!"N/A"}

## Differences
- Line count difference: ${(lines1?size - lines2?size)?abs}

Example 9: Configuration File Conditional Processing

[#assign config_lines = bjio.lines(txt)]
[#assign env = "production"]

# Configuration for ${env}
# Generated from: ${bjio.relative_path(txt.file)}

[#foreach line in config_lines]
  [#if line?contains("DEBUG=") && env == "production"]
DEBUG=false
  [#elseif line?contains("PORT=") && env == "production"]
PORT=80
  [#else]
${line}
  [/#if]
[/#foreach]

This example demonstrates:

  • Reading configuration files
  • Conditionally modifying values based on environment
  • Preserving other settings unchanged

Example 10: Import Statement Generator

// Auto-generated imports for ${bjio.base_name(file)}
// Package: ${bjio.dir_name(file)}

[#assign deps = bjio.lines(txt)]
[#foreach dep in deps]
  [#if !dep?starts_with("#") && dep?trim != ""]
import com.example.${bjio.dir_name(file)}.${dep?trim};
  [/#if]
[/#foreach]

public class ${bjio.base_name(file)} {
    // Implementation
}

Migration from file?api

Complete Mapping Table

Old Syntax New Syntax
File Operations
${file?api.name()} ${bjio.name(file)}
${file?api.base_name()} ${bjio.base_name(file)}
${file?api.absolute_path()} ${bjio.absolute_path(file)}
${file?api.relative_path()} ${bjio.relative_path(file)}
${file?api.path('relative')} ${bjio.path(file, 'relative')}
${file?api.path('absolute')} ${bjio.path(file, 'absolute')}
${file?api.dir_name()} ${bjio.dir_name(file)}
${file?api.absolute_dir_path()} ${bjio.absolute_dir_path(file)}
${file?api.relative_dir_path()} ${bjio.relative_dir_path(file)}
${file?api.dir_path('relative')} ${bjio.dir_path(file, 'relative')}
Line Operations
${txt?api.lines()} ${bjio.lines(txt)}
${csv?api.lines()} ${bjio.lines(csv)}
${txt?api.line(5)} ${bjio.line(txt, 5)}
${csv?api.line(3)} ${bjio.line(csv, 3)}
[#assign lines = txt?api.lines()] [#assign lines = bjio.lines(txt)]
[#assign lines1 = csv1?api.lines()] [#assign lines1 = bjio.lines(csv1)]

Automatic Migration Script

Use the find_builtin.sh script to find all usages:

./find_builtin.sh /path/to/templates

Then replace with sed or use the migrate_api_to_bjio.sh script:

# File operations
sed -i 's/\${file?api\.name()}/\${bjio.name(file)}/g' template.ftl
sed -i 's/\${file?api\.base_name()}/\${bjio.base_name(file)}/g' template.ftl
sed -i 's/\${file?api\.absolute_path()}/\${bjio.absolute_path(file)}/g' template.ftl
sed -i 's/\${file?api\.relative_path()}/\${bjio.relative_path(file)}/g' template.ftl
sed -i "s/\${file?api\.path('\([^']*\)')}/\${bjio.path(file, '\1')}/g" template.ftl

# Line operations - assign statements
sed -i 's/\[#assign \([a-zA-Z0-9_]*\) = \([a-zA-Z0-9_]*\)?api\.lines()]/[#assign \1 = bjio.lines(\2)]/g' template.ftl

# Line operations - foreach loops
sed -i 's/\[#foreach line in \([a-zA-Z0-9_]*\)?api\.lines()]/[#foreach line in bjio.lines(\1)]/g' template.ftl

# Line operations - line access
sed -i 's/\${*\([a-zA-Z0-9_]*\)?api\.line(\([0-9]*\))}/\${bjio.line(\1, \2)}/g' template.ftl

OR use the automatic script:

./migrate_api_to_bjio.sh template.ftl

Error Messages

If you forget to migrate, the error is explicit:

UnsupportedOperationException: file?api.name() is deprecated.
Use: ${bjio.name(file)} instead of ${file?api.name()}
See doc/RefactoringBuiltin.md for migration guide.

Important Notes

1. base_name() Behavior

⚠️ Warning: bjio.base_name() removes everything after the first dot!

"MyFile.java"           → "MyFile"       ✅ Expected
"archive.tar.gz"        → "archive"      ⚠️ Not "archive.tar"!
"My.File.With.Dots.txt" → "My"           ⚠️ Not "My.File.With.Dots"!

This is the behavior of FileHelper.getBaseName() - identical to the old syntax.

If you need to remove only the last extension, use FreeMarker:

[#assign filename = bjio.name(file)]
[#assign basename = filename?keep_before_last(".")]

2. Required file Variable

All bjio methods require a File object as parameter.

This variable is automatically provided by Bidji tasks:

  • <bj:txtt file="..." />
  • <bj:xmlt file="..." />
  • etc.

3. Relative Paths

Relative paths depend on the FileHelper.truncatePath() method which uses the Bidji project root.

4. FreeMarker Validation

FreeMarker automatically validates:

  • Number of arguments
  • Argument types
  • Method existence

Errors are detected at template compilation time (not runtime).

5. Error Messages

Error messages are clear and instructive:

Unknown method:

Unknown bjio method: 'invalid_method'.
Available methods: name, base_name, absolute_path, relative_path, path, ...

Wrong number of arguments:

bjio.name() requires 1 argument: bjio.name(file)
Example: ${bjio.name(file)}

6. Line Operations Use 1-Based Indexing

Unlike most programming languages that use 0-based indexing, bjio.line() uses 1-based indexing:

  • First line: bjio.line(txt, 1)
  • Second line: bjio.line(txt, 2)
  • Last line: bjio.line(txt, bjio.lines(txt)?size)

This is consistent with how line numbers are typically displayed in editors and text files.


Tests

Java Unit Tests

mvn test -Dtest=BjioNamespaceTest

Coverage:

  • 29 tests
  • 9 methods tested
  • Error tests included
  • 100% success

AntUnit Tests

cd test
ant -f build-builtin-bjio.xml

Included tests:

  • All bjio methods
  • Edge cases (multiple dots, no extension)
  • Integration tests
  • Code generation
  • Error handling

See Also


Document Version: 2.0 Created: 2026-02-07 Last Updated: 2026-02-12