Eclipse ウィザードを使って開発を高速化する

ウィザードを使って再利用可能な手順を確立する

Eclipse のフレームワークと IDE (integrated development environment: 統合開発環境) の持つ最も優れた特徴の 1 つが、その拡張性です。この記事では、新しいファイルの追加手順を自動化するウィザードを素早く構築する方法を学びます。ウィザードを使うと、作成するファイルの内容を事前定義できるため、これにより開発手順が自動化され、なおかつ手順の一貫性が保たれるようになります。

Nathan A. Good, Author and Software Engineer, Alliance of Computer Professionals

Nathan A. Good はミネソタ州の Twin Cities エリアに住んでいます。彼はソフトウェアを書いている時以外は、PC やサーバーを構築し、新しい技術について資料を読み、そうした技術に取り組み、そして彼の友人達にオープソース・ソフトウェアに移行するように勧めて回っています。コンピューターに向かっていない時には (実はそういう時が多いのですが)、家族と時間を過ごしたり、教会に行ったり、そして映画を見に行ったりしています。



2007年 6月 21日 (初版 2007年 6月 05日)

はじめに

この記事では、ウィザードを使って既存の Eclipse プロジェクトに新しいファイルを追加する方法について解説します。Eclipse ウィザードは、ファイルの作成にあたって組み込みのテンプレート機能では不十分な場合に、さまざまなファイル・タイプに対して繰り返し使用可能なテンプレートを定義するのに非常に適しています。この記事を通じて、Eclipse で新しいファイルを作成するための、皆さん独自のウィザードを実装できるようになるはずです。

この記事を最大限に活用するための前提として、Java™ プログラミング言語におけるクラスの作成に慣れていることに加え、継承や、インターフェースの使い方にも慣れている必要があります。Eclipse の起動方法を知っている必要はありますが、この記事は特にEclipse の熟練者向けというわけではありません。

この記事で取り上げる例を実行するためには、下記が必要です。

Eclipse V3.2 以上
以前のバージョンでも動作するかもしれませんが、この記事のコードは、この記事の執筆時点で最新の正式リリースであった Eclipse V3.2.2 でテストされています。
IBM あるいは Sun の JDK V1.5 以上
この記事のコードは Mac OS® X V10.4.8 上の Java V1.5.0_07 で作成されました。使用するオペレーティング・システムは重要ではありませんが、マシンにインストールされた Java 環境のバージョンは重要です。私は Java 5 バージョンを使用することをお勧めします。

Eclipse ウィザードの概要

私が気に入っている Eclipse IDE の特徴はたくさんありますが、そのうちの 1 つが拡張性です。この IDE は、ある機能を提供するプラグインを追加することで容易にカスタマイズすることができます。そうしたプラグインには、クラスやインターフェース、プロジェクト、その他のリソースの作成を自動化するウィザードも含まれます。これは大企業にとっては魅力的で、Eclipse IDE 用プラグインの作成や配布を、効率化された作業手順として確立しておくことにより、多くの人達がプラグインの機能を自動的に利用できるようになります。

どのような企業であっても、さまざまなチームが同じ方法でアプリケーションを作成できる手順を使用することには、多くの利点があります。アプリケーションが一貫した方法で作成されれば、それらの維持管理はずっと楽になります。一貫性を保つことは誤りを減らすためにも役立ちます。また、アプリケーションは同じ方法で作成されるため、チームのメンバーがプロジェクトからプロジェクトへとスムーズに移っていくことができます。

Eclipse フレームワーク用のカスタム・ウィザードを作成できると、企業はその企業独自のウィザードを作成することができます。そうしたウィザードによって、さまざまなチームが一貫してベスト・プラクティスに沿ったアプリケーションの作成を行うことができるようになります。


新しいウィザードを作成する

カスタム・ウィザードは、Eclipse のプラグイン・プロジェクト内に作成していきます。他のウィザードによりある程度ベースとなるコードが提供されるおかげで、簡単にカスタム・ウィザードを作り始めることができます。次のステップでは、Plug-in Project Wizard を使ってプラグイン作成の導入部分とも言える作業を行っていきます。

新しいプラグイン・プロジェクトを作成するための手順は下記の通りです。

  1. File > New > Project を選択し、プロジェクト・ウィザードを選択します。
  2. Plug-in Developmentを展開し、下記のようにPlug-in Projectを選択します。
  3. Next をクリックして次のステップに進みます。
    図 1. プラグイン・プロジェクトを選択する
    Select a plug-in project
  4. プロジェクト名を追加します (この記事では、図 2 に示すように、あまり創造的ではない ExampleWizard という名前です)。Location は、特に変更する理由がない限り、デフォルトのままにしておきます。Next をクリックします。
    図 2. 新しいプラグイン・プロジェクト
    New plug-in project
  5. バージョン番号を Plug-in Version に入力し、そしてプラグインの名前と、プラグインの提供者 (おそらく、皆さんか、あるいは皆さんのチーム) の名前を追加します。必ず Activator, のパッケージ名 (デフォルトで小文字のプロジェクト名になっています) を変更してください。この場合、皆さんの会社の標準に従ったパッケージ名を使うのが良いでしょう (例えば com.example.eclipse.wizards など)。下記のように情報を入力したら、Next をクリックします 。
    図 3. プラグイン・プロジェクトを選択する
    Select a plug-in project
  6. Custom plug-in wizard を選択します。このオプションを選択すると、プロジェクトに含めるコンポーネントを細かく指定することができます。今までに新しいプラグイン・プロジェクトを作成したことがない人は、どんなものが利用できるのか、この機会に他のテンプレートの記述を見てください。Next をクリックします。
  7. Template Selection ウィンドウで Deselect All をクリックし、すべてのオプションの選択を外します。次に、下記のように New File Wizard を選択し、Next をクリックします。
    図 4. テンプレートを選択する
    Choose a template
  8. Eclipse ウィザードが、作成しようとする新しいウィザードに関するいくつかの情報の入力を要求してきます (図 5)。パッケージ名は必ず変更してください。できれば Activator に使用したものと同じ名前 (com.example.eclipse.wizards) にするのが望ましいでしょう。Wizard Category Name を、新しいウィザード用のフォルダーの名前に変更します。この値は、図 1で見た Plug-in Development カテゴリーのように使われます。Wizard Class Name は、Wizard を継承して INewWizard インターフェースを実装するクラスにつける Java クラス名です。Wizard Page Class NameWizardPage クラスを継承します。
    図 5. New Wizard Options ウィンドウ
    New Wizard Options window
  9. Finish をクリックします。Eclipse は、新しいプロジェクトを作成し、中に必要なクラスとライブラリーを追加します

これで終わりというわけではありませんが、スタートとしては十分です。以上で、ウィザードに少しばかり実装を追加する作業の開始準備が整ったことになります。


Wizard クラスと INewWizard インターフェース

今、プロジェクトの中には、NewXHTMLFileWizardNewXHTMLFileWizardPage 、そして Activator という 3 つのクラスがあります。以下のセクションでは、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...
    }
}

最後のメソッド init() は、INewWizard インターフェースを実装するために必要です。次に、この記事では、このインターフェースと、このテンプレートに自動的に含まれる、それ以外のメソッドについて説明します。

addPages() メソッド

addPages() メソッドは、ウィザードにページを追加します。このメソッド (リスト 2) はウィザードに NewXHTMLFileWizardPage という 1 つのページを追加します。

リスト 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() メソッド

performFinish() メソッドは、ユーザーがウィザードの Finish ボタンをクリックすると呼び出されます。このメソッドはいくつかチェックを行った後、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 ファイルの中に、有効な XHTML (Extensible Hypertext Markup Language) V1.0 Strict Web ページがあります。このページはいくつかの基本的なタグを含み、エンタープライズ・スタイルシートと JavaScript ファイルの疑似セットを指しています。このファイルをリスト 7 に示します。このファイルは NewXHTMLFileWizard クラスと同じパッケージの中にあるため、この記事の場合、ファイルの場所は com.example.eclipse.wizards パッケージとなります。このファイルを他のパッケージに置きたい場合には、このファイルがディレクトリー内のファイルであるかのようにアクセスします (つまり ccom.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 プラグインを実行して、その様子を確認することができます。


新しいウィザードをテストする

ウィザードの中で使用される 3 つのクラスが (Eclipse により) 作成されていれば、それ以降この記事のどの時点でも、Eclipse のインスタンスを別途起動して、その中でプラグインを実行し、テストすることができます。プラグイン・プロジェクトを起動するためには、そのプロジェクトを右クリックし、Run As > Eclipse Application を選択します (図 6)。すると Eclipse の新しいインスタンスが起動します。

図 6. プロジェクトを Eclipse アプリケーションとして実行する
Run the project as an Eclipse application

今度は、一時的に使用するプロジェクトを作成する必要があります (このプロジェクトの中に、ウィザードを使って新しいファイルを作成します)。プロジェクトの名前はどんなものでもよく、—例えば「temp」のようなもので構いません。新しいプロジェクトがワークスペースに作成できたら、File > New > Other を選択して新しいテンプレートを追加します。

すべてが想定通りに動作していれば、Select a wizard ウィンドウには、ウィザードに対して定義したカテゴリーの下に新しいウィザードがリストされます。私は下記のように Example.com Enterprise Templates としました。

図 7. 新しいテンプレートを使う
Use the new template

このウィザードの残りのステップも完了させると、Eclipse は先ほど定義した内容を含む新しいファイルを作成します。もしテンプレートが必要とする機能が、この単純な機能のみである場合には、ここで終わりにすることができます。一方で、さらにユーザーに入力を促し、その入力をファイルの内容に反映させることもできます。


ウィザード・ページをカスタマイズする

オリジナルの NewXHTMLFileWizardPage には、フォームに対して 2 つのコントロールしかありません。1 つはコンテナー (プロジェクトあるいはフォルダー) の名前に対するコントロールであり、もう 1 つは作成される新しいファイルの名前に対するコントロールです。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...	
}

今度はテキストに対するゲッターを追加します。NewXHTMLFileWizard クラスは、新しいファイルを作成する際に、このゲッターを使います。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 の値が入力されていない場合には、エラー・メッセージを表示します。また、Title の値が指定されるまで Finish ボタンは無効になります。先ほど概要を説明した手順に従ってプラグイン・プロジェクトを実行し、この機能を確認します。


カスタム・コンテンツを追加する

ウィザード・ページ 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);
	}

最後に、ファイルの中にある $title の値をユーザーが提供する値で置き換えられるように、openContentStream() メソッドを大幅に修正する必要があります (リスト 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 クラスが最初に生成された時に使われたものと同じストリーム・オブジェクトです。


新しいプロジェクト・ウィザードを作成する

スレッド・アクセス

私が作成した最初のコードは、ウィザードで Finish をクリックすると Invalid thread access エラーを起こしました。私は openContentStream() メソッドから直接 page.getTitle() を呼び出していたのですが、これは有効ではありません。NewXHTMLFileWizard が決定した値を、performFinish() メソッドの op オブジェクトに渡すようにする必要があったのです。

これまでの手順に従っていれば、既存のプロジェクトの中に新しいファイルを作成するウィザードが作成されているはずです。しかし、そこでやめる必要はありません。多くの企業では XHTML ファイルのようなリソースに関して従うべき規則がありますが、同様にプロジェクトの構成に関しても、おそらく何らかの規則があるはずです。

既存のプロジェクトに比較的わずかな追加をするだけで、フォルダーやいくつかの初期ファイルを含むプロジェクトをワークスペースに追加するウィザードを作成することができます。このウィザードは、Web サイトである Example.com に対する新しいフォルダーを作成し、そして画像とスタイル用のフォルダーを作成します。ウィザードはスタイル・フォルダーの中に、site.css という CSS (Cascading Style Sheets) ファイルを作成します。ウィザードは、NewXHTMLFileWizard クラスのメソッドを再利用して新しい XHTML ファイル (タイトルは新しいプロジェクトの名前にテキストを少し加えたものに初期化されています) を追加し、終了します。


新しい NewSiteProjectWizard クラスを作成する

この状態で既にプラグイン・プロジェクトが設定され、動作しているので、ウィザードを使って新しいクラスを作成する必要はありません。ウィザードを使う代わりに、Wizard を継承して INewWizardIExecutableExtension という 2 つのインターフェースを実装する新しいクラスを作成することで、皆さん自身が新しいウィザードを作成することができます。

新しい NewSiteProjectWizard クラスを、NewXHTMLFileWizard クラスと同じパッケージの中に追加します。リスト 16 で、 NewSiteProjectWizardクラスの宣言部分を見てください。このように、必ず Wizardクラスを継承するようにします。また INewWizard インターフェースと IExecutableExtension インターフェースも追加します

NewSiteProjectWizard クラスは NewXHTMLFileWizard クラスと同じクラスを継承し、また NewXHTMLFileWizard クラスが実装するインターフェースの 1 つを実装するので、この 2 つを比較すると共通のメソッドに気付くはずです。リスト 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 に加えた変更により、新しい NewSiteProjectWizard クラスがウィザードであり、Eclipse がこれを呼び出せるということを Eclipse は識別できるようになります。またこのクラスは、先ほど説明した NewXHTMLFileWizard クラスと同じカテゴリーに記載されます。属性 project="true" によって、これがプロジェクトであることが Eclipse に伝わり、適切に表示されます。

addPages() メソッド

Eclipse API には、基本的な機能を実行するのみで大量のカスタマイズが必要ない場合に便利ないくつかのウィザード・クラスとウィザード・ページ・クラスが含まれています。技術的には、NewSiteProjectWizardBasicNewProjectResourceWizard (基本的なプロジェクトの作成に使用される既存のプロジェクト・ウィザード) を継承することができます。しかし、このクラスの 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);
   }

このメソッドは、ページ・クラスの新しいインスタンスを作成し、説明とタイトルを設定して、それを 1 つのウィザード・ページとして追加します。

performFinish() メソッド

NewXHTMLFileWizard クラスと同様、NewSiteProjectWizard クラスには、ユーザーがウィザードでのステップを完了して Finish をクリックすると実行される performFinish() メソッド (リスト 19) があります。performFinish() メソッドは createProject() メソッドを呼び出す処理を実行しており、この 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() を呼び出した後、2 つの static メソッドを呼び出すことにより、現在のパースペクティブが更新されて、新しく作成したプロジェクトが IDE の中で選択されるようにします。

createProject() メソッド

リスト 20 に示す createProject() メソッドは、新しいプロジェクトを作成し、そしてその新しいプロジェクトを開きます。次にこのメソッドは、2 つの新しいファイルと 2 つの新しいフォルダーをプロジェクトに追加します。これらのファイルは、addFileToProject() という private メソッドによって追加されます (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() メソッド

You may recognize most of the code in the addFileToProject() method as being essentially the same as that in the doFinish() method shown in Listing 14. The signature of the method is quite different: It's been modified to accept parameters to make it more reusable within the context of the createProject() method. 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 の内容が設定されます。ファイルがまだ存在していない場合には、contentStream の内容を含んだファイルが作成されます。

完全な NewSiteProjectWizard クラスは、この記事の「ダウンロード」セクションから入手できます。ここで示したメソッド実装と、INewWizard インターフェースと IExecutableExtension インターフェースの実装を追加すると、このプロジェクトを先ほど示したように Eclipse アプリケーションとして実行することができます。今度は、NewXHTMLFileWizard を使って新しいファイルを作成する他、新しいプロジェクトを作成することもできるようになっています


トラブルシューティング

ウィザードの .jar ファイル (ここでは NewFileWizard_1.0.0.jar ) をデプロイする場合は、.jar ファイルをご自分の環境のプラグイン・フォルダーに置くようにして下さい。.jar ファイルを作成するには、Eclipse で plugin.xml ファイルをダブルクリックし、Overview タブにナビゲートして、右手の Export Wizard リンクをクリックします。そして .jar ファイルを Eclipse プラグイン・フォルダーに移動しますが、解凍してはいけません。

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 の最も優れた特徴の 1 つは、新しいファイルを作成するための新しいウィザードを追加するプラグインを作成することで、機能を容易に拡張できることです。ファイルの作成に企業独自のウィザードを使うことによって、一貫性のある迅速なアプリケーション開発を行うことができます。


ダウンロード

内容ファイル名サイズ
Codeos-eclipse-custwiz_NewFileWizard.zip12KB

参考文献

学ぶために

  • David Gallardo による「Eclipseプラグインの開発」を読んでください
  • Eclipse オススメ情報リスト」を調べてみてください。
  • developerWorks には他にも Eclipse に関する資料が豊富に用意されています。
  • Eclipse が初めての人は、Eclipse project resources の Start Here を見るとよいでしょう。
  • IBM developerWorks の Eclipse project resources を利用して Eclipse のスキルを磨いてください。
  • developerWorks podcasts では、ソフトウェア開発者のための興味深いインタビューや議論を聞くことができます。
  • Eclipse プラットフォームへの入門として、「Eclipse Platform 入門」を読んでください。
  • developerWorks の Technical events and webcasts で最新情報を入手してください。
  • IBM のオープンソース技術や製品機能を調べ、学ぶために、無料の developerWorks On demand demos をご覧ください。
  • IBM オープンソース開発者にとって関心のある、世界中で今後開催される会議や業界展示会、ウェブキャスト、その他のEventsについて調べてみてください。
  • developerWorks の Open source ゾーンをご覧ください。オープンソース技術を使った開発や、IBM 製品でオープンソース技術を使用するためのハウ・ツー情報やツール、プロジェクトの更新情報など、豊富な情報が用意されています。

製品や技術を入手するために

  • Eclipse.org を訪れ、Eclipse をダウンロードしてください。
  • Eclipse Plug-in Central には他にも Eclipse のためのプラグインがあります。
  • alphaWorks に用意された最新の Eclipse technology downloads を調べてください。
  • IBM 製品の評価版をダウンロードし、DB2® や Lotus®、Rational®、Tivoli®、WebSphere® などのアプリケーション開発ツールやミドルウェア製品を試してみてください。
  • 皆さんの次期オープンソース開発プロジェクトを IBM trial software を使って革新してください。ダウンロード、あるいは DVD で入手することができます。

議論するために

  • Eclipse に関する質問を議論するための最初の場所として、Eclipse Platform newsgroups があります (このリンクをクリックすると、デフォルトの Usenet ニュース・リーダー・アプリケーションが起動し、eclipse.platform が開きます)。
  • Eclipse newsgroups には、Eclipse を利用し、拡張することに関心を持つ人達のために、さまざまなリソースが用意されています。
  • developerWorks blogs から developerWorks のコミュニティーに加わってください。

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Open source
ArticleID=237804
ArticleTitle=Eclipse ウィザードを使って開発を高速化する
publish-date=06212007