使用 Eclipse 向导进行快速开发

使用向导构建可重用过程

Eclipse 框架和集成开发环境 (IDE) 的最优秀特性之一是可扩展性。在本文中,您将了解如何快速构建用于自动完成添加新文件过程的向导。由于可以预定义文件的内容,因此向导通过提供一致性和自动化使您可以更好地进行开发。

Nathan A. Good, 作家兼软件工程师, Freelance Developer

Nathan A. Good 居住在明尼苏达州的双子城。其专职工作是软件开发、软件架构和系统管理。在不编写软件时,他喜欢组装 PC 和服务器、阅读和撰写技术文章,鼓励他的所有朋友转用开源软件。他自己编著以及与他人合著了很多书籍和文章,这其中包括 Professional Red Hat Enterprise Linux 3Regular Expression Recipes: A Problem-Solution ApproachRegular Expression Recipes for Windows Developers: A Problem-Solution ApproachPHP 5 Recipes: A Problem-Solution Approach 和最新的力作 Foundations of PEAR: Rapid PHP Development



2007 年 8 月 16 日 (最初于 2007 年 6 月 05 日)

入门

本文将演示如何使用向导把新文件添加到已有 Eclipse 项目中。当内置模板功能不足时,Eclipse 向导是定义可重复文件类型模板的优秀方法。阅读完本文后,您应当能够在 Eclipse 中实现自己的向导以创建新文件。

为了发挥本文的最大功效,您必须熟悉如何构建 Java™ 编程语言类,并且还应当熟悉继承和使用接口。您应当能够启动 Eclipse,但是本文假定您并不精通 Eclipse。

要运行本文的示例,需要:

Eclipse V3.2 或更高版本
虽然您可能在使用早期版本时获得了一些成功,但是本文中的代码都是使用 Eclipse V3.2.2 测试的,这是撰写本文时的最新官方发行版。
IBMSun JDK V1.5 或更高版本
本文是在 Mac OS® X V10.4.8 上使用 Java V1.5.0_07 创建的。您使用哪个操作系统并不重要,但是计算机上安装的 Java 环境的版本十分重要。建议使用 Java 5 版。

Eclipse 向导概览

我喜欢 Eclipse IDE 的很多特性。其中之一就是可扩展性。通过添加提供功能的插件(包括自动创建类、接口、项目、其他资源的向导),可以轻松地自定义 IDE。此特性对于大型企业来说非常重要,在这类企业中可以基于 Eclipse IDE 流线化地构建和分发插件,使许多人都可以自动利用功能。

在任何企业中,让团队以相同的样式构建应用程序可以提供许多优点。如果应用程序是以一致的样式构建的,则应用程序将更易于维护。一致性可以帮助减少错误。此外,由于应用程序是以相同的样式构建的,因此团队成员可以更轻松地完成一个又一个项目。

能够为 Eclipse 框架创建自定义向导使企业可以构建企业专有的向导,这些向导将给团队提供使用一致的最佳实践创建应用程序的机会。


创建新向导

本文中构建的自定义向导位于 Eclipse 的插件项目中。开始创建自定义向导十分简单,这要感谢为您开始编写代码提供前期帮助的其他向导。在接下来的步骤中,将使用 Plug-in Project 向导创建插件的开头部分。

要构建新插件项目,请执行以下步骤:

  1. 选择 File > New > Project 以选择项目向导。
  2. Plug-in Development 下选择 Plug-in Project,如下所示:
  3. 单击 Next 前进到下一步。
    图 1. 选择插件项目
    选择插件项目
  4. 添加项目名称(在本文中,该名称为 ExampleWizard(不是那么有创造性),如图 2 所示)。如果没有特殊原因,请使用默认位置。单击 Next
    图 2. 新插件项目
    新插件项目
  5. Plug-in Version 中输入版本号,并添加插件名称和插件提供者的名称(可能是您,也可能是协作的团队)。请一定要更新 Activator 的包名称,它默认为小写版本的项目名称。最好使用符合您公司标准的包名称:例如 com.example.eclipse.wizards。当您填写完下图中所示的信息后,单击 Next
    图 3. 选择插件项目
    选择插件项目
  6. 选择 Custom plug-in wizard,因为使用此选项可以让您对所包括的组件进行微调。如果您以前从未创建过新插件项目,那么现在最好看看其他模板的描述以了解可用信息。单击 Next
  7. 在 Template Selection 窗口中,单击 Deselect All 取消选中所有选项。然后,选择 New File Wizard,如下所示。单击 Next
    图 4. 选择模板
    选择模板
  8. Eclipse 向导将给您提示一些关于正在创建的新向导的信息(参见图 5)。确保更新包名称,理想情况下将其更新为 Activator 所使用的相同名称 (com.example.eclipse.wizards)。将 Wizard Category Name 更新为新向导文件夹的名称。该值的使用方法与 图 1 中的 Plug-in Development 类别相同。Wizard Class Name 是从 Wizard 继承的类的 Java 类名,该类将实现 INewWizard 接口。Wizard Page Class Name 将扩展 WizardPage 类。
    图 5. New Wizard Options 窗口
    New Wizard Options 窗口
  9. 单击 Finish。Eclipse 将给新项目添加必要的类和库。

