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 objectmode: (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 objectmode: (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)?sizeto 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
- bjmda.md - MDA Namespace (entity/property metadata)
- RefactoringBuiltin.md - Complete refactoring guide
- Phase3-Bjio-Implementation.md - Implementation details
- PHASE3-SUMMARY.md - Phase 3 summary
- BjioNamespaceTest.java - Java tests
Document Version: 2.0 Created: 2026-02-07 Last Updated: 2026-02-12