Eclipse 是出色的集成開發環境(IDE),因為它促進了可擴展的體系結構。Eclipse 認識到現代開發需要許多技能。它不再滿足於僅僅使用 Java 或Html 或C++。現代的 IDE 需要支持所有這些語言以及其它一些語言。
為滿足這一需要,Eclipse 使用插件。插件擴展了 IDE,以支持新的語言、編譯器以及其它開發工具。在 Eclipse 中沒有缺省語言。Eclipse 通過插件支持每種語言 - 包括 Java 語言。因此,倘若您編寫適當的插件,那麼 Eclipse 所能支持的語言數目是沒有限制的。正如我通過此項目發現的,編寫 Eclipse 插件並不是那麼困難。要從 Eclipse 啟動 XM,我只需編寫兩個類。對於開發人員,這是個特棒的消息。根據我的經驗,無論供應商在工具箱中包括了什麼 -不管它是自產的實用程序、第三方工具或是軟件管理 - 我始終需要其它工具。
任何 IDE 都可以啟動外部工具,但是這往往意味著我必須在 IDE 和外部工具之間來回切換。我確信使用 Eclipse 可以將工具集成到 IDE 中並且有條理地組織每樣東西。要學習如何編寫這樣的插件,請繼續閱讀。
向 Eclipse 開發團隊脫帽致敬 - 我開始非常喜歡你們的工作了。
XM 背景知識
距離我上次在本專欄討論 XM 已有很長時間了。從我所收到的郵件來看,一些讀者對它究竟是什麼產生了誤解。XM 是用於發布使用 XML 和 XSLT 的靜態網站的低成本解決方案。它和 WebSphere Portal 或 Cocoon 並非競爭關系。我發起 XM 是因為我發現用於 XML 發布的簡單解決方案很缺乏,對此深感不便。
大多數發布項目最後都發展到同一個終點:它們需要應用程序服務器,但並不是每個項目一開始就有數量巨大的文檔。XM 旨在在早期階段幫助您。例如,撰寫這篇專欄文章時,我剛訪問一個 XM 新用戶後回來。這個組織大約有 400 個極少更新的簡單 XML 文檔。它們的樣式表長度少於 100 行。那麼,僅僅為了發布這 400 個文檔而獲取並安裝成熟的應用程序服務器有意義嗎?我認為沒什麼意義,而他們也持相同觀點。目前,每個晚上批處理運行 XM 更有意義。顯然,當他們更熟悉 XML 時,他們會想要功能更強大的解決方案,但是現在 XM 滿足了他們的需求,它也滿足了許多小到中型網站的需求。
為 XM 編寫插件
編寫 Eclipse 插件時,第一步是決定擴展哪個元素。插件可以擴展導航器或概述文件、添加菜單項、替換編輯器或繪制新的控制台和狀態窗口。
如圖 1 所示,我決定從將新的 Run XM項添加到導航器的彈出菜單開始。我還添加了新的 XM Console窗口來顯示狀態消息。通過這兩個添加,有可能不用離開 Eclipse 窗口就可使用 XM。
圖 1. 從 Eclipse 啟動 XM
查看原圖(大圖)
這兒有個難題是確定何時顯示 Run XM 菜單。在 XM 開發的較早時期,我選擇了消除配置文件。遺憾的是,沒剩下什麼可以在上面單擊鼠標右鍵了。因此,第一步是定義 .xmp (XM 項目)文件。配置插件以使 Run XM 和 .xmp 文件相關聯。
簡而言之, .xmp 文件是帶有以下四個特性的 Java 特性文件,這四個特性對應於命令行參數(請參閱 清單 1,以獲取示例):
source :指向源目錄(缺省值: src )
publish :指向發布或目標目錄(缺省值: publish )
rules :指向樣式表目錄(缺省值: rules )
build :為 true 或 false 值 - true 強制執行重新構建,其中,XM 處理每個文件(缺省值: false )
清單 1. 樣本 .xmp 文件# this publishes the ananas.org site
source=src
publish=ananas-Html
rules=style-sheet
build=false
XM 控制台
XM 控制台是在 MessengerVIEw 中實現的。它是一個 部件(part)- 工作台中“窗口”的 Eclipse 術語。因為 上個月的專欄文章包括了用作部件的插件,因此代碼對您來說應該很熟悉了,所以我將只是重新生成摘錄。如果您沒有閱讀上一篇專欄文章(該文件簡介了 Eclipse 插件體系結構),那麼我建議您現在就回頭閱讀它。
本文和上個月的專欄文章最顯著的差異是使用 SWT 表代替了標簽。該表有兩列,其中第一列包含了消息狀態(出錯、警告等),第二列包含了消息本身。對於一列消息來說,表很方便,因為它允許用戶用滾動欄上下翻頁。
在 SWT 中,表是 org.eclipse.swt.widgets.Table 的實例。使用 TableColumn 類定義每個列的寬度及其標題,如清單 2 所示。
清單 2. 創建 SWT 表table = new Table(parent,SWT.SINGLE);
TableColumn column = new TableColumn(
table
,SWT.NONE);
column.setText("Status");
column.setWidth(50);
column = new TableColumn(
table
,SWT.NONE);
column.setText("Message");
column.setWidth(500);
table.setHeaderVisible(true);
請注意,這裡沒有 addColumn() 方法。相反,您把表實例傳入 TableColumn 構造器中。 TableItem 代表了表行。要對它進行初始化,將字符串數組(每列一個字符串)傳遞給它的 setText() 方法,如清單 3 所示。
清單 3. 添加新行TableItem item = new TableItem(table,SWT.NONE);
String[] text = new String[2];
text[0] = type;
text[1] = t.getLocalizedMessage();
item.setText(text);
MessengerView 還實現了 Messenger 接口。老讀者會記得 Messenger 是 XM 用來打印消息的接口。 MessengerVIEw 只是將消息重定向到窗口。
運行 XM 菜單
彈出菜單在 XMRunner 類中實現。當用戶選擇菜單時,Eclipse 使用 IObjectActionDelegate 接口通知插件。 XMRunner 可在清單 4 中獲得。
清單 4. 應答用戶點擊package org.ananas.xm.eclipse.runner;
import Java.io.*;
import Java.util.*;
import org.ananas.xm.*;
import org.eclipse.ui.*;
import org.eclipse.ui.actions.*;
import org.eclipse.jface.action.*;
import org.eclipse.core.runtime.*;
import org.eclipse.jface.dialogs.*;
import org.eclipse.jface.vIEwers.*;
import org.eclipse.core.resources.*;
public class XMRunner
extends ActionDelegate
implements IObjectActionDelegate
{
public static final String PLUGIN_ID =
"org.ananas.xm.eclipse.runner";
public static final String CONSOLE_ID =
"org.ananas.xm.eclipse.runner.vIEw.Console";
private IFile selectedFile;
private IWorkbenchPart part;
public void run(IAction action)
{
try
{
if(part != null)
{
saveDirtyEditors();
MessengerVIEw messenger = showConsole();
if(messenger != null)
{
messenger.clean();
runXM(messenger);
}
else
throw new NullPointerException("Failed.");
}
}
catch(Exception x)
{
ErrorDialog.openError(part.getSite().getShell(),
"XM","Exception.",makeStatus(x));
}
}
public void selectionChanged(IAction action,
ISelection selection)
{
selectedFile = null;
if(selection instanceof IStructuredSelection)
{
IStructuredSelection structuredSelection =
(IStructuredSelection)selection;
if(structuredSelection.size() == 1)
{
Object selectedResource =
structuredSelection.getFirstElement();
if(selectedResource instanceof IFile)
selectedFile = (IFile)selectedResource;
}
}
}
public void setActivePart(IAction action,
IWorkbenchPart targetPart)
{
part = targetPart;
}
private MessengerVIEw showConsole()
throws PartInitException
{
IWorkbenchPage page =
part.getSite().getWorkbenchWindow().getActivePage();
MessengerVIEw messenger = null;
if(page != null)
messenger = (MessengerView)page.showVIEw(CONSOLE_ID);
return messenger;
}
private void runXM(Messenger messenger)
throws CoreException, IOException, XMException
{
InputStream is = selectedFile.getContents();
Properties properties = new PropertIEs();
propertIEs.load(is);
String rulesPath =
propertIEs.getProperty("rules","rules"),
sourcePath =
propertIEs.getProperty("source","src"),
publishPath =
propertIEs.getProperty("publish","publish"),
buildString =
propertIEs.getProperty("build","false");
boolean build =
Boolean.valueOf(buildString).booleanValue();
IResource parent = selectedFile.getParent();
if(parent != null)
{
IPath parentPath = parent.getLocation();
rulesPath =
parentPath.append(rulesPath).toOSString();
sourcePath =
parentPath.append(sourcePath).toOSString();
publishPath =
parentPath.append(publishPath).toOSString();
}
DirectoryWalker walker =
new DirectoryWalker(messenger,rulesPath,build);
walker.walk(sourcePath,publishPath);
}
private IStatus makeStatus(Exception x)
{
Throwable t = MessengerVIEw.popThrowables(x);
if(t instanceof CoreException)
return ((CoreException)t).getStatus();
else
return new Status(IStatus.ERROR,
PLUGIN_ID,
IStatus.ERROR,
x.getMessage(),
t);
}
private void saveDirtyEditors()
{
IWorkbenchWindow window =
part.getSite().getWorkbenchWindow();
IWorkbenchWindow[] Windows =
window.getWorkbench().getWorkbenchWindows();
for(int i = 0;i < Windows.length;i++)
{
IWorkbenchPage[] pages = Windows[i].getPages();
for(int j = 0;j < pages.length;j++)
pages[j].saveAllEditors(false);
}
}
}
您應該把這個類當作 事件偵聽器。Eclipse 工作台使用它將用戶的選擇轉發給插件。該類從 ActionDelegate 繼承 - ActionDelegate 為 IObjectActionDelegate 的大部分提供了缺省實現。當用戶選擇導航器中的新文件時,Eclipse 工作台調用 selectionChanged() 。當用戶選擇菜單時,它調用 run() 方法,了解這一點非常重要。
在 run() 中,插件保存編輯器的內容(如果用戶正在編輯文件,就保存它),將 XM 控制台調到前台並啟動 XM。 runXM() 和 XM 本身的 main() 方法之間的主要區別在於, main() 從命令行獲得其參數,而 runXM() 從特性文件讀取這些參數。
額外時間
這樣就完成了本月專欄文章要介紹的集成工作。但是請稍候,這裡還有一個工具!我發現 Eclipse 提供了基本 XML 編輯器。您只須編譯它。現在您編輯 XML 文檔時語法會突出顯現,並且可以通過 XM 發布它們。
XML 編輯器
Eclipse 的項目向導可以生成基本 XML 編輯器。過程如下:
從 File菜單,選擇 New然後選擇 Project。
在項目向導中,選擇 Plug-in Development和 Plug-in Project。如果您沒看到 Plug-in Development選項,請從 Eclipse 網站下載 Plug-in SDK。
單擊 Next。
給您的項目取個名字,比如 org.ananas.eclipse.XML.editor ,然後單擊 Next。
單擊 Next以接受下一屏幕中的缺省值 - Plug-in Project Structure。
確保選擇了 Create a plug-in project using a code generation wizard,並指向 Plug-in with an editor(請參閱 圖 2)。該向導自動生成帶有語法突出顯示的基本 XML 編輯器。
單擊 Next。
單擊 Finish接受下一屏幕中的選項 - Plug-in Content。Eclipse 創建新項目並編寫 XML 編輯器。
從 Project菜單,選擇 Rebuild All來構建項目。
用該類文件生成名為 editor.jar 的 JAR 壓縮文檔。要從 Eclipse 創建 JAR,使用 File菜單中的 Export選項。這很重要 - 如果不生成 JAR 文件,Eclipse 就不會裝入您的插件。
退出 Eclipse,在 workspace 目錄下尋找新項目。
將項目目錄從 workspace 復制到 plug-in 目錄,然後重新啟動 Eclipse。
當您雙擊 XML 文件時,XML 編輯器自動啟動。要與更多文件相關聯(比如 .xsl ),請選擇 Window > Preferences > Workbench,然後選擇 File Associations。
圖 2. 編譯隱藏的 XML 編輯器
XM 更新
當我使用 XM 時,我修正了幾個錯誤並添加一個選項以更改文件擴展名。缺省情況下,XM 把 .html 擴展名給 HTML 文件。XML 文件得到 .XML 擴展名。有些用戶可能需要將缺省值更改成 .htm 、 .sHtml 、 .rss 、 .wml 或其它擴展名。如果您需要這項功能,請復制清單 5 的代碼。請注意新的 rules:extension 屬性。
清單 5. 更改文件擴展名<?XML version="1.0"?>
<xsl:stylesheet version="1.0"
XMLns:xsl="http://www.w3.org/1999/XSL/Transform"
XMLns:rules="http://ananas.org/2001/XM/XSLT/Rules">
<xsl:output method="Html"
rules:extension="htm"
/>
<!-- style sheet comes here -->
</xsl:stylesheet>
這一特性和 XM 鏈接管理完全集成在了一起,因此您的所有鏈接都保持有效。
工作將繼續進行
只用了兩個類就從 Eclipse 啟動了 XM,這是對插件功能的證明。在我的下一篇專欄文章中,我打算編寫向導插件來初始化新的 XM 項目。同時,您可以下載並測試 XM 運行器插件。您將看到通過 Eclipsee 編輯和發布網站是件很愉快的事。然而,要提醒的是:目前插件只可用於 JDK 1.4。如果需要在 JDK 1.3.x 中運行 Eclipse,您必須安裝 Xalan 並相應地調整插件。