当前页面: 开发资料首页 → 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 插件添加日志框架