虽然还没完成,但是已经有了很好的开端并且准备好开始在向导背后添加一些实现。


Wizard 类和 INewWizard 接口

现在项目中有三个类:NewXHTMLFileWizardNewXHTMLFileWizardPageActivator。下面的部分将处理 NewXHTMLFileWizard 类。该类如清单 1 所示,不过没有显示方法中的所有代码。

清单 1. NewXHTMLFileWizard 类
public class NewXHTMLFileWizard extends Wizard implements INewWizard {

    private NewXHTMLFileWizardPage page;
    private ISelection selection;

    public NewXHTMLFileWizard() {
        // snipped...
    }
    
    public void addPages() {
        // snipped...
    }

    public boolean performFinish() {
        // snipped...
    }
    
    private void doFinish(
        // snipped...
    }
    
    private InputStream openContentStream() {
        // snipped...
    }

    private void throwCoreException(String message) throws CoreException {
        // snipped...
    }

    public void init(IWorkbench workbench, IStructuredSelection selection) {
        // snipped...
    }
}

实现 INewWizard 接口必须使用最后一个方法 init()。接下来,本文将介绍此方法以及此模板中自动包括的其余方法。

addPages() 方法

addPages() 方法将把页面添加到向导中。清单 2 中所示的方法将把单个页面添加到向导 NewXHTMLFileWizardPage 中。

清单 2. addPages() 方法将把页面添加到向导中
    /**
     * Adding the page to the wizard.
     */

    public void addPages() {
        page = new NewXHTMLFileWizardPage(selection);
        // You can add more pages here...
        addPage(page);
    }

NewXHTMLFileWizardPage 类包含为用户提供指定页面名称功能的控件。您可以稍后把控件添加到页面中,使最终用户可以输入更多信息。

performFinish() 方法

当用户单击向导中的 Finish 按钮时将调用 performFinish() 方法。在执行一些检查之后,它将使用 IRunnableWithProgress 接口调用 doFinish() 方法。使用此接口意味着在执行 doFinish() 方法时(在本例中需要花很长时间运行)不必编写显示进度条的所有 UI 元素。下面完整地列出了该方法。

清单 3. performFinish() 方法
    /**
     * This method is called when 'Finish' button is pressed in
     * the wizard. We will create an operation and run it
     * using wizard as execution context.
     */
    public boolean performFinish() {
        final String containerName = page.getContainerName();
        final String fileName = page.getFileName();
        IRunnableWithProgress op = new IRunnableWithProgress() {
            public void run(IProgressMonitor monitor) throws InvocationTargetException {
                try {
                    doFinish(containerName, fileName, monitor);
                } catch (CoreException e) {
                    throw new InvocationTargetException(e);
                } finally {
                    monitor.done();
                }
            }
        };
        try {
            getContainer().run(true, false, op);
        } catch (InterruptedException e) {
            return false;
        } catch (InvocationTargetException e) {
            Throwable realException = e.getTargetException();
            MessageDialog.openError(getShell(), "Error", realException.getMessage());
            return false;
        }
        return true;
    }

doFinish() 方法

如下所示,doFinish() 方法将创建新文件并通过 IDE 中的编辑器打开新文件。将调用 openContentStream() 方法以获得给新文件填充内容的输入流。

清单 4. 初始的 doFinish() 方法
    /**
     * The worker method. It will find the container, create the
     * file if missing or just replace its contents, and open
     * the editor on the newly created file.
     */

    private void doFinish(
        String containerName,
        String fileName,
        IProgressMonitor monitor)
        throws CoreException {
        // create a sample file
        monitor.beginTask("Creating " + fileName, 2);
        IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
        IResource resource = root.findMember(new Path(containerName));
        if (!resource.exists() || !(resource instanceof IContainer)) {
            throwCoreException("Container \"" + containerName + "\" does not exist.");
        }
        IContainer container = (IContainer) resource;
        final IFile file = container.getFile(new Path(fileName));
        try {
            InputStream stream = openContentStream();
            if (file.exists()) {
                file.setContents(stream, true, true, monitor);
            } else {
                file.create(stream, true, monitor);
            }
            stream.close();
        } catch (IOException e) {
        }
        monitor.worked(1);
        monitor.setTaskName("Opening file for editing...");
        getShell().getDisplay().asyncExec(new Runnable() {
            public void run() {
                IWorkbenchPage page =
                    PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
                try {
                    IDE.openEditor(page, file, true);
                } catch (PartInitException e) {
                }
            }
        });
        monitor.worked(1);
    }

openContentStream() 方法

如下所示,openContentStream() 方法将返回包含生成的静态字符串作为模板一部分的 ByteArrayInputStream。对于本文,字符串将被替换为模板文件的内容。

此方法中的代码是首先必须更改的,这样才能在创建时允许把更多有用的内容添加到新文件中。

清单 5. openContentStream() 方法
    /**
     * Initialize file contents with a sample text.
     */

    private InputStream openContentStream() {
        String contents =
            "This is the initial file contents for *.html " +
            "file that should be word-sorted in the Preview " +
            "page of the multi-page editor";
        return new ByteArrayInputStream(contents.getBytes());
    }

添加基本内容

新文件的内容不使用静态字符串值,您可以使用 getResourceAsStream() 方法把文件的内容载入到 InputStream 中,并且 doFinish() 方法可以用它来填充新文件。请做出如下所示的修改。

清单 6. 从资源获得输入流
    /**
     * Initialize the file contents to contents of the 
     * given resource.
     */
    private InputStream openContentStream() {
        return this.getClass()
                    .getResourceAsStream("templates/index-xhtml-template.resource");
    }

index-xhtml-template.resource 文件中是有效的可扩展超文本标记语言(Extensible Hypertext Markup Language,XHTML)V1.0 Strict Web 页面。它有针对一组模拟企业样式表和 JavaScript 文件的一些基本标记和点。该文件列于清单 7 中。此文件与 NewXHTMLFileWizard 类在同一个包中,因此在本文中此文件位于 com.example.eclipse.wizards 包中。如果需要将文件放在其他包中,则可以像访问目录内的文件一样访问它(即,com.example.resources/com/example/resources)。

清单 7. index-xhtml-template.resource 文件
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>

  <title>This is an Example.com Web page</title>
  <link rel="stylesheet" href=
  "http://www.example.com/enterprise/styles/main.css" type=
  "text/css" />
  <script type="text/javascript" language="JavaScript" src=
  "http://www.example.com/scripts/main.js">
</script>
</head>

<body>
  <div id="search">
    <form id="searchForm" name="searchForm" action=
    "http://www.example.com/search.jsp">
      <input type="text" name="searchText" /> <input type="button"
      value="Search Example.com" />
    </form>
  </div>

  <div id="mainMenu">
    <p class="menu">Main menu</p>

    <ul class="mainMenu">
      <li><a href="#home">Home</a></li>

      <li><a href="#item1">Item 1</a></li>
    </ul>
  </div><!-- Put the body of your page here -->

  <div id="body"></div>
</body>
</html>

现在,您可以运行 Eclipse 插件来查看该插件的内容。


测试新向导

在 Eclipse 创建向导所使用的三个类之后,您可以在阅读本文的过程中随时启动另一个 Eclipse 实例来运行和测试插件。要启动插件项目,请在项目上右击,并选择 Run As > Eclipse Application,如图 6 所示。Eclipse 的新实例将启动。

图 6. 将项目作为 Eclipse 应用程序来运行
将项目作为 Eclipse 应用程序来运行

现在需要创建包含新文件的临时项目。项目的名称无关紧要 —— 诸如 “temp” 之类的名称即可。当新项目已在工作区中后,请通过选择 File > New > Other 来添加新模板。

如果一切按预期运行正常,则新类别将列于您为向导定义的类别下的 Select a Wizard 窗口中。我使用了 Example.com 企业模板,如下所示:

图 7. 使用新模板
使用新模板

当您完成向导的其余部分后,Eclipse 将创建包含定义内容的新文件。如果此简单功能就是模板所需的全部功能,则可以止于此处。但是,可能还需要提示用户提供一些用来整合文件内容的输入。


自定义向导页面

初始 NewXHTMLFileWizardPage 的表单中只有两个控件:一个用于容器(项目或文件夹)的名称,而另一个用于创建新文件时使用的名称。createControl() 方法(完整的方法代码如清单 8 所示)负责创建这些控件和将其添加到对话框中。

清单 8. createControl() 方法
    /**
     * @see IDialogPage#createControl(Composite)
     */
    public void createControl(Composite parent) {
        Composite container = new Composite(parent, SWT.NULL);
        GridLayout layout = new GridLayout();
        container.setLayout(layout);
        layout.numColumns = 3;
        layout.verticalSpacing = 9;
        Label label = new Label(container, SWT.NULL);
        label.setText("&Container:");

        containerText = new Text(container, SWT.BORDER | SWT.SINGLE);
        GridData gd = new GridData(GridData.FILL_HORIZONTAL);
        containerText.setLayoutData(gd);
        containerText.addModifyListener(new ModifyListener() {
            public void modifyText(ModifyEvent e) {
                dialogChanged();
            }
        });

        Button button = new Button(container, SWT.PUSH);
        button.setText("Browse...");
        button.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                handleBrowse();
            }
        });
        label = new Label(container, SWT.NULL);
        label.setText("&File name:");

        fileText = new Text(container, SWT.BORDER | SWT.SINGLE);
        gd = new GridData(GridData.FILL_HORIZONTAL);
        fileText.setLayoutData(gd);
        fileText.addModifyListener(new ModifyListener() {
            public void modifyText(ModifyEvent e) {
                dialogChanged();
            }
        });
        initialize();
        dialogChanged();
        setControl(container);
    }

必须先在文件的顶部声明新控件和其他控件,然后才可以将新控件添加到此方法中。

清单 9. 声明新控件
public class NewXHTMLFileWizardPage extends WizardPage {
 /* Newly added for the page title */
    private Text titleText;

    // the rest of the class...	
}

现在添加文本的 getter。NewXHTMLFileWizard 类将在构建新文件时使用此 getter。getTitle() 方法如下所示:

清单 10. getTitle() 方法
    /**
     * Gets the HTML title for the new file
     */
    public String getTitle() {
        return titleText.getText();
    }

修改 createControl() 方法以添加标题的新输入控件和标签。新代码如下所示:

清单 11. 修改后的 createControl() 方法
    /**
     * @see IDialogPage#createControl(Composite)
     */
    public void createControl(Composite parent) {
        Composite container = new Composite(parent, SWT.NULL);
        GridLayout layout = new GridLayout();
        container.setLayout(layout);
        layout.numColumns = 3;
        layout.verticalSpacing = 9;
        Label label = new Label(container, SWT.NULL);
        label.setText("&Container:");

        containerText = new Text(container, SWT.BORDER | SWT.SINGLE);
        GridData gd = new GridData(GridData.FILL_HORIZONTAL);
        containerText.setLayoutData(gd);
        containerText.addModifyListener(new ModifyListener() {
            public void modifyText(ModifyEvent e) {
                dialogChanged();
            }
        });

        Button button = new Button(container, SWT.PUSH);
        button.setText("Browse...");
        button.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                handleBrowse();
            }
        });
        label = new Label(container, SWT.NULL);
        label.setText("&File name:");

        fileText = new Text(container, SWT.BORDER | SWT.SINGLE);
        gd = new GridData(GridData.FILL_HORIZONTAL);
        fileText.setLayoutData(gd);
        fileText.addModifyListener(new ModifyListener() {
            public void modifyText(ModifyEvent e) {
                dialogChanged();
            }
        });
       
			 
        /* Need to add empty label so the next two controls
         * are pushed to the next line in the grid. */
        label = new Label(container, SWT.NULL);
        label.setText("");
        
        /* Adding the custom control here */
        
        label = new Label(container, SWT.NULL);
        label.setText("&Title:");
        
        titleText = new Text(container, SWT.BORDER | SWT.SINGLE);
        gd = new GridData(GridData.FILL_HORIZONTAL);
        titleText.setLayoutData(gd);
        titleText.addModifyListener(new ModifyListener() {
            public void modifyText(ModifyEvent e) {
                dialogChanged();
            }
        });
				

        /* Finished adding the custom control */
        
        initialize();
        dialogChanged();
        setControl(container);
    }

在将代码添加到 NewXHTMLFileWizard 类中之前,请通过执行先前概述的步骤测试到目前为止的更改。


验证用户输入

用户在新向导的 Title 字段中输入的文本将用作新 HTML 文件的 <title> 元素中的文本。出于本文的目的,该值是必需的,因此需要添加检查输入的代码以确保用户输入的内容是没有问题的。

dialogChanged() 方法的修改如下所示:

清单 12. 检验 dialogChanged() 中的输入
    /**
     * Ensures that both text fields are set.
     */

    private void dialogChanged() {
        IResource container = ResourcesPlugin.getWorkspace().getRoot()
                .findMember(new Path(getContainerName()));
        String fileName = getFileName();
       	 
        String titleText = getTitle();  

        if (getContainerName().length() == 0) {
            updateStatus("File container must be specified");
            return;
        }
        if (container == null
                || (container.getType() & (IResource.PROJECT | IResource.FOLDER)) == 0) {
            updateStatus("File container must exist");
            return;
        }
        if (!container.isAccessible()) {
            updateStatus("Project must be writable");
            return;
        }
        if (fileName.length() == 0) {
            updateStatus("File name must be specified");
            return;
        }
        if (fileName.replace('\\', '/').indexOf('/', 1) > 0) {
            updateStatus("File name must be valid");
            return;
        }
        int dotLoc = fileName.lastIndexOf('.');
        if (dotLoc != -1) {
            String ext = fileName.substring(dotLoc + 1);
            if (ext.equalsIgnoreCase("html") == false) {
                updateStatus("File extension must be \"html\"");
                return;
            }
        }
       	 
        if (titleText.length() ==0 )
        {
            updateStatus("Title must be specified");
            return;
        }
				
        
        updateStatus(null);
    }

完成这些更改后,如果不输入 Title 值,向导将提供错误消息。此外,Finish 按钮将被禁用,直至为 Title 指定了值为止。通过使用先前概述的步骤运行插件项目来检验此功能。


添加自定义内容

向导页面 NewXHTMLFileWizardPage 现在将捕捉用户输入的 HTML 标题的值,但是它尚未把该值合并到文件中。要开始将该值添加到文件中,首先需要编辑 index-xhtml-template.resource 文件使其包含该值的占位符。您可以将 <title> 元素更改为 <title>${title}</title>,这样可以更轻松地包含占位符。

修改 performFinish() 方法以从向导页面获得标题并将标题传递给 doFinish() 方法以及其余值。

清单 13. 最终的 performFinish() 方法
    /**
     * This method is called when 'Finish' button is pressed in the wizard. We
     * will create an operation and run it using wizard as execution context.
     */
    public boolean performFinish() {
        final String containerName = page.getContainerName();
        final String fileName = page.getFileName();
        final String title = page.getTitle();
        IRunnableWithProgress op = new IRunnableWithProgress() {
            public void run(IProgressMonitor monitor)
                    throws InvocationTargetException {
                try {
                    doFinish(containerName, fileName, title, monitor);
                } catch (CoreException e) {
                    throw new InvocationTargetException(e);
                } finally {
                    monitor.done();
                }
            }
        };
        try {
            getContainer().run(true, false, op);
        } catch (InterruptedException e) {
            return false;
        } catch (InvocationTargetException e) {
            Throwable realException = e.getTargetException();
            MessageDialog.openError(getShell(), "Error", realException
                    .getMessage());
            return false;
        }
        return true;
    }

接下来,略微修改 doFinish() 方法使其接受标题作为参数并将其传递给 openContentStream() 方法。

清单 14. 接受标题作为参数的最终 doFinish() 方法
    /**
     * The worker method. It will find the container, create the file if missing
     * or just replace its contents, and open the editor on the newly created
     * file.
     */
    private void doFinish(String containerName, String fileName, String title,
            IProgressMonitor monitor) throws CoreException {

        monitor.beginTask("Creating " + fileName, 2);
        IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
        IResource resource = root.findMember(new Path(containerName));

        if (!resource.exists() || !(resource instanceof IContainer)) {
            throwCoreException("Container \"" + containerName
                    + "\" does not exist.");
        }
        IContainer container = (IContainer) resource;

        final IFile file = container.getFile(new Path(fileName));
        try {

            InputStream stream = openContentStream(title);

            try {
                if (file.exists()) {
                    file.setContents(stream, true, true, monitor);
                } else {
                    file.create(stream, true, monitor);
                }
            } finally {
                stream.close();
            }

        } catch (IOException e) {
        }
        monitor.worked(1);
        monitor.setTaskName("Opening file for editing...");
        getShell().getDisplay().asyncExec(new Runnable() {
            public void run() {
                IWorkbenchPage page = PlatformUI.getWorkbench()
                        .getActiveWorkbenchWindow().getActivePage();
                try {
                    IDE.openEditor(page, file, true);
                } catch (PartInitException e) {
                }
            }
        });
        monitor.worked(1);
	}

最后,需要修改 openContentStream() 方法的很大一部分才能够将文件中的 $title 值替换为用户提供的值(参见清单 15)。在配有大量不同值的模板中,您可以使用更精确的解决方案,例如扩展 FilterInputStream 并替换一整组不同值的新类。

清单 15. 最终的 openContentStream() 方法
    /**
     * Initialize the file contents to contents of the given resource.
     */
    private InputStream openContentStream(String title) throws CoreException {

        
        final String newline = "\n"; // System.getProperty("line.separator");
        String line;
        StringBuffer sb = new StringBuffer();
        try {
            InputStream input = this.getClass().getResourceAsStream(
                    "index-xhtml-template.resource");
            BufferedReader reader = new BufferedReader(new InputStreamReader(
                    input));
            try {

                while ((line = reader.readLine()) != null) {
                    line = line.replaceAll("\\$\\{title\\}", title);
                    sb.append(line);
                    sb.append(newline);
                }

            } finally {
                reader.close();
            }

        } catch (IOException ioe) {
            IStatus status = new Status(IStatus.ERROR, "ExampleWizard", IStatus.OK,
                    ioe.getLocalizedMessage(), null);
            throw new CoreException(status);
        }

        return new ByteArrayInputStream(sb.toString().getBytes());
        

    }

openContentStream() 方法的功能现在不仅限于装入资源文件的内容和将其作为 InputStream 返回。新代码将迭代流,使用 InputStreamReader 读取流,并替换每行中的值 $title。结果将以 ByteArrayInputStream 的形式返回,这与首次生成 NewXHTMLFileWizard 类时使用的流对象相同。


创建新项目向导

如果您从头阅读了文本,则应当有一个在已有项目中创建新文件的向导。但是为什么不到此为止呢?因为在诸如 XHTML 文件之类的资源中企业可能需要遵循一些约定,项目布局可能也有相应的约定。

通过向已有项目中添加相对较少的内容,就可以构建将整个项目连同文件夹和一些初始文件添加到工作区中的向导。向导将为 Web 站点 Example.com 创建新文件夹并创建 images 文件夹和 styles 文件夹。在 styles 文件夹中,向导将创建名为 site.css 的层叠样式表(Cascading Style Sheet,CSS)文件。向导最后通过重用 NewXHTMLFileWizard 类中的方法来添加新的 XHTML 文件,该文件的初始名称是新项目的名称附带一些新文本。


构建新 NewSiteProjectWizard

由于有一个插件项目已经设置并正在运行,因此无需使用向导构建新类。相反,您自己可以通过创建扩展自 Wizard 并且实现两个接口 INewWizardIExecutableExtension 的新类来构建新向导。

NewSiteProjectWizard 新类添加到 NewXHTMLFileWizard 类所在的包中。查看清单 16 中的 NewSiteProjectWizard 类声明,并确保扩展 Wizard 类。同时添加 INewWizardIExecutableExtension 接口。

由于 NewSiteProjectWizard 类扩展同一个类并且实现由 NewXHTMLFileWizard 类实现的一个接口,因此如果将两者相比较,您会发现共有方法。清单 16 中显示了 NewSiteProjectWizard,这里为了简短起见省略了方法的内容(您将稍后在本文中看到这些内容)。

清单 16. NewSiteProjectWizard 类
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExecutableExtension;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.wizard.Wizard;
import org.eclipse.ui.INewWizard;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.actions.WorkspaceModifyOperation;
import org.eclipse.ui.dialogs.WizardNewProjectCreationPage;
import org.eclipse.ui.wizards.newresource.BasicNewProjectResourceWizard;

public class NewSiteProjectWizard extends Wizard implements INewWizard,
        IExecutableExtension {

    /*
     * Use the WizardNewProjectCreationPage, which is provided by the Eclipse
     * framework.
     */
    private WizardNewProjectCreationPage wizardPage;

    private IConfigurationElement config;

    private IWorkbench workbench;

    private IStructuredSelection selection;

    private IProject project;

    /**
     * Constructor
     */
    public NewSiteProjectWizard() {
        super();
    }

    public void addPages() {
        // snipped...
    }

    @Override
    public boolean performFinish() {
        // snipped...
    }

    /**
     * This creates the project in the workspace.
     * 
     * @param description
     * @param projectHandle
     * @param monitor
     * @throws CoreException
     * @throws OperationCanceledException
     */
    void createProject(IProjectDescription description, IProject proj,
            IProgressMonitor monitor) throws CoreException,
            OperationCanceledException {
        // snipped...
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.eclipse.ui.IWorkbenchWizard#init(org.eclipse.ui.IWorkbench,
     *      org.eclipse.jface.viewers.IStructuredSelection)
     */
    public void init(IWorkbench workbench, IStructuredSelection selection) {
        // snipped...
    }

    /**
     * Sets the initialization data for the wizard.
     */
    public void setInitializationData(IConfigurationElement config,
            String propertyName, Object data) throws CoreException {
        // snipped...
    }

    /**
     * Adds a new file to the project.
     * 
     * @param container
     * @param path
     * @param contentStream
     * @param monitor
     * @throws CoreException
     */
    private void addFileToProject(IContainer container, Path path,
            InputStream contentStream, IProgressMonitor monitor)
            throws CoreException {
        // snipped
    }
}

修改 plugin.xml

在添加新类之后在 Eclipse 中将其作为向导执行之前,需要对位于项目库中的 plugin.xml 文件进行一些更改。

清单 17. plugin.xml 文件
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.2"?>
<plugin>

   <extension
         point="org.eclipse.ui.newWizards">
      <category
            name="Example.com Enterprise Templates"
            id="ExampleWizard">
      </category>
      <wizard
            name="Example.com Static Web Page"
            icon="icons/sample.gif"
            category="ExampleWizard"
            class="com.example.eclipse.wizards.NewXHTMLFileWizard"
            id="com.example.eclipse.wizards.NewXHTMLFileWizard">
      </wizard>
      <wizard
            category="ExampleWizard"
            class="com.example.eclipse.wizards.NewSiteProjectWizard"
            icon="icons/sample.gif"
            id="com.example.eclipse.wizards.NewSiteProjectWizard"
            name="Example.com Static Web Site"
            project="true">
      </wizard>
   </extension>

</plugin>

更改 plugin.xml 的目的是让 Eclipse 知道 NewSiteProjectWizard 类是可以由 Eclipse 调用的向导。它也被归到先前讨论的 NewXHTMLFileWizard 类所在的类别下。project="true" 属性告诉 Eclipse 它是一个项目,因此它将被显示在相应的上下文中。

addPages() 方法

Eclipse API 包括一些向导类和向导页面类,如果您要执行基本功能并且不需要进行定制,则这些类将十分有用。从技术上讲,NewSiteProjectWizard 可以扩展 BasicNewProjectResourceWizard —— 用于创建基本项目的已有项目向导 —— 虽然设计者在 JavaDoc 中注明不适于创建该类的子类。要获得基本的项目信息(如项目名称),可以使用 BasicNewProjectResourceWizard 所使用的同一个向导页面 —— WizardNewProjectCreationPage 类,如下所示:

清单 18. addPages() 方法
   public void addPages() {
      /*
       * Unlike the custom new wizard, we just add the pre-defined one and
       * don't necessarily define our own.
       */
      wizardPage = new WizardNewProjectCreationPage(
            "NewExampleComSiteProject");
      wizardPage.setDescription("Create a new Example.com Site Project.");
      wizardPage.setTitle("New Example.com Site Project");
      addPage(wizardPage);
   }

此方法将创建页面类的新实例,设定描述和标题,然后将其添加为向导页面。

performFinish() 方法

NewXHTMLFileWizard 类一样,NewSiteProjectWizard 也有 performFinish() 方法(如清单 19 所示),该方法将在用户完成向导中的步骤并单击了 Finish 时执行。此方法将调用执行 createProject() 方法的过程,该方法将执行大部分繁琐的创建工作,如创建项目、文件夹和文件。

清单 19. performFinish() 方法
    @Override
    public boolean performFinish() {

        if (project != null) {
            return true;
        }

        final IProject projectHandle = wizardPage.getProjectHandle();

        URI projectURI = (!wizardPage.useDefaults()) ? wizardPage
                .getLocationURI() : null;

        IWorkspace workspace = ResourcesPlugin.getWorkspace();

        final IProjectDescription desc = workspace
                .newProjectDescription(projectHandle.getName());

        desc.setLocationURI(projectURI);

        /*
         * Just like the ExampleWizard, but this time with an operation object
         * that modifies workspaces.
         */
        WorkspaceModifyOperation op = new WorkspaceModifyOperation() {
            protected void execute(IProgressMonitor monitor)
                    throws CoreException {
                createProject(desc, projectHandle, monitor);
            }
        };

        /*
         * This isn't as robust as the code in the BasicNewProjectResourceWizard
         * class. Consider beefing this up to improve error handling.
         */
        try {
            getContainer().run(true, true, op);
        } catch (InterruptedException e) {
            return false;
        } catch (InvocationTargetException e) {
            Throwable realException = e.getTargetException();
            MessageDialog.openError(getShell(), "Error", realException
                    .getMessage());
            return false;
        }

        project = projectHandle;

        if (project == null) {
            return false;
        }

        BasicNewProjectResourceWizard.updatePerspective(config);
        BasicNewProjectResourceWizard.selectAndReveal(project, workbench
                .getActiveWorkbenchWindow());

        return true;
    }

performFinish() 方法将在调用 createProject() 创建文件和文件夹之后调用两个静态方法更新当前透视图并选择在 IDE 中新创建的项目。

createProject() 方法

清单 20 中所示的 createProject() 方法将创建并打开新项目。然后,方法将向项目中添加两个新文件和两个新文件夹。文件都是由名为 addFileToProject() 的私有方法添加的,该方法的作用是保持 createProject() 相对整洁。

清单 20. createProject() 方法
    /**
     * This creates the project in the workspace.
     * 
     * @param description
     * @param projectHandle
     * @param monitor
     * @throws CoreException
     * @throws OperationCanceledException
     */
    void createProject(IProjectDescription description, IProject proj,
            IProgressMonitor monitor) throws CoreException,
            OperationCanceledException {
        try {

            monitor.beginTask("", 2000);

            proj.create(description, new SubProgressMonitor(monitor, 1000));

            if (monitor.isCanceled()) {
                throw new OperationCanceledException();
            }

            proj.open(IResource.BACKGROUND_REFRESH, new SubProgressMonitor(
                    monitor, 1000));

            /*
             * Okay, now we have the project and we can do more things with it
             * before updating the perspective.
             */
            IContainer container = (IContainer) proj;

            /* Add an XHTML file */
            addFileToProject(container, new Path("index.html"),
                    NewXHTMLFileWizard.openContentStream("Welcome to "
                            + proj.getName()), monitor);

            /* Add the style folder and the site.css file to it */
            final IFolder styleFolder = container.getFolder(new Path("styles"));
            styleFolder.create(true, true, monitor);
            
            InputStream resourceStream = this.getClass().getResourceAsStream(
            "templates/site-css-template.resource");

            addFileToProject(container, new Path(styleFolder.getName()
                    + Path.SEPARATOR + "style.css"),
                    resourceStream, monitor);

            resourceStream.close();
            
            /*
             * Add the images folder, which is an official Exmample.com standard
             * for static web projects.
             */
            IFolder imageFolder = container.getFolder(new Path("images"));
            imageFolder.create(true, true, monitor);
        } catch (IOException ioe) {
            IStatus status = new Status(IStatus.ERROR, "ExampleWizard", IStatus.OK,
                    ioe.getLocalizedMessage(), null);
            throw new CoreException(status);
        } finally {
            monitor.done();
        }
    }

addFileToProject() 方法

您可能会发现 addFileToProject() 方法中的大部分代码基本上与清单 14 中所示的 doFinish() 方法中代码相同。方法的签名差别很大:它已被修改为接受参数以使其在 createProject() 方法的上下文内具有更好的可重用性。

清单 21. addFileToProject() 方法
    /**
     * Adds a new file to the project.
     * 
     * @param container
     * @param path
     * @param contentStream
     * @param monitor
     * @throws CoreException
     */
    private void addFileToProject(IContainer container, Path path,
            InputStream contentStream, IProgressMonitor monitor)
            throws CoreException {
        final IFile file = container.getFile(path);

        if (file.exists()) {
            file.setContents(contentStream, true, true, monitor);
        } else {
            file.create(contentStream, true, monitor);
        }

    }

如果文件已存在,则文件的内容将被设为传递到方法中的 contentStream 的内容。如果文件尚不存在,则以文件中的内容创建文件。

完整的 NewSiteProjectWizard 类包含在本文的下载中。在添加了此处的方法实现及 INewWizardIExecutableExtension 接口的实现后,您可以如先前所示把项目作为 Eclipse 应用程序来运行。这一次,除了可以用 NewXHTMLFileWizard 创建新文件之外,还可以创建新项目。


故障检修

当部署 wizard .jar 文件(此处称为 NewFileWizard_1.0.0.jar)时,将其放入 plugin 文件夹中。转到 plugin.xml 文件,在 Eclipse 中双击该文件,导航到 Overview 附签并单击右边的 Export Wizard 链接,从而创建 .jar 文件。然后将 .jar 移到 Eclipse plugin 文件夹中,不要对它解包。

如果启动 Eclipse 时遇到 “Plugin does not have a valid identifier” 或 “Plugin does not have a valid version” 错误,请尝试在命令行使用 -clean 参数启动 Eclipse。

如果遇到错误,可以在调试时把项目作为 Eclipse 插件来运行。我喜欢使用 Eclipse IDE 中的 Debug Perspective。选择 Run > Debug Last Launched 在逐步浏览代码时开始运行 Eclipse 中的插件。

您很有可能需要在 performFinish() 方法的第一行中设置一个断点,因为那是操作的起点。一旦您单击了向导中的 Finish,调试器就应当停在断点处(只要错误不出现在断点之前)。

结束语

Eclipse IDE 最优秀的特性之一是通过创建用于添加创建新文件的新向导的插件轻松地提供扩展功能。使用企业专有的向导来创建文件将使企业可以快速一致地开发应用程序。


下载

描述名字大小
代码os-eclipse-custwiz_NewFileWizard.zip12KB

参考资料

学习

获得产品和技术

讨论

  • Eclipse Platform 新闻组 应当是讨论关于 Eclipse 的问题的第一站(选择此链接将启动默认的 Usenet 新闻阅读器应用程序并打开 eclipse.platform)。
  • Eclipse 新闻组 中有很多参考资料适用于对使用和扩展 Eclipse 感兴趣的人员。
  • 参与 developerWorks blog 并加入 developerWorks 社区。

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Open source
ArticleID=231784
ArticleTitle=使用 Eclipse 向导进行快速开发
publish-date=08162007