Eclipse JET (Java Emitter Templates) 是 EMF 核心組件,語法類 JSP 但編譯為 Java 類,支援 Skeleton 復用、JMerger 代碼合併,結合 IJavaProject/IProject API 可實現「從模板到可編譯項目」的全自動化流水線。

org.eclipse.emf.codegen.jet.JETEmitter 核心類generate() 完成:模板編譯 → 實例化執行類 → 調用 generate(argument) 輸出字符串*.javajet、*.xmljet 區分輸出類型argument任意對象傳入模板,常用 List、Map 承載數據上下文。
<%@jet package="demo.translated" imports="java.util.List" class="Demo1"%>
Hello, <%=((List)argument).get(0)%>!
Time: <%=new java.util.Date()%>.
調用:
JETEmitter emitter = new JETEmitter(bundleEntry + "templates/demo1.jet");
String result = emitter.generate(new NullProgressMonitor(), new Object[]{ argumentList });
demo.skeleton
import java.util.Date;
public class CLASS {
public String s = "Mission Completed!";
private Date getCurrentDate() { return new Date(); }
public String generate(Object argument) { return ""; }
}
demo2.jet
<%@jet package="demo.translated" class="Demo2" skeleton="demo.skeleton"%>
Hello, <%=((List)argument).get(0)%>!
Time: <%=getCurrentDate()%>.
<%=s%>
編譯後 Demo2 自動擁有 s、getCurrentDate(),CLASS 佔位符被替換為實際類名。注意:骨架最後一個方法必須是 generate(Object)。
合併規則採用 EMF 內建 emf-merge.xml:
@generated NOT → 整方法保留<!-- begin-user-doc --> ... <!-- end-user-doc --> → 區塊保留流程:
JMerger merger = new JMerger();
merger.setControlModel(new JControlModel(mergeRuleUri));
merger.setSourceCompilationUnit(merger.createCompilationUnitForContents(generatedCode));
merger.setTargetCompilationUnit(merger.createCompilationUnitForInputStream(new FileInputStream(existingFile)));
merger.merge();
return merger.getTargetCompilationUnit().getContents().getBytes();
實現「重複生成不覆蓋手工修改」的工程化需求。
| 優勢 | 劣勢 |
|---|---|
| 語法類 JSP,學習曲線平緩 | 無專用模板編輯器(無語法高亮、補全) |
| 編譯為 Java 類,執行效能高 | 強依賴 Eclipse 插件機制,獨立運行麻煩 |
| Skeleton 復用、JMerger 合併解決工程化痛點 | 運行前難預覽生成類結構 |
| 輸出任意文本格式 |
JET*,本文主角JET2*,功能重疊但定位不同// 1. IProject 創建與打開
IProject project = workspace.getRoot().getProject("demo");
project.create(null);
project.open(null);
IProjectDescription desc = workspace.newProjectDescription("demo");
desc.setNatureIds(new String[]{ JavaCore.NATURE_ID });
project.setDescription(desc, null);
// 2. 轉為 IJavaProject,調整 Classpath
IJavaProject javaProject = JavaCore.create(project);
List cp = new ArrayList<>(Arrays.asList(javaProject.getRawClasspath()));
// 移除默認錯誤 source root
cp.removeIf(e -> e.getEntryKind()==IClasspathEntry.CPE_SOURCE && e.getPath().toString().equals("/demo"));
// 3. 添加正確 source root、JRE 容器、輸出目錄
cp.add(JavaCore.newSourceEntry(new Path("/demo/src")));
cp.add(JavaCore.newContainerEntry(new Path("org.eclipse.jdt.launching.JRE_CONTAINER")));
javaProject.setRawClasspath(cp.toArray(new IClasspathEntry[0]), null);
javaProject.setOutputLocation(new Path("/demo/bin"), null);
// 4. 創建物理目錄
project.getFolder("src").create(true, true, null);
project.getFolder("bin").create(true, true, null);
javaProject.open(null);

IProjectDescription.setReferencedProjects()org.eclipse.pde.ui.NewProjectCreationOperation
// 檢測 ClearCase
Object obj = resource.getSessionProperty(TeamPlugin.PROVIDER_PROP_KEY);
RepositoryProvider provider = (RepositoryProvider) obj;
if (provider.getID().contains("clearcase")) {
FileModificationValidator validator = provider.getFileModificationValidator2();
IStatus status = validator.validateEdit(new IFile[]{ file }, null);
if (status.isOK()) { /* 已自動 checkout */ }
}
統一 IResource 接口屏蔽 CVS/SVN/ClearCase 差異。
org.eclipse.core.resources.builders 擴展點IncrementalProjectBuilder,覆寫 build(kind, args, monitor)
Bundle bundle = InternalPlatform.getDefault().getBundle(pluginId);
ILog log = Platform.getLog(bundle);
log.log(new Status(Status.ERROR, pluginId + "-" + location, message, throwable));
避免 Log4j 等第三方日志,融入 IDE 原生診斷視圖。
Eclipse 代碼生成體系三層次:
掌握這三層,即可構建企業級代碼生成工廠,將 Eclipse 從「編輯器」升級為「生產力平台」。