内容


使用 JET 在 Eclipse 中创建更多更好的代码

如何掌握专家的最佳实践并提高您的模型驱动开发进度

Comments

代码生成不是一个新概念。它出现有一段时间了,而且作为提高生产力的一种方式,随着模型驱动开发(MDD)的发展而普及开来。Eclipse 项目有一个称为 JET 的技术项目就是一个专门的代码生成器。JET 所能生成的不仅仅是 “代码”,无论如何,在本文中我们称这些非编码的物件为工件(artifiact)

开始

在本节中,我们将介绍 JET 项目设置基础,讨论 JET 项目的结构,然后运行一个快速转换。

创建一个 JET 项目

在实际接触 JET 之前,我们需要创建一个项目。使用创建项目的标准 Eclipse 方法来实现。就 JET 来说,使用命令 File > New > Other > EMFT JET Transformations > EMFT JET Transformation Project(请参见图 1)创建一个 EMFT JET Transformation Project。

图 1. JET 项目向导
JET 项目向导
JET 项目向导

JET 项目结构

让我们分析项目结构来搞清楚 JET 是如何工作的。在上面的部分,我们创建了一个 JET 项目(参见图 2)。在该 JET 项目中,我们需要浏览六个文件。

图 2. JET 项目结构
JET 项目结构
JET 项目结构
Eclipse 项目文件(MANIFEST.MF, plugin.xml, build.properties)
这是使用 Eclipse 项目时所创建的标准文件。对于这些文件需要注意的一件重要事情是:在 plugin.xml 中,JET 将自动添加 org.eclipse.jet.transform。通过扩展该扩展点,我们让 JET 知道我们在提供一个 JET 转换。
控制和模板文件(dump.jet, main.jet)
这是在转换中所使用的模板和控制文件。将在下面的概念部分讨论其细节。
输入模型(sample.xml)
这里我们可以看到用于转换的一个示例输入文件。注意该输入可以来自任何源,并不限于项目。

运行 JET 转换

一旦有了一个项目,拥有合适的模板、控制文件和一个输入模型,我们就可以运行转换。通过熟悉的 Eclipse 概念 —— 启动配置(参见图 3),JET 提供了一个方便的方式来调用转换。要访问 JET 启动配置,我们转到 Run > JET Transformation,填充合适的选项,然后单击 Run

图 3. JET 启动配置
JET 启动配置
JET 启动配置

概念

JET 是指定模板输出工件的语言。实现一些应用程序的模板集合被称为蓝图(blueprint)(用我们的术语)。JET 范例可以用下列等式表示:

参数 + 蓝图 = 所需的工件

蓝图是由 JET 创建的,而参数是由蓝图用户提供的。蓝图由三个不同的文件集组成:

1. 参数
用于蓝图的参数使用 XML 格式。这赋予它强大的表现力,因为 XML 允许使用层次化关系、允许每个节点具有属性。输入参数被称为输入模型。蓝图将定义描述其期望参数的模式。例如,下面是蓝图创建网络嗅探器的输入实例:
清单 1. 网络嗅探器蓝图的输入
<app project="NetworkSniffer" >
	<sniffer name="sampler" sample_probability=".7" >
		<logFile name="packet_types" />
		<packet type="TCP" subType="SYN" >
			<logToFile name="packet_types" />
			<findResponse type="TCP" subType="ACK" timeout="1" />
		</packet>
		<packet type="UDP" >
			<logToFile name="packet_types" />
		</packet>
	</sniffer>
</app>

蓝图将转换这些输入参数为实现该网络嗅探器的代码。蓝图的参数可视为自定义编程语言,而蓝图扮演 “编译器” 的角色,将输入转换为本机工件。
2. 控制文件
这些文件控制代码生成的执行。控制标记中最重要的标记是 <ws:file>,它将执行一个模板并将结果转储至指定的文件。代码生成执行从 main.jet 开始,这与程序的 main 函数类似。
3. 模板文件
模板文件指定如何以及何种情况下生成文本。该文本可以是代码、配置文件或其他。

XPath

既然任何 JET 蓝图的输入都是一个 XML 模型,XPath 语言被用来引用节点和属性。此外,在表达式里 XPath 有自己的参数使用方式,这在 JET 里使用得非常多。要点如下:

  1. 路径表达式 与文件系统路径相似。路径是由斜杠分开的一系列步(/)。
  2. 从左向右估算步,如果这样做,通常会下行模型树。
  3. 每步通过其名字定义树节点(尽管存在其他可能性)。
  4. 在步的结尾,步可以在方括号([])中编写可选的过滤器条件。
  5. 初始斜杠(/)指示表达式开始于模型树的根。
  6. 路径表达式还可以以变量开始,变量是以美元符号($)开头的名字。

