当前页面: 开发资料首页 → Eclipse 专题 → 使用 XML: Eclipse 任务列表
摘要: 一年之后,Benoit 又回到了 XM (XSLT Make) 项目。他将介绍 Eclipse 平台的变化,并着手对 Eclipse 作一次较大的更新,使其与 XML 更紧密地集成在一起。首先他将考察一种简单的界面增强,用户经常提出这类请求,即支持问题和任务列表,确切地说是支持做标记。正如您将看到的,您需要间接地使用这些列表。他还将研究 Eclipse 自身的资源管理,讨论编写在 Eclipse 和命令行中同样也能运行的代码的技术。
在这个新的系列中,我将重温 使用 XML 专栏的一位老朋友:XSLT Make 或 XM。它也可以算做使用 Eclipse 插件的一个理由。2001 年 7 月,我在 使用 XML 专栏中介绍的第一个项目就是 XM。它是一种轻量级的、价格低廉的、使用 XML 和 XSLT 发布文档的工具。
2002 年 10 月,我决定为 XM 工具添加图形用户界面。我没有从头开发整个界面,而是求助于刚刚出现的一种 IDE:Eclipse。之所以选择 Eclipse,是因为它是可扩展的、用 Java 编写的,并且提供了一个神奇的小部件库。
重温 XM,使我有机会从两个方面改进 Eclipse 集成:我将修正一个讨厌的用户界面限制(本文中),并重新编写核心 XM 引擎,以便与 Eclipse 更好地集成。通过改进,我还将提高 Eclipse 的扩展性和功能。关于 XM 引擎的工作计划,将在后面的两篇文章中阐述。
简要的历史回顾
XM 已经存在一段时间了。从我的咨询经验和读者的反映来看,它在很多项目中证明了自身的价值。比如,我使用 XM 作为一种教学工具,为客户管理 Web 站点,并以 HTML 和 PDF 格式发表了大量的文档。关于该项目的文章,请参阅 参考资料。
XM 的优点
开发 XM 的最初原因是使 XML 和 XSLT 的使用更方便。我需要一种简单而有效地解决方案,依靠小型或中等大小的团队维护 Web 站点。我知道 XML 和 XSLT 提供了一个很好的基础,但当时我没有找到合适的工具。最后我卷起袖子自己做了一个这样的工具。2001 年那时出现的工具不是太简单(只能用于单个文件,而不是整个网站),就是太复杂(以大型团队为目标)。 XM 的功能非常强大(我曾经将其用于包含数千页面的项目),但有足够简单,能适应中小型团队的需要。 XM 有两个最重要的特性: 第二点可能更容易引起争议,但根据我的经验,维护静态站点的工作量更小,效率也更高。一些站点需要结合静态和动态网页,但是以静态方式为主维护站点可以避免很多问题:使用的软件包更少,因而减少了失效的机会。此外,因为可以使用更成熟的缓冲技术,站点的响应速度也更快。关于 XM 的独到之处,我建议您阅读一下原文(请参阅
参考资料)。
从 XM 的角度看
两年之中情况发生了很多变化。现在,有大量的开源项目能够满足您的需要(请参阅
参考资料)。我曾经用过其中的一些项目,虽然不敢说有广泛的经验,但确实发现其中一些项目的功能比 XM 更强大,但没有一种像 XM 那样易于使用。
Eclipse 平台也发生了根本的变化。现在,Eclipse 是最受人瞩目的开源 IDE 之一,拥有上千种插件。更重要的是,文档得到了更新,提供了更多的例子。我还记得当时和源代码与调试器搏斗以便获得特定效果的情景,因为当时还没有文档,那种情形不复存在了。 从技术上说,Eclipse 项目从 2.0 发展到了 3.0。新的 API 预计将为今后的很长时间奠定基础。所幸的是,不同的版本在很大程度上都是兼容的(事实上为 Eclipse 2.0 编写的 XM 插件在 3.0 中也能很好地工作),但有些变化不是向后兼容的。一个好的办法是清理代码,尽可能地使用新的 API。 本系列文章有两个目标: Eclipse 资源管理
在以前的专栏文章中,我曾多次提到,Eclipse 不仅仅是一种 IDE。最好将其看作是构建 IDE 的平台。Eclipse 可以归结为管理插件的一个系统。它提供了诸如加载插件、管理插件之间的联系和依赖性、管理插件之间的接口(通过扩展点)等服务。 显然,一些插件提供的服务是每个应用程序都需要的,所以可以将它们作为核心的一部分。部件库 SWT 就是其中之一。另一些插件,如 XM 插件,具有更强的专用性,则由用户在需要的时候安装。 还有一种核心服务是资源管理,该服务由
虽然有一定的关系,但
此外,当添加、删除或编辑资源时,资源和文件系统就不再同步。
记号和任务列表
从用户的观点看,Eclipse 支持有两个问题:XM 有自己的项目重建逻辑和错误报告逻辑。最终,这两个问题表现为 XM 忽略了 Eclipse 的资源管理。 我准备在本系列的后两篇文章中讨论构建过程,现在主要解决错误报告的问题。 记号
Eclipse 为构建人员和编译人员提供了任务列表和问题列表来报告错误,如图 1 所示。当用户双击其中的错误项时,编辑器就会打开有问题的文件。不幸的是,编写 XM 插件的第一个版本时,我没有找到如何添加列表项的文档,所以忽略了它。结果,该插件有自己的控制台,但不支持双击。 最后发现,向标准列表中添加消息并不难,但是不能直接添加。一开始,我试图寻找一个任务列表对象,但是没有发现添加列表项的方法。最后发现,无法添加或者至少无法
直接添加列表项。要添加错误消息,需要在资源上创建一个
记号(接口 IMarker)。从列表中删除一个消息,也要从资源上去掉记号。列表会自动更新翻译记号的变化。
定义插件专用的记号是一种不错的选择。新记号的 ID 在
用户可以根据不同的条件过滤消息,比如问题的类型(警告、错误)、优先级和记号 ID。定义插件专用的记号可以帮助用户对插件消息应用专门的过滤规则。 警告:Eclipse 有可能过滤掉插件消息。如果没有看到 XM 的任何消息,应该看看这些消息是不是过滤掉了。要改变过滤器,请在任务列表或问题列表中单击过滤器图标,一定要选中 XM 记号。 了解其中的窍门之后,集成到 XM 中并不难。从一开始,就通过 Messenger 接口将用户界面抽象化了。Messenger 定义了核心需要报告错误或进程信息的方法。为了支持方法列表,只需要编写新的 Messager 实现来创建适当的记号,如清单 2 所示。注意,
进一步抽象
XM 一直围绕这两个组件来组织:核心,独立于 Eclipse 并提供命令行界面;Eclipse 插件。要将 XM 移植到其他界面,只需要像
虽然我计划进一步加强 XM 与 Eclipse 的集成,但是也想保留命令行选项。两种界面各有自己的用途。对于日常操作而言,我多数时间都在用 Eclipse 环境,但是命令行版本对于 crontab(计划工作执行的一种 UNIX 工具)非常方便。为了同时支持两种方式,我抽象了 XM 核心引擎中的资源和文件。 最初的 XM 使用的是 JDK
如果代码需要和几种不同的库进行交互该怎么办?可以使用代理模式(请参阅
参考资料)来抽象各个库。在代理模式中,由一个(或多个)对象为底层的库提供通用的接口。可以实例化该对象,把请求转发给任何一个库。采用这种模式的好处是,调用代码时无需担心代理要转发的库。
XM 引入
XM 核心中的所有类(如
结束语
这两年中,Eclipse 已经成为 Java 平台事实上的标准开源 IDE,因此加强 Eclipse 对 XM 的支持非常必要。 更多采用 Eclipse 的好处之一是,能够使现有的更多文档可用,使编写插件更容易。在赞美 Eclipse 的同时,我仍然相信抽象插件的核心是值得的。对于 XM,我选择了抽象用户界面和资源管理。在下一期文章中,我将开始讨论 XM 用户界面的另一个主要问题:Eclipse 构建。 关于作者<table border="0" cellspacing="0" cellpadding="0" width="100%"><tr><td colspan="3"></td></tr><tr align="left" valign="top"><td></td><td></td><td width="100%"> Beno?t Marchal 是一位比利时籍顾问。他是
XML
by Example, Second Edition
和其他几本 XML 书籍的作者。 Beno?t 乐意为您的 XML 项目提供帮助。您可以通过
bmarchal@pineapplesoft.com
或者他的个人网站
marchal.com与他联系。
<table border="0" cellspacing="0" cellpadding="0" width="100%"><tr><td>
</td></tr></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tr align="right"><td>
<table border="0" cellpadding="0" cellspacing="0"><tr><td valign="middle">
</td><td valign="top" align="right"></td></tr></table></td></tr></table>org.eclipse.core.resources 插件提供。对于 Eclipse 来说,工作区之下的一切都是资源。资源的基本接口是
IResource(非常明确)。最常用的后代有
IFile、
IFolder 和
IProject,分别代表文件、文件夹和项目。
IResource 和 JDK 中的
File 对象实际上是两码事。JDK
File 代表文件系统中的一个记录,而 Eclipse
IResource 在文件系统之上又添加了几层抽象。首先,资源有属性,属性代表关于资源的信息,帮助插件处理资源。比如,插件可以把
<?xml-stylesheet?> 处理指令的内容作为属性来进行缓冲。同时将数据缓冲在属性中,这样就避免了每次运行插件时都需要解析文件。属性可以存储在内存中(用户退出编辑器时将丢失)或者持久存储到文件系统中。
IResource 记录资源的状态,并提供与文件系统同步的方法。更重要的是,Eclipse 可以通知插件资源和文件系统的变化。当资源与文件系统同步时,Eclipse 将传递给插件一个 delta,即上一次同步之后的变动列表。显然,这样就能够进行智能构建,也就是说仅对修改过的资源进行重新编译。
<table border="0" cellspacing="0" cellpadding="0" width="100%"><tr><td>
</td></tr></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tr align="right"><td>
<table border="0" cellpadding="0" cellspacing="0"><tr><td valign="middle">
</td><td valign="top" align="right"></td></tr></table></td></tr></table>
createMarker() 方法用于创建记号。该方法以记号 ID 作为参数。平台中定义了以几种标准的记号 ID:
org.eclipse.core.resources.marker —— 记号层次结构的根。
org.eclipse.core.resources.problemmarker —— 表示问题或错误消息,出现在问题列表中。
org.eclipse.core.resources.taskmarker —— 表示待办事项,出现在任务列表中。
org.eclipse.core.resources.bookmark —— 表示文件,比如搜索结果。
org.eclipse.core.resources.textmarker —— 表示文件的位置,比如出现错误的位置。
plugin.xml 文件(与 Eclipse 中的其他声明一样)重定义。清单 1 显示了一个记号声明,定义了记号 ID(
org.eclipse.core.resources.markers)的一个扩展。它还声明了新的记号,这些记号分别从
problemmarker(显示在问题列表中)和
textmarker(为了记录行号)中继承而来。将记号声明为持久的是为了在会话之间保存这些记号。
清单 1. 记号声明
<table bgcolor="#eeeeee" width="100%" cellpadding="5" cellspacing="0" border="1"><tr><td>
</td></tr></table>
begin() 方法将删除所有的记号,以便在构建之前清除问题列表。
清单 2. 记号的 Messenger 实现
<table bgcolor="#eeeeee" width="100%" cellpadding="5" cellspacing="0" border="1"><tr><td>
</td></tr></table>
package org.ananas.xm.eclipse;
import java.text.MessageFormat;
import org.eclipse.ui.IWorkbench;
import org.ananas.xm.core.Filename;
import org.ananas.xm.core.Location;
import org.ananas.xm.core.Messenger;
import org.eclipse.ui.IWorkbenchPage;
import org.ananas.xm.core.XMException;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.ui.views.markers.MarkerViewUtil;
public class MessengerTaskList
implements Messenger, EclipseConstants
{
private IProject project = null;
private IWorkbench workbench = null;
private boolean noMarkerSoFar = true;
private static class ShowMarkerView
implements Runnable
{
private IWorkbench workbench;
private IMarker marker;
public ShowMarkerView(IWorkbench workbench,IMarker marker)
{
this.workbench = workbench;
this.marker = marker;
}
public void run()
{
IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
if(window == null)
{
IWorkbenchWindow[] windows = workbench.getWorkbenchWindows();
if(windows != null && windows.length > 0)
window = windows[0];
else
return;
}
IWorkbenchPage page = window.getActivePage();
if(page != null)
MarkerViewUtil.showMarker(page,marker,true);
}
}
public MessengerTaskList(IWorkbench workbench,IProject project)
{
if(null == project || null == workbench)
throw new NullPointerException("null argument in TaskListMessenger constructor");
this.project = project;
this.workbench = workbench;
}
protected void addMarker(String msg,Location location,int severity,int priority)
throws XMException
{
IResource resource = null;
if(null == location || location.equals(Location.UNKNOWN))
resource = project;
else
resource = (IResource)location.getFilename().asPlatformSpecific();
try
{
IMarker marker = resource.createMarker(MARKER_ID);
if(null != location && Location.UNKNOWN_POSITION != location.getLine())
marker.setAttribute(IMarker.LINE_NUMBER,location.getLine());
if(null != msg)
marker.setAttribute(IMarker.MESSAGE,msg);
marker.setAttribute(IMarker.SEVERITY,severity);
marker.setAttribute(IMarker.PRIORITY,priority);
if(noMarkerSoFar)
showMarkerView(marker);
else
noMarkerSoFar = false;
}
catch(CoreException e)
{
throw new XMException(e,location);
}
}
public void error(XMException x)
throws XMException
{
addMarker(x.getMessage(),x.getLocation(),IMarker.SEVERITY_ERROR,IMarker.PRIORITY_NORMAL);
}
public void fatal(XMException x)
throws XMException
{
addMarker(x.getMessage(),x.getLocation(),IMarker.SEVERITY_ERROR,IMarker.PRIORITY_HIGH);
}
public void warning(XMException x)
throws XMException
{
addMarker(x.getMessage(),x.getLocation(),IMarker.SEVERITY_WARNING,IMarker.PRIORITY_LOW);
}
public boolean progress(Filename sourceFile,Filename resultFile)
{
return true;
}
public void info(String msg,Location location)
throws XMException
{
addMarker(msg,location,IMarker.SEVERITY_INFO,IMarker.PRIORITY_NORMAL);
}
public void info(String pattern,Object[] arguments,Location location)
throws XMException
{
info(MessageFormat.format(pattern,arguments),location);
}
public void begin(String source,String target)
throws XMException
{
try
{
project.deleteMarkers(MARKER_ID,true,IResource.DEPTH_INFINITE);
noMarkerSoFar = true;
}
catch(CoreException e)
{
throw new XMException(e);
}
}
public void end()
{
}
protected void showMarkerView(IMarker marker)
{
Display display = Display.getCurrent();
if(display == null)
display = Display.getDefault();
ShowMarkerView showMarkerView = new ShowMarkerView(workbench,marker);
display.syncExec(showMarkerView);
}
}
Messenger(请参阅
上一节)那样抽象用户界面的接口即可。我曾经为一些项目定义了 Eclipse 之上的 servlet 用户界面。
File 对象,以后您会看到它是造成多数集成问题的根源,Eclipse 没有使用
File 对象。相反,它使用了自己的
IResource 接口。此外,经验告诉我,依靠
File 有很大的局限性。Eclipse 不是惟一没有使用文件的软件包,SAX 使用
InputSource,而 JAXP 使用
Source。
Filename 接口来抽象文件或资源的概念。
Filename 已经在 Eclipse
IResource(为了在 Eclipse 内使用)和 JDK
File 对象(为了在命令行中使用)上得以实现。清单 3 是
Filename 的声明。Eclipse 专用版提供了源代码(请参阅
参考资料)。
清单 3. 文件和资源的抽象
<table bgcolor="#eeeeee" width="100%" cellpadding="5" cellspacing="0" border="1"><tr><td>
</td></tr></table>
package org.ananas.xm.core;
import java.io.File;
import org.xml.sax.InputSource;
import org.ananas.xm.core.XMException;
public interface Filename
extends CoreConstants
{
public boolean isRoot()
throws XMException;
public boolean isFile();
public boolean isFolder()
throws XMException;
public boolean exists()
throws XMException;
public String getName()
throws XMException;
public String getShortName()
throws XMException;
public String getSuffix()
throws XMException;
public String getProjectPath()
throws XMException;
public Filename getParent()
throws XMException;
public Filename[] getChildren()
throws XMException;
public void setPersistentMetadata(String key,String value)
throws XMException;
public void setPersistentMetadata(String key,String[] values)
throws XMException;
public void setTransientMetadata(String key,Object value)
throws XMException;
public Object getMetadata(String key)
throws XMException;
public String getMetadataAsString(String key)
throws XMException, ClassCastException;
public String[] getMetadataAsArray(String key)
throws XMException, ClassCastException;
public File asFile()
throws XMException;
public InputSource asInputSource()
throws XMException;
public Object asPlatformSpecific()
throws XMException;
public boolean hasSamePath(Filename document)
throws XMException;
public boolean isDescendantOf(Filename document)
throws XMException;
public boolean remove()
throws XMException;
}
Messenger)都使用
Filename 进行了改写。
<table border="0" cellspacing="0" cellpadding="0" width="100%"><tr><td>
</td></tr></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tr align="right"><td>
<table border="0" cellpadding="0" cellspacing="0"><tr><td valign="middle">
</td><td valign="top" align="right"></td></tr></table></td></tr></table>
<table border="0" cellspacing="0" cellpadding="0" width="100%"><tr><td>
</td></tr></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tr align="right"><td>
<table border="0" cellpadding="0" cellspacing="0"><tr><td valign="middle">
</td><td valign="top" align="right"></td></tr></table></td></tr></table>
<table border="0" cellpadding="0" cellspacing="0"><tr><td valign="middle">
</td><td valign="top" align="right"></td></tr></table></td></tr></table>
<table border="0" cellspacing="0" cellpadding="0" width="100%"><tr><td>
</td></tr></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tr align="right"><td>
<table border="0" cellpadding="0" cellspacing="0"><tr><td valign="middle">
</td><td valign="top" align="right"></td></tr></table></td></tr></table>
↑返回目录
前一篇: 任何人都可以重构
后一篇: 为 Eclipse 插件添加日志框架