developerWorks 中国网站

来源:互联网 发布:汇编语言和c语言区别 编辑:程序博客网 时间:2024/04/29 07:05
一年之后,BenoÎt 又回到了 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 更强大,但没有一种像 XM 那样易于使用。

Eclipse 平台也发生了根本的变化。现在,Eclipse 是最受人瞩目的开源 IDE 之一,拥有上千种插件。更重要的是,文档得到了更新,提供了更多的例子。我还记得当时和源代码与调试器搏斗以便获得特定效果的情景,因为当时还没有文档,那种情形不复存在了。

从技术上说,Eclipse 项目从 2.0 发展到了 3.0。新的 API 预计将为今后的很长时间奠定基础。所幸的是,不同的版本在很大程度上都是兼容的(事实上为 Eclipse 2.0 编写的 XM 插件在 3.0 中也能很好地工作),但有些变化不是向后兼容的。一个好的办法是清理代码,尽可能地使用新的 API。

本系列文章有两个目标:

  • 改进 Eclipse 集成。虽然 Eclipse 的功能很齐全,但原来的插件还有一些粗糙之处。通过与 Eclipse 资源管理更紧密地集成在一起,我希望能够稍微缓解一下不足之处。
  • 重写核心引擎。我曾经在很多项目中使用过 XM,在一些项目中遇到了核心引擎最初设计中的一些局限,不得不临时改变实现。现在是时候将这些修改加入到项目中了。

Eclipse 资源管理
在以前的专栏文章中,我曾多次提到,Eclipse 不仅仅是一种 IDE。最好将其看作是构建 IDE 的平台。Eclipse 可以归结为管理插件的一个系统。它提供了诸如加载插件、管理插件之间的联系和依赖性、管理插件之间的接口(通过扩展点)等服务。

显然,一些插件提供的服务是每个应用程序都需要的,所以可以将它们作为核心的一部分。部件库 SWT 就是其中之一。另一些插件,如 XM 插件,具有更强的专用性,则由用户在需要的时候安装。

还有一种核心服务是资源管理,该服务由 org.eclipse.core.resources 插件提供。对于 Eclipse 来说,工作区之下的一切都是资源。资源的基本接口是 IResource(非常明确)。最常用的后代有 IFileIFolderIProject,分别代表文件、文件夹和项目。

虽然有一定的关系,但 IResource 和 JDK 中的 File 对象实际上是两码事。JDK File 代表文件系统中的一个记录,而 Eclipse IResource 在文件系统之上又添加了几层抽象。首先,资源有属性,属性代表关于资源的信息,帮助插件处理资源。比如,插件可以把 <?xml-stylesheet?> 处理指令的内容作为属性来进行缓冲。同时将数据缓冲在属性中,这样就避免了每次运行插件时都需要解析文件。属性可以存储在内存中(用户退出编辑器时将丢失)或者持久存储到文件系统中。

此外,当添加、删除或编辑资源时,资源和文件系统就不再同步。IResource 记录资源的状态,并提供与文件系统同步的方法。更重要的是,Eclipse 可以通知插件资源和文件系统的变化。当资源与文件系统同步时,Eclipse 将传递给插件一个 delta,即上一次同步之后的变动列表。显然,这样就能够进行智能构建,也就是说仅对修改过的资源进行重新编译。

记号和任务列表
从用户的观点看,Eclipse 支持有两个问题:XM 有自己的项目重建逻辑和错误报告逻辑。最终,这两个问题表现为 XM 忽略了 Eclipse 的资源管理。

我准备在本系列的后两篇文章中讨论构建过程,现在主要解决错误报告的问题。

记号
Eclipse 为构建人员和编译人员提供了任务列表和问题列表来报告错误,如图 1 所示。当用户双击其中的错误项时,编辑器就会打开有问题的文件。不幸的是,编写 XM 插件的第一个版本时,我没有找到如何添加列表项的文档,所以忽略了它。结果,该插件有自己的控制台,但不支持双击。

图 1. 任务和问题列表
任务和问题列表

最后发现,向标准列表中添加消息并不难,但是不能直接添加。一开始,我试图寻找一个任务列表对象,但是没有发现添加列表项的方法。最后发现,无法添加或者至少无法直接添加列表项。要添加错误消息,需要在资源上创建一个记号(接口 IMarker)。从列表中删除一个消息,也要从资源上去掉记号。列表会自动更新翻译记号的变化。

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 —— 表示文件的位置,比如出现错误的位置。

定义插件专用的记号是一种不错的选择。新记号的 ID 在 plugin.xml 文件(与 Eclipse 中的其他声明一样)重定义。清单 1 显示了一个记号声明,定义了记号 ID(org.eclipse.core.resources.markers)的一个扩展。它还声明了新的记号,这些记号分别从 problemmarker(显示在问题列表中)和 textmarker(为了记录行号)中继承而来。将记号声明为持久的是为了在会话之间保存这些记号。

清单 1. 记号声明
<extension id="marker"           name="XM Message"           point="org.eclipse.core.resources.markers">   <super type="org.eclipse.core.resources.problemmarker"/>   <super type="org.eclipse.core.resources.textmarker"/>   <persistent value="true"/></extension>

用户可以根据不同的条件过滤消息,比如问题的类型(警告、错误)、优先级和记号 ID。定义插件专用的记号可以帮助用户对插件消息应用专门的过滤规则。

警告:Eclipse 有可能过滤掉插件消息。如果没有看到 XM 的任何消息,应该看看这些消息是不是过滤掉了。要改变过滤器,请在任务列表或问题列表中单击过滤器图标,一定要选中 XM 记号。

了解其中的窍门之后,集成到 XM 中并不难。从一开始,就通过 Messenger 接口将用户界面抽象化了。Messenger 定义了核心需要报告错误或进程信息的方法。为了支持方法列表,只需要编写新的 Messager 实现来创建适当的记号,如清单 2 所示。注意,begin() 方法将删除所有的记号,以便在构建之前清除问题列表。

清单 2. 记号的 Messenger 实现
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);  }}

进一步抽象
XM 一直围绕这两个组件来组织:核心,独立于 Eclipse 并提供命令行界面;Eclipse 插件。要将 XM 移植到其他界面,只需要像 Messenger(请参阅上一节)那样抽象用户界面的接口即可。我曾经为一些项目定义了 Eclipse 之上的 servlet 用户界面。

虽然我计划进一步加强 XM 与 Eclipse 的集成,但是也想保留命令行选项。两种界面各有自己的用途。对于日常操作而言,我多数时间都在用 Eclipse 环境,但是命令行版本对于 crontab(计划工作执行的一种 UNIX 工具)非常方便。为了同时支持两种方式,我抽象了 XM 核心引擎中的资源和文件。

最初的 XM 使用的是 JDK File 对象,以后您会看到它是造成多数集成问题的根源,Eclipse 没有使用 File 对象。相反,它使用了自己的 IResource 接口。此外,经验告诉我,依靠 File 有很大的局限性。Eclipse 不是惟一没有使用文件的软件包,SAX 使用 InputSource,而 JAXP 使用 Source

如果代码需要和几种不同的库进行交互该怎么办?可以使用代理模式(请参阅参考资料)来抽象各个库。在代理模式中,由一个(或多个)对象为底层的库提供通用的接口。可以实例化该对象,把请求转发给任何一个库。采用这种模式的好处是,调用代码时无需担心代理要转发的库。

XM 引入 Filename 接口来抽象文件或资源的概念。Filename 已经在 Eclipse IResource(为了在 Eclipse 内使用)和 JDK File 对象(为了在命令行中使用)上得以实现。清单 3 是 Filename 的声明。Eclipse 专用版提供了源代码(请参阅参考资料)。

清单 3. 文件和资源的抽象
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;}

XM 核心中的所有类(如 Messenger)都使用 Filename 进行了改写。

结束语
这两年中,Eclipse 已经成为 Java 平台事实上的标准开源 IDE,因此加强 Eclipse 对 XM 的支持非常必要。

更多采用 Eclipse 的好处之一是,能够使现有的更多文档可用,使编写插件更容易。在赞美 Eclipse 的同时,我仍然相信抽象插件的核心是值得的。对于 XM,我选择了抽象用户界面和资源管理。在下一期文章中,我将开始讨论 XM 用户界面的另一个主要问题:Eclipse 构建。

转载自

原创粉丝点击