簡介:Eclipse 最有魅力的地方就是它的插件體系結構,在Eclipse中實現的絕大部分功能是由相應的 插件完成的。本文介紹了Eclipse WTP Wizard插件開發,它源於實際應用中開發IBM WebSphere Multichannel Bank Transformation Toolkit(BTT)的創建應用程序向導 (New Application Wizard)。 文章首先概要介紹Wizard;然後詳細分析JFace Wizard,WTP Wizard 設計模式,包括需要使用的接口和 函數。最後以一個實例的形式引導讀者深入理解WTP Wizard擴展方法。
引言
眾所周知 Eclipse 是一個成熟的、精心設計的以及可擴展的體系結構。Eclipse 中除了小型的運行時 內核之外,其余所有功能模塊都是插件。其中 Web Tools Platform (WTP) 就是在 Eclipse 平台上擴展 的,用來開發 J2EE Web 應用程序的插件集合。既然 WTP 是插件,那麼為什麼還需要針對它進行擴展呢 ? WTP 提供了豐富的功能,比如源碼編輯器、圖形編輯、J2EE 項目構建和 J2EE 向導 WEB 服務以及數 據庫操作等,由於業務需求,需要編輯特定語法格式的文檔,如進行語法高亮顯示、校驗、編輯助手 (Code Assist)等,這時就需要對 WTP 進行擴展。總之,當 WTP 提供的通用功能需要定制,或者不符 合業務需求時,需要進行 WTP 擴展開發。
向導(Wizard)是一種交互式的幫助實用程序,向導通過多步操作中的每一步引導用戶,提供有用的 幫助信息,並在這一過程中解釋選項功能,最終引導用戶完成特定任務。向導在 Eclipse 中隨處可見, 選擇 File > New > Project, 對話框所列每一項都是一個獨立的功能向導。
圖 1. Eclipse 向導
WEB Tools Platform(WTP)作為一個基於 Eclipse 開發 J2EE WEB 應用程序的工具集,它提供了創 建 J2EE 工程向導、創建 WEB 服務向導、創建 J2EE Servlet 向導以及導入導出 J2EE 工程向導等。下 圖示例了 WTP 的一些常用向導:
圖 2. Java EE project creation wizards(1)
圖 3. Java EE project creation wizards(2)
圖 4. Java EE components import and export wizards(1)
圖 5. Java EE components import and export wizards(2)
圖 6. Web and EJB artifacts wizards(1)
圖 7. Web and EJB artifacts wizards(2)
Eclipse 向導設計模式
在 Eclipse 中,向導裝載一系列向導頁面(WizardPage),構造出一個復雜的界面,裝載領域類來處 理具體業務邏輯,維護向導頁面之間以及領域類之間的數據傳遞和狀態共享。向導必須具備一個完成操作 (Finish Operation)。其中的 WizardPage 是一些 SWT/JFace Widget 容器,他們之間按照業務規則存 在跳轉關系。
為了便於理解,我們從 JFace Wizard 開始,下圖是 JFace Wizard 原理圖,它的數據存在於 Page 中,相當於 View-Control 方式,沒有統一的數據模型(Model),因此它適合於做簡單頁面跳轉向導。
圖 8. JFace Wizard
數據模型向導(Data Model Wizard)擴展於 JFace Wizard,其內嵌一個數據模型(Data Model), 通過使用 Synchronize Helper 完成頁面控件(Page widget)與 Data Model 數據之間的同步。
Data Model 很像是一個數據(屬性)集合,每一個屬性(Property)是一個鍵值對(key-value), 可以注冊一些屬性監聽器(Listener)來監視屬性值變化。Data Model 中用 Property 來記錄功能構件 的狀態,並提供了訪問和修改 Property 的接口。這些接口中大部分都是提供給後台的 MVC 機制使用, 例如 View 對 Property 的訪問和修改,以及 Operation 在執行動作時對 Property 的訪問等。用戶可 以在這些訪問和修改的接口中定義 Property 訪問和修改規則,例如在訪問 Property 的接口中,根據特 定的條件返回不同的 Property 值。用戶還可以在 Data Model 中定義自己的 Property,並通過 Data Model 提供的接口對自定義的 Property 進行初始化(Init)和驗證(Validate)。
用戶也可以自己訪問和修改 Data Model,Data Model 為用戶提供了統一的方法,getProperty() 和 setProperty()。
Data Model 提供了用戶收集數據的智能途徑;簡化了 Wizard Operation 執行並為實現和擴展 Wizard 提供了便利。下圖為 Data Model 類圖。
圖 9. Data Model
Data Model Wizard 使用 DataModel-View-Operation 模式,該模式在 Eclipse 的插件開發中經常用 到 , 被用來實現一個特定的功能構件。它的基本原理是:DataModel 用來封裝功能構件的一組狀態; View 用來與用戶進行交互,它將用戶需要的狀態顯示出來,並提供用戶的輸入接口;Operation 負責根 據狀態執行特定的動作。Data Model Wizard 完整類圖如下所示:
圖 10. Data Model Wizard
WTP 向導設計模式
WTP Wizard 是 Data Model Wizard 的一個擴展應用,它在 Data Model Wizard 的 DataModel-View -Operation 模式基礎上,添加了一個新的單元 DataModeProvider,形成 DataModel- DataModelProvider-View-Opration 模式。DataModeProvider 的出現削弱了 Data Model 的能力,使得 後者完全變成一個單純 Property 的集合,而不再具有任何的額外功能,例如 Property 初始化,驗證, 可定義的訪問和設置等。Data Model 對用戶完全是一個黑盒。用戶如果想要訪問 Data Model 或者為 Data Model 定義特定的規則,需要通過 DataModelProvider 來實現。DataModelProvider 接管了 DataModel – View-Opration 中 Data Mode 除緩存 Propety 外其余的所有功能(包括初始化,驗證, 可定義的訪問和設置等)。下圖顯示了 DataModel – DataModelProvider – View-Opration 的基本原 理。
圖 11. DataModel-DataModelProvider-View-Operation
在 DataModel-DataModelProvider-View-Operation 中,Data Model 不具有語義信息,它已經退化為 一個單純的鍵值對的集合,而鍵值的語義由 DataModelProvider 附加上去。用戶在 DataModelProvider 中定義 Property 的名稱,DataModelProvider 將會根據這些 Property 的定義在 DataModel 中自動創 建鍵值對。因此無論是訪問還是修改特定的 Property,都需要通過 DataModelProvider。
典型的訪問 Property 方法的代碼片斷如下:
IDataModel dataModel = DataModelFactory.createDataModel(new DataModelProvider ());
dataModel.getProperty(IDataModelProperties.PROPERTY_NAME);
dataModel.setProperty(IDataModelProperties.PROPERTY_NAME, property);
與 DataModel-View-Opration 相比 DataModel-DataModelProvider-View-Opration 具有如下特點:
Data Model 可以專心存儲數據,而不需要考慮與其它單元的交互。因此在形式上更為統一。而事實上 ,在 WTP 中,Data Model 就僅包含了 DataModel 和 DataModelImp 兩種形式。
將 Data Model 進一步解耦,使得狀態的保存和存取功能分開。有利於對存取功能的進一步擴展。
WTP 向導擴展實例
動態 WEB 應用向導 (Dynamic Web Application Project Wizard) 能夠創建出 J2EE 規范的 Web 應用程序,但有時候需要創建訂制過的 (Customized) WEB 應用程序,例如創建 Portlet 應用程序,必 須要創建 Portlet 描述文件。
我們通過創建一個 New Project Wizard,該 Wizard 具備部分 Dynamic WEB Application Project Wizard 特征,同時能夠創建 Portlet 部署描述文件。
New Project Wizard 界面如圖所示:
圖 12. 擴展實例主界面
首頁中新增 Main Class Group Panel,方便用戶輸入新建的 package 名稱和主程序入口文件名稱。
1. 創建 Plug-in Project 並注冊 Wizard 擴展點,插件清單文件 plugin.xml 如下所示:
清單 1. 插件擴展描述文件
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.2"?>
<plugin>
<extension
point="org.eclipse.ui.newWizards">
<category
id="com.sample.tools.app.wizard.category.ApplicationWizard"
name="Sample"/>
<wizard
category="com.sample.tools.app.wizard.category.ApplicationWizard"
class="com.sample.tools.app.wizard.SampleProjectWizard"
icon="icons/newapp_wiz.gif"
id="com.sample.tools.app.wizard.SampleProjectWizard"
name="Sample Project"
project="true">
<description>
Create a WTP Sample Project
</description>
</wizard>
</extension>
</plugin>
org.eclipse.ui.newWizards 擴展點,是“新建向導”擴展點;category 定義的是對這個擴展點的歸 類;wizard 標記是 org.eclipse.ui.newWizards 擴展點自定義的格式,name 屬性定義的是顯示的名稱 ,category 屬性代表此向導的分類。class 屬性表示此擴展點對應的實現類,大部分擴展點都需要編寫 實現代碼,因此需要這個屬性來指定此擴展點使用的是哪個類;接下來詳細介紹如何實現該 Wizard class。
2. 創建 WTP Wizard 所需要的類
根據前面分析,WTP Wizard 所采用的 DataModel-DataModelProvider-View-Operation 設計模式涉及 以下四個實體類:
DataModelWizard
DataModelWizardPage
AbstractDataModelProvider
AbstractDataModelOperation
清單 2. Wizard
public class SampleProjectWizard extends DataModelWizard implements INewWizard
{
public SampleProjectWizard(IDataModel model) {
super(model);
setWindowTitle("My Wizard Titile");
}
public SampleProjectWizard() {
super();
setWindowTitle("My Wizard Titile");
}
protected void doAddPages() {
addPage(new SampleProjectFirstPage(getDataModel(), "first.page"));
}
protected IDataModelProvider getDefaultProvider() {
return new SampleProjectCreationDataModelProvider();
}
public void init(IWorkbench arg0, IStructuredSelection arg1) {
// do nothing
}
}
為了簡化 Sample 理解復雜度,doAddPages 方法中只加入首頁(SampleProjectFirstPage),而忽略 了其他頁面。在構造函數中,使用 SetWindowTitle 方法設置 Wizard 標題。getDefalutProvider 中注 冊 SampleProjectCreationDataModelProvider 用來執行控制操作。
清單 3. Wizard Page
protected void createPresetPanel(Composite top)
{
final Group group = new Group(top, SWT.NONE);
group.setText("Sample Main Class");
group.setLayoutData(gdhfill());
group.setLayout(new GridLayout(2, false));
Label lp = new Label(group, SWT.NULL);
lp.setText("Package");
Text tp = new Text(group, SWT.BORDER);
tp.setLayoutData(gdhfill());
Label lc = new Label(group, SWT.NULL);
lc.setText("Name");
Text tc = new Text(group, SWT.BORDER);
tc.setLayoutData(gdhfill());
synchHelper.synchText(tp,
SampleProjectCreationDataModelProvider.PACKAGE, null);
synchHelper.synchText(tc,
SampleProjectCreationDataModelProvider.MAIN_CLASS_NAME, null);
}
Sample Wizard 的首頁隱蔽了 Dynamic Web Project Wizard 首頁中 Dynamic WEB Module Version 與 Configuration Group。因此 Sample Page 通過繼承 WEB Project Page,並重寫相關 createPresetPanel,createPrimaryFacetComposite 方法來達到目的。
這裡通過 synchHelper 方法的 synchText 方法實現 Text 空間與 provider 想關聯的 Model 同步, 當 Text 值發生改變時,helper 通過自身 Listener 機制通知 Model 來同步 UI 數據。相應地, synchHelper 還提供了與 Label、Combo\ Tree ,CheckBox 等 Widget 同步方法。
清單 4. Provider
public class SampleProjectCreationDataModelProvider extends
WebFacetProjectCreationDataModelProvider
{
public static final String PACKAGE = "MAIN_CLASS_PACKAGE";
public static final String MAIN_CLASS_NAME = "MAIN_CLASS_NAME";
public static final String DEFAULT_PACKAGE = "com.sample.app";
public static final String DEFAULT_MAIN_CLASS_NAME = "NewsListSample";
public Object getDefaultProperty(String propertyName) {
if (PACKAGE.equals(propertyName))
return DEFAULT_PACKAGE;
if (MAIN_CLASS_NAME.equals(propertyName))
return DEFAULT_MAIN_CLASS_NAME;
return super.getDefaultProperty(propertyName);
}
public Set getPropertyNames() {
Set propertyNames = super.getPropertyNames();
propertyNames.add(PACKAGE);
propertyNames.add(MAIN_CLASS_NAME);
return propertyNames;
}
public IStatus validate(String propertyName) {
//do validate
return super.validate(propertyName);
}
public IDataModelOperation getDefaultOperation() {
return new SampleCreationOperation(getDataModel());
}
}
如前圖所示,Page 中新增了兩個字段 Main Class Package 和 Main Class Name,所以 Provider 繼 承 WebFacetProjectCreationDataModelProvider 之後重寫 getPropertyNames 方法,加入上述字段。為 了方便用戶使用 wizard, 重寫 Provider 中 getDefaultProperty 方法,為 Package 和 Class Name 提 供默認值。Data Model Wizard 作為一套完善的 MVC 框架,實現了 Model 檢驗功能,當 UI 輸入值發生 改變時會觸發 validate 方法,並將檢驗結果顯示在 Wizard 的 Title 區域。
清單 5. Operation
public IStatus execute(IProgressMonitor monitor, IAdaptable info)
throws ExecutionException
{
IStatus status = super.execute(monitor, info);
if (OK_STATUS == status) {
try {
// copy default resource file
ResourceUtil.copyFiles(project.getProject(), monitor);
String spackage = getDataModel().getStringProperty(
SampleProjectCreationDataModelProvider.PACKAGE);
String sname = getDataModel().getStringProperty(
SampleProjectCreationDataModelProvider.MAIN_CLASS_NAME);
// You can use package and class name to create the main
// class here
} catch (Exception e) {
e.printStackTrace();
}
return status;
}
上一小節通過 Provider 的 getDefaultOperation 告訴 DataModelWizard,當 Wizard 完成的時候所 執行的具體操作。這裡只需重寫父類的 execute 方法,當父類 execute 執行完畢後,可以執行額外的創 建工作。
總結
上述擴展 WTP Wizard 方式可以歸納為使用面向對象技術擴展已有 Wizard :擴展 Data Model Wizard 的子類,注冊一個新的 Wizard,該新 Wizard 可以使用重寫、覆蓋等技術改變已有 Wizard 特性 。事實上還有另外一種擴展已有 Wizard 的方式 —— 通過擴展點擴展。該擴展方式將會影響到所有被擴 展的實例,且只能增強被擴展 Wizard,不能隱蔽或者減少已有 Wizard 的功能,讀者可以自行查閱相關 文檔。