创建 UML 模型并生成代码
Adrian Powell
Advisory I/T Specialist, IBM
2004 年 4 月
Eclipse Modeling Framework(EMF)是一个开放源代码的模型驱动应用程序开发框架。它可以基于 XML Schema、UML 或经过注释的 Java 中指定的模型,创建 Java 代码,实现图形化的数据编辑、操纵、读取和序列化。EMF 是 IBM WebSphere Studio 和 Eclipse 项目中很多工具的基础。本文将帮助您逐步了解创建模型、生成代码、使用生成的应用程序和定制编辑器的整个过程。
EMF 究竟是什么?
Eclipse Modeling Framework(EMF)是一个开放源代码的框架,它的目标是实现模型驱动架构(Model-Driven Architecture)的开发。如果我们当中的少数人有幸得到了某个 UML 模型,那么这个框架就可以帮助我们将文档变成代码。至于其他人,这个工具也使您又有一次机会向老板证实,把时间花在为解决方案建模上是值得的。除了可以生成令人赞叹的 Java 代码之外,EMF 还可以生成 Eclipse 插件,以及图形化的可定制编辑器。当您改变模型时(这种情况真的会出现),EMF 可以通过单击一个按钮,就使代码和模型保持同步。
EMF 生成的代码也不是一种只配丢进垃圾箱的解决方案。这种代码支持标准的创建、获取、更新和删除操作,而且还支持元数约束、复杂关系和继承结构、屏蔽定义,以及一套属性描述。生成的代码还提供通知、参照完整性和可定制的 XMI 持久性。您所需要做的全部工作就是创建一个对象模型,就像您以前也想做的那样。
EMF 是比较新的事物,但前景广阔,对它持续支持的力度也很强。它实现的是一项公共标准,即对象管理组织(Object Management Group)的元对象工具(Meta-Object Facility,MOF)。现在 EMF 已经对 MOF 的第二版进行了增强。更进一步看,EMF 还是 EMF:XSD 以及 Hyades 等 Eclipse 项目的基础,大多数 IBM WebSphere Studio 产品也都使用它。EMF 第二版的开发已经开始,开发构建应该很快就会出炉。第二版开发计划中包括更好的 XML Schema 支持、更灵活的代码生成方式以及模型之间的映射机制。
让工具自己说话
商业宣传已经说得够多了。现在让我们直接进入代码中,看看 EMF 到底能做些什么。下面的例子都是用 Eclipse 3.0M7 和 EMF 2.0.0,再加上与之匹配的 XSD 工具箱实现的。现在有四种独立的 EMF 开发流程,每一种都适用于不同版本的 Eclipse,所以一定要保证根据您的 Eclipse 版本选择了正确的 EMF 版本(请参阅 参考资料中的链接,获取这些插件)。
我们将以一个简单的 Web 论坛为例,向您展示最重要的特性。模型的根为 Forum
,下面包括一组 Member
和 Topic
。每一个 Topic
都具有一个 TopicCategory
(枚举类型), Member
和 Topic
通过 Post
类间接相关联,这两者之间也存在直接关联,因为 Member
可以创建 Topic
。
用 UML 和 Omondo 创建 EMF 模型
Omondo 的 UML 插件是在 Eclipse 中创建 UML 文档的方便可靠的工具。它看起来就像是 Rational Rose 受冷落的小兄弟,但除非是您需要特别强大的功能,否则用它就可以工作得很好了。不过,该工具尚不支持 Eclipse 3,所以我采用 Eclipse 2.1 来创建 UML 类图。
一开始,我们创建一个新的 Java 项目 UMLForum,以及一个新包 com.ibm.example.forum
。再创建一个新的 EMF 类图, forum.ucd
,存放在 src/com/ibm/example/forum 下。目录中创建了两个文件,forum.ecd 和 forum.ecore。向类图中增加一个新类,名为 Forum
,然后单击 Finished。向 Forum
类中增加一条属性描述,类型为 EString
(对于所有的简单 Java 类都有相应的 Ecore 类),如图 1 所示。对于属性的特性,只选择 changeable
,并将范围设为从 0 到 1。
如果您过一会改主意了,想使用其他的特性,可以打开 Properties 视图,选择其中的类或属性。
图 1. 新建的 Forum 类及其属性的性质
对于下列接口重复上述步骤:
接口
|
属性
|
类型
|
Member |
nickname |
EString
|
Topic |
title |
EString
|
Post |
comment |
EString
|
为定义关联,我们可以选中关联按钮,然后单击关联的源( Forum
)和目标( Member
)。这样将打开关联属性设置对话框。在其中将名字设置为 members
,确保仅仅选择了 changeable 和 containment,然后将上限设为 -1。在第二个 Association End 选项卡中,取消选中的 Navigable,然后单击 Ok。对 Forum
和 Topic
也执行相同的操作,属性名称从 members
改为 topics
。取消选中的 navigable,从而创建一个无方向的关联,但我们想让其他属性都保持为双向。
按照下表所示完成关联设置:
源
|
目标
|
关联
|
名称
|
特性
|
范围
|
Member |
Topic |
1st Association |
topicsCreated
|
changeable |
0 到 1 |
|
|
2nd Association |
creator
|
changeable |
0 到 1 |
Topic |
Post |
1st Association |
posts
|
Containment, changeable |
0 到 -1 |
|
|
2nd Association |
topic
|
changeable |
0 到 1 |
Member |
Post |
1st Association |
posts
|
changeable |
0 到 -1 |
|
2nd Association |
author
|
changeable |
0 到 1 |
最后,我们要定义一个枚举类型,用于表示 topic 有多少不同的类型。创建一个新的枚举类型,名字叫做 TopicCategory
。Literal 中加入以下的内容:
-
ANNOUNCEMENT
, value = 0
-
GUEST_BOOK
, value = 1
-
DISCUSSION
, value = 2
然后,为 Topic 定义一个新属性,叫做 category
,类型为 TopicCategory
,changeable,范围 0-1。如果您愿意的话,可以在属性标签上对默认值进行修改,但我们将接受 ANNOUNCEMENT
的默认值。
图 2. 完成后的 UML 类模型
一旦您完成了图 2 所示的 UML 类图,下一步就是创建一个 EMF 模型。为此,需要先创建一个新的 EMF 项目( File > New > Project... > Eclipse Modeling Framework > EMF Project),并用 com.ibm.example.forum
作为该项目的名称(这是插件名称的基础,因此我们遵从 Eclipse 插件的命名规范)。在下一个页面上,选择 Load from an EMF core model,然后单击 Next。从文件系统中加载 ecore 文件,它将自动填充 Generator 的模型名。在最后一个页面上,单击包旁边的复选框,然后单击 Finish。这样就创建好了 EMF 模型,它的名字叫做 forum.genmodel。您可以从 使用生成的 EMF 模型一节中了解到这个模型是什么,以及如何使用它。
用 XML Schema
创建 EMF 模型
XML Schema(XSD)的表现能力不如 UML 或带注释的 Java 代码那么强大,例如,它不能表达出双向引用的关联。但是由于默认的的序列化方法要使用到您的方案,因此 XSD 对定制序列化来说是最快的方法。如果您希望为模型生成非常详细的 XML/XMI,那么 XSD 就是必然的选择。
清单 1. forum.xsd 的片段
<xsd:simpleType name="TopicCategory">
<xsd:restriction base="xsd:NCName">
<xsd:enumeration value="Announcement"/>
<xsd:enumeration value="GuestBook"/>
<xsd:enumeration value="Discussion"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="Post">
<xsd:sequence>
<xsd:element name="comment" type="xsd:string"/>
<xsd:element name="author" type="xsd:anyURI" ecore:reference="forum:Member"/>
<xsd:element name="topic" type="xsd:anyURI" ecore:reference="forum:Topic"/>
</xsd:sequence>
</xsd:complexType>
|
在清单 1 中,您可以看到枚举是如何表示的,也能从中了解到如何定义一个具有指向其他类型的元素和引用的类型。在 Forum 这个例子中,我们仅仅使用了字符串属性 "xsd:string"
,但是其他简单 Java 类型也是支持的。有关 XML Schema 和 forum.xsd 文件的更多信息,请参阅 参考资料。
一旦完成了 XSD,下一步就是创建 EMF 模型。方法与 UML 模型中类似,先创建一个新的 EMF 项目( File > New > Project... > Eclipse Modeling Framework > EMF Project),项目名称为 com.ibm.example.forum(这是插件名称的基础,因此我们遵从 Eclipse 插件的命名规范)。在下一个页面上选择 Load from an XML Schema,然后单击 Next。在文件系统中找出 XSD 文件并加载,然后 Generator 中的模型名就会自动填充。在最后一个页面上,单击包旁边的复选框,然后单击 Finish。这样就创建了一个 EMF 模型,名字叫做 forum.genmodel。 您可以从 使用生成的 EMF 模型一节中了解到这个模型是什么,以及如何使用它。
用带注释的 Java 代码创建 EMF 模型
如果通过 Java 代码定义 EMF 模型,我们可以用 Interface 列出每一个类的属性,以及类之间的关系。这样得到的内容并不充足,无法定义我们想要的全部信息,所以 EMF 使用了特殊的 JavaDoc 标签。每一个属性或类,如果是 EMF 模型的一部分,就必须在其 JavaDoc 中包含一个 @model
标签,也可以包含一个附加属性列表。比如说,如果要构造如上面图 2 所示的一个对象模型,我们对 Forum 的定义看起来应该像清单 2 的样子。
清单 2. 带注释的 Forum.java
package com.ibm.example.forum;
import java.util.List;
/**
*
@model
*/
public interface Forum {
/**
*
@model type="Topic" containment="true"
*/
List getTopics();
/**
*
@model type="Member" containment="true"
*/
List getMembers();
/**
*
@model
*/
String getDescription();
}
|
清单 2 声明了一个叫做 Forum
的对象,它具有一条 String 类型的描述信息和两个孩子,一个是 Topic
列表,还有一个是 Member
列表。这两个孩子都包含在 Forum
之内。
对于简单的属性,如 描述信息
, @model
标签就足够了,但对于 list 而言,您也需要为其指明类型。 containment
属性是可选的,但是如果某个对象是被包含的,那么它就和其容器一起被序列化。为了简化序列化的过程,我们要保证所有的对象都是直接或者间接包含在 Forum
中的。其他一些有用的可选属性如下:
-
opposite
(用于双向属性)。
-
default
(属性的默认值)。
-
transient
(该属性不能被序列化)。
要获得完整的属性列表,请您参阅 参考资料中的 EMF user's guide。
惟一需要当心的是枚举类型。它被定义成一个 Class,而不是其他模型类中的 Interface! 为了明确这一点,清单 3 展示了 TopicCategory 枚举类型是如何实现的。
清单 3. 枚举类型 TopicCategory.java
package com.ibm.example.forum;
/**
* @model
*/
public
class TopicCategory{
/**
* @model name="Announcement"
*/
public static final int ANNOUNCEMENT = 0;
/**
* @model name="GuestBook"
*/
public static final int GUEST_BOOK = 1;
/**
* @model name="Discussion"
*/
public static final int DISCUSSION = 2;
}
|
最后,生成如下所示的三个接口,模型就完成了:
接口
|
方法
|
模型标签
|
Member |
List getPosts()
|
type="Post" opposite="author" |
|
List getTopicsCreated()
|
type="Topic" opposite="creator" |
|
String getName()
|
|
Topic |
List getPosts()
|
type="Post" opposite="author" |
|
Member getCreator()
|
opposite="topicsCreated" |
|
String getTitle()
|
|
|
TopicCategory getCategory()
|
|
Post |
Member getAuthor
|
opposite="posts" |
|
Topic getTopic()
|
opposite="posts" |
|
String getComment()
|
|
模型定义完成之时,可以生成 EMF 模型( File > New > Other > Eclipse Modeling Framework > EMF Models)。将父目录设为 com.ibm.example.forum/src/model, File name设为 forum.genmodel。在下一个页面上,选择 Load from annotated Java,然后选中包“forum”旁边的复选框。然后单击 Finish。这样就创建了一个名为 forum.genmodel 的 EMF 模型。
使用生成的 EMF 模型
现在您的工作空间中应该有一个生成好的 EMF 模型 forum.genmodel。这个模型中包含您输入其中的所有信息。用默认的编辑器打开这个模型(参见图 3),再打开 Properties 视图,然后检查模型树中每一个节点的属性。前面输入的所有属性都可以定制,但是也有一些用于定制代码生成的属性。为了验证这一点,让我们试着修改“Copyright Text”或“Generate Schema”之类的属性,看看会发生什么事情。
图 3. 在默认的编辑器中打开生成的 EMF 模型
如果对模型描述(UML、XSD、带注释的 Java)进行了修改,也可以在 Package Explorer 中用右键单击该模型,然后选择 Reload,这样就能够重新加载模型。这实现了用 EMF 生成的模型与模型描述之间的同步。重新加载后将会改变您在生成的模型中修改过的属性。
生成 Java 代码
如果您对模型描述感到满意,或者如果您仅仅是想看看所有这一切到底是什么意思,那么现在就可以生成代码了。在根节点上单击鼠标右键,选择其中一个生成选项:Model、Edit、或 Editor code。 Generate Model将在当前项目中创建该 EMF 模型的 Java 实现代码。其中会包含下列内容:
-
com.ibm.example.forum
-- 创建该 Java 类的接口和工厂。
-
com.ibm.example.forum.impl
-- com.ibm.example.forum 中定义的接口的具体实现。
-
com.ibm.example.forum.util
-- AdapterFactory。
Generate Editor Code将创建 com.ibm.example.forum.edit 项目。其中仅仅包含一个包, com.ibm.example.forum.provider
,用于控制每一个模型对象出现在编辑器中的方式。 Generate Editor Code将在 com.ibm.example.forum.editor 项目中创建一个插件编辑器示例,其中包含了 com.ibm.example.forum.presentation。这些类提供了一系列简单的 JFace 编辑器,可以与您的模型进行交互。
为了测试生成的插件,请依次进入 Run > Run... > Run Time Workbench > New。输入一个描述性的名称,然后在 plug-ins 选项卡中,选择 launch with all workspace and enabled external plug-ins。再在 Common 页下,单击 Display in favorites menu > Run和 Launch in background。最后保存设置并运行。
这时将出现一个新的 Eclipse 工作台,您可以在 Help > About Eclipse Platform > Plug-in Details下面验证您的插件是否可用,如图 4 所示。
图 4. Forum 的插件详细信息
为了测试生成的插件,您可以创建一个新的 Simple 项目,名为“Forum Demo”,然后依次进入 New > Other... > Example EMF Model Creation Wizards > Forum Model。给文件取名叫做 sample.forum,然后选择 Forum 作为 Model Object。这时会打开一个窗口,您可以在这里向根中增加新的模型元素。其中包含几种视图:Selection、Parent、List、Tree、Table 和 TreeTable。所有这些视图都显示相同的数据,也和 Outline 视图保持同步。虽然所有视图都会在右键菜单选项中显示 New Sibling/New Child,但是我发现,有些视图在加入兄弟节点或子节点时不能正确响应。如果您也遇到这种情况,可以使用 TableTree 视图,或是在 Outline 视图中创建新的节点。图 5 展示了所生成的插件编辑器。
图 5. 所生成的插件编辑器
定制生成的代码
生成的代码都很不错,但是这只是真正应用程序的起点。为了满足我们的需要,我们必须对其进行调整和定制。我们可以改变所生成的模型类的实现,也可以对编辑器进行扩展和定制。好在 EMF 没有让我们失望,我们可以按照自己的想法做任何定制,当重新生成代码时也不会丢掉这些内容。我们需要做的全部工作就是删除 @generated
JavaDoc 标签,EMF 的 jmerge
将保证这些方法、属性或类不被打扰。
为着重说明您能对代码进行哪些修改,让我们来看一个简单的例子。在所生成编辑器的 Table 视图中,两个字段都显示出相同的的值。这一点并不是完全没有用处。为了改善一下,我们可以修改第二个字段,让它在选中一个 Topic 的时候显示 Author,然后增加第三个字段,给出该 Topic 中的帖子数。
第一步,向 Table 视图中额外增加一个字段。这一步在 com.ibm.example.forum.editor
项目中实现,即 createPages()
方法中的 com.ibm.example.forum.presentation.ForumEditor
。把 @generated 标签删除,这样就能持久保存我们的修改,然后定位到表浏览窗口所在的位置。按照清单 4 的内容对这段代码进行修改。
清单 4. 修改后的 createPages()
TableColumn selfColumn = new TableColumn(table, SWT.NONE);
layout.addColumnData(new ColumnWeightData(2, 100, true));
selfColumn.setText("Author");
selfColumn.setResizable(true);
TableColumn numberColumn = new TableColumn(table, SWT.NONE);
layout.addColumnData(new ColumnWeightData(4, 100, true));
numberColumn.setText("Number of Posts");
numberColumn.setResizable(true);
tableViewer.setColumnProperties(new String [] {"a", "b", "c"});
|
这样就额外增加了一个字段,但是现在所有的三个字段都显示相同的数据。为了定制每一个字段中的数据,我们需要提供一些 ITableItemLabelProvider
的实现。打开 com.ibm.example.forum.provider.TopicItemProvider
,在实现列表中加入 ITableItemLabelProvider
。我们需要增加两个方法, getColumnText(Object, int)
和 getColumnImage(Object, int)
,如清单 5 所示。
清单 5. 加入 TopicItemProvider
public String getColumnText(Object obj, int index) {
if( index == 0 ){
return getText(obj);
}
else if( index == 1 ) {
return ((Topic)obj).getCreator().getNickname();
} else if( index == 2 ) {
return " + ((Topic)obj).getPosts().size();
}
return "unknown";
}
public Object getColumnImage(Object obj, int index) {
return getImage( obj );
}
|
最后,我们需要注册这个提供程序。实现方法是编辑 com.ibm.example.forum.provider.ForumItemProviderAdapterFactory
的构造函数,向支持的类型中增加 ITableItemLabelProvider
,如清单 6 所示。
清单 6. ForumItemProviderFactory 构造函数
public ForumItemProviderAdapterFactory() {
supportedTypes.add(ITableItemLabelProvider.class); supportedTypes.add(IStructuredItemContentProvider.class);
supportedTypes.add(ITreeItemContentProvider.class);
supportedTypes.add(IItemPropertySource.class);
supportedTypes.add(IEditingDomainItemProvider.class);
supportedTypes.add(IItemLabelProvider.class);
}
|
现在我们再运行这个插件,打开表视图,就能看到图 6。请注意,没有实现的 ITableItemLabelProvider
元素将在所有的字段中显示相同的文本。
图 6. 修改后的 Table 编辑器
在 Java 中操纵模型
生成的模型代码看起来就像是 Java 代码中增加了一些有用的东西。系统还提供了一种灵活的定制反射 API,对工具很有用。您也许注意到了,这就是 eGet()
和 eSet()
两个方法。在大多数情况下,我们并不需要关心它,所以我们还是看看我们感兴趣的东西:如何创建、保存和加载模型。让我们从头开始:加载 EMF 模型。
清单 7. 加载 Forum
// Register the XMI resource factory for the .forummodel extension
Resource.Factory.Registry reg = Resource.Factory.Registry.INSTANCE;
Map m = reg.getExtensionToFactoryMap();
m.put("forummodel", new XMIResourceFactoryImpl());
ResourceSet resSet=new ResourceSetImpl();
Resource res = resSet.getResource(URI.createURI("model/forum.forummodel"),true);
Forum forum = (Forum)res.getContents().get(0);
|
清单 7 展示了如何给文件关联一个符合 XMI 格式的扩展名“forummodel”,然后用 EMF 的 ResourceSet 解析并加载 forum 模型。我们知道,Forum 是惟一的根元素,所以可以想象, res.getContents().get(0)
将返回一个且仅有一个 Forum
对象。如果情况不是这样,我们还可以从 getContents().iterator()
中取出一个 Iterator,然后分别检查每一个元素。
我们还可以换一种方法,创建一个新的 Forum,然后用程序组装起来,如清单 8 所示。
清单 8. 初始化 Forum
// initialize model and dependencies
ForumPackageImpl.init();
// retrieve the default Forum factory singleton
ForumFactory factory = ForumFactory.eINSTANCE;
Forum forum = factory.createForum();
forum.setDescription("programmatic forum example");
Member adminMember = factory.createMember();
adminMember.setNickname("Administrator");
forum.getMembers().add( adminMember );
Topic noticeTopic = factory.createTopic();
noticeTopic.setTitle("Notices");
noticeTopic.setCategory(TopicCategory.ANNOUNCEMENT_LITERAL);
noticeTopic.setCreator(adminMember);
forum.getTopic().add( noticeTopic );
|
在这个例子中,我们首先初始化包,然后创建 ForumFactory,用它生成所有的子对象。创建完毕之后,就可以像标准的 JavaBean 那样访问这些对象。然而,由于我们把 Topic
和 Memeber
之间的 creator/topicsCreated
关系声明为双向,当我们调用 noticeTopic.setCreator(adminMember)
的时候, adminMember
的 topicsCreated
清单中就包括 noticeTopic
。
一旦我们创建并操纵了 EMF 模型,就很容易将其保存为我们选定的格式(参见清单 9)。
清单 9. 保存 Forum
URI fileURI = URI.createFileURI("model/forum.ecore");
Resource resource = new XMIResourceFactoryImpl().createResource(fileURI);
resource.getContents().add( forum );
try {
resource.save(Collections.EMPTY_MAP);
} catch (IOException e) {
e.printStackTrace();
}
|
在本例中,我们给 URI.createFileURI()
提供了希望保存成的文件名与目标格式。这个例子因为是保存为 XMI,所以使用了 XMIResourceFactoryImpl
。一旦创建完毕,所有的模型对象就如我们所愿的持久保存起来了。在这个例子中,除 Forum
之外的每一个对象都被另一个类包含,所以我们只需要对包含所有孩子的 root 增加这条命令即可。如果某些对象没有 包含
关系,那么也必须通过 resource.getContents().add()
显式地将它们加进去。否则,当您调用 resource.save()
时就会出现异常。
结束语
Eclipse Modeling Framework 提供了进行模型驱动开发的工具。它包含了将您的开发精力集中在模型上而不是实现细节上所必需的元素。其主要关注的领域是:生成模型时支持定制、通知、参照完整性以及其他基本特性;生成可定制的模型编辑器;默认的序列化。正像例子中展示的那样,生成的过程既简单又直接,所有的定制代码都支持定制。序列化或图形化编辑器等独立的工具也可以拉出来单独使用,但所有的部件一起使用才能发挥完整的效力。EMF 已经在很多成功的项目中得到应用,它正在蓬勃成长。
参考资料
使用 Eclipse Modeling Framework 进行建模,第 2 部分
使用 Eclipse 的 Java Emitter Templates 生成代码
Adrian Powell
资深软件开发人员, IBM
2004 年 6 月
Eclipse 的 Java Emitter Templates(JET) 是一个开放源代码工具,可以在 Eclipse Modeling Framework(EMF)中生成代码。 JET 与 JSP 非常类似,不同之处在于 JET 功能更强大,也更灵活,可以生成 Java、 SQL 和任何其他语言的代码,包括 JSP。本文将介绍如何创建和配置 JET,并将其部署到各种环境中。
Java Emitter Templates(JET) 概述
开发人员通常都使用一些工具来生成常用的代码。 Eclipse 用户可能对一些标准的工具非常熟悉,这些工具可以为选定的属性生成 for(;;) 循环, main() 方法, 以及选定属性的访问方法。将这些简单而机械的任务变得自动化,可以加快编程的速度,并简化编程的过程。在某些情况中,例如为 J2EE 服务器生成部署代码,自动生成代码就可以节省大量时间,并可以隐藏具体实现特有的一些复杂性,这样就可以将程序部署到不同的 J2EE 服务器上。自动生成代码的功能并不只是为开发大型工具的供应商提供的,在很多项目中都可以使用这种功能来提高效率。 Eclipse 的 JET 被包装为 EMF 的一部分,可以简单而有效地向项目中添加自动生成的代码。本文将介绍在各种环境中如何使用 JET 。
JET 是什么?
JET 与 JSP 非常类似:二者使用相同的语法,实际上在后台都被编译成 Java 程序;二者都用来将呈现页面与模型和控制器分离开来;二者都可以接受输入的对象作为参数,都可以在代码中插入字符串值(表达式),可以直接使用 Java 代码执行循环、声明变量或执行逻辑流程控制(脚本);二者都可以很好地表示所生成对象的结构,(Web 页面、Java 类或文件),而且可以支持用户的详细定制。
JET 与 JSP 在几个关键的地方存在区别。在 JET 中,可以变换标记的结构来支持在不同的语言中生成代码。通常 JET 程序的输入都是一个配置文件,而不是用户的输入(当然也不禁止这样使用)。而且对于一个给定的工作流来说,JET 通常只会执行一次。这并不是技术上的限制;您可以看到 JET 有很多完全不同的用法。
开始
创建模板
要使用 JET,创建一个新 Java 项目 JETExample,并将源文件夹设置为 src。为了让 JET 启用这个项目,请点击鼠标右键,然后选择 Add JET Nature。这样就会在新项目的根目录下创建一个 templates 目录。JET 的缺省配置使用项目的根目录来保存编译出来的 Java 文件。要修改这种设置,打开该项目的 properties 窗口,选择 JET Settings,并将 source container 设置为 src。这样在运行 JET 编译器时,就会将编译出来的 JET Java 文件保存到这个正确的源文件夹中。
现在我们已经准备好创建第一个 JET 了。JET 编译器会为每个 JET 都创建一个 Java 源文件,因此习惯上是将模板命名为 NewClass.javajet,其中 NewClass 是要生成的类名。虽然这种命名方式不是强制的,但是这样可以避免产生混乱。
首先在模板目录中创建一个新文件 GenDAO.javajet。这样系统会出现一个对话框,警告您在这个新文件的第 1 行第 1 列处有编译错误。如果您详细地看以下警告信息,就会发现它说 "The jet directive is missing"(没有 jet 指令)。虽然这在技术上没有什么错误,因为我们刚才只不过是创建了一个空文件,但是这个警告信息却很容易产生混乱并误导我们的思路。单击 'OK' 关闭警告对话框,然后单击 'Cancel' 清除 New File 对话框(这个文件已经创建了)。为了防止再次出现这种问题,我们的首要问题是创建 jet 指令。
每个 JET 都必须以 jet 指令开始。这样可以告诉 JET 编译器编译出来的 Java 模板是什么样子(并不是模板生成了什么内容,而是编译生成的模板类是什么样子;请原谅,这个术语有些容易让人迷惑)。此处还要给出一些标准的 Java 类信息。例如,在下面这个例子中使用了以下信息:
清单 1. 样例 jet 声明
明
<%@ jet
package="com.ibm.pdc.example.jet.gen"
class="GenDAO"
imports="java.util.* com.ibm.pdc.example.jet.model.*"
%>
|
清单 1 的内容是真正自解释的。在编译 JET 模板时,会创建一个 Java 文件 GenDAO
,并将其保存到 com.ibm.pdc.example.jet.gen
中,它将导入指定的包。重复一遍,这只是说明模板像什么样子,而不是模板将要生成的内容 -- 后者稍后将会介绍。注意 JET 输出结果的 Java 文件名是在 jet
的声明中定义的,它并不局限于这个文件名。如果两个模板声明了相同的类名,那么它们就会相互影响到对方的变化,而不会产生任何警告信息。 如果您只是拷贝并粘贴模板文件,而没有正确地修改所有的 jet
声明,那就可能出现这种情况。因为在模板目录中创建新文件时会产生警告,而拷贝和粘贴是非常常见的,因此要自己小心这个问题。
JSP 可以通过预先声明的变量(例如会话、错误、上下文和请求)获取信息, JET 与此类似,也可以使用预先声明的变量向模板传递信息。JET 只使用两个隐式的变量: stringBuffer
,其类型为 StringBuffer
(奇怪吧?),它用来在调用 generate()
时构建输出字符串;以及一个参数,出于方便起见,我们称之为 argument
,它是 Object
类型。典型的 JET 模板的第一行会将其转换为一个更适合的类,如清单 2 所示。
清单 2. JET 参数的初始化
<% GenDBModel genDBModel = (GenDBModel)argument; %>
package <%= genDBModel.getPackageName() %>;
|
正如您可以看到的一样,JET 的缺省语法与 JSP 相同:使用 <%...%> 包括代码,使用 <%= ... %> 打印表达式的值。与 JSP 类似,正确地使用 <% ... %> 标签就可以添加任何逻辑循环或结构,就像是在任何 Java 方法中一样。例如:
清单 3. 脚本和表达式
Welcome <%= user.getName() %>!
<% if ( user.getDaysSinceLastVisit() > 5 ) { %>
Whew, thanks for coming back. We thought we'd lost you!
<% } else { %>
Back so soon? Don't you have anything better to do?
<% } %>
|
在定义完 JET 之后,保存文件并在包浏览器中在这个文件上点击鼠标右键,选择 Compile Template。如果一切正常,就会在 com.ibm.pdc.example.jet.gen
包中创建一个类 GenDAO
。其中只有一个方法 public String generate(Object argument)
(参见清单 4),这样做的结果就是在 javajet
模板中定义的内容。
清单 4. 一个基本的 JET 编译后的 Java 类,其功能是打印 "Hello <%=argument%>"
package com.ibm.pdc.example.jet.gen;
import java.util.*;
public class GenDAO
{
protected final String NL = System.getProperties().getProperty("line.separator");
protected final String TEXT_1 = NL + "Hello, ";
protected final String TEXT_2 = NL + "\t ";
public String generate(Object argument)
{
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append(TEXT_1);
stringBuffer.append( argument );
stringBuffer.append(TEXT_2);
return stringBuffer.toString();
}
}
|
准备公共代码
编写好模板之后,您可能就会注意到一些公共的元素,这些元数会反复出现,例如所有生成的代码中都添加的版权信息。在 JSP 中,这是通过 include
声明处理的。将所有想要添加的内容都放到一个文件中,并将该文件命名为 'copyright.inc',然后在 javajet
模板中添加 <%@ include file="copyright.inc" %>
语句。所指定的包含文件会被添加到编译后的输出结果中,因此它可以引用到现在为止已经声明的任何变量。扩展名 .inc
可以任意,只是不要采用以 jet
或 JET 结尾的名字,否则将试图编译包含文件,这样该文件的理解性自然很差。
定制 JET 编译
如果只使用包含文件还不能满足要求,您可能会想添加其他一些方法,或者对代码生成过程进行定制;最简单的方法是创建一个新的 JET 骨架。骨架文件就是描述编译后的 JET 模板样子的一个模板。缺省的骨架如清单 5 所示。
清单 5. 缺省的 JET 骨架
public class CLASS
{
public String generate(Object argument)
{
return "";
}
}
|
所有的 import 语句都位于最开始, CLASS
会被替换为在 jet 声明的 class
属性中设置的类名, generate()
方法的代码会被替换为执行生成操作的代码。因此,要修改编译后的模板代码的样子,我们只需要创建一个新的骨架文件并进行自己想要的定制即可,但是仍然要在原来的地方保留基本的元素。
要创建一个定制的骨架,在 custom.skeleton
模板目录中创建一个新文件,如清单 6 所示。
清单 6. 定制 JET 骨架
public class CLASS
{
private java.util.Date getDate() {
return new java.util.Date();
}
public String generate(Object argument) {
return "";
}
}
|
然后在想要使用这个定制骨架的任何 JET 模板中,向 javajet
文件中的 jet
声明添加 skeleton="custom.skeleton"
属性。
或者,也可以使用它对基类进行扩充,例如 public class CLASS extends MyGenerator
,并在基类中添加所有必要的帮助器方法。这样可能会更加整洁,因为它保留了代码的通用性,并可以简化开发过程,因为 JET 编译器并不能总是给出最正确的错误消息。
定制骨架也可以用来修改方法名和 generate()
方法的参数列表,这样非常挑剔的开发人员就可以任意定制模板。说 JET 要将 generate()
的代码替换为要生成的代码,其实有些不太准确。实际上,它只会替换在骨架中声明的最后一个方法的代码,因此如果粗心地修改骨架的代码,就很容易出错,而且会让您的同事迷惑不解。
使用 CodeGen
正如您可以看到的一样,模板一旦编译好之后,就是一个标准的 Java 类。要在程序中使用这个类,只需要分发编译后的模板类,而不需要分发 javajet
模板。或者,您可能希望让用户可以修改模板,并在启动时自动重新编译模板。EMF 可以实现这种功能,任何需要这种功能或对此感兴趣的人都可以进入 plugins/org.eclipse.emf.codegen.ecore/templates
中,并修改 EMF 生成模型或编辑器的方式。
如果您只是希望可以只分发编译后的模板类,那么编译过程可以实现自动化。迄今为止,我们只看到了如何使用 JET Eclipse 插件来编译 JET 模板,但实际上我们可以编写一些脚本来实现这种功能,或者将生成代码的工作作为一项 ANT 任务。
运行时编译模板
要让最终用户可以定制模板(以及对模板的调试),可以选择在运行时对模板进行编译。实现这种功能有几种方法,首先我们使用一个非常有用的类 org.eclipse.emf.codegen.jet.JETEmitter
,它可以对细节进行抽象。常见的(但通常是错误的)代码非常简单,如清单 7 所示。
清单 7. JETEmitter 的简单用法(通常是错误的)
String uri = "platform:/templates/MyClass.javajet";
JETEmitter jetEmitter = new JETEmitter( uri );
String generated = jetEmitter.generate( new NullProgressMonitor(), new Object[]{argument} );
|
如果您试图在一个标准的 main()
方法中运行这段代码,就会发现第一个问题。 generate()
方法会触发一个 NullPointerException
异常,因为 JETEmitter
假设自己正被一个插件调用。在初始化过程中,它将调用 CodeGenPlugin.getPlugin().getString()
,这个函数会失败,因为 CodeGenPlugin.getPlugin()
为空。
解决这个问题有一个简单的方法:将这段代码放到一个插件中,这样的确可以管用,但却不是完整的解决方法。现在 JETEmitter
的实现创建了一个隐藏项目 .JETEmitters
,其中包含了所生成的代码。然而, JETEmitter
并不会将这个插件的 classpath 添加到这个新项目中,因此,如果所生成的代码引用了任何标准 Java 库之外的对象,都将不能成功编译。2.0.0 版本初期似乎解决了这个问题,但是到 4 月初为止,这还没有完全实现。要解决这个问题,必须对 JETEmitter
类进行扩充,使其覆盖 initialize()
方法,并将其加入您自己的 classpath 项中。Remko Popma 已经编写了很好的一个例子 jp.azzurri.jet.article2.codegen.MyJETEmitter
(参阅 参考资料),这个例子可以处理这个问题,在 JET 增加这种正确的特性之前都可以使用这种方法。修改后的代码如清单 8 所示。
清单 8. 正确的 JETEmitter 调用
String base = Platform.getPlugin(PLUGIN_ID).getDescriptor().getInstallURL().toString();
String uri = base + "templates/GenTestCase.javajet";
MyJETEmitter jetEmitter = new MyJETEmitter( uri );
jetEmitter.addClasspathVariable( "JET_EXAMPLE", PLUGIN_ID);
String generated = jetEmitter.generate( new NullProgressMonitor(),
new Object[]{genClass} );
|
命令行
在命令行中编译 JET 非常简单,不会受到 classpath 问题的影响,这个问题会使编译一个 main()
方法都非常困难。在上面这种情况中,难点并不是将 javajet 编译成 Java 代码,而是将这个 Java 代码编译成 .class
。在命令行中,我们可以更好地控制 classpath,这样可以分解每个步骤,最终再组合起来,就可以使整个工作顺利而简单。唯一一个技巧是我们需要以一种 "无头" 模式(没有用户界面)来运行 Eclipse,但即便是这个问题也已经考虑到了。要编译 JET,请查看一下 plugins/org.eclipse.emf.codegen_1.1.0/test
。这个目录中包含了 Windows 和 Unix 使用的脚本,以及一个要验证的 JET 例子。
作为一个 ANT 任务执行
有一个 ANT 任务 jetc
,它要么可以采用一个 template
属性,要么对多个模板有一个 fileset
属性。一旦配置好 jetc
任务的 classpath 之后,模板的编译就与标准的 Java 类一样简单。有关如何获取并使用这个任务的更多信息,请参阅 参考资料。
定制 JET 以生成 JSP
最终,JET 使用 "<%" 和 "%>" 来标记模板,然而这与 JSP 使用的标记相同。如果您希望生成 JSP 程序,那就只能修改定界符。这可以在模板开头的 jet
声明中使用 startTag
和 endTag
属性实现,如清单 9 所示。在这种情况中,我使用 "[%" 和 "%]" 作为开始定界符和结束定界符。正如您可以看到的一样, "[%= expression %]" 可以正确处理,就像前面的 "<%= expression %>" 一样。
清单 9. 修改标签后的 JET 模板
<%@ jet
package="com.ibm.pdc.example.jet.gen"
class="JspGen"
imports="java.util.* "
startTag = "[%"
endTag = "%]"
%>
[% String argValue = (String)argument; %]
package [%= argValue %];
|
结束语
有一个不幸的事实:很多代码都是通过拷贝/粘贴而实现重用的,不管是大型软件还是小型软件都是如此。很多时候这个问题并没有明显的解决方案,即使面向对象语言也不能解决问题。在重复出现相同的基本代码模式而只对实现稍微进行了一些修改的情况中,将通用的代码放到一个模板中,然后使用 JET 来生成各种变化,这是一种很好的节省时间和精力的办法。JSP 早已采用了这种方法,因此 JET 可以从 JSP 的成功中借鉴很多东西。JET 使用与 JSP 相同的基本布局和语义,但是允许更灵活的定制。为了实现更好的控制,模板可以进行预编译;为了实现更高的灵活性,也可以在运行时编译和分发。
在本系列的下一篇文章中,我们将介绍如何为 Prime Time 生成代码,这包括允许用户定制代码,以及集成以域或方法甚至更细粒度级别的修改,从而允许重新生成代码。我们还会将它们都绑定到一个插件中,从而展示一种将生成的代码集成到开发过程的方法。
参考资料
关于作者
Adrian Powell 从刚加入 VisualAge for Java Enterprise Tooling 小组开始使用 IBM 的 Java 开发工具,在这儿他花费了两年的时间来手工编写一个代码生成器。从那以后,他一直从事于 Eclipse 和 VisualAge for Java 中的工具和插件的开发,他现在几乎为 Eclipse 和 VisualAge for Java 的每一个版本都开发了这种工具和插件。Adrian 目前在 Vancouver Centre for IBM e-Business Innovation 工作,在这儿他正在开发替代软件。
|
使用 Eclipse Modeling Framework 进行建模,第 3 部分
使用 Eclipse 的 JMerge 定制生成的代码和编辑器
Adrian Powell
资深软件开发人员, IBM
2004 年 6 月
Eclipse Modeling Framework(EMF)中包含了一个开放源代码的工具 JMerge,这个工具可以使代码生成更加灵活,可定制性更好。本文使用一个例子来展示如何将 JMerge 添加到一个应用程序中,并为不同的环境定制 JMerge 的行为。
概述
本系列文章的 前一篇 介绍了有关 Eclipse 的 Java Emitter Templates (JET)和代码生成的知识,在那篇文章中,您已经看到如何通过使用模板和代码生成器来节省时间,并实现模式级的代码重用。然而在大部分情况中,这都还不够。您需要能够将所生成的代码插入现有的代码中,或者允许以后的开发人员来定制所生成的代码,而不需要在重新生成代码时重新编写任何内容。理想情况下,代码生成器的创建者希望可以支持今后开发人员所有的需求:从修改方法的实现、修改各种方法签名,到修改所生成类的继承结构。这是一个非常有趣的问题,目前还没有很好的通用解决方案;但是有一个很好的纯 Java 的解决方案,称为 JMerge。
JMerge 是 EMF 中包含的一个开放源代码的工具,可以让您定制所生成的模型和编辑器,而重新生成的代码不会损坏已经修改过的内容。如果描述了如何将新生成的代码合并到现有定制过的代码中,那么 JETEmitter 就可以支持 JMerge。本文通过一个例子来展示其中的一些可用选项。
第一步
假设您已经添加了一个新项目,在这个项目中需要为编写的每个类都创建一个 JUnit 测试类,这样必须要对编写的每个方法都进行测试。作为一个认真且高效的(或者比较懒的)程序员来说,您决定要编写一个插件,它接受一个 Java 类作为输入,并生成 JUnit 测试例子的存根(stub)。您热情高涨地创建了 JET 和插件, 现在想允许用户定制所生成的测试类;然而在原有类的接口发生变化时,仍然需要重新生成代码。要实现这种功能,可以使用 JMerge。
从插件中调用 JMerge 的代码非常简单(参见清单 1)。这会创建一个新的 JMerger 实例,以及一个 URI merge.xml,设置要合并的来源和目标,并调用 merger.merge()。然后合并的内容就可以展开为 merger.getTargetCompilationUnit()。
清单 1. 调用 JMerge
// ...
JMerger merger = getJMerger();
// set source
merger.setSourceCompilationUnit(
merger.createCompilationUnitForContents(generated));
// set target
merger.setTargetCompilationUnit(
merger.createCompilationUnitForInputStream(
new FileInputStream(target.getLocation().toFile())));
// merge source and target
merger.merge();
// extract merged contents
InputStream mergedContents = new ByteArrayInputStream(
merger.getTargetCompilationUnit().getContents().getBytes());
// overwrite the target with the merged contents
target.setContents(mergedContents, true, false, monitor);
// ...
// ...
private JMerger getJMerger() {
// build URI for merge document
String uri =
Platform.getPlugin(PLUGIN_ID).getDescriptor().getInstallURL().toString();
uri += "templates/merge.xml";
JMerger jmerger = new JMerger();
JControlModel controlModel = new JControlModel( uri );
jmerger.setControlModel( controlModel );
return jmerger;
}
|
要启动这个过程,可以使用清单 2 这个简单的 merge.xml。其中声明了 <merge>
标签,以及缺省的命名空间声明。这段代码最主要的部分在 merge:pull
元素中。此处,源类中每个方法的代码都会被替换为目标类的对应方法的代码。如果一个方法在目标类不存在,就会被创建。如果一个方法只在源类中存在,而在目标类不存在,就会被保留。
清单 2. 一个非常简单的 merge.xml
<?xml version="1.0" encoding="UTF-8"?>
<merge:options xmlns:merge=
"http://www.eclipse.org/org/eclipse/emf/codegen/jmerge/Options">
<merge:pull
sourceGet="Method/getBody"
targetPut="Method/setBody"/>
</merge:options>
|
区分生成的方法
这种简单的方法有一个非常明显的问题:每次修改源类并重新生成代码时,此前所做的修改就全部丢失了。我们需要增加某种机制来告诉 JMerge 有些方法已经被修改过了,因此这些方法不应该被重写。要实现这种功能,可以使用 <merge:dictionaryPattern>
元素。 merge:dictionaryPattern
允许您使用正则表达式来区分 Java 元素(参见清单 3)。
清单 3. 一个简单的 dictionaryPattern
<merge:dictionaryPattern
name="generatedMember"
select="Member/getComment"
match=
"\s*@\s*(gen)erated\s*\n"/>
<merge:pull
targetMarkup=
"^gen$"
sourceGet="Method/getBody"
targetPut="Method/setBody"/>
|
dictionaryPattern
定义了一个正则表达式,它可以匹配注释中包含 " @generated
" 的成员。 select
属性列出了要对这个成员的哪些部分与在 match
属性中给出的正则表达式进行比较。 dictionaryPattern
是由字符串 gen
定义的,它就是 match
属性值中圆括号中的内容。
merge:pull
元素多了一个附加属性 targetMarkup
。这个属性可以匹配 dictionaryPattern
,它必须在应用合并规则之前对目标代码进行匹配。此处,我们正在检查的是目标代码,而不是源代码,因此用户可以定制这些代码。当用户删除注释中的 " @generated
" 标签时, dictionaryPattern
就不会与目标代码匹配,因此就不会合并这个方法体。请参见清单 4。
清单 4. 定制代码
/**
* test case for getName
*
@generated
*/
public void testSimpleGetName() {
// because of the @generated tag,
// any code in this method will be overridden
}
/**
* test case for getName
*/
public void testSimpleSetName() {
// code in this method will not be regenerated
}
|
您或许会注意到有些元素是不能定制的,任何试图定制这些代码的企图都应该被制止。为了支持这种功能,要定义另外一个 dictionaryPattern
,它负责在源代码(而不是目标代码)中查找其他标记,例如 @unmodifiable
。然后再定义一条 pull
规则,来检查 sourceMarkup
,而不是 targetMarkup
,这样就能防止用户删除标签或阻碍合并操作。请参见清单5。
清单 5. 不可修改代码的 merge.xml
<merge:dictionaryPattern
name="generatedUnmodifiableMembers"
select="Member/getComment"
match=
"\s*@\s*(unmod)ifiable\s*\n"/>
<merge:pull
sourceMarkup="^unmod$"
sourceGet="Member/getBody"
targetPut="Member/setBody"/>
|
细粒度的定制
在使用这种解决方案一段时间之后,您将注意到有些方法在定制的代码中具有一些通用的不可修改的代码(例如跟踪和日志记录代码)。此时我们既不希望禁止生成代码,也不希望全部生成整个方法的代码,而是希望能够让用户定制一部分代码。
要实现这种功能,可以将前面的 pull
目标替用清单 6 来代替。
清单 6. 细粒度的定制代码
<!-- if target is generated, transfer -->
<!-- change to sourceMarkup if the source is the standard -->
<merge:pull
targetMarkup="^gen$"
sourceGet="Method/getBody"
sourceTransfer=
"(\s*//\s*begin-user-code.*?//\s*end-user-code\s*)\n"
targetPut="Method/setBody"/>
|
这样会只重写字符串 " // begin-user-code
" 之前和 " // end user-code
" 之后的内容,因此就可以在定制代码中保留二者之间的内容。在上面的正则表达式中, "?" 表示在目标代码中,除了要替换的内容之外,其他内容全部保留。您可以实现与 JavaDoc 注释类似的功能,这样就可以拷贝注释,同时为用户定制预留了空间。请参见清单 7。
清单 7. 细粒度的 JavaDoc 定制
<!-- copy comments except between the begin-user-doc
and end-user-doc tags -->
<merge:pull
sourceMarkup="^gen$"
sourceGet="Member/getComment"
sourceTransfer="(\s*<!--\s*begin-user-doc.*?end-user-doc\s*-->\s*)\n"
targetMarkup="^gen$"
targetPut="Member/setComment"/>
|
要支持这种注释,首先要修改开始标签和结束标签,使其遵循 HTML 注释语法,这样它们就不会出现在所生成的 JavaDoc 中;然后修改 sourceGet
和 targetPut
属性,以便使用 "Member/ getComment"
和 "Member/ setComment"
。 JMerge 允许您在细粒度级别上存取 Java 代码的不同部分。(更多内容请参见 附录 A)。
下一步
到现在为止,我们已经介绍了如何转换方法体,但是 JMerge 还可以处理域、初始化、异常、返回值、import 语句以及其他 Java 元素。它们也采用类似的基本思想,可能只需稍加修改即可。参考 plugins/org.eclipse.emf.codegen_1.1.0/test/merge.xml
就可以知道如何使用这些功能(我使用的是 Eclipse 2.1,因此如果您使用的是其他版本的 Eclipse,那么 ecore 插件的版本可能会不同)。这个例子非常简单,其中并没有使用 sourceTransfer
标记,但是该例显示了处理异常、标志和其他 Java 元素的方法。
更复杂的例子请参见 EMF 使用 JMerge 的方法: plugins/org.eclipse.emf.codegen.ecore_1.1.0/templates/emf-merge.xml
。从这个例子中可以看出 EMF 只允许部分定制 JavaDoc,但是采用上面介绍的一些技巧,就可以为方法体添加支持(这样可以增强 JET 的功能)。
附录 A:有效的目标选项
在 dictionaryPattern
和 pull
规则中,我们已经使用了 " Member/getComment
" 和 " Member/getBody
" 以及它们的 setter 方法,但是还有很多其他可用的选项。JMerge 支持 org.eclipse.jdt.core.jdom.IDOM*
中定义的任何类的匹配和取代。所有可用的选项如表 1 所示。
表 1. 有效的目标选项
类型
|
方法
|
注释
|
CompilationUnit |
getHeader/setHeader |
getName/setName |
Field |
getInitializer/setInitializer |
不包含 "=" |
getName/setName |
变量名 |
getName/setName |
类名 |
Import |
getName/setName |
要么是一个完全限定的类型名,要么是一个随需应变的包 |
Initializer |
getName/setName |
|
getBody/setBody |
|
Member |
getComment/setComment |
|
getFlags/setFlags |
例如: abstract, final, native 等。 |
Method |
addException |
|
addParameter |
|
getBody/setBody |
|
getName/setName |
|
getParameterNames/setParameterNames |
|
getParameterTypes/setParameterTypes |
|
getReturnType/setReturnType |
|
Package |
getName/setName |
|
Type |
addSuperInterface |
|
getName/setName |
|
getSuperclass/setSuperclass |
|
getSuperInterfaces/setSuperInterfaces |
|
附录 B:merge:pull 属性
表2 给出了 merge:pull
元素的属性。
表 2. merge:pull 属性
属性
|
条件
|
sourceGet |
必需的。该值必须是 附录 A中列出的一个选项,例如 "Member/getBody"。 |
targetPut |
必需的。该值必须是 附录 A中列出的一个选项,例如 "Member/setBody"。 |
sourceMarkup |
可选的。用来在触发 merge:pull 规则之前过滤必须匹配源代码的 dictionaryPatterns 。格式如 "^dictionaryName$",也可以使用 "|" 将多个 dictionaryPatterns 合并在一行中。 |
targetMarkup |
可选的。用来在触发 merge:pull 规则之前过滤必须匹配目标代码的 dictionaryPatterns 。格式如 "^dictionaryName$",也可以使用 "|" 将多个 dictionaryPatterns 合并在一行中。 |
sourceTransfer |
可选的。一个正则表达式,指定要传递给目标代码的源代码的数量。 |
参考资料
下载
|
Name
|
|
|
|
Size
|
|
|
|
Download method
|
|
|
|
os-ecemf3/com.ibm.pdc.example.jet_1.0.0.zip |
|
|
|
|
|
|
|
FTP
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
关于作者
Adrian Powell 从刚开始加入 VisualAge for Java Enterprise Tooling 小组开始使用 IBM 的 Java 开发工具,在这里他花费了两年的时间来手工编写一个代码生成器。从那以后,他一直从事 Eclipse 和 VisualAge for Java 中的工具和插件的开发,他现在几乎为 Eclipse 和 VisualAge for Java 的每一个版本都开发了这种工具和插件。Adrian 目前在 IBM 的 Vancouver Centre 从事于电子商务创新方面的研究,在这里他阅读源代码,为同事提供一些支持。 |