现在的位置: 首页 > 综合 > 正文

为 Eclipse 构建片段扩展

2013年08月09日 ⁄ 综合 ⁄ 共 20666字 ⁄ 字号 评论关闭

了解如何为 Eclipse 和 Rational Application Developer V7 构建一个插件。可以使用这个插件定义片段,从而轻松地添加符合企业标准的代码。这个插件与 Web Tools Project 提供的 Snippets 视图相似,允许开发人员将代码片段拖放到编辑器中。我们采用面向对象的最佳实践,所以可以从任何来源装载片段,比如数据库(例如 Apache Derby)、文件系统或 Web 服务。

本文不仅仅要为 Eclipse 构建一个新的插件,尽管这里要讨论创建插件的详细过程。在为 Eclipse 构建插件的过程中,您将学习如何使用 Eclipse 集成开发环境(IDE)的特性进行快速开发。我还将讲解如何扩展 Eclipse IDE,为插件提供丰富的功能。在阅读本文之后,您应该能够使用 Eclipse IDE 插件开发向导构建新的插件。您还要学习如何扩展类并实现接口,从而在插件中添加首选项页面、属性页面和拖放支持。

请访问 Eclipse 技术资源中心,获取 Eclipse 相关信息,包括大量 Eclipse 技术文章、教程、下载和相关技术资源。

RSS 订阅 Eclipse 相关文章和教程的 RSS 提要

您应该熟悉面向对象(OO)概念,比如实现接口和扩展类。还应该相当熟悉 Eclipse IDE,但是不需要了解如何为 Eclipse 创建插件。需要 Eclipse V3.2 或更高版本。需要运行嵌套的 Eclipse 实例来测试插件,所以您的计算机应该有足够的 RAM,可以同时运行两个 Eclipse 实例。

开始插件项目

Eclipse IDE 是在一个可扩展框架上构建的,所以可以为 IDE 编写自己的插件。但是,不止如此:Eclipse 还附带一些模板,可以使用这些模板快速开始构建插件项目。

为了让本文有意义,我选择了一个涉及许多活动的场景,以此演示如何使用 Eclipse 框架构建插件。我选择的场景是一个企业代码片段插件。这个插件让开发人员能够使用预定义的分类的代码片段,并将它们插入编辑器中。这些片段可以从 Eclipse 之外的来源获得。

这个代码片段插件 “Example.com” 具有以下特性:

  1. 一个树视图,可以用来按类别寻找片段
  2. 一个首选项页面,可以用来配置片段的来源位置
  3. 一个上下文敏感的菜单,可以用来将片段插入编辑器
  4. 片段,包括 ${variable} 形式的模板变量和一个用来为这些变量收集值的向导

创建项目

首先,创建一个用来开发插件的项目。幸运的是,有一个向导可以引导您创建项目。执行以下步骤:

  1. 选择 File > New > Project
  2. Plug-in Development 下面,选择 Plug-in Project 并单击 Next
  3. Project name 中,输入 SnippetsPlugin 并单击 Next
  4. Plug-in Content 屏幕上,保留所有默认值并单击 Next
  5. Templates 视图上,选择 Custom plug-in wizard 并单击 Next
  6. 在可用模板列表中,单击 Deselect All 按钮,然后选择以下模板:
    1. “Hello world” Action Set
    2. Popup Menu
    3. Preference Page
    4. View

    下面的 “了解模板” 一节中将详细描述这些模板对项目的作用。

  7. Sample Action Set 下面:
    1. Action Class Name 改为 SnippetAction
    2. 点击 Next 继续。
  8. Sample Popup Menu 下面:
    1. Name Filter 改为 *.*
    2. Submenu Name 改为 Snippet Submenu
    3. Action Class 改为 SnippetPopupAction
    4. 单击 Next 继续。
  9. Sample Preference Page 下面:
    1. Page Class Name 改为 SnippetPreferencePage
    2. Page Name 改为 Snippet Preferences
    3. 单击 Next 继续。
  10. Main View Settings 中:
    1. View Class Name 改为 SnippetsView
    2. View Name 改为 Example.com Snippets View
    3. View Category Name 改为 Example.com Enterprise
    4. 选择 Tree viewer 作为查看器类型。
    5. 单击 Next 继续。
  11. View Features 屏幕上,选中所有选项并单击 Finish 创建插件项目。

在 Eclipse IDE 完成处理时,就会出现一个新的插件项目,其中包含许多文件。前面步骤中选择和配置的模板会创建几个包和 Java™ 源文件。关于这些模板的更多信息,请参见 “了解模板” 一节。如果您已经掌握了模板,或者不愿意花时间研究细节,那么可以跳到 “测试插件” 一节。


