EMF 是 Eclipse 平台的主要部分,并且是一些相关技术和框架的基础,比如 Eclipse Visual
Editor、SDO、XSD 和 UML — 其中的许多技术都被集成到 Rational? Application Developer 和
WebSphere? Business Modeler 等 IBM? 平台中。现在,EMF 已经吸收了许多 Java
技术特性,比如枚举类型、注释和泛型。如果您还不熟悉 EMF,请参阅 参考资料 中的文章获得入门知识。
在大多数文档和教程中,EMF 都被用于建模数据 和接口(比如 EMF 发行文档中的 Library 和 Books),而不用于建模行为。
当然,还有一些针对数据对象生成的默认方法实现,但这些实现涉及到模型元素之间的关系。而且,将 EMF 用作 “元模型” 的经过归档的示例非常少 —
除了 Eclipse Foundation 文章 “Modeling Rule-Based Systems with EMF”(参见 参考资料)— 但是这个示例并没有展示如何扩展 Ecore 元模型。
最后,使用和扩展 EMF JET 模板的过程也没有被很好地进行归档。此外,JET Editor 项目最近已经迁移到另一个
Eclipse 项目(M2T)上。本文旨在澄清这些问题,并使您能够在 EMF 上下文中使用动态模板实现更多的功能。因此,本文假设您对 EMF
有基本的了解。
为什么要扩展 Ecore 元模型?
Ecore 元模型是一个强大的工具,可用于设计模型驱动架构(Model-Driven Architecture,MDA),后者可以作为软件开发的起点。通常情况下,我们定义应用程序范围内的对象(EClass
类型)、对象属性以及它们之间的关系。我们还使用 EOperation
模型元素定义属于这些对象的特定操作。默认情况下,EMF 将会为这些操作生成骨架 或方法签名,但是我们必须返回并实现这些操作,常常要反复地重新编写类似的逻辑。
但是,如果我们想在模型中指定某种任意的实现行为该怎么办呢?一种方法是添加基于文本的注释(EAnnotation
类型),以建模对象并在代码生成期间解释模板中的这些注释。关于这种方法的出色示例,可以查阅 Eclipse Foundation 文章 “Implementing Model Integrity in EMF with MDT OCL”(参见 参考资料)。但是,正如这篇文章中所描述的,我们的目标不是验证模型元素,而是对实现本身进行建模,以使任何具体的模型能够重用这些元模型元素。为此,我们需要扩展 Ecore 元模型。
扩展了的元模型
本文附带了一个高度简化的用来扩展 Ecore 的编程式模型。它不是一个完整或连贯的元模型或框架;严格来讲,它是一个元素的原型集合,用于演示使用 EMF 对代码实现进行元建模的能力。图 1 显示了我们的扩展元模型示例 EcoreX 的快照,下面是每个元素的简短描述。
图 1. EcoreX 模型
EcoreX 元素
-
EPackageX
扩展 EPackage
- 这是 Ecore 元素
EPackage
的一个简单 “标记” 扩展,没有任何附加属性。这个元素是必需的,因为在默认情况下,元素 EPackage
EMF 编辑器插件不允许将
EClass
的子类作为子元素添加(参阅下面的 EClassX
)。通过提供一个可扩展
EPackage
的模型元素,代码将会自动生成,从而允许将一个 EClassX
子元素添加到 EPackageX
中。
-
EClassX
扩展 EClass
- 同样地,这是 Ecore 元素
EClass
的一个简单标记扩展,没有任何附加属性。与上面的元素类似,此元素也是必需的,因为在默认情况下,EClass
的编辑器插件不允许添加 EOperation
的子类 — 这正是我们要在本文中实现的目标。
-
EOperationImpl
扩展 EOperation
- 这是用于向 Ecore 模型添加具体的元功能的基本实体和入口点。此元素被赋予 Ecore 的基础
EOperation
元素中没有的属性。下面描述的所有其他元素都属于
EOperationImpl
并用于构成编程式实现。例如,EOperationImpl
包含变量和语句,可以返回一个引用或值。
-
LocalVariable
扩展 ETypedElement
-
LocalVariable
是一个本地变量。变量包含一个名称和一个 Java 类型(比如 String
、Integer
、Object
),而且由于这些属性已经存在于其超级超类(super-superclass)EParameter
中,所以 LocalVariable
不需要额外属性。
-
Statement
扩展 EClass
- 在我们的简化逻辑模型中,一个
EOperationImpl
包含许多将会按给定顺序计算的
statement
。Statement
是一个抽象超类。
-
LiteralAssignment
扩展 Statement
LiteralAssignment
引用一个变量,并且有一个 String
属性,允许用户输入一个要被解析的值并将其分配给一个变量(例如,“hello”、“4.5” 可以分别分配给 String
或 float
)。
-
Access
扩展 Statement
Access
表示引用 Java 字段或操作的动作。
-
FieldReferenceAssignment
扩展 Access
- 访问一个字段,以分配一个值(例如,
var1 =
var2.name
)。
-
Invoke
扩展 Access
- 调用一个操作(Java 方法)。
Invoke
的结果可以分配给一个变量(例如,myVar = obj.toString()
)。
图 2 展示了 EcoreX 元模型的一种更加类似 UML 的表示。
图 2. Ecorex 模型图
入门
本文包括六个高级的步骤:
- 扩展 Ecore 元模型,添加新语义
- 为被扩展的元模型创建一个
genmodel
。
- 为此元模型生成一个 EMF 编辑器,并作为插件安装。
- 使用这个新编辑器,构建一个具体的模型来描述编程行为。
- 为这个具体的模型创建并配置一个
genmodel
。
- 基于这个具体的模型生成具体的 Java 代码。
可以创建或导入上面描述的元模型。两种情况都需要从一个现有 EMF 项目或创建一个新项目入手(New > Other > Eclipse Modeling Framework > Empty EMF Project)。我们的项目名为 EMFX,并且它应包含一个名为 model 的文件夹。可以将这个 EcoreX.ecore 模型(参见 参考资料)复制到 model 目录并跳至 构建和启动 Editor Metamodel 插件 小节,也可以执行以下步骤,从头创建一个元模型。
扩展 Ecore 元模型 — 从头开始
右键单击项目,从上下文菜单中选择 New > Other >
Example EMF Model Creation Wizards > Ecore Model。(对于 Eclipse V3.5+ [Galileo,
Helios],则应选择 New > Other > Eclipse Modeling Framework > Ecore
Model。)选择 model
文件夹和名称 EcoreX.ecore
。
默认情况下,我们将模型包称为 ecorex
。在模型窗口中右键单击并选择 Load Resource > Browse Registered Packages。选择具有名称空间 http://www./emf/2002/Ecore
的 Ecore Model。
导入 Ecore 元模型之后,就可以对其进行扩展了。要重新创建 ecorex.ecore
模型,首先在包元素 ecorex
上右键单击并选择 New Child EClass。将此元素称为 EPackageX
(参阅上面的模型元素描述)。然后需要将基元素 EPackage
作为这个新元素的 ESuper Type
添加。
通过将 EClass
指定为 ESuperType
,使用相同的过程创建新元素 EClassX
。根据需要对 Ecore 对象划分子类,在 EcoreX 模型中继续定义其他 EClass
。使用图 1 和 EcoreX.ecore 文件了解要为哪个 EClass
创建什么属性。
构建并启动
the Editor Metamodel 插件
I在构建步骤中,我们将创建元模型 genmodel
并构建模型和编辑器项目。右键单击 EcoreX 项目并选择 New > Other >
Eclipse Modeling Framework > EMF Model。(对于 EMF V2.5+ [Galileo, Helios],则应选择 New > Other > Eclipse Modeling Framework > EMF
Generator Model。)可以提供一个名称或接受默认的名称 EcoreX.genmodel
。EcoreX 模型应该被预选择为 genmodel
的基模型。单击 Load 验证
EcoreX.ecore
元模型。
图 3. 新 EMF 模型
当要求指定要生成和从其他生成器模型引用的包时,选择 Root packages 下面的 EcoreX 包和 Referenced generator models 下面的 Ecore。
现在,向导将为元模型创建一个 genmodel
。突出显示 genmodel
中的顶级元素之后,从上下文菜单中选择 Generate All,这样可以自动生成关联的代码。根据在 genmodel
中配置的行为,这将生成 4 个 Eclipse 项目。本文不会关注 .test 项目,所以您可能不希望生成这个插件。
现在我们继续启动步骤。在大多数 Eclipse 教程中,都会要求您在单独的 Eclipse
过程中启动所开发的插件。在本节中,我们将采用一种不同的方法:我们将在当前 Eclipse
和工作区中激活插件。这样更容易将预构建的元模型与下一节中具体的模型开发集成。为此:
- 双击 EMFX plugin.xml 打开插件配置编辑器。
- 单击 Exporting 选项卡下的 Export Wizard。
- 选择基本的建模插件和两个编辑器插件。
- 在 Destination 选项卡下,选择 Eclipse 安装目录,或托管存储库(如果可用)。
图 4. 导出
单击 Finish 时,会自动构建生成的插件 JAR 文件,并自动将其复制到插件目录。此时,您需要重新启动 Eclipse,激活新插件。现在我们已经准备好启动编辑器插件了,创建一个新项目来保存我们的具体模型(我们的模型命名为 Test2
)。
在这个新项目中,导航到 New > Other Example EMF Model Creation
Wizards > Ecorex Model 并提供一个模型名称。注意:在 EMF 的最新版本 (V2.5+) 中,
具体模型的文件扩展名必须被设为 .ecore,而不是 .ecorex;否则,这个具体的 genmodel
将不能在后续步骤中被成功创建。选择 EPackageX
元素。您现在有了一个空的具体模型。后续小节将讨论如何构建这些编程模型元素;完成后的文件 My.ecore 可以在 参考资料 部分找到。
建模具体的测试模型
在本节中,我们将对一个具体的 Java 类(EClassX
的实例)进行建模,这个类包含两个具体的方法,我们将对这两个方法的实现进行建模。第一个示例方法接受 String
参数消息,并输出消息和一个时间戳 — 这有利于调试消息。以下是期望结果的表示。
清单 1. printTimestampMessage
void printTimestampMessage(String message) {
System.out.print(message);
System.out.print("; Timestamp= ");
System.out.println(System.currentTimeMillis());
}
|
第二个示例接受 3 个基于日期的参数,并返回一个数字值,表示该日期对应的是星期几。
清单 2. getDayOfWeek
int getDayOfWeek(int year, int month, int date) {
int result;
Calendar calendar = Calendar.getInstance();
calendar.set(year, month, date);
result = calendar.get(Calendar.DAY_OF_WEEK);
return result;
}
|
第一步是填入在上一节最后一步中创建的新 EPackageX
元素下的 3 个必需属性。如果在建模窗口下看不到 Properties 选项卡,可以从上下文菜单中选择 Show Properties View。在这个示例中,我们的包名为 mypackage。
图 5. EPackageX 属性
接下来,向 mypackage 添加一个新 EClassX
。可以在 mypackage 突出显示时使用上下文菜单完成此任务。填入 name 属性,为类提供一个名称(比如 MyClass),向新类添加两个 EOperationImpl
元素,并为它们指定方法名 printTimeStampMessage
和 getDayofWeek
。然后,向每一个操作添加 Ecore 参数。
图 6. EOperationImpl
getDayOfWeek()
图 7. getDayOfWeek()
属性
上面的操作 printTimestampMessage()
接受一个 EString
类型的参数,而 getDayOfWeek()
接受 3 个 EInt
类型的参数。此外,操作 getDayOfWeek
返回一个 EInt
,这可以在 property 属性 EType
下进行配置(参见图 7)。
剖析 EOperationImpl
到现在为止,我们仅使用了继承的 Ecore 元素和属性。现在是时候使用我们扩展的元模型元素来构建 Java 实现了。
-
LocalVariable
- 查看一下图 8,
printTimestampMessage()
将需要两个 LocalVariable
元素 — 一个为
EString
类型,另一个为 ELong
类型。
图 8. printTimestampMessage()
图 9. LiteralAssignment
在图 9 中,Value
属性的字符串被内联到 LiteralAssignment
。您可以设想一个不同的元模型,其中的文字值(常量)被建模为单独的元素。
接下来,我们插入一个 LiteralAssignment
类型的元素,它允许选择一个 LocalVariable
并为其分配值。在本例中,我们选择 String
变量并提供上面的原型方法中的文本值(记住在文本两边加上引号)。
-
DataType
- 再次查看上图,注意,有一个名为
SystemType
的 Ecore DataType
,它是 java.lang.System 的一个包装器。必须将其添加到我们的 mypackage 包,因为它将会被随后的 Invoke
元素引用。
-
Statement
- 添加到这个操作的第一个
Statement
是 SystemType
中的静态方法 currentTimeMillis()
的一个 Invoke
,已经在上面定义了。
图 10. 调用 currentTimeMillis()
属性
根据我们的元模型(我们将在下一节提供代码模板),上面的 Invoke
将转换为 Java 语句:timestamp = java.lang.System.currentTimeMillis();
。
下一个 Invoke
与之前的那个稍有不同。首先,没有 Assignment
。其次,我们将把 message
参数的引用作为 Args
属性的一个参数。
图 11. 调用 out.print
属性
另外请注意,Access Name
属性的值为
out.print
— 在这里我们实际上从 Java System
间接引用了字段 out
,然后调用方法 print
。我们使用了这种快捷方式,而没有结合使用一个 FieldReferenceAssignment
和一个 PrintStream
类型的 LocalVariable
。
操作中的第 3 个(最后一个)Invoke
是一个使用 LocalVariable
timestamp
作为单个参数的 println()
。这就完成了具体操作 printTimestampMessage()
的建模。
让我们看看第二个 EOperationImpl
getDayOfWeek()
的完整模型。
图 12. getDayOfWeek()
-
DataTypes
- 在模型的底部,我们创建了一个额外的
DataType
,名为 CalendarType
,这是该操作所必需的。
-
LocalVariables
- 在操作模型的 3 个
LocalVariable
中,我们主要关注称为 result
的 LocalVariable
,因为它将会保存执行完操作的最后一条语句之后返回的值。在 EOperationImpl
属性中有一个名为 Return Ref
的属性,而且在我们的实现中,我们使用下拉菜单选择 LocalVariable
结果。
-
Statement
- 正如图 12 所示,3 个
LocalVariable
之后是 3 个 Statement
。第一个是 Invoke
,它使用 CalendarType
元素上的 getInstance()
,为 calendar
变量分配一个值,与图 10 中的操作类似。
接下来是对 calendar
变量执行的 set()
方法的 Invoke
,现在它传递 3 个与 EOperationImpl
参数(year
、month
和 date
)相对应的 Arg
。
图 13. 带有参数的 set()
图 14. FieldReferenceAssignment
根据我们的元模型,这个元素将会生成与
DAY = Calendar.DAY_OF_WEEK;
类似的 Java 代码。
在图 15 中,DAY
变量用于这个 EOperationImpl
的最后一个 Invoke
:一个 get()
,其返回值被分配给变量 result
(我们的实现的 Return Ref
)
图 15. Return Ref
实现动态模板
我们现在设计了一个扩展的元模型,并用其描述了一个具体的模型 My.ecore(请参见上述的 EMF V2.5+
文件名称说明)。现在终于可以用 JET 最终实现一些代码实现了。要查看 JET 模板的语法突出显示功能,您需要安装 JET Editor
Plugin(参见 参考资料 和 “JET Editor 局限性”)。
默认情况下,在为模型生成代码时,EMF 不会使用动态模板。它使用预构建的 Java 类。要开始定制 JET 模板,我们需要从插件 JAR 文件 org.eclipse.emf.codegen.ecore_2.3.0.XYZ.jar 复制一些文件,其中 XYZ 是 Eclipse 插件文件夹中您的 EMF 版本的时间戳。本文使用 org.eclipse.emf.codegen.ecore_2.3.0.v200706262000.jar。要复制这些文件,请使用任意一种解压缩工具打开 JAR 文件,并执行以下操作:
- 从这个 JAR 文件将模板目录提取到您的具体模型的 Java 项目中。
- 在模板/模型中创建一个目录,名为 Class。
- 在 Class 文件夹中创建一个新的空文件,名为
implementedGenOperation.TODO.override.javajetinc 或从 参考资料 中复制。
由名称可以看出,第 3 步中的新文件是一个 JET 模板,我们将在其中加入模型对象 EOperationImpl
的代码生成逻辑。默认情况下,这个文件并不存在,因为 EMF 只为每个 EOperation
提供一个空的方法签名。一旦激活了动态模板功能,我们的新文件将被作为 Java 方法体自动包括,正如 EOperationImpl
所定义的。
以下是 implementedGenOperation.TODO.override.javajetinc
的完整代码。
清单 3. implementedGenOperation
// created by implementedGenOperation.TODO.override.javajetinc
<%
if ( ! (genOperation.getEcoreOperation() instanceof EOperationImpl) ) { %>
// TODO: implement this method
// Ensure that you remove @generated or mark it @generated NOT
throw
new UnsupportedOperationException();
<% } else { %>
// ** EOperationX implementation **
<% EOperationImpl opx = (EOperationImpl)genOperation.getEcoreOperation();
Statement stm = null;
Iterator iterator = null;
EList<LocalVariable> pList = opx.getLocalVariables();
LocalVariable lvar = null;
String iname = null;
StringBuffer paramsString = null;
StringBuffer varString = null;
for (int i = 0;i < pList.size(); i++) {
lvar = pList.get(i);
iname = lvar.getEType().getInstanceClassName();%>
<%=iname%>
<%=lvar.getName()%><%
if (iname.startsWith("java")) { %> = null
<% } %>;
<% }
iterator = opx.getStatements().iterator();
while (iterator.hasNext()) {
paramsString = new StringBuffer();
varString = new StringBuffer();
iname = null;
stm = (Statement)iterator.next();
if (stm instanceof LiteralAssignment) {%>
<%= stm.getAssignment().getName()%> = <%= ((LiteralAssignment)stm).getValue()%>;
<%} else
//
if (stm instanceof FieldReferenceAssignment) {
Access ax = (Access)stm;
if (stm.getAssignment() != null) {
varString.append(stm.getAssignment().getName());
varString.append(" = ");
}
if ( ax.getStaticType() != null) {
// STATIC
iname = ax.getStaticType().getInstanceClassName();
} else {
// NON STATIC
iname = ax.getTarget().getName();
} %>
<%=varString.toString()%><%=iname%>.<%=ax.getAccessName()%>;
<% } else
if (stm instanceof Invoke) {
// INVOKE
Invoke iv = ((Invoke)stm);
if (stm.getAssignment() != null) {
varString.append(stm.getAssignment().getName());
varString.append(" = ");
}
for (int p = 0; p < iv.getArgs().size(); p++) { paramsString.append(iv.getArgs().get(p).getName());
if ( p + 1 < iv.getArgs().size() ) {
paramsString.append(" , ");
}
}
if (iv.getStaticType() != null) {
// STATIC
iname = iv.getStaticType().getInstanceClassName();
} else {
// NON STATIC
iname = iv.getTarget().getName();
} %>
<%=varString.toString()%><%=iname%>.<%=iv.getAccessName() %>(<%=paramsString.toString()%>);
<% }
} // STATEMENTS
if (opx.getReturnRef() != null) { %>
return
<%=opx.getReturnRef().getName()%>;
<% }
} // EOPERATIONIMPL %>
|
对 JET 的详细讨论超出了本文的范围。但是,因为 JET 模板对我们的操作过程至关重要,我们将在伪代码方面回顾一下模板的内容。请记住,在处理模板之前,第一个变量 genOperation
已经被 Ecore/JET 预初始化。
清单 4. genOperation
被 Ecore/JET 预初始化
Is this GenOperation is an EOperationImpl?
If false, emit default UnsupportedOperationException
STOP;
Else, cast it to EOperationImpl;
continue;
Find and declare all elements of type LocalVariable, initializing Java Objects to null;
Iterate through all Statements;
Emit Java code according to the subtype;
Does the implementation return something?
If yes, emit the return statement;
|
在构建具体模型之前,需要执行一些操作。首先,在 templates/model/Class.javajet 顶部,我们必须将以下内容添加到导入列表(标记为粗体的前两行):
<%@
jet
package="org.eclipse.emf.codegen.ecore.templates.model"
imports="ecorex.* org.eclipse.emf.common.util.* java.util.* org.eclipse.emf.codegen.ecore.genmodel.*"…
|
当然,EcoreX 包是经过扩展的元模型。接下来,我们需要为我们的具体模型(My.ecore
,类型为 '.ecorex')创建和配置一个 EMF(GenModel
)。为此,在模型上右键单击并选择 New > Other > Eclipse Modeling Framework > EMF Model(对于 EMF V2.5+ [Galileo, Helios],应选择 New > Other >
Eclipse Modeling Framework > EMF Generator Model。)创建完成之后,需要在属性组 Templates &
Merge 下配置 3 个属性,在 Model 下配置第四个属性。
图 16. GenModel
— Templates & Merge
- 将 Dynamic Templates 设置为 true。
- 指定 Template Directory。
- 将 EMFX(扩展的元模型插件 ID)添加到 Template Plug-in Variables。
- 最近版本:在 Model 组属性下,将 Suppress Interfaces 设置为 true。
现在可以进行构建了,右键单击 GenModel
并选择 Generate Model Code。
如果一切顺利,在具体的 Test 项目(我们的项目称为 Test2)的源文件夹(src)中,您应该可以看到生成的 Java
源代码包和类,其中一个名为 mypackage.impl.MyClassImpl.java。打开该文件,您应该会看到两个生成的方法。
清单 5. MyClassImpl.java
public
void printTimestampMessage(String message) {
// created by implementedGenOperation.TODO.override.javajetinc
// ** EOperationX implementation **
java.lang.String timestampStr = null;
long timestamp;
timestampStr = "; Timestamp = ";
timestamp = java.lang.System.currentTimeMillis();
java.lang.System.out.print(message);
java.lang.System.out.print(timestampStr);
java.lang.System.out.println(timestamp);
}
public
int getDayOfWeek(int year, int month, int date) {
// created by implementedGenOperation.TODO.override.javajetinc
// ** EOperationX implementation **
int result;
int DAY;
java.util.Calendar calendar = null;
calendar = java.util.Calendar.getInstance();
calendar.set(year , month , date);
DAY = java.util.Calendar.DAY_OF_WEEK;
result = calendar.get(DAY);
return result;
}
|
可以添加一个 main 方法测试这个类。
警告和故障诊断
Ecore 文件命名 (EMF V2.5+)
在 EMF V2.5 之前,正如上面的几个屏幕快照所示,从一个扩展了的 Ecore 模型生成的具体模型应该保留
'.ecorex' 的扩展名(如创建时的向导所建议的那样)。这有助于区别扩展了的模型与 ‘初级的’ Ecore 模型。然而,在 EMF
的最新版本中,genmodel
向导(如在图 16 之前所解释的)不接受除 .ecore 之外的其他文件扩展名。
JET Editor 局限性
要获得 JET 模板的语法突出显示功能,您需要安装 Eclipse JET Editor(JET Editor Plugin 最近已经从 EMF 迁移到 M2T)。
但是,在撰写本文时,JET Editor 的最新版本不能正确处理 Java 内容帮助或嵌套 JET 包含文件(比如
.javajetinc 文件)的动态编译。此外,为了确保构建成功,只能在父文件(比如上面的
Class.javajet)中指定导入操作,而不能在包含的文件中指定。
实际上,使用一些额外配置(即,使用项目的上下文菜单),您可以将 EMF 动态模板项目(本文示例中的 Test2)转换为 JET 项目。在实践中,上面提及的局限性以及 EMF 和 M2T/JET 之间缺少集成,使得这种方法不太可行。
因此,很难捕获和改正包含的模板文件中的错误。由于在生成最终代码之前,JET 模板首先会被编译为一种中间 Java
文件(默认情况下位于一个隐藏的 Java 项目 JETEmitter 中),所以通过从 Eclipse 的 Package Explorer
视图中删除过滤器,您能够看到这些编译错误。如果这只是模板文件中的格式错误,在构建期间将会出现一个 Eclipse 弹出窗口。或许在未来的 JET
版本中,我们会看到更多的改进功能。
未进行模型验证
本文中的示例未使用 EMF Validation Framework 或 OCL 功能。所以,模型中的不一致性将会导致构建失败,例如,一个 EOperationImpl
可以声明某种返回类型,但是
Return Ref
属性可以引用不同的类型或者为空。在构建模型期间,将不会发现这些错误,而且生成的代码将无法编译。可以对元模型进行改进,使用 OCL 增强完整性和约束(参见 参考资料)。
结束语
我们看到了如何扩展 Ecore 元模型,将合成的 Java 方法中的编程式行为概念化。通过导入 Ecore 本身,我们扩展了一些 Ecore 模型元素
— 尤其是 EOperation
。然后构建了元模型,并使用编辑器设计了一个具体的测试模型,包括以 EOperationImpl
形式建模的两个 Java 方法。我们配置并构建了 JET 模板,用于为 EOperationImpl
生成代码。