关于 JET 中的 XPath ,应记住以下几个要点:

  1. 变量是由几个 JET 标记定义的 - 注意 var 属性。它们可能也是由 c:setVariable 标签定义的。
  2. 需要路径表达式的 JET 标签有一个 select 属性。
  3. 任何标签属性都可能包含一个动态的 XPath 表达式,是由括号({})所包含的 XPath 表达式。

JET 标签

下例将使用下列输入模型。

清单 2. 输入模型
<app middle="Hello" >
	<person name="Chris" gender="Male" />
	<person name="Nick" gender="Male" />
	<person name="Lee" gender="Male" />
	<person name="Yasmary" gender="Female" />
</app>
ws:file
该标签属于蓝图的 control 部分,它初始化一个模板。例如:
<ws:file template="templates/house.java.jet"
path="{$org.eclipse.jet.resource.project.name}/house1.java">

将在输入模型上运行 house.java.jet 模板并将结果转储在 $(Project Root)/house1.java 中。{$org.eclipse.jet.resource.project.name} 是一个动态 XPath 表达式,用 org.eclipse.jet.resource.project.name 变量的值替换部分字符串。该变量是由 JET 引擎定义的。
c:get
该标签将输出 XPath 表达式的结果。例如,Pre<c:get select="/app/@middle" />Post 将输出 PreHelloPost。注意 select 参数将使用 XPath 表达式。要在期望静态字符串的参数中使用 XPath 表达式,可以通过将表达式封装在括号({})中来调用动态 XPath 表达式。
c:iterate

该标签将遍历具有特定名称的节点,为每个节点执行 iterate 的主体。例如:

<c:iterate select="/app/person" var="currNode" delimiter="," > 
Name = <c:get select="$currNode/@name" />
</c:iterate>

将输出 Name = Chris, Name = Nick, Name = Lee, Name = Yasmary

iterate 标签通常也用于控制模板的其实标记。例如,如果要为模型中的每个人创建 Java™ 类,可使用如下代码:

<c:iterate select="/app/person" var="currPerson">
<ws:file template="templates/PersonClass.java.jet"
path="{$org.eclipse.jet.resource.project.name}/{$currPerson/@name}.java"/>
</c:iterate>

这将创建四个 Java 类文件:Chris.java、Nick.java、Lee.java 和 Yasmary.java。注意启动标记 path 属性中的 {$currPerson/@name} 字符串。既然 path 参数不需要 XPath 表达式(像 select 参数一样),{...} 字符告知 JET 引擎通过计算 XPath 表达式代替这部分字符串。$currPerson/@name 告诉引擎用 currPerson 节点(是定义在 iterate 标签中的变量)的 name 属性来代替其字符串。

此外,在 PersonClass.java.jet 模板中,它可以引用定义在 iterate 标签中的 currPerson 节点变量。例如,假设 PersonClass.java.jet 如下所示:

清单 3. PersonClass.java.jet
class <c:get select="$currPerson/@name" />Person { 
	public String getName() { 
		return "<c:get select="$currPerson/@name" />"; 
	}
	public void shout() {
		System.out.println("Hello!!!"); 
	} 
}

Yasmary.java 的形式将如下:

清单 4. Yasmary.java
class YasmaryPerson { 
	public String getName() {
    	return "Yasmary"; 
    } 
    public void shout() {
    	System.out.println("Hello!!!"); 
    } 
}

Lee.java 的形式如下:

清单 5. Lee.java
class LeePerson { 
	public String getName() { 
		return "Lee"; 
	} 
	public void shout() {
		System.out.println("Hello!!!"); 
	} 
}
c:choosec:when
这些标签允许模板根据值有条件地转储文本。例如,下列代码:
清单 6. c:choose/c:when 示例
<c:iterate select="/app/person" var="p" >
	<c:choose select="$p/@gender" > 
		<c:when test="'Male'" > Brother </c:when>
		<c:when test="'Female'" > Sister </c:when> 
	</c:choose>
</c:iterate>

将输出:

Brother
Brother
Brother
Sister

注意 c:when 标签需要 test 参数,这需要一个 XPath 表达式。既然我们要通过一个常量比较 select 参数,可用单引号 ('') 包含常量。

c:set
该标签允许模板动态更改输入模型的属性。一个例子是:在一个字符串以多个方式映射输出文本时,像 Chris 可能映射到 ChrischrisChrisClassCHRIS_CONSTANT 等。c:set 将其内容设置为指定的属性。下面的例子为每个人存储名为 className 的属性并在名字之后简单添加词 Class
清单 7. c:set 例子
<c:iterate select="/app/person" var="p" >
	<c:set select="$p" name="className" >
	<c:get select="$p/@name" />Class</c:set>
</c:iterate>
setVariable
该标签允许模板声明和使用一个全局变量,使用 XPath 的全部能力来在任何时候操纵该变量。例如,假设要输出在输入模型中提供了多少个 person 节点。可以利用以下代码:
清单 8. c:setVariable 示例
<c:setVariable select="0" var="i" />
	<c:iterate select="/app/person" var="p" >
		<c:setVariable select="$i+1" var="i" />
	</c:iterate> 
Number of people = <c:get select="$i" />.

输出 Number of people = 4。

可以使用 get 输出变量,如上例所示。

有超过 45 个标签,这使输出文本具有强大的表现力。表现力大多源于存在条件逻辑、动态更改输入模型和控制执行流的标签。

扩展 JET

JET 是可扩展的通过使用 Eclipse 的扩展点机制。以下是 JET 提供的六个扩展点。

org.eclipse.jet.tagLibraries
该扩展点负责定义标记库。JET 已经包含四个标记库(控制、格式、工作空间、Java),如果您要添加自己的标签功能,可从这里入手。
org.eclipse.jet.xpathFunctions
这允许在 JET XPath 执行时启用自定义 XPath 表达式。一个 JET 中这样的例子是:通过扩展该扩展点,在 XPath 表达式中使用 camelcase(参见 JET 源代码中的 CamelCaseFunction)。
org.eclipse.jet.transform
用于声明您的插件在提供 JET 转换。这是更改您使用什么来启动模板(取代 main.jet)的位置。
org.eclipse.jet.modelInspectors
这允许您定义检查器,使得 JET XPath 引擎来将加载的 Java 对象解释为 XPath 节点。检查器是将对象适配为 XPath 信息模型。作为例子,JET 使用一个模型来浏览 Eclipse 工作空间。注意这是临时 API,并可能随时间而发生变化。
org.eclipse.jet.modelLoaders
这允许您定义 JET 转换和从文件系统加载的 JET <c:load> 标签以怎样的方式使用模型。作为示例,JET 提供模型加载器 loader org.eclipse.jet.resource,将加载 Eclipse IResource(文件,文件夹或项目)并允许通过该资源导航 Eclipse 工作空间。
org.eclipse.jet.deployTransforms
这允许您来将一个 JET 转换打包为一个用于简单发行的插件(包)。这可以被 UI 用来查看哪些转换可用。

实例:编写代码来生成代码

下列实例是一个模板,用于创建拥有任意数量属性的类。每个属性将有 getter 和 setter 与之关联,还有一些初始值。此外,所调用的函数的名称将输出到命令行,通过这种方式,模板即可为各函数添加简单的日志。

清单 9. 属性模板
class <c:get select="/app/@class" /> {
<c:iterate select="/app/property" var="p" >
	private <c:get select="$p/@type" /> <c:get select="$p/@name" />;
</c:iterate>

	public <c:get select="/app/@class" />() {
	<c:iterate select="/app/property" var="p" >
		this.<c:get select="$p/@name" /> = <c:choose select="$p/@type" >
		<c:when test="'String'">"<c:get select="$p/@initial" />"</c:when>
		<c:otherwise><c:get select="$p/@initial" /></c:otherwise>
		</c:choose>
;
	</c:iterate>
	}

<c:iterate select="/app/property" var="p" >
	public void set<c:get select=\
	"camelCase($p/@name)" />(<c:get select="$p/@type" />
	<c:get select="$p/@name" />) {
		System.out.println\
		("In set<c:get select=\
		"camelCase($p/@name)" />()");
		this.<c:get select="$p/@name" /> = <c:get select="$p/@name" />;
	}
	
	public <c:get select=\
	"$p/@type" /> get<c:get select="camelCase($p/@name)" />() {
		System.out.println("In get<c:get select="camelCase($p/@name)" />()");
		return <c:get select="$p/@name" />;
	}
	
</c:iterate>
}

这里是该模板的输入模型实例:

清单 10. 输入参数
<app class="Car">
	<property name="model" type="String" initial="Honda Accord" />
	<property name="horsepower" type="int" initial="140" />
	<property name="spareTires" type="boolean" initial="true" />
</app>

这些输入参数生成如下类:

清单 11. 生成的类
class Car {
	private String model;
	private int horsepower;
	private boolean spareTires;

	public Car() {
		this.model = "Honda Accord";
		this.horsepower = 140;
		this.spareTires = true;
	}

	public void setModel(String model) {
		System.out.println("In setModel()");
		this.model = model;
	}
	
	public String getModel() {
		System.out.println("In getModel()");
		return model;
	}
	
	public void setHorsepower(int horsepower) {
		System.out.println("In setHorsepower()");
		this.horsepower = horsepower;
	}
	
	public int getHorsepower() {
		System.out.println("In getHorsepower()");
		return horsepower;
	}
	
	public void setSparetires(boolean spareTires) {
		System.out.println("In setSparetires()");
		this.spareTires = spareTires;
	}
	
	public boolean getSparetires() {
		System.out.println("In getSparetires()");
		return spareTires;
	}
	
}

实例:编写处理代码

为强调 JET 不仅仅可用来生成代码,我们给出了下面这个实例,这是一个模板,生成具有不同语气的电子邮件消息。所生成的各电子邮件的目的是是向某人索要求各种东西。下面提供控制文件(main.jet)及其调用的模板(email.jet)。

清单 12. main.jet
<c:iterate select="/app/email" var="currEmail" >
	<ws:file template="templates/email.jet"
	path="{$org.eclipse.jet.resource.project.name}/{$currEmail/@to}.txt" />
</c:iterate>
清单 13. email.jet
<c:setVariable var="numItems" select="0" />
<c:iterate select="$currEmail/request" var="r">
	<c:setVariable var="numItems" select="$numItems+1" />
</c:iterate>
<c:set select="$currEmail" name="numItems"><c:get select="$numItems" /></c:set>
	<c:choose select="$currEmail/@mood" >
	<c:when test="'happy'">My dear</c:when>
	<c:when test="'neutral'">Dear</c:when>
	<c:when test="'angry'">My enemy</c:when>
</c:choose> <c:get select="$currEmail/@to" />,

I am writing you <c:choose select="$currEmail/@mood" >
<c:when test="'happy'">in joy </c:when>
<c:when test="'neutral'"></c:when>
<c:when test="'angry'">in burning anger </c:when>
</c:choose>to ask for <c:choose select="$currEmail/@numItems" >
<c:when test="1">
a <c:get select="$currEmail/request/@item" />. 
</c:when>
<c:otherwise>
the following:

<c:setVariable var="i" select="0" />
<c:iterate select="$currEmail/request" var="r">
	<c:setVariable var="i" select="$i+1" />
	<c:get select="$i" />. <c:get select="$r/@item" />
</c:iterate>

</c:otherwise>
</c:choose>
<c:choose select="$currEmail/@mood">
	<c:when test="'happy'">Please</c:when>
	<c:when test="'neutral'">Please</c:when>
	<c:when test="'angry'">Either suffer my wrath, or</c:when>
</c:choose> send me <c:choose select="$currEmail/@numItems">
<c:when test="1">
this item</c:when>
<c:otherwise>
these items</c:otherwise>
</c:choose> <c:choose select="$currEmail/@mood" >
<c:when test="'happy'">at your earliest convenience.</c:when>
<c:when test="'neutral'">promptly.</c:when>
<c:when test="'angry'">immediately!</c:when>
</c:choose>

<c:choose select="$currEmail/@mood" >
<c:when test="'happy'">Your friend,</c:when>
<c:when test="'neutral'">Sincerely,</c:when>
<c:when test="'angry'">In rage,</c:when>
</c:choose>

<c:get select="/app/@from" />

该模板的输入模型实例如下:

清单 14. sample.xml
<app from="Nathan" >
	<email to="Chris" mood="angry" >
		<request item="well-written article" />
	</email>
	<email to="Nick" mood="happy" >
		<request item="Piano" />
		<request item="Lollipop" />
		<request item="Blank DVDs" />
	</email>
</app>

mood 电子邮件蓝图应用于这些参数,生成下列两个文件。

清单 15. Chris.txt
My enemy Chris,

I am writing you in burning anger to ask for a well-written article. 
Either suffer my wrath, or send me this item immediately!

In rage,
Nathan
清单 16. Nick.txt
My dear Nick,

I am writing you in joy to ask for the following:

1. Piano
2. Lollipop
3. Blank DVDs

Please send me these items at your earliest convenience.

Your friend,
Nathan

结束语

在结束之前,我们希望感谢 Paul Elder 提供了宝贵的意见。整体上来说,JET 的用途不仅限于简化代码生成。JET 是一个新的 Eclipse 技术项目,我们期待有更多的开发人员在工作中使用它。


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Open source, Java technology
ArticleID=171512
ArticleTitle=使用 JET 在 Eclipse 中创建更多更好的代码
publish-date=10302006