回页首

了解模板

看到 Eclipse 创建了新项目之后,您可能想了解这个过程中发生的情况。如果您按照前面的步骤进行操作的话,项目会包含几个包含 Java 源文件的包,还有许多其他文件。如果您是刚接触插件开发的新手,那么可能不知道这些文件的作用以及为什么会出现在这些位置,这是使用向导的一个缺点。

本节讨论本文中使用的模板。讨论各个配置选项以及各个部分如何组合成插件。在阅读下一节之后,如果愿意的话,您可以手工构建这些组成部分。

在 Eclipse IDE 中,动作集(action set)是一组在逻辑上应该分组在一起的菜单项或按钮动作。插件可以有一个动作集,因为将针对一个插件的所有动作分组在一起是顺理成章的。

“Hello world” Action Set 模板在 snippetssample.action 包中添加 SnippetAction.java 文件。这个类的名称根据 Sample Action Set 配置中的 Action Class Name 设置,如下图所示。

图 1. 配置动作集
配置动作集

在 Eclipse 中运行这个插件时,“Hello, world” Action Set 在菜单栏中显示菜单 Sample Menu。菜单标签使用的文本是在 plugin.xml 文件中定义的,如下所示:

清单 1. plugin.xml 中的动作集

                
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.2"?>
<plugin>

   <extension
         point="org.eclipse.ui.actionSets">
      <actionSet
            label="Sample Action Set"
            visible="true"
            id="SnippetsPlugin.actionSet">
         <menu
               label="Sample &Menu"
               id="sampleMenu">
            <separator
                  name="sampleGroup">
            </separator>
         </menu>
         <action
               label="&Sample Action"
               icon="icons/sample.gif"
               class="snippetsplugin.actions.SnippetAction"
               tooltip="Hello, Eclipse world"
               menubarPath="sampleMenu/sampleGroup"
               toolbarPath="sampleGroup"
               id="snippetsplugin.actions.SnippetAction">
         </action>
      </actionSet>
   </extension>
...
</plugin>

如果在 Eclipse 中单击这个菜单,就会执行 SnippetAction 类中的 run 方法。下面的模板代码显示一个消息框。

清单 2. SnippetAction 的 run() 方法

                
public void run(IAction action) {
    MessageDialog.openInformation(
        window.getShell(),
        "SnippetsPlugin Plug-in",
        "Hello, Eclipse world");
}

回页首

弹出菜单

Popup Menu 模板提供在右键单击文件时出现的一个菜单项 — 有时候称为上下文菜单(context-sensitive menu)。在 Package Explorer 中右键单击文件时,会看到菜单 Snippet Submenu,其中只包含一个菜单项 New Action。Popup Menu 配置示例如下所示:

图 2. 配置弹出菜单
配置弹出菜单

Target Object's Class 在默认情况下指向 org.eclipse.core.resources.IFile,这个设置告诉 Eclipse 只在用户右键单击文件时应用弹出菜单。可以使用的其他资源是 IProjectIFolder。如果使用 IProject,那么只在用户右键单击项目时显示菜单;如果使用 IFolder,那么只在用户右键单击文件夹时显示菜单。

Name Filter 表示只有在选择某些文件时才显示弹出菜单。例如,如果指定过滤器为 *.html,那么只有在用户右键单击文件名以 .html 结尾的文件时菜单才会出现。在向导创建项目时,这个值定义在 plugin.xml 中。

Submenu Name 是弹出菜单中显示子菜单的菜单项名称。在创建项目之后,可以在 plugin.xml 文件中修改这个值。

Action Label 是子菜单中显示的菜单项名称。这个值也写入 plugin.xml 文件中,可以在这个文件中进行修改。

Java Package Name 是包含新类的包的名称。类名由 Action Class 定义。与其他值一样,以后也可以修改这个名称,但是不太容易。修改包和类的名称要求使用 Eclipse 的重构特性,确保相应地更新 plugin.xml 中的引用。

清单 3 显示弹出菜单扩展使用的 plugin.xml 文件部分。

清单 3. 弹出菜单扩展

                
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.2"?>
<plugin>
...
   <extension
         point="org.eclipse.ui.popupMenus">
      <objectContribution
            objectClass="org.eclipse.core.resources.IFile"
            nameFilter="*.*"
            id="SnippetsPlugin.contribution1">
         <menu
               label="Snippet Submenu"
               path="additions"
               id="SnippetsPlugin.menu1">
            <separator
                  name="group1">
            </separator>
         </menu>
         <action
               label="New Action"
               class="snippetsplugin.popup.actions.SnippetPopupAction"
               menubarPath="SnippetsPlugin.menu1/group1"
               enablesFor="1"
               id="SnippetsPlugin.newAction">
         </action>
      </objectContribution>
   </extension>
