comes from network
XML和XSLT实现代码生成器(I)
摘要
XML和XSLT为开发WEB应用提供了非常好的解决方案,然而,它们的能力不仅限制在WEB开发上,其实它们提供了很好的基于元数据(meta data)编程的模型,利用XML作为元数据并用XSLT就可以将它转换为任何想要的形式或其他数据。本文第一部分介绍了如何利用Java以及XML和XSLT实现代码生成器功能,,第二部分讨论了使用这种解决方案的优点和不足,以及如何改进该方案使用,本文同时还展示了JAXP和JDOM API的使用。
介绍
编写代码是程序员的日常工作,通常编写程序代码被认为是有创造性的工作,这也是很多人热爱编程的一大理由。然而有时人们不得不面对重复编写一些简单的,另人乏味的代码,最好的例子就是Java中的JavaBean,尤其是在J2EE应用中,程序员需要在Web层编写许多JavaBean和数据层(通常是EJB)交换数据,为每一个JavaBean编写get, set方法是十分机械而枯燥的工作。如果这些类似的工作能由机器自动完成,程序员就可以花费更多的努力在有创造的开发上了。代码生成器就是为了这个目的而出现的,基本上所有流行的IDE都或多或少的包含代码生成的能力。例如,在VC中创建项目时IDE生成MFC的基本框架代码以及Jbuilder高版本中自动生成JavaDoc注释的功能。要实现一个代码生成器,首先必须提供有关于生成什么代码的基本信息,也即元数据,然后程序提取这些元数据并自动生成实际的代码。不难想象,提供元数据的最好方法就是使用一种统一的,容易验证的而且便于提取的数据格式,XML,DTD或XML Schema(可选)以及XSLT的组合正好提供了这一切。XML提供统一的数据,DTD或XML Schema用于验证数据的有效性,而XSLT则用于提取XML元数据并进行转换。后面的部分将以一个JavaBean代码生成器为例,详细介绍如何将这些技术组合起来用于实际应用。
建立简易模型
JavaBean是包含一组属性的简单Java类,通常包括字段定义,构造函数以及几对get/set方法。可以首先为XML元数据建立一个DTD表示它应包含的基本信息。列表1.1展示了一个这样的DTD模型。<?xml version="1.0" encoding="UTF-8"?>
<!-- edited with XMLSPY v5 rel. 4 U (http://www.xmlspy.com) by starchu1981 (melbourne university) -->
<!ELEMENT xgen (javabean?)>
<!ELEMENT javabean (name, package, implement*, property*)>
<!ELEMENT package (name, description?)>
<!ELEMENT name (#PCDATA)>
<!ELEMENT description (#PCDATA)>
<!ELEMENT implement (#PCDATA)>
<!ELEMENT property (name, exception*)>
<!ELEMENT exception (#PCDATA)>
<!ATTLIST property
type CDATA #REQUIRED
set (yes | no) #IMPLIED
get (yes | no) #IMPLIED>
列表1.1
javabean的implement子元素代表该Bean所实现的接口,当然也可定义一个extend子元素表明Bean所扩展的类,Property元素的属性列表还可以在以后加入isIndex属性用来表明该属性是否是一个index属性。由于DTD基本是自解释,所以就不一一详细说明了。DTD设计完成后就可以利用它来书写XML数据了。列表1.2是一个简单XML例子
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xgen SYSTEM "../dtd/xgen-javabean.dtd">
<xgen>
<javabean>
<name>test2</name>
<package>
<name>com.xs.xgen.test</name>
<description>Test For XGen Dtd and XML model</description>
</package>
<property type="java.lang.String" set="yes" get="yes">
<name>description</name>
</property>
<property type="int" set="no" get="yes">
<name>innerData</name>
<exception>AccessNotAllowedException</exception>
</property>
<property type="javax.xml.transform.Source" set="yes" get="no">
<name>source</name>
<exception>SAXNotRecognizedException</exception>
<exception>SAXNotSupportedException</exception>
</property>
<property type="int" set="no" get="no">
<name>nonsenseData</name>
</property>
</javabean>
</xgen>
XML和XSLT实现代码生成器(II)
XSLT处理元数据
如前文所述,当建立元数据以后,就可以使用XSLT将XML数据转换为实际的代码了,列表1.3展示了一个XSL文档,它将处理上述的XML元数据,完成转换工作。
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!—Java代码是文本格式的,所以需要设定输出方法为testà
<xsl:output method="text"/>
<xsl:variable name="properties" select="/xgen/javabean/property"/>
<xsl:template match="/">
<xsl:apply-templates select="xgen"/>
</xsl:template>
<!—匹配xgen元素 à
<xsl:template match="xgen">
<xsl:text>/*Generated By XGen Xingchen Chu@XS Group Copyright(c) Reserved*/</xsl:text>
<xsl:apply-templates select="javabean"/>
</xsl:template>
<!—匹配javabean元素,实际代码生成在此完成à
<xsl:template match="javabean">
<xsl:text>package </xsl:text> <!—输出包名à
<xsl:value-of select="package/name"/>
<xsl:text>;</xsl:text>
<xsl:text>public class </xsl:text> <!—输出JavaBean类名 à
<xsl:value-of select="name"/>
<xsl:text> implements </xsl:text> <!—输出实现的接口à
<xsl:if test="implement">
<xsl:apply-templates select="implement"/>
</xsl:if>
<xsl:text>java.io.Serializable</xsl:text> <!—每个JavaBean必须实现的接口à
<xsl:text>{</xsl:text>
<xsl:call-template name="printField"/> <!—输出字段信息—>
<xsl:call-template name="printConstructor"/> <!—输出构造函数à
<xsl:apply-templates select="property"/> <!—输出所有属性的get和set方法à
<xsl:text>}</xsl:text>
</xsl:template>
<!—匹配implement元素,简单的输出其值并跟逗号à
<xsl:template match="implement">
<xsl:value-of select="text()"/><xsl:text>,</xsl:text>
</xsl:template>
<!—匹配property元素,输出适当的方法à
<xsl:template match="property">
<xsl:if test="@set='yes'">
<xsl:call-template name="printSetMethod">
<xsl:with-param name="property" select="."/>
</xsl:call-template>
</xsl:if>
<xsl:if test="@get='yes'">
<xsl:call-template name="printGetMethod">
<xsl:with-param name="property" select="."/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="printField">
<xsl:for-each select="$properties">
<xsl:text>private </xsl:text>
<xsl:value-of select="@type"/>
<xsl:text> </xsl:text>
<xsl:value-of select="name"/>
<xsl:text>=</xsl:text>
<xsl:call-template name="printDefaultValue"> <!—输出字段缺省值à
<xsl:with-param name="type" select="@type"/>
</xsl:call-template>
<xsl:text>;</xsl:text>
</xsl:for-each>
</xsl:template>
<xsl:template name="printConstructor">
<xsl:text>public </xsl:text>
<xsl:value-of select="/xgen/javabean/name"/>
<xsl:text>(</xsl:text>
<xsl:for-each select="$properties"> <!—输出构造函数参数列à
<xsl:value-of select="@type"/>
<xsl:text> </xsl:text>
<xsl:value-of select="name"/>
<xsl:if test="position()<last()">
<xsl:text>,</xsl:text>
</xsl:if>
</xsl:for-each>
<xsl:text>){</xsl:text>
<xsl:for-each select="$properties"> <!—输出构造函数主体à
<xsl:text>this.</xsl:text>
<xsl:value-of select="name"/><xsl:text>=</xsl:text>
<xsl:value-of select="name"/><xsl:text>;</xsl:text>
</xsl:for-each>
<xsl:text>}</xsl:text>
</xsl:template>
</xsl:stylesheet>
XML和XSLT实现代码生成器(III)
XSLT处理元数据(续)
<!—命名模板,打印set方法à
<xsl:template name="printSetMethod">
<xsl:param name="property"/>
<xsl:text>public void set</xsl:text>
<xsl:call-template name="translateHeadLetter"> <!—属性首字母需大写à
<xsl:with-param name="propertyName" select="name"/>
</xsl:call-template>
<xsl:text>(</xsl:text>
<xsl:value-of select="@type"/>
<xsl:text> </xsl:text>
<xsl:value-of select="name"/>
<xsl:text>)</xsl:text>
<xsl:if test="exception"> <!—输出任何方法抛出的异常 à
<xsl:text>throws </xsl:text>
<xsl:apply-templates select="exception"/>
</xsl:if>
<xsl:text>{ this.</xsl:text> <!—set方法主体à
<xsl:value-of select="name"/>
<xsl:text>=</xsl:text>
<xsl:value-of select="name"/>
<xsl:text>;}</xsl:text>
</xsl:template>
<!—命名模板,打印get方法à
<xsl:template name="printGetMethod">
<xsl:param name="property"/>
<xsl:text>public </xsl:text>
<xsl:value-of select="@type"/>
<xsl:choose>
<xsl:when test="@type='boolean' or @type='java.lang.Boolean'">
<xsl:text> is</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text> get</xsl:text>
</xsl:otherwise>
</xsl:choose>
<xsl:call-template name="translateHeadLetter">
<xsl:with-param name="propertyName" select="name"/>
</xsl:call-template>
<xsl:text>()</xsl:text>
<xsl:if test="exception">
<xsl:text>throws </xsl:text>
<xsl:apply-templates select="exception"/>
</xsl:if>
<xsl:text>{ return </xsl:text>
<xsl:value-of select="name"/>
<xsl:text>;}</xsl:text>
</xsl:template>
<xsl:template name="translateHeadLetter">
<xsl:param name="propertyName"/>
<xsl:variable name="length" select="string-length($propertyName)"/>
<xsl:variable name="headLetter" select="substring($propertyName,1,1)"/>
<xsl:variable name="remainLetters" select="substring($propertyName,2,$length)"/>
<xsl:value-of
select="translate($headLetter,'abcdefghijklmnopqrstuvwxyz',
'ABCDEFGHIJKLMNOPQRSTUVWXYZ')"/>
<xsl:value-of select="$remainLetters"/>
</xsl:template>
<xsl:template name="printDefaultValue">
<xsl:param name="type"/>
<xsl:choose>
<xsl:when test="contains($type,'int')">
<xsl:text>0</xsl:text>
</xsl:when>
<xsl:when test="contains($type,'boolean')">
<xsl:text>false</xsl:text>
</xsl:when>
<xsl:when test="contains($type,'long')">
<xsl:text>0</xsl:text>
</xsl:when>
<xsl:when test="contains($type,'float')">
<xsl:text>0</xsl:text>
</xsl:when>
<xsl:when test="contains($type,'char')">
<xsl:text>''</xsl:text>
</xsl:when>
<xsl:when test="contains($type,'String')">
<xsl:text>""</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>null</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="exception">
<xsl:value-of select="."/>
<xsl:if test="position()<last()">
<xsl:text>,</xsl:text>
</xsl:if>
</xsl:template>
列表1.3续
不熟悉XSLT的读者可以参看[2]以便了解更多的信息。该XSL分析XML数据,针对匹配的元素将元数据转换为实际的Java代码。在支持XSLT的浏览器上如IE6可以直接键入XML文档的URL,就可以看见转换结果,注意:需要在XML中加入如下指令
<?xml-stylesheet type="text/xsl" href="../xsl/javabean.xsl"?>
这样经过简单的两个步骤,代码转换的功能已经基本实现,只要利用一个简单的Java小程序就可以完成该代码生成器的初始模型。
简单代码生成器
列表1.5显示了一个这样目的的Java类。
Package com.xs.xgen;
Import javax.xml.transform.*;
Import javax.xml.transform.stream.*;
/**
* <p>Title: Code Generator based on XML and XSLT</p>
* <p>Description: Beta Version For Code Generator</p>
* <p>Copyright: xchu@Copyright (c) 2004</p>
* <p>Company: XS Group</p>
* @author Xingchen Chu
* @version 0.1
*/
Public class SimpleCodeGenerator{
public static void main(String [] args){
if(args.length<3){
System.err.println(“Usage : java SimpleCodeGenerator [xml] [xsl] [output]”);
System.exit(1);
}
try{
Source xmlSource=
new javax.xml.transform.stream.StreamSource(new File(args[0]));
javax.xml.transform.Source xslSource=
new javax.xml.transform.stream.StreamSource(new File(args[1]));
javax.xml.transform.Result result=
new javax.xml.transform.stream.StreamResult(new File(args[2]);
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer(xslSource);
Transformer.transform(xmlSource,result);
}catch(Exception e){
e.printStackTrace();
}
}
列表1.4
结果分析
对于这个代码生成器来说,虽然基本的目的已经达到,机器已经完全可以自动的生成所有的代码,程序员只需要根据DTD编写一小段XML元数据即可,使用像XMLSpy这样的工具可以很容易编写出合适的XML元数据并可进行验证,然后使用java命令行即可得到想要的JavaBean类文件。而且如果想在现有的基础上添加更多的功能,比如支持索引属性,只需要稍微更改DTD以及XSL即可实现,如果还想为EJB生成基本接口文件,可以重新定义一个新的DTD和XSL,而不需要改变任何一点的Java代码。
然而,一切都还不完美。首先,XSLT的输出文本结果十分难阅读,因为XSLT在处理空格和缩近上能力有限,可读性好的结果文档是一个可以改进的方面;其次,程序员还是必须编写XML文件,并手动传递它和XSLT给代码生成器,这种静态方式可以被图形化的动态方式替代是更好的方案。本文的下一部分将说明这些问题并提出合适的解决方法。