代码生成器的设计理念和实践
李俊杰
概论
代码生成器十分有利于提高编码效率和项目进度,并且有利于代码的规范化管理,把程序员从繁琐的重复性的代码编写中解放出来,特别是同时产生代码和配置文件(config),这样避免了多个文件之间的互相引用带来的错误(当你查找bug精疲力竭,最后发现仅仅是由于你的配置文件中的配置时某一个参数的字母大小写的错误,你什么感受),所以颇受的广大程序员和项目管理者的青睐。
设计思想分类
代码生成器的设计思路其实很简单。简而言之,包括输入模块,加工模块,输出模块。
输入模块
所谓输入模块主要是组织“原料”,该原料可以是数据库表(如hibernate的代码生成器),也可以是文本文件(最常用的是xml文件,或者java代码文件如doclet代码生成器)或者别的途径。
加工模块
加工模块是根据输入模块产生的原料按照一定的规范(模版)生成字符串(广义的字符串,即包括要生成文件的内容 )。
输出模块
输出模块的功能是把代码生成器生成的代码字符串写入一系列指定目录和文件名称文件。
设计分类
1) 无模版设计类型。事实上该模版在设计者的心中,也就是说设计者非常清楚生成的文件的格式和内容。后面的篇幅中会简单介绍该设计思想,其优点是很灵活,缺点是扩展性和适应性较差。如同样的“原料“,生成新格式的文件就会很被动,需要重新编程。
2) 模版设计类型。这是现在最广泛的的运用设计思路。就是加工模块首先读模版文件,根据模版文件中的标志,把相应的“原料“填充到替换标志。然后生成新的文档。
实现原理简介
1)无模版设计类型示例:
//设置文件中的格式
//回车
public static final String RETURN_ROW = "\n";
//2个空格
public static final String BLANK2 = " ";
//4个空格
public static final String BLANK4 = BLANK2 + " ";
//6个空格
public static final String BLANK6 = BLANK4 + " ";
public static final String BLANK8 = BLANK6 + " ";
public static final String BLANK10 = BLANK8 + " ";
public static final String BLANK12 = BLANK10 + " ";
public static final String BLANK14 = BLANK12 + " ";
public static final String BLANK16 = BLANK14 + " ";
public static final String BLANK18 = BLANK16 + " ";
public static final String BLANK20 = BLANK18 + " ";
public static final String BLANK22 = BLANK20 + " ";
public static final String BLANK24 = BLANK22 + " ";
下面是读。Xml文件,根据读进来的原料,进行加工。
String serviceID = serviceConfig.getServiceID();
String serviceName = serviceConfig.getServiceName();
String serviceDesc = serviceConfig.getServiceDesc();
String serviceTypeName = serviceConfig.getServiceTypeName();
String packageName = serviceConfig.getPackageName();
String pureServiceName = serviceName.substring(0,serviceName.lastIndexOf("Service"));
String serviceClassName = packageName + "." + serviceName;
String inServiceBeanClassName = packageName + "." + pureServiceName + "InServiceBean";
String outServiceBeanClassName = packageName + "." + pureServiceName + "OutServiceBean";
String serviceUnitTestClassName = packageName + "." + pureServiceName + "ServiceUnitTest";
String canTest = serviceConfig.getCanTest();
String noSecurityCheck = serviceConfig.getNoSecurityCheck();
String dbServerName = serviceConfig.getDbServerName();
String serviceNo = serviceConfig.getServiceNo();
//下面是生成新的字符串
StringBuffer sb = new StringBuffer();
sb.append(beginTagRow(ProxyXMLCodeGenerator.BLANK2,"service"));
sb.append(generateRow(ProxyXMLCodeGenerator.BLANK4,"serviceNo",serviceNo));
sb.append(generateRow(ProxyXMLCodeGenerator.BLANK4,"serviceID",serviceID));
sb.append(generateRow(ProxyXMLCodeGenerator.BLANK4,"serviceName",serviceName));
sb.append(generateRow(ProxyXMLCodeGenerator.BLANK4,"serviceDesc",serviceDesc));
sb.append(generateRow(ProxyXMLCodeGenerator.BLANK4,"serviceTypeName",serviceTypeName));
sb.append(generateRow(ProxyXMLCodeGenerator.BLANK4,"packageName",packageName));
sb.append(generateRow(ProxyXMLCodeGenerator.BLANK4,"serviceClassName",serviceClassName));
sb.append(generateRow(ProxyXMLCodeGenerator.BLANK4,"inServiceBeanClassName",inServiceBeanClassName));
sb.append(generateRow(ProxyXMLCodeGenerator.BLANK4,"outServiceBeanClassName",outServiceBeanClassName));
sb.append(generateRow(ProxyXMLCodeGenerator.BLANK4,"serviceUnitTestClassName",serviceUnitTestClassName));
sb.append(generateRow(ProxyXMLCodeGenerator.BLANK4,"canTest",canTest));
sb.append(ProxyXMLCodeGenerator.BLANK4).append("<noSecurityCheck>1</noSecurityCheck>").append(ProxyXMLCodeGenerator.RETURN_ROW);
sb.append(generateRow(ProxyXMLCodeGenerator.BLANK4,"dbServerName",dbServerName));
sb.append(ProxyXMLCodeGenerator.BLANK4).append("<callServices/>").append(ProxyXMLCodeGenerator.RETURN_ROW);
System.out.println(sb.toString());
这样就生成了某一定格式的xml的字符串。
2)模版设计类型示例
小试牛刀,最基础的替换原理
public static String transit(HashMap map, String template){
if(template == null){
return null;
}
String ret;
StringBuffer sb = new StringBuffer();
int beginIndex = 0;
int endIndex = -1;
int t = 0 ;
int i = 0;
String key = null;
while((t = template.indexOf("$",beginIndex)) != -1 ){
endIndex = t;
if(beginIndex + 1 == endIndex){
beginIndex = endIndex + 1;
continue;
}
key = template.substring(beginIndex, endIndex);
i++;
if(i % 2 == 1){
sb.append(key);
}else{
sb.append((String)map.get(key));
}
beginIndex = endIndex + 1;
}
if((t = template.lastIndexOf("$"))!= -1){
sb.append(template.substring(t+1));
}else{
sb.append(template);
}
ret = sb.toString();
return ret;
}
public static void main(String[] args){
HashMap map = new HashMap();
map.put("name","lisi");
map.put("age","25");
map.put("address","beijing");
map.put("year"," years old");
map.put("desrc","Read me : ");
String[] names = new String[]{"name", "age","address","year","desrc"};
String[] values = new String[]{"lisi", "25", "beijing"," years old","Read me : "};
String template = "$desrc$My name is $name$, my old is $age$$year$, I live in $address$.";
System.out.println(StringUtils.transit(map, template));
}
下面是工业化版本
//seq语句模版
#loopBegin#
INSERT INTO "TX"(
"TXID",
"DEPT_CLASS",
"TXNAME",
"SEP_GRANT_FLAG",
"CROSS_OP_FLAG",
"MGRBRANCH_OP_FLAG",
"SECURITY_CHK_FLAG",
"SECURITY_TX_FLAG",
"VALCONSTR_FLAG",
"TX_TYPE",
"CI_GROUP_NO"
)
VALUES(
'$serviceId$',
'$deptClass$',
'$serviceDesc$',
'1',
'1',
'1',
'1',
'1',
'0',
'U',
'1'
)
/
#loopEnd#
首先我要解释一下该模版中的标志#loopBegin#和#loopEnd#表示这中间的代码要进行循环,其中“$$,##”为识别符,因为在模版中要识别该代码段要进行特殊的操作或者替换(另外识别符的选择一般是比较生僻的字符,在原始的代码中难以看到,如果原始的代码中真的存在该字符,可以用转字符来替代,如“\$,\#”),$serviceId$表示把“原料”中的serviceId的值替换到该处,生成新的代码字符串。
实现代码(下面的代码是我从相关的类中摘取的代码),我们的模版是以文件存在的,而文件是由一行行字符串组成的,因此我把处理的最小单元设计为一行。
/**
*
* @author lijunjie
* @param
* @return String
* @exception
* 方法描述:把从模板文件中读取的某一行进行转换,把其中的参数替换成实际的代码
*/
public String transit(ArrayList lines,HashMap map){
StringBuffer sb = new StringBuffer();
String line = (String)lines.get(0);
StringTokenizer tokenizer = new StringTokenizer(line,"$");
int i = 0;
String name;
while(tokenizer.hasMoreTokens()){
i++;
if(i % 2 == 1){
sb.append(tokenizer.nextToken());
}else{
if((name = (String)map.get(tokenizer.nextToken())) != null){
sb.append(name);
}
}
}
return sb.toString();
}
/**
*
* @author lijunjie
* @param
* @return String
* @exception
* 方法描述:依据模板生成文件内容的字符串,
* 根据模板文件的内容来进行不同的处理,使用策略模式来创建不同的处理类.
*/
public String generateCode(String templateFile) {