...
</plugin>

在单击弹出菜单项时,Eclipse 执行 SnippetPopupAction 类的 run() 方法中的代码(见清单 4)。在默认情况下,这些代码显示一个消息框。

清单 4. SnippetPopupAction 的 run() 方法

                
public void run(IAction action) {
    Shell shell = new Shell();
    MessageDialog.openInformation(
        shell,
        "SnippetsPlugin Plug-in",
        "New Action was executed.");
}

回页首

首选项页面

Preference Page 模板提供一个首选项页面,可以在这个页面中为插件设置首选项。在 Eclipse 中,通过 Window > Preferences 菜单项调用这个页面。设置首选项页面模板的向导页面如下所示。

图 3. 设置首选项页面
设置首选项页面

Java Package Name 指定一个包,其中包含用来显示首选项页面的页面,以及支持首选项页面的所有类。Page Class Name 是页面类的名称,Page Name 是首选项列表中显示的名称。可以在 plugin.xml 文件中修改首选项页面的名称(已经改为 Snippet Preferences)。

除了 SnippetPreferencePage 类之外,这个模板还生成另外两个支持类。它们在同一个包中。其中一个类 PreferenceConstants 包含用来识别首选项的常量。另一个类 PreferenceInitializer 将首选项初始化为默认值。

使用 Preference Page 模板的出色之处是,不需要为保存和获取首选项编写代码 — 这由首选项页面上的控件自动处理。


回页首

视图

View 模板创建一个视图,当测试这个插件时,可以在 Eclipse IDE 中看到这个视图(参见 “测试插件” 一节)。

下面显示的 Main View Settings 屏幕集合了视图扩展的配置。

图 4. 配置视图
配置视图

Java Package Name 是包含新类的包的名称。

View Class Name 是用来显示视图的 Java 类的名称。

View Name 是视图的名称,这个名称会显示在 Window > Show View 菜单中。

View Category ID 识别类别,View Category Name 是类别的名称。类别是一个组,当使用 Window > Show View 选择视图时,视图显示在这个组下面。

查看器类型影响生成视图类(SnippetView)的方式。如果选择 Table viewer,那么视图只显示条目的列表。条目的列表由 getElements() 方法生成。

如果查看器类型是 Tree viewer,那么会以不同的方式生成视图。getElements() 方法会返回一组 TreeObject 对象,而且修改它的内部类 ViewContentProvider 还实现 ITreeContentProvider 接口。

视图类

View 模板生成的 SnippetView 类是这个项目中最复杂的类。如果在 Eclipse 的 Outline 视图中查看这个类,就会看到它包含下面的内部类:

TreeObject
这个类表示条目树的叶节点(leaf node)。这些节点不包含其他条目。
TreeParent
这个类表示树中的条目可以包含其他条目。
ViewContentProvider
这个类实现 IStructuredProviderITreeContentProvider 接口,它获取要在视图中显示的内容。在本文后面,我们要修改这个类,从而向视图提供代码片段的细节。
ViewLabelProvider
这个类为树条目提供图标图像。
NameSorter
可以修改这个类来为视图中的条目实现定制的排序特性。在本文中,我们不修改这个类。关于这个类的更多信息,请参考 Eclipse Platform API 文档中关于 ViewerSorter 的内容(参阅 参考资料)。

回页首

测试插件

到目前为止,您选择的模板已经在项目中生成了许多 Java 类。在继续开发之前,应该测试这个插件,看看模板生成的代码有什么作用。为此,需要运行一个 Eclipse 实例。要想在 Eclipse 中运行嵌套的 Eclipse 实例,简单的方法是在 Package Explorer 视图中右键单击这个插件项目,并从弹出菜单中选择 Run As > Eclipse Application

嵌套的 Eclipse 实例启动之后,就可以看到这个插件运行时的样子。要想看到动作集类的效果,就看看 Eclipse 中的菜单栏。菜单栏中出现了 Sample Menu 菜单。单击 Sample Menu > Sample Item,Eclipse 就会显示一个消息框,这个消息框的标题是 SnippetSample Plug-in,其中的消息是 “Hello, Eclipse world”。

可以退出这个嵌套的 Eclipse 实例,并修改清单 5 中粗体显示的字符串值。这些代码位于 SnippetAction 类。

清单 5. SnippetAction 显示的消息

                
    public void run(IAction action) {
        MessageDialog.openInformation(
            window.getShell(),
            "SnippetsPlugin Plug-in",
            "Hello, Eclipse world");
    }

再次运行这个嵌套的 Eclipse 实例,选择菜单项,就会看到它显示新的值。如果不喜欢菜单或菜单项的名称,那么可以在 plugin.xml 文件中进行修改,见下面的粗体代码。

清单 6. plugin.xml 中的菜单项名称

                
<menu
      label="Sample &Menu"
      id="sampleMenu">
   <separator
         name="sampleGroup">
   </separator>
</menu>
<action
      label="&Sample Action"
      icon="icons/sample.gif"
      class="snippetsplugin.actions.SnippetAction"
      tooltip="Hello, Eclipse world"
      menubarPath="sampleMenu/sampleGroup"
      toolbarPath="sampleGroup"
      id="snippetsplugin.actions.SnippetAction">
</action>

Eclipse 中的弹出菜单

为了看到 Eclipse 中的弹出菜单,启动嵌套的 Eclipse 实例。它启动之后,在 Package Explorer 视图中找到一个文件。如果没有文件存在,就创建一个包含一个示例文件的项目。当右键单击这个文件时,会看到 Snippet Submenu 菜单,其中包含菜单项 New Action。单击这个菜单项,Eclipse 会显示一个消息框,其中显示 “New Action was executed”。

Eclipse 中的首选项页面

为了看到 Eclipse 中的首选项页面,在嵌套的 Eclipse 实例中选择 Window > Preferences。应该会看到一个称为 Snippet Preferences 的首选项类别。从列表中选择这个类别,就会打开一个首选项页面,其中包含许多控件。这些控件是模板自动生成的。

以后我们会修改这些首选项,让它们对这个插件更有意义。目前,可以修改这个首选项页面上的任何值,并通过 ApplyOK 按钮应用它们,然后返回,就会看到所做的修改已经保存并重新装载。而这些都不需要编写定制的代码。

下面所示方法的 SnippetPreferencePage 类包含显示首选项页面的代码。

清单 7. SnippetPreferencePage 的 createFieldEditors() 方法

                
public void createFieldEditors() {
    addField(new DirectoryFieldEditor(PreferenceConstants.P_PATH, 
            "&Directory preference:", getFieldEditorParent()));
    addField(
        new BooleanFieldEditor(
            PreferenceConstants.P_BOOLEAN,
            "&An example of a boolean preference",
            getFieldEditorParent()));

    addField(new RadioGroupFieldEditor(
            PreferenceConstants.P_CHOICE,
        "An example of a multiple-choice preference",
        1,
        new String[][] { { "&Choice 1", "choice1" }, {
            "C&hoice 2", "choice2" }
    }, getFieldEditorParent()));
    addField(
        new StringFieldEditor(PreferenceConstants.P_STRING, 
        "A &text preference:", getFieldEditorParent()));
}

Eclipse 中的视图

为了在嵌套的 Eclipse 实例中看到插件的视图,选择 Window > Show View > Other。这个视图组织在 Example.com Snippets 类别下面,这是在 View 配置中指定的(参见 图 4)并保存在 plugin.xml 中。选择 Snippet View 并单击 OK,就可以打开这个视图。

在默认情况下,这个视图包含几个条目。应该会在视图中看到图 5 这样的条目树。

图 5. 填充了条目的视图
填充了条目的视图

如果双击视图中的任何条目,Eclipse 就会显示一个消息框,其中显示 “Double-click detected on XXX”,这里的 XXX 是条目的文本。视图中还添加了两个动作;它们显示在视图的菜单栏上,并在右键单击视图中的任何位置时出现。这些动作是在下面的 makeActions() 方法中动态地添加的。

清单 8. makeActions() 方法

                
    private void makeActions() {
        action1 = new Action() {
            public void run() {
                showMessage("Action 1 executed");
            }
        };
        action1.setText("Action 1");
        action1.setToolTipText("Action 1 tooltip");
        action1.setImageDescriptor(PlatformUI.getWorkbench().getSharedImages().
            getImageDescriptor(ISharedImages.IMG_OBJS_INFO_TSK));
        
        action2 = new Action() {
            public void run() {
                showMessage("Action 2 executed");
            }
        };
        action2.setText("Action 2");
        action2.setToolTipText("Action 2 tooltip");
        action2.setImageDescriptor(PlatformUI.getWorkbench().getSharedImages().
                getImageDescriptor(ISharedImages.IMG_OBJS_INFO_TSK));
        doubleClickAction = new Action() {
            public void run() {
                ISelection selection = viewer.getSelection();
                Object obj = ((IStructuredSelection)selection).getFirstElement();
                showMessage("Double-click detected on "+obj.toString());
            }
        };
    }

回页首

构建 SnippetProvider

为了构建一个获取代码片段的类框架,首先要编写一个接口,然后构建执行实际工作的实现类。SnippetView 将使用这个接口装载要显示的片段。但是,SnippetView 类应该不必了解片段的细节 — 从哪里装载它们或如何装载它们 — 这就是使用接口的意义。

首先创建 SnippetProvider 接口。选择 File > New > Interface,并将这个接口放在 com.example.plugins.snippets.providers 包中,实现类和工厂类也将放在这个包中。

SnippetProvider.java 的内容如下所示。

清单 9. SnippetProvider 接口

                
public interface SnippetProvider {
    
    public String[] getLanguages() throws SnippetProviderException;
    public String[] getCategories(String language) throws SnippetProviderException;
    public SnippetInfo[] getSnippetInfo(String language, String category) 
      throws SnippetProviderException;
    public Snippet getSnippet(SnippetInfo info);
    public void configure(Properties props) throws SnippetProviderConfigurationException;

}

这个插件的用途是从某种存储库中取出代码片段,所以不需要保存或更新片段的方法。只需要用来获取语言(Java 或 XML)、类别、关于片段的信息以及片段本身的方法。这些方法以及描述如下所示。

表 1. SnippetProvider 接口中的方法

方法 描述
configure(Properties props) 配置适配器。
getCategories(String language) 每种语言(Java 或 XML)都有一组类别,片段可以按照这些类别进行组织。
getLanguages() 获得语言的列表,这是对片段进行分类的高级方式。
getSnippet(SnippetInfo info) 返回特定的片段。
getSnippetInfo(String language, String category) 获得一组 SnippetInfo 对象,可以在视图中显示其中的信息。

SnippetFileProvider 实现类

编写了接口之后,就要开始编写使用这个接口的实现类。在本节中,我们要编写一个实现类,它用来从文件系统目录中获取代码片段。

但这不是全部……

您可能会注意到,我们没有详细讨论两个类:SnippetInfoSnippet。在本文提供的 下载文件 中可以找到这两个类。还有几个异常类(比如 SnippetProviderException)、一个用来装载 SnippetProvider 实现的工厂类(SnippetProviderFactory)和其他几个类。这些支持类是有用的,但是在这里讨论构建它们的过程是没有意义的。

我们不需要在一个新类中手工添加实现接口所需的方法,而是使用 Eclipse 构建这个类的开始部分。为了让 IDE 替我们做尽可能多的工作,右键单击 com.example.plugins.snippets.providers 包并选择 New > Class。在 Name 中输入 SnippetFileProvider,并单击接口列表旁边的 Add 按钮。在 Choose interfaces 中,输入 SnippetFileProvider 并单击 OK

Eclipse IDE 会生成一个正确地实现了 SnippetProvider 接口的新的类文件。所需的所有方法以及返回语句都已经生成了,所以这个项目可以顺利地编译。

这个新的类要根据语言和类别从文件系统中装载代码片段。它从一个 XML 文件(snippets.info)中装载代码片段的信息。这个文件包含一组 SnippetInfo 条目的 XML 表示,这些条目是使用 XMLEncoder 串行化为 XML 的。

下面的 configure() 方法提供了配置带有 Properties 对象的适配器的方法。对于 SnippetFileProvider,惟一需要设置的属性是基统一资源标识符(Uniform Resource Identifier,URI),这是包含代码片段结构的目录的基路径。

清单 10. SnippetFileProvider 的 configure() 方法

                
    public void configure(Properties props)
            throws SnippetProviderConfigurationException {
        this.properties = props;
        String uriPath = "";
        if (baseUri == null) {
            try {
                uriPath = properties
                        .getProperty("snippetFileProvider.base.directory");

                if (uriPath == null || uriPath.length() == 0) {
                    throw new SnippetProviderConfigurationException(
                            "Please supply a value for property " +
                            "'snippetFileProvider.base.directory'");
                }

                baseUri = new URI(uriPath);

            } catch (URISyntaxException urie) {
                throw new SnippetProviderConfigurationException("URI '"
                        + uriPath + "' incorrectly formatted.", urie);
            }
        }
    }

getLanguages() 方法获取基目录下的直接子目录的名称。这些目录用来区分代码片段的语言 — 比如 Java、XML 和 HTML — 这是对片段进行分类的高级方式。SnippetFileProvider 类中实现的这个方法如下所示。

清单 11. SnippetFileProvider 的 getLanguages() 方法

                
    public String[] getLanguages() throws SnippetProviderException {
        /*
         * The languages will be the high-level directories right underneath the
         * base directory.
         */
        if (languages == null) {

            languages = getFormattedNamesFromLocation(getBaseUri());
        }
        return languages;
    }

这个方法调用一个私有静态方法 getFormattedNamesFromLocation,并将基 URI 传递给它。这个静态方法返回一个 String 数组,其中包含目录中找到的条目的名称,并做了适当的格式化。每个语言目录包含一组文件夹,这些文件夹用来将片段分类到子类别中。例如,Java 语言目录可能包含一个用于异常处理的类别,或者用于日志记录的类别。装载语言的类别的方法如下所示。

清单 12. SnippetFileProvider 的 getCategories() 方法

                
    public String[] getCategories(String language)
            throws SnippetProviderException {
        try {
            return getFormattedNamesFromLocation(new URI(getBaseUri().getPath()
                    + "/" + language));
        } catch (URISyntaxException e) {
            throw new SnippetProviderException(
                    "Error while loading the categories", e);
        }
    }

指定一种语言和一个类别,getSnippetInfo() 方法就会从在这个类别目录中找到的 snippets.info 文件装载一个 SnippetInfo 对象数组。这个 XML 文件包含这个类别中的代码片段的相关信息,比如名称、描述和变量。这个方法如下所示。

清单 13. SnippetFileProvider 的 getSnippetInfo() 和 getSnippet() 方法

                
    public SnippetInfo[] getSnippetInfo(String language, String category)
            throws SnippetProviderException {
        /* Dehydrate the snippet info from the filesystem */
        XMLDecoder decoder = null;
        SnippetInfo[] snippetInfo = null;

        try {
            decoder = new XMLDecoder(new BufferedInputStream(
                    new FileInputStream(buildSnippetInfoPath(getBaseUri(),
                            language, category))));
            snippetInfo = (SnippetInfo[]) decoder.readObject();

        } catch (FileNotFoundException e) {
            throw new SnippetProviderException(
                    "Could not load the snippet info index.", e);
        } finally {
            if (decoder != null) {
                decoder.close();
            }
            decoder = null;
        }

        return snippetInfo;
    }
    
    public Snippet getSnippet(SnippetInfo info) {
        Snippet snippet = null;

        String snippetPath = buildSnippetPath(getBaseUri(), info);

        /* Load the snippet from the file */
        BufferedInputStream stream = null;
        BufferedReader reader = null;
        try {
            stream = new BufferedInputStream(new FileInputStream(snippetPath));
            reader = new BufferedReader(new InputStreamReader(
                    stream));
            String line;
            StringBuffer sb = new StringBuffer();
            while ((line = reader.readLine()) != null) {
                sb.append(line);
                sb.append("/n");
            }
            
            snippet = new Snippet();
            snippet.setContent(sb.toString());
            
            sb = null;
            
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException ioe) {
            // TODO Auto-generated catch block
            ioe.printStackTrace();
        }
        finally
        {
            try {
                if (reader != null) {
                    reader.close();
                }
                if (stream != null) {
                    stream.close();
                }
            } catch (IOException ioe) {
            }
        }

        return snippet;
    }

最后,getSnippet() 方法(见上面的清单)从文件系统装载代码片段,并在一个 Snippet 对象中返回它。这个类中的其他方法是用于支持公共方法的私有方法。可以在本文提供的源代码中仔细研究这些方法。


回页首

填充视图

构建了从文件系统目录获取代码片段的框架之后,就应该在 SnippetView 类中添加适当的代码,以便在插件的视图中以树的形式显示片段信息。为了在视图中显示片段,修改 SnippetView 中内部类 ViewContentProviderinitialize() 方法。

initialize() 方法构建在视图中显示的条目树。当模板最初创建这个类时,它包含一些伪数据,所以在用 Eclipse 运行这个插件时看到了一些显示效果。如果您已经执行了前面的步骤,现在就该修改这个方法,让它使用 SnippetProvider 装载片段信息。

新的方法如下所示。

清单 14. 新的 SnippetsView initialize() 方法

                
  private void initialize() {

      invisibleRoot = new TreeParent("");

      /*
       * Get the high-level elements from the provider, which are the
       * languages.
       */
      String[] topLevelNodes;

      try {

          Properties properties = new Properties();

          InputStream is = null;

          is = SnippetProviderFactory.class
                  .getResourceAsStream("/SnippetProvider.properties");
          properties.load(is);

          if (is != null) {
              try {
                  is.close();
              } catch (IOException innerE) {
                  throw new SnippetProviderException(
                          "Could not close resource stream.", innerE);
              }
          }

          snippetProvider = SnippetProviderFactory.createInstance();
          snippetProvider.configure(properties);
          topLevelNodes = snippetProvider.getLanguages();

          for (int i = 0; i < topLevelNodes.length; i++) {
              TreeParent parent = new TreeParent(topLevelNodes[i]);

              /* Get the categories for each one of the parents */
              String[] categories = snippetProvider
                      .getCategories(topLevelNodes[i]);
              for (int j = 0; j < categories.length; j++) {
                  TreeParent categoryParent = new TreeParent(
                          categories[j]);

                  /* Now get the snippet names for the categories */
                  SnippetInfo[] info = snippetProvider.getSnippetInfo(
                          topLevelNodes[i], categories[j]);

                  for (int k = 0; k < info.length; k++) {
                      TreeObject leaf = new TreeObject(info[k]);
                      categoryParent.addChild(leaf);
                  }

                  parent.addChild(categoryParent);
              }

              invisibleRoot.addChild(parent);
          }

      } catch (SnippetProviderConfigurationException spce) {
          topLevelNodes = new String[] { "Configuration error:  "
                  + spce.getLocalizedMessage() };
      } catch (SnippetProviderException spe) {
          topLevelNodes = new String[] { "Error while loading snippets" };
      } catch (IOException ioe) {
          topLevelNodes = new String[] { "Error loading configuration properties:"
                  + ioe.getLocalizedMessage() };
      }

  }

新的代码使用 SnippetProviderFactory 动态地装载一个类,并立即将结果转换为 SnippetProvider 接口。这使视图无法知道片段的来源。获得提供者的接口之后,对提供者进行配置,让它知道在哪里获得代码片段结构。然后,为每种语言添加一个顶级节点。

繁琐吗?

查看 SnippetProvider 的实现之后,您很可能会觉得它太繁琐,因为它循环遍历每种语言或类别来获取条目,需要进行多次调用。比较简洁的实现是同时装载整个结构。这种方式可能更适合于 Web 服务实现。实现类可以自由选用性能和可伸缩性最好的方法。

然后,代码循环遍历各种语言,获取每种语言的类别并将它们添加到树中父节点下面。然后,对于每个类别,视图调用 getSnippetInfo() 方法获取类别中包含的代码片段的信息。它循环遍历数组并为每个 SnippetInfo 构建新的 TreeObject

关于新代码,一定要注意两点。首先,SnippetProviderFactory 装载一个默认的实现类。必须修改这个操作,插件才能够从不同的位置获得代码片段。第二,使用从一个文件装载的属性配置适配器。也必须修改这个操作,因为在发布这个插件时,用户需要能够修改属性。这两个设置是从首选项装载的。后面的 “添加用户首选项” 一节将讨论从首选项装载这些值的过程。

修改 TreeObject

为了获得在树视图中选择的片段的所有信息,需要将这些信息与 TreeObject 关联起来。这需要对 TreeObject 内部类做一些调整。需要将私有的 String 名称字段替换为一个包含 SnippetInfo 对象的私有字段。还需要修改构造器和 getName() 方法。

修改后的 TreeObject 类如下所示,修改的地方以粗体显示。

清单 15. 修改后的 TreeObject

                
    class TreeObject implements IAdaptable {
        private SnippetInfo info;

        private TreeParent parent;

        public TreeObject(SnippetInfo info) {
            this.info = info;
        }

        public String getName() {
            return info.getName();
        }

        public void setParent(TreeParent parent) {
            this.parent = parent;
        }

        public TreeParent getParent() {
            return parent;
        }

        public String toString() {
            return getName();
        }

        public SnippetInfo getInfo() {
            return info;
        }

        public Object getAdapter(Class key) {
            return null;
        }
    }

因为 TreeParent 对象扩展 TreeObject,所以也需要对它做两处修改。修改之处见下面的粗体代码。

清单 16. 修改后的 TreeParent

                
    class TreeParent extends TreeObject {
        private ArrayList children;

        private String name;

        public TreeParent(String name) {
            super(null);
            this.name = name;
            children = new ArrayList();
        }

        @SuppressWarnings("unchecked")
        public void addChild(TreeObject child) {
            children.add(child);
            child.setParent(this);
        }

        public void removeChild(TreeObject child) {
            children.remove(child);
            child.setParent(null);
        }

        @SuppressWarnings("unchecked")
        public TreeObject[] getChildren() {
            return (TreeObject[]) children.toArray(new TreeObject[children
                    .size()]);
        }

        public boolean hasChildren() {
            return children.size() > 0;
        }

        @Override
        public String getName() {
            // TODO Auto-generated method stub
            return this.name;
        }

        @Override
        public String toString() {
            return this.getName();
        }
    }

修改包括覆盖 getName()toString() 方法以及修改构造器,让它不把 null 传递给超类构造器并将名称赋值给新的私有 String 字段。

准备目录结构

在 Eclipse 中运行新的片段提供者实现之前,需要创建片段示例的存储库。幸运的是,很容易创建基于文件的存储库。可以自己构建基于文件的结构,也可以使用这里提供的示例。

要想自己创建基于文件的存储库,首先为语言建立一个或多个目录。在这些目录下面,为类别建立一个或多个目录。最后,在每个类别下,添加一个称为 snippets.info 的文件;在这个文件中添加清单 17 的内容。

清单 17. snippets.info 文件示例

                
<?xml version="1.0" encoding="UTF-8"?> 
<java version="1.5.0_07" class="java.beans.XMLDecoder"> 
 <array class="com.example.plugins.snippets.SnippetInfo" length="1"> 
  <void index="0"> 
   <object class="com.example.plugins.snippets.SnippetInfo"> 
    <void property="category"> 
     <string>Exception_Handling</string> 
    </void> 
    <void property="language"> 
     <string>Java</string> 
    </void> 
    <void property="name"> 
     <string>Exception class</string> 
    </void> 
    <void property="variables"> 
     <array class="java.lang.String" length="3"> 
      <void index="0"> 
       <string>exception.classname</string> 
      </void> 
      <void index="1"> 
       <string>author</string> 
      </void> 
      <void index="2"> 
       <string>package.name</string> 
      </void> 
     </array> 
    </void> 
   </object> 
  </void> 
 </array> 
</java> 

需要根据语言和类别更新每个文件。在添加这个文件之后,添加包含每个片段的文件。下面是目录结构的一个示例。

清单 18. 基于片段文件的存储库结构

                
+- basedir
 +- Java
 | +- Exception_Handling
 |   +- snippets.info
 |   +- Exception_Class.snippet
 +- XML
   +- Ant_Build_Files
     +- snippets.info
     +- Simple_File.snippet

查看修改的效果

创建了所有支持类和基于文件的代码片段存储库之后,现在可以启动一个嵌套的 Eclipse 实例,看看新的片段效果如何。如果一切正常,应该会看到与图 6 相似的视图。

图 6. 显示片段的视图
显示片段的视图

代码片段信息已经装载了,现在进行下一步:将 .snippet 文件的内容添加到编辑器中。


回页首

编写 InsertSnippetAction 类

既然 SnippetFileProvider 实现已经将片段信息装载进视图中,就可以使用片段信息从提供者获得代码片段,并将其插入编辑器中。片段信息存储在一个 SnippetInfo 实例中,这个实例是从 Example.com Snippets 视图中选择的条目获得的。

首先构建 InsertSnippetAction 内部类,这个类中的代码获取片段内容并执行插入。它扩展 Action 类,所以其他插件组件可以很容易地使用它。它是 SnippetsView 的内部类,因为它由视图私有地使用,并提供对视图中对象的访问。

新的 InsertSnippetAction 类如下所示,其中的 run() 方法显示一个消息框。这个消息框有助于确保新的 InsertSnippetAction 已经正确地构建并添加到适当的位置。

清单 19. InsertSnippetAction

                
class InsertSnippetAction extends Action {

    @Override
    public void run() {
        MessageDialog.openInformation(
            window.getShell(),
            "SnippetsSample Plug-in",
            "Running Insert Action Now!");
    }
}

修改 SnippetView

在最初从模板创建 SnippetView 时,它包含两个动作示例,可以从视图的菜单栏和弹出菜单访问这些动作。这些动作是局部动作,没有在 plugin.xml 文件中设置为扩展点。这是合适的,因为视图完全拥有这些动作,不向 IDE 的其他部分提供这些动作。

在构建 InsertSnippetAction 类之后,打开 SnippetsView 类并删除 action2doubleClickAction。目前保留 action1 — 可以使用 Eclipse 的重构工具将它重命名为 insertAction 并将类型从 Action 改为 InsertAction。因为 InsertAction 扩展 Action,所以不需要修改别的地方。清单 20 显示新的 SnippetsView 类,修改之处以粗体显示。

清单 20. SnippetsView

public class SnippetsView extends ViewPart implements ISelectionListener {
private TreeViewer viewer;

private DrillDownAdapter drillDownAdapter;

private InsertSnippetAction insertAction;

private SnippetProvider snippetProvider;

class InsertSnippetAction extends Action {
// Snipped...
}

class TreeObject implements IAdaptable {
// Snipped...
}

class TreeParent extends TreeObject {
// Snipped...
}

class ViewContentProvider implements IStructuredContentProvider,
ITreeContentProvider {
private TreeParent invisibleRoot;

public void inputChanged(Viewer v, Object oldInput, Object newInput) {
}

public void dispose() {
}

public Object[] getElements(Object parent) {
if (parent.equals(getViewSite())) {
if (invisibleRoot == n

抱歉!评论已关闭.