2016-04-30 51 views
0

我正在使用FreeMarker构建一个基于模板的代码生成器。由于用户可以用任何语言生成代码,因此在数据模型中提供语言特定的设置(例如包)是不合适的。但是,如果它们在FreeMarker模板中定义,则必须定义它们(除非它们是可选的)。检测FreeMarker中的未定义变量

此代码利用FreeMarker引发的异常来查找缺失值。然后用临时值填充它们,以便找到其他缺失的值。

当值在根数据模型中时,这很好用(除了我似乎无法抑制FreeMarker的错误消息)。但是,只要其中一个缺失变量处于更深层次,似乎有必要解析整个模板以找出问题。

这样做的原因是,我可以检测到缺失值并提示用户即时。如果他们正在生成Java,它可能会提示打包。 C++?也许编译指示。

无论如何,有没有人有任何想法如何更有效地做到这一点?

工作代码和模板如下。

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

模板java_error.ftl

package ${package}; 

/** 
* 
* @author ${user} 
*/ 
public class ${name} { 
    <#list vars as var> 
    private ${var.type} _${var.name}; 
    nontrivial ${var.notthere}; 
    </#list> 
} 
+0

由于“我似乎无法抑制FreeMarker的错误消息”,我想你会参考日志消息。然后使用'mConfig.setLogTemplateExceptions(false)'。 (另外,如果你看到控制台上的消息,那么你需要正确设置日志记录。) – ddekany

+0

@ddekany啊,谢谢。我错过了 - 肯定会尝试。 – Chris

回答

1

我觉得这不应该追赶InvalidReferenceException例外来完成,而是通过使用特殊的数据模型。数据模型本身应该提示输入缺失的变量。因此,您总是知道用户提供的值应该添加到数据模型中的哪个位置,并且您不需要处理模板本身。

+0

我不确定我了解这一建议。这个想法是从单个数据模型生成代码。我将如何与用户(来自模板)沟通,以便填充特定于语言的变量? – Chris

+1

在这个问题中,你说你想“提醒用户即时”。无论何时需要返回一个'null',你都可以在'TemplateHashModel.get'中做到这一点。因为那是(返回'null')导致你想要在原始问题中捕获和分析的'InvalidReferenceException'。请注意,由于您本身防止了错误情况,因此您无需在发现缺少的变量后重新运行模板。 – ddekany

+0

谢谢。通过将我的数据模型放入TemplateHashModel(如您所建议的),我可以在返回null(例如,请求的数据模型没有值的请求值)时注入一些用户交互。 – Chris