Reputation: 694
I am building a template-based code generator using FreeMarker. Since users could generate code in any language, it is not appropriate to provide language-specific settings (e.g. package) in the data model. However, if they are defined in the FreeMarker template, they must be defined (unless they are optional).
This code makes use of the exceptions thrown by FreeMarker to find missing values. It then populates them with a temporary value so other missing values can be found.
When values are in the root data model, this works great (except that I cannot seem to suppress FreeMarker's error messages). However, as soon as one of the missing variables is in a deeper level, it seems necessary to parse the whole template to figure out the problem.
The reason for doing this is so that I can detect missing value and prompt the user on-the-fly. If they are generating java, it could prompt for package. C++? Maybe pragma directives.
Anyway, does anyone have any idea how to do this more effectively?
Working code and with template follows.
Source FMCodeGenTest.java
:
package codegen;
import freemarker.cache.*;
import freemarker.core.ParseException;
import freemarker.template.*;
import java.io.*;
import java.util.*;
public class FMCodeGenTest {
private Configuration mConfig = null;
private HashMap mDataModel = null;
private Template mTemplate = null;
public void init() {
mConfig = new Configuration(Configuration.VERSION_2_3_22);
try {
mConfig.setDirectoryForTemplateLoading(new File("./templates"));
mConfig.setDefaultEncoding("UTF-8");
mConfig.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
} catch (IOException ie) {
System.out.println("Error reading templates.");
}
}
public void buildDataModel() {
mDataModel = new HashMap();
mDataModel.put("user", "Foo");
ArrayList vars = new ArrayList();
mDataModel.put("vars", vars);
HashMap var = new HashMap();
vars.add(var);
var.put("name", "apple");
var.put("type", "String");
}
public void getTemplate() {
try {
mTemplate = mConfig.getTemplate("java_error.ftl");
} catch (MalformedTemplateNameException ex) {
System.out.println("Malformed Template Name : " + ex.getMessage());
} catch (ParseException ex) {
System.out.println("Parse Error : " + ex.getMessage());
} catch (IOException ex) {
System.out.println("IO Exception : " + ex.getMessage());
}
}
public void detectUndefinedVariables() {
boolean hasBadVars = false;
do {
hasBadVars = false;
try {
mTemplate.process(mDataModel, new NullWriter());
} catch (TemplateException ex) {
hasBadVars = true;
mDataModel.put(ex.getBlamedExpressionString(), "<temporary value>");
} catch (IOException ex) {
System.out.println("IO Exception : " + ex.getMessage());
}
} while (hasBadVars);
}
public void generateCode() {
/* Merge data-model with template */
Writer out = new OutputStreamWriter(System.out);
try {
mTemplate.process(mDataModel, out);
} catch (TemplateException ex) {
System.out.println("Template Exception : " + ex.getMessage());
} catch (IOException ex) {
System.out.println("IO Exception : " + ex.getMessage());
}
}
static public void main(String [] args) {
FMCodeGenTest test = new FMCodeGenTest();
test.init();
test.buildDataModel();
test.getTemplate();
test.detectUndefinedVariables();
test.generateCode();
}
}
Template java_error.ftl
:
package ${package};
/**
*
* @author ${user}
*/
public class ${name} {
<#list vars as var>
private ${var.type} _${var.name};
nontrivial ${var.notthere};
</#list>
}
Upvotes: 0
Views: 1300
Reputation: 31162
I think this should be done not with catching InvalidReferenceException
exceptions, but by using a special data-model. The data-model itself should prompt for the missing variable. Thus you always know where the value provided by the user should be added to the data-model, and you don't need to deal with the template itself.
Upvotes: 1