EclipseでのJava Development Toolsの拡張

何ができ、どこから始め、どうやって進めていくのか?

EclipseのJava開発環境におけるリファクタリング機能は、提供されている機能の中で最も役に立つものの1つです。この記事では、Eclipseでの拡張に際してリファクタリングを作成するためのステップについて手ほどきをいたします。なお、この記事に提供されているソリューションの一部は、最近出版された本「The Java Developer's Guide to Eclipse 」からの抜粋です。

Dan Kehn, Senior Software Engineer, IBM

Dan Kehnは、ノース・カロライナ州Research Triangle Parkで勤務するIBMのシニア・ソフトウェア・エンジニアです。彼は今日では幅広く受け入れられているオブジェクト指向のプログラミングに1985年より関心を持っています。彼は豊富なソフトウェア経験を持っており、VisualAge for Smalltalkのようなツールの開発やオペレーティング・システムやメモリー分析、ユーザー・インターフェースの設計などに従事してきました。Danはヨーロッパで4年間さらにU.S.の至る所でオブジェクト指向開発プロジェクトのコンサルタントとして働いてきました。彼は最近、オブジェクト指向の分析/設計、アプリケーション開発ツール、WebSphere Application ServerでのWebプログラミングに興味を持っています。2001年5月には、Eclipse Jumpstartチームに加わりました。チームの主な目的は、Eclipse Platformベースの製品を開発するISVの支援です。彼とJumpstartチームのメンバーで、この記事でソリューションを抜粋している「The Java Developer's Guide to Eclipse 」を執筆しました。



2003年 7月 22日

Eclipseは、強力なJava開発環境のため、多くの賞賛を浴びてきました。Java開発者にとって大きなニュースは、Eclipseの開発環境にチーム開発環境や他の基本機能が結び付けられて、注目せずにはいられないような統合開発環境となったことです。さらに、Eclipseはオープンソース・プロジェクトでもあります。しかし、Eclipseを本当に面白くしているものはEclipseが提供している拡張可能性です。

Eclipseベースの市販の製品の多くは、同様の方法で出荷された統合製品と密接な関係にあります。例えば、IBM WebSphere Application DeveloperやRational XDEは、Eclipseが既に持っていた影響力を証明しています。これらの製品および他のEclipseベースの製品は似たようなユーザー・インタフェースなのでユーザーが取り組みやすくなっています。確かに、これは大きなソフトウェア・ハウスにとっては有益なことですが、「普通の人」にとってはどんな利点があるのでしょうか?

ここがEclipseの拡張性の話が面白くなってくる所です。大きな開発組織を持っている人々だけでなく、いつか少しEclipseフレームワークを学習しようとしている人にも面白くなってくる所です。みなさんは「うわぁ。これ以上フレームワークを学習する時間はない。」と思っているかもしれません。心配しないでください。これは簡単なことです。この記事は取るに足りない「hello world」的なEclipseの拡張ではありません。ですからご安心ください。EclipseのJava開発環境での生産性の向上という実際の価値および明瞭なデモンストレーションをご覧になられるはずです。びっくりするようなことをするために必要なコードが、数十行であることが分かって、少し驚くことになるかもしれません。

この記事では、何ができ、どこから始めればよいのかを解説しています。そして、みなさんは最終的には真価を認めることなるでしょう。Eclipseの拡張は高度なテーマですが、EclipseのJava開発環境を使用する方法について多少の知識があれば始めることができます(そして一層の研究のため 参考文献 で推奨されている文献を参照してください)。

メンバーの可視性の簡単なリファクタリング

最初にコードを書く場合、私はメソッドの可視性が、default(package)、private、public、protectedのいずれに分類するべきであるかについてはあまり心配していません。私はメソッドを作成する際、すべてをpublicにします。一度パッケージの構成をファイナライズして、(既存のコードから新しいメソッドを抽出するか、階層のメソッドをプル・アップかプッシュダウンするか、別のクラスにそれらを完全に移動させるかによって)リファクタリング・メソッドを終了してから、メソッドの可視性を検討します。私はfinal classを把握し、コードの実際の使い方を少しでも分かるまでは、「クライアント」が何を必要とするかを宣言したくないと考えています。言いかえると、新しいフレームワークを共有する前までに、実装の詳細および拡張のために必要となることを決めておかなければなりません。

もしOutlineビューHierarchyビューでメソッドを選択することができるか、どこからでもメソッドの参照が可能でメニューを選択すると望ましい可視性の1つ以上のメソッドをセットできれば便利なことでしょう。正直なところ、私はVisualAge for Smalltalkの頃からこの機能に慣れているのです。図1は、JavaエディターのOutlineビューのコンテキストにおけるEclipseのJava開発環境の拡張を示しています。

図1. メソッドのコンテキストメニューの拡張
図1. メソッドのコンテキストメニューの拡張

ユーザー・インタフェースへ自然に導入されているために、ユーザーからはこれが拡張であるとは分かりにくいです。これらの新しいメニューが、EclipseのオリジナルのJava Development Tools(JDT)ではないとはほとんど分かりません。実際には、「soln」の後にメニューがカスケード表示されているので、みなさんにはこれが拡張であることがお分かりになるでしょう。その上、このメニューはメソッドが表示されるところならどんな場所でも表示されるので、開発者はこのメニューが特別のビューかエディターにおいて利用可能であることを覚えておく必要はありません。


「Hello World」の簡単なガイドツアー

「ちょっと待ってください。『Hello, World』はやらないと約束したじゃないですか!」そのとおりです。しかし、本当に面白いことを実現する前に少しEclipseの基盤に関して学んでおく必要があります。したがって、Eclipseを拡張したことがない方は、Eclipseのアーキテクチャーおよびプラグ・イン開発環境のクイックツアーを体験してみてください。既に経験のある方は、ここを 飛ばして読んでもらっても かまいません。ではツアーのはじまりです!

本来、Eclipseはコードの断片が内部的に緩やかに結合した集まりです。これらのコードの断片を「見出す」方法、そしてそれらを互いに見出して拡張する方法が、Eclipseアーキテクチャーの根本原理を占めています。

図2. Eclipseプラットフォームのアーキテクチャー
図2. Eclipseプラットフォームのアーキテクチャー

拡張 vs 拡張ポイント

拡張と拡張ポイントのXMLタグが全く同じであることに気づいてください。拡張ポイント は、他のプラグインに対するプラグイン機能のアベイラビリティーを 宣言 し、 <extension-point> タグで表示されます。拡張 は拡張ポイントを 使用 して、使用したい拡張ポイントを指定するポイント属性をもつ <extension> タグによって表示されます。

これらの機能はプラグイン と呼ばれます。図2のEclipseのPlatform Runtimeは、plugin.xmlというファイルでplug-inマニフェスト と呼ばれているプラグインの宣言を見つける働きをします。それぞれのplugin.xmlはpluginsというEclipseのインストール・ディレクトリー(厳密には<inst_dir>¥eclipse¥plugins)の共通ディレクトリー配下のサブディレクトリーに置かれます。Platform Runtimeはスタートアップ時に、これらのファイルからプラグイン・レジストリー と呼ばれるグローバルなメモリー内レジストリーを作成します。このレジストリーからプラグインは他のプラグインが拡張を望んでいるかを、実行時に判断することができます。拡張ポイント を宣言することと、他のプラグインが拡張できるようになります。拡張宣言によって、他のプラグインが利用できるようになるので、これはプラグインのある種の「サージ・プロテクター」です。

今回の例に戻ると、ここでのミッションは私たちのニーズに合う適切な拡張ポイントを見つけることで、Eclipseに「plugを挿入する」場所を決定することです。幸運にも、Eclipseを使用したことがあれば、おそらくそのことを明確に理解していなくとも、何が利用可能かについてはよく知っているはずです。これは、Eclipseユーザー・インタフェースとEclipseプラグインを構築するクラスによってモデル化されるものが、ほぼ1対1で互いに一致するためです。図3はこのポイントを明らかにしています。

図3.ビューとモデル
図3.ビューとモデル

ここで右側のコマンドプロンプト・ウィンドウの dir コマンドで表示されているファイルシステム・コンテンツの最小公分母を通して、左側のJDTのPackage Explorerの高度な専門ビューに続く一般的な一連のユーザー・インターフェースを見ることができます。ユーザー・インターフェースからは、これらのビューはすべて同じ「モデル」(すなわちいくつかのファイル)を視覚化して表示しています。Eclipseユーザーとしては、2つのEclipseビューは同じものを同時に見るための異なる方法を提供していると当然期待します。つまり、Navigatorは、オペレーティング・システムのファイル部分(Eclipseのワークスペース)についての専門の見方を示し、Package Explorerは、Javaプログラマーにとって、より自然で効率的な方法で体系づけられ提供された同一のファイルを表示している、ということです。

Eclipseユーザー・インターフェースが基本モデルを反映する方法、また、そのモデルが互いを構築する方法を確かめると、拡張を「プラグイン」する最良の場所を見つける重要な手掛かりを得ることができます。ビュー( IFileICompilationUnit )の下に表示されているEclipseインタフェース名は、Eclipseを構築するモデルから予想できるインタフェースの例です。これらはユーザー・インターフェースで表示されるものと一致しているので、みなさんはこれをプログラムで利用できると既に直感的に認識されていることでしょう。

ここまでがツアーのパートIです。パートIIはソリューションの開発について見ていきます。ソリューションを紹介し一つ一つについて説明するのではなく、そのうちのいくつかに気付いていただく方が興味深いでしょう。では、手近な問題と関係するいくつかの質問から始めましょう。まずはメソッド可視性のリファクタリング機能を備えたJDTの拡張からです。


適切な質問をすることは答えを知るより重要です

私たちの探究はいくつかの一般的な質問から始まります。

Eclipseランドスケープでの基本的な手がかりを得たら、次はJDT特有の質問に移行していきましょう。

そしてもちろん、最後の大きな質問

拡張は、ユーザー・インターフェースのどこでどうやって表示されるのか?

これには既に答えが出ているので、簡単な確認をしておきます。1回のアクションで可視性を変更可能な1つ以上の選ばれたメソッドに対するコンテキスト・メニューを表示したいです。私たちは、HierarchyビューやPackage Explorerのように、メソッドを表示できるところならどこにでもメニュー選択が利用可能であると良いと考えています。では次の質問ヘ入ります。

ユーザ・インターフェースを拡張する一般的な方法とは?

例を使っての学習は楽しいものです。プラグイン・プロジェクト・ウィザードは、私たちのニーズを満たすような修正可能なサンプルコードを提供し私たちに手を貸してくれます。これが質問に対するほんの少しの答えです。このウィザードにより、テスト可能な Plug-in Development Environment (PDE)として知られているプラグイン開発専門のパースペクティブが自動的に開始されます。ウィザードには、多くの例が含まれています。実際、私たちの旧友「Hello World」も含まれています。「Hello World」を生成し、環境が正確に設定されているかを確認するために結果を調べてみましょう。それからこの問いに答え、次の問い( ユーザ・インターフェースの拡張が、selectionのような基本的なイベントを知る方法は? )へと進むために「Hello World」を修正しましょう。現在のselectedメソッドに、新しく紹介するメニュー選択を機能させたいので、次の問いは重要になります。

ここでの解説は、新しくEclipseインストールから開始しようとしていることを前提としていることに注意してください。環境を修正しているか設定を変更しているならば、以下で記述されるようには正確に動作しないかもしれません。コマンドプロンプト・ウィンドウを開いて、<inst_dir>¥eclipseディレクトリーに移動して、リスト1のように -data パラメーターでEclipseを開始すると新規のワークスペースでEclipseを開始することができます。

リスト1. Eclipseの新規インスタンスの開始
cd c:\eclipse2.1\eclipse eclipse.exe -data
workspaceDevWorks

まず新しいプラグイン・プロジェクト・ウィザードを使用して、プラグイン・プロジェクトを作成することから始めます。 File > New > Project を選択してください。新しいプロジェクト・ダイアログで、ウィザードのリストからPlug-in DevelopmentおよびPlug-in Projectを選択して、次に、 Next を選択してください。プロジェクト名に com.ibm.lab.helloworld を指定してください。ウィザードは、この名前に基づいたプラグインidを作成します。したがって、プラグインidは、システムにおいてユニークとなるはずです(慣習により、プロジェクト名およびプラグインidは同じです)。「Project contents」の下に表示されているデフォルトのワークスペースの場所で問題はありません。 Next を選んでください。

Next を選択することで、次のページのデフォルトのプラグイン・プロジェクト構造を許可します。プラグイン・コード・ジェネレータのページでは、ウィザードがパラメター化をサポートする多くのサンプルを提案しています。「Hello, World」オプションを選択して、 Next を選択してください。図4に表示されている次のページでは、プラグイン名およびプラグイン・クラス名を提案しています。これらは、プラグイン・プロジェクトの最後のワード、 com.ibm.lab. helloworld に基づいています。この例は、プラグイン・クラスのコンビニエンス・メソッドを必要としません。ですから、図4のように、3つのコード生成オプションを外して、 Next を選択してください( Finish を選択するのではありません。もう1ページあります)。

図4. 単純なプラグイン・コンテンツ
図4. 単純なプラグイン・コンテンツ

図5の次のページでは、表示メッセージのように「Hello, World」の例にユニークなパラメーターを指定することができます。

図5. サンプル・アクション・セット
図5. サンプル・アクション・セット

生成されるコードをシンプルにするためには、アクションがターゲットとするパッケージ名を com.ibm.lab.helloworld .actions から com.ibm.lab.helloworld (プロジェクトと同じ名前)へと変更してください。実在のプラグインに関連するクラスをグループ化するためにパッケージを分割してもよいのですが、この場合、2つのクラスだけなのでその必要はありません。さらに、「メイン」パッケージはプロジェクトと同じ名称であるという慣習を厳守してください。では、 Finish を選択してください。

次のようなメッセージが表示されるはずです。「Plug-ins required to compile Java classes in this plug-in are currently disabled.The wizard will enable them to avoid compile errors」継続するために OK を選択してください。これが新規のワークスペースならば、さらに、別のメッセージが表示されます「This kind of project is associated with the Plug-in Development Perspective.Do you want to switch to this perspective now?」メッセージのとおり、切り替えるために Yes を選択してください。

すべてが正確に設定されていることを確認するために、新しいプラグインをテストしましょう。 Run > Run As > Run-Time Workbench を選択してください。これで、あなたのプラグインを含むEclipseのもう一つのインスタンスが開始されるでしょう。この新しいインスタンスは、ランタイム・ワークスペースという新しいワークスペース・ディレクトリーを作成します。ですから心配する必要はありません。このインスタンスで行うどんなテストも開発の設定には影響しません。 Sample Menu という新しいプルダウン・メニューが、 Sample Action という1つの選択肢を備えて図6のように表示されるはずです。Sample Actionを選択すると下のメッセージが表示されます。新規のワークスペースから始めていない場合は、新しく提供されたプルダウン・メニューを見るためには Window > Reset Perspective を選択してください。Workbenchは、前回Eclipseが起動した時、どのアクション・セットがアクティブだったかを「覚えている」ので(プルダウン・メニュー選択の Window > Customize Perspective... からアクション・セットを追加/削除することもできます)、既存のワークスペースから開始する際、新しいプルダウン・メニューは表示されません。

図6. Hello, Eclipse world
図6. Hello, Eclipse world

プラグインのマニフェスト・ファイルplugin.xmlを簡単に見ておきましょう。このファイルを開くには、プラグインのマニフェスト・エディターで、plugin.xmlをダブルクリックしてください。このエディターはウィザードのようなページや「そのままの」ソースページを表示することができます。Sourceタブを選択して、ソースに目を向けてください。リスト2のように表示されます。ここでは太字の部分に注目してください。

リスト2. 生成された「Hello, World」のplugin.xml
<extension
point="org.eclipse.ui.actionSets">>
<actionSet
label="Sample Action Set"
visible="true"
id="com.ibm.lab.helloworld.actionSet">
<menu
label="Sample &Menu"
id="sampleMenu">
<separator name="sampleGroup">
</separator>
</menu>
<action
label="&Sample Action"
icon="icons/sample.gif"
class="com.ibm.lab.helloworld.SampleAction"
tooltip="Hello, Eclipse world"
menubarPath="sampleMenu/sampleGroup"
toolbarPath="sampleGroup"
id="com.ibm.lab.helloworld.SampleAction">
</action>
</actionSet>
</extension>

このファイルを入念に検討する必要はありません。ツアーのパートIIでの目的は、JDTに拡張を導入する基本的なメカニズムのうちのいくつかを習熟することです。これは、アクション・セットとしてWorkbenchにメニューおよびメニューを加えるための、1つのテクニックであり、 <extension point="org.eclipse.ui.actionSets"> タグで宣言されている拡張機能で始まっています。Workbenchのユーザー・インターフェース・プラグインは、この拡張ポイント org.eclipse.ui.actionSets や他のプラグインが様々なユーザー・インターフェース要素を提供することができる拡張ポイントを定義しています。

まだJavaメソッドのコンテキスト・メニューにメニューを加える方法について答えられていません。単純な例がいくつかのヒントを与えてくれます。「Hello, World」メッセージを表示するクラスのオープンから始め( SampleAction )、その実行メソッドに注目してください。それは特に面白い事ではありません。しかし、別のメソッド selectionChanged も参照してみます。なるほど!次の質問に対する答えが待ち構えています。

ユーザ・インターフェースの拡張が、selectionのような基本的なイベントを知る方法は?

メニューのプルダウン選択のように、提供されているアクションはWorkbenchの選択が変更されると通知されます。これはメソッドの前にあるJavadocコメントで確認できます。Selectionについてもう少し理解するために、このメソッドを修正してみましょう。まず、Workbenchのランタイム・インスタンスをクローズしていない場合は、クローズしてください。次に、 selectionChanged メソッドにリスト3のコードを追加してください。

リスト3. selectionChangedメソッド、第1の修正
public void selectionChanged(IAction action, ISelection
selection) {

System.out.println("==========>
selectionChanged"); System.out.println(selection);
}

このデバッグ・コードで、何が選択されているか、何がEclipseを機能させているかに関して少し知ることができます。メソッドを保存して、再度ランタイムWorkbenchを開始してください。

重要 : Eclipseは、ユーザーがコードが必要となることを行うまでは、プラグインをロードしないようにするロード機構を持っています。したがって、 selectionChanged メソッドが呼ばれる前に、まずプラグインをロードする Sample Action メニューを選ばければなりません。

エディターのテキスト、Navigatorのファイル、およびOutlineビューのメンバーなどの異なったものを選択してください(ランタイムの例が異なるワークスペースを使用するので、新たにJavaプロジェクトや例のJavaクラスを作成しなければならないということを思い起こしてください)。リスト4は、Eclipseの開発に際してコンソールに表示される出力例です。

==========> selectionChanged
[package com.ibm.lab.soln.jdt.excerpt [in [Working copy] ChangeIMemberFlagAction.java 
[in com.ibm.lab.soln.jdt.excerpt [in src [in com.ibm.lab.soln.jdt.excerpt]]]]]
==========> selectionChanged
<empty selection>
==========> selectionChanged
org.eclipse.jface.text.TextSelection@9fca283
==========> selectionChanged
<empty selection>
==========> selectionChanged
[package com.ibm.lab.soln.jdt.excerpt [in [Working copy] ChangeIMemberFlagAction.java 
[in com.ibm.lab.soln.jdt.excerpt [in src [in com.ibm.lab.soln.jdt.excerpt]]]]]
==========> selectionChanged
[IMember[] members [in ChangeIMemberFlagAction [in [Working copy] ChangeIMemberFlagAction.java 
[in com.ibm.lab.soln.jdt.excerpt [in src [in com.ibm.lab.soln.jdt.excerpt]]]]]]
==========> selectionChanged
<empty selection>
==========> selectionChanged
[ChangeIMemberFlagAction.java [in com.ibm.lab.soln.jdt.excerpt 
[in src [in com.ibm.lab.soln.jdt.excerpt]]]
package com.ibm.lab.soln.jdt.excerpt
import org.eclipse.jdt.core.Flags
import org.eclipse.jdt.core.IBuffer
...lines omitted...
void selectionChanged(IAction, ISelection)]
==========> selectionChanged
[boolean isChecked(IAction, IMember) [in ToggleIMemberFinalAction 
[in ToggleIMemberFinalAction.java [in com.ibm.lab.soln.jdt.excerpt 
[in src [in com.ibm.lab.soln.jdt.excerpt]]]]]]

この結果は私たちが望むほど得るところが多くはありません。選択は明らかに String の例のように初歩的なものではありません。しかし、これらのクラスがデフォルト toString メソッドをオーバーライドしているので、どのクラスが深く関係しているのかは明らかになっていません。もう少し調査をしなければ、どんな情報が示されているかを認識できる状況ではまだありません。 selectionChanged メソッドに戻って、選択パラメーター ISelection のインタフェースの階層を見てください。この階層には、(リスト用の) IStructuredSelection 、および ITextSelection のような汎用サブタイプ・インタフェースがないことが明らかです。選択されているクラスの出力で selectionChanged メソッドをもう少しスマートにします。リスト5のように、 selectionChanged メソッドを修正してください。

リスト5. selectionChangedメソッド、第2の修正
public void selectionChanged(IAction action, ISelection selection) {
System.out.println("==========> selectionChanged");
if (selection != null) {
if (selection instanceof IStructuredSelection) {
IStructuredSelection ss = (IStructuredSelection) selection;
if (ss.isEmpty())
System.out.println("<empty selection>");
else
System.out.println("First selected element is " + ss.getFirstElement().getClass());
} else if (selection instanceof ITextSelection) {
ITextSelection ts = (ITextSelection) selection;
System.out.println("Selected text is <" + ts.getText() + ">");
}
} else {
System.out.println("<empty selection>");
}     
}

忘れずにランタイム・インスタンスをクローズして再び開始してください。ユーザー・インターフェースの様々な要素を選択する場合、リスト6の方がはるかに参考になります。

リスト6. selectionChanged出力 第2の修正
selected some methods in the Outline view
==========> selectionChanged First selected element
is class org.eclipse.jdt.internal.core.SourceMethod
==========> selectionChanged First selected element
is class org.eclipse.jdt.internal.core.SourceMethod
==========> selectionChanged <selection is
empty>
activated the Java editor
==========> selectionChanged Selected text is
<isChecked> ==========> selectionChanged
<selection is empty>

selected same methods and classes, package in the
Package Explorer

==========> selectionChanged First selected element
is class org.eclipse.jdt.internal.core.SourceMethod
==========> selectionChanged First selected element
is class org.eclipse.jdt.internal.core.SourceType
==========> selectionChanged First selected element
is class org.eclipse.jdt.internal.core.PackageFragment

activated the Navigator view, selected some files,
folders, and projects

==========> selectionChanged First selected element
is class org.eclipse.core.internal.resources.File
==========> selectionChanged <selection is
empty> ==========> selectionChanged First selected
element is class
org.eclipse.core.internal.resources.File ==========>
selectionChanged First selected element is class
org.eclipse.core.internal.resources.Project
==========> selectionChanged First selected element
is class org.eclipse.core.internal.resources.Folder
==========> selectionChanged <selection is
empty>

reactivated the Package Explorer, selected some
classes and methods in JARs of reference libraries

==========> selectionChanged First selected element
is class
org.eclipse.jdt.internal.core.JarPackageFragment
==========> selectionChanged First selected element
is class org.eclipse.jdt.internal.core.ClassFile
==========> selectionChanged First selected element
is class org.eclipse.jdt.internal.core.BinaryMethod

ユーザー・インターフェースとJDTのモデル・クラスが1対1で一致することをはっきりと確認することができます。もちろん、selectionのようにモデルであるように見えて、stringやイメージのような低レベルのプリミティブではないと判断できるのは、JFace と呼ばれる別のEclipseフレームワークのおかげです。JFaceは、オペレーティング・システムに近いウィジェットが望んでいるstringのようなプリミティブと、コードの稼動を優先する高レベルのモデル・オブジェクト間をマッピングしています。ここでの目的はJDTの拡張ですので、この記事ではJfaceに関しては詳しく解説いたしません。 参考文献 に、JFaceについてのレファレンスを提供していますので理解を広げるために参照してください。この記事は、JDTを拡張する基本部分を理解するのに必要なことをカバーしているにすぎません。

出力の例に戻って、特定のselectionの結果-ユーザー・インターフェースでJavaメンバーのselectionに相当するものに注目しましょう。それはリスト7で繰り返されています。

リスト7. 再びselectionChangedの出力
==========> selectionChanged
First selected element is class org.eclipse.jdt.internal.core.SourceMethod
==========> selectionChanged
First selected element is class org.eclipse.jdt.internal.core.BinaryMethod

これらのクラスのパッケージ名の中間にある internal は、少し気になるかもしれません。しかし、よくあるようにここでも、Eclipseは(internal)実装クラスに相当するpublicなインタフェースを持っています。簡単なクラス探索により、これらのクラスがすべて共通のインタフェース、すなわち ISourceReferenceIJavaElementIMember を実装していることがわかります。これでついに、次の問いに対する答えに進むことができます。

Outlineビューで表示されるメンバーのように、JDT特有の要素のユーザー・インターフェースをどのように拡張しますか。ビューあるいは基本モデルを拡張しますか?

簡単な「Hello, World」の例で、プラグイン・マニフェスト・ファイルの数行のXML( <extension point="org.eclipse.ui.actionSet"> )や、実際のアクションを扱うクラス( com.ibm.lab.helloworld.SampleAction )を必要とするメニュー選択の追加の例を提示いたしました。ビューのプルダウン・メニュー、共通のエディターのツールバー、ポップアップ・メニューにアクションを追加することは、どれも同じくらい簡単なことです。提供されているポップアップ・メニューには、2種類あります。1つ目は、1つのビューだけに関係し選択されていないオブジェクト(すなわち、「余白」で右クリックすると、表示される「デフォルト」ポップアップ・メニュー)であり、2つ目(こちらが一般的)は、選択されたオブジェクトに属する選択と関係しているものです。今回のケースでは、特定の選択されたオブジェクトだけをターゲットにしたいので、リスト8のように、プラグインのマニフェストに拡張を定義して、ポップアップ・メニューにいわゆるアクション・オブジェクトをコントリビュートします。(下記の識別子のうちのいくつかは省略されて'…'と表示されています)。

リスト8. 修飾アクション
id="...imember">
<menu
label="Soln: Modifiers"
path="group.reorganize"
id="...imember.modifiers">
<separator name="group1"/>
<separator name="group2"/>
</menu>
<action
label="Private"
menubarPath="...imember.modifiers/group1"
class="...jdt.excerpt.MakeIMemberPrivateAction"
id="...imember.makeprivate">
</action>
<action
label="Protected"
menubarPath="...imember.modifiers/group1"
class="...jdt.excerpt.MakeIMemberProtectedAction"
id="...imember.makeprotected">
</action>
...all menu choices not shown...
</objectContribution>
</extension>

拡張ポイントは org.eclipse.ui.popupMenus と名付けられています。名前が示すように、これはWorkbenchに現われるポップアップ・メニューへのコントリビューションを定義しています。この特定の例は、特に選択されたオブジェクトのみにコントリビュートし、(Java言語仕様に定義されるようにメンバーがメソッドとフィールドの両方を含むことを思い出してください) IMember インタフェースを実装しています。これで問いに対する答えがでましたので、次の質問に移ることができます。

次の質問に移る前に、簡単な「Hello, World」アクションの例で見出したパターンが他のメニューのアクション・コントリビューションで繰り返されていることに気付いてください。すなわち、クラス属性で指定されたクラスは、( selectionChanged メソッドにより)選択変更についての通知を受け、ユーザーがメニューを選択した時も(実行メソッドにより)通知を受けることになります。これで、我々のツアーにおけるユーザー・インターフェース部分はほとんど終わりました。希望するような変更をもたらすには、まだこの先に難関が待ち受けています。次の質問にもあるように、まだ見ておくべきことがいくつかあります。

Package Exploreで表示される要素と、Outlineビューのような他のビューで表示される同一の要素はどのような関係なのか?拡張するにあたって、それらの違いに気づいている必要があるのか、ないのか?

OutlineビューやHierarchyビューのメソッドを選択した時、選択されたオブジェクトのクラスは必ずしも同じではなかったことにお気付きになったかもしれません。例えば、あなたが Package Explorerでライブラリー(JARファイル)のコンテンツを拡張し、それからクラスかメソッドを選択したとしても、JavaエディターのOutlineビューでの選択とは同じクラスではありません。どうなっているのでしょう?

ここで、常に読取専用であるJDTのJavaモデルと、「編集可能な」JDTのJavaモデルとの違いを見ておきましょう。双方のJavaモデルは IMember のような共通インタフェースを実装していますが、根本的な制約を理解できる異なった実装クラスを持っています。その例としては、Package Explorerで表示されるJARファイル中の .class ファイルに生成されたJavaコンパイル・ユニットを表わす実装クラス、そして .java ファイルに直接生成されたコンパイル・ユニットを表わす実装クラスがあげられます。後者の実装では修正が可能ですが、前者の実装では修正はできません。しかし、2つのAPIの共有部分は、 ICompilationUnit インタフェースによって表わされます。

Javaソース・コードを編集する際、メソッド・シグニチャーをタイプすると、Outlineビューが更新されることにすでに気付かれているでしょう(気付かれていない場合はやってみてください!)。これは、既に保存され、コンパイルされ、Javaモデルへ統合された変更に対して、個別のエリアで「コミットされていない変更」をJDTがどのように行なうかの例です。JavaエディターのOutlineビューのように、コミットされていない変更も、コミットした変更も認識するビューもありますが、Navigatorビューのように、ファイルシステムに保存されるコミットした変更だけに関係があるビューもあります。

さらに、Javaメンバーを修正するコントリビュート・アクションは、少なくともある程度それが起動されるコンテキストを認識していなければなりません。すなわち、(JavaエディターのOutlineビューで)選択されたメンバーは変更可能であり、他のメンバー(JARファイルに格納され、Package Explorerに表示される .class ファイルのメンバー)は変更できないことを認識しておかなければなりません。これを覚えておいて、次の質問へ続きましょう。

プログラムでJDTモデルを変更する方法

先のツアー中で少し検討をしたので、次のことに気付かれたかもしれません。 ImemberIjavaElement 、選択されたJava関連項目によって実装されている大多数のインタフェースであるように見えるものは setXXX メソッドを持っていないということです。そうすると、これらの修正はどのように行うのでしょう?

それは驚くほど簡単ではあるものの、おそらく直感的でないことが分かることでしょう。JDTのJavaモデルの最も実用的な点は「読取専用」にあります。Javaコンパイラーとの統合により、ある要素の基本Javaソースを変更すると残りのJavaモデルもそれに同期させられます。ですので、実際にしなければならないのはJavaソースの更新です。そして、モデル変更が必要となる残りの部分は、それらに左右されるすべてに広められます。例えば、Javaソース/Javaモデルに変更が生じると、探索が直ちに機能し続けられるように、JDTのインデックスは自動的に更新され、(プロジェクトのプロパティで指定されたJavaビルドパスに指定されたように)関係するクラスは再コンパイルされます。

これは素晴らしい機能です!ここでのポイントは、なぜJavaモデルがプラグイン統合の鍵であるかです。Javaモデルは、Java環境全体で共有される共通のメモリー内モデルを提供し、そのスコープはプロジェクトから始まり、参照付けられたライブラリーや、ファイルシステムの .java ファイル、 .class ファイルおよび .jar ファイルの操作について心配する必要はないすべてにまで及んでいます。ハイレベルのモデルに注目すると、JDTにそれらの多くの面倒な詳細を処理させることができます。

まだ簡単だとは思えませんか?リスト9は、コントリビュート・アクションの実行メソッドから抽出されたものを少し読みやすくするために単純化した、ソリューションの中核を占めるコードの一部です。

リスト9. selectionChangedメソッド、小さなソリューション
public void selectionChanged(IAction action, ISelection selection) {			
IMember member = (IMember) 
((IStructuredSelection) selection).getFirstElement();
ICompilationUnit cu = member.getCompilationUnit();

if (cu.isWorkingCopy()) {
IBuffer buffer = cu.getBuffer();
buffer.replace(...);
cu.reconcile();
}
}

少しあっけなく思えますか?コントリビュート・アクションは選択されたメンバーを与えられ、その親コンテナーに誰が基本ソースを管理しているかを尋ねます(Javaの .class.java ファイルのモデルはJDTでは集合的にコンパイル・ユニット とみなされます)。それが「コミットされていない」Javaモデル(言いかえれば、エディターで現在開いている)の一部であることを確認し、バッファーとして返されたソース・コードを修正します。 IBuffer のインタフェースは StringBuffer に似ていますが、主な違いは、コンパイル・ユニットに関係するバッファーの変更が、Javaモデルの対応する要素を更新するということです。 reconcile という最後の呼出しは、モデルが更新するPackage Explorerビューのような他と関係する部分がpublic consumptionであることをJDTに通知することを意味しています。

上記のコードにおける省略に気付いておられることでしょう。それは修正を適用するためにソース・コード自体を分析しなければならないところです。この次の問いには再び、JDTが助けとなってくれるはずです。

Javaコードを修正するための分析方法とは?

JDTは、コード分析に役立ついくつかのツールを提供しています。この記事は、最も説明が簡単であり、最も制限のある有効範囲をもち、 IScanner インタフェースを備えたツールを敢えて選んでいます。このインタフェースはJDTツールボックスの一部で、JDTの ToolFactory クラスからアクセス可能です。その createScanner メソッドは、一連のJavaコードのトークン化を簡単にするスキャナーを返します。それは、簡単な構文解析や返されたトークンの分類といった特に難しいものを扱っているわけではありません。例えば、それは、次のトークンがpublicキーワードであり、その後のトークンは識別子で、その後のトークンは左括弧で・・ということを示します。また、このスキャナーはまさに理解したいと思っているちょっとしたコードを分析したい場合にはふさわしいです。Javaソース全体を分析するためにスキャナーは使用しないで下さい。それについては、コンパイラーのマニアにおなじみのJDTのAbstract Syntax Tree(AST)フレームワークを参照してください。

単純なスキャナーとは異なり、ASTは言語要素間(もはや単なる「トークン」ではない)の関係を理解しています。ASTはローカル変数、インスタンス変数、expression、 if 文など60以上の異なる言語要素を認識することができます。ASTは、幅広いスコープをカバーしたり、トークンの1対1のカテゴリー化に合わないようなあいまいさを持つリファクタリングの際に役に立ちます。スキャナーかASTのどちらを使用するのかはっきりさせるために、リスト10のコードを参照してください。

リスト10. 曖昧な変数参照
public class Foo {
int foo = 1;
public int foo(int foo) {
return foo + this.foo;
}
public int getFoo() {
return foo;
}
}

リファクタリングの一部としてのインスタンス変数 foo への参照を見つけたいのであれば、単純な構文解析がローカル参照とインスタンス変数の参照をどのように識別しているかを見ることができます。Javaソースの要素がそれぞれ表わされ識別される場合、ASTは完全な分析ツリーを作成します。この特殊な場合、「foo」リファレンスは FieldDeclarationSimpleNameThisExpression のような考慮をコンテキストに加える別のクラスによりASTのノードとして表わされ、それ故どれがどれであるかの認識が簡単になるでしょう。

既にふれたように、この記事は特に選択した単純なケースにあてはまります。もっと複雑な修正や分析の例については、 参考文献 セクションを参照してください。では、以前スキップした省略コードの話に戻りましょう。このコードは、メンバーの可視性を決定するソースでのキーワードを識別し交換するために IScanner のインスタンスを使用しています。ここで扱う可視修飾子は publicprivateprotectedfinal です。私たちは強引なアプローチでソリューションを単純化することができます。すなわち、2つのステップをふんでいくことになります。最初にメソッド・シグニチャーで可視修飾子をすべて削除し(少なくともスキャンをして、可視修飾子が見つかった場合はそれらを削除してください)、次に希望の修飾子を挿入します。特に

  1. メソッド・シグニチャーで publicprivateprotected が見つかった場合は削除してください。
  2. 要求された可視修飾子を挿入してください(パッケージが可視の場合には、それがデフォルトであるので何もしないでください。すなわち、修飾子は存在しません)。

最後の修飾子は簡単です。希望の振る舞いがそれをon,offに切り替えるので、最後の修飾子が存在する場合は最後の修飾子を削除しなければならないだけです。存在しない場合は、最後の修飾子を挿入してください。リスト11のコードはpublicからprivateへ無条件にメンバーの可視性を変更している例です。この記事に関連したソリューションで、それぞれのアクションの共通のコードが抽象スーパークラスに移動されたことを理解できるでしょう。それは重複を除去するために整理され、基本的に以下のようなコードになっています。

リスト11. publicキーワードのスキャン
ICompilationUnit cu = member.getCompilationUnit();
if (cu.isWorkingCopy()) {
IBuffer buffer = cu.getBuffer();
IScanner scanner =
ToolFactory.createScanner(false, false, false, false);
scanner.setSource(buffer.getCharacters());
ISourceRange sr = member.getSourceRange();
scanner.resetTo(
sr.getOffset(),
sr.getOffset() + sr.getLength() - 1);
int token = scanner.getNextToken();
while (token != ITerminalSymbols.TokenNameEOF
&& token != ITerminalSymbols.TokenNameLPAREN)
token = scanner.getNextToken();
if (token == ITerminalSymbols.TokenNamePUBLIC) {
buffer.replace(
scanner.getCurrentTokenStartPosition(),
scanner.getCurrentTokenEndPosition(),
scanner.getCurrentTokenStartPosition() + 1,
"private");
break;
}
}
cu.reconcile();
}

注:ITerminalSymbols は、Java文法の標準トークンに対応しているスキャナーが返すトークン名を定義しています。バッファーのどこで現在のトークンが始まり終了するのか、何行目に現われるのか、トークン自体(とりわけ ITerminalSymbols.TokenNameStringLiteralITerminalSymbols.TokenNameIdentifier のような予約語でない場合)を知るために、スキャナーに照会することができます。

上記のコードでは、 scanner.setSource メソッドは、コンパイル・ユニットの全ソース・コード(すなわちJavaソース・ファイルのすべて)を与えられています。既に説明したように、スキャナーは大きなものの分析には適していません。したがって、 setSourceRange メソッドを呼び、分析は最後まで、ターゲット・メソッドの最初の文字から始まるソース部分だけに限定しなければなりません。 IMember インタフェースは、 ISourceReference の拡張であり、ソース・ストリングを照会することができるインタフェースであり、コンパイル・ユニットのソースの場所です。 IMembe rインタフェースにより、Javaソース内のどこでメソッドが始まり終了するかを把握しておく必要はありません。これでASTでそれを行うことができたはずです。が、 ISourceReference インタフェースではそれは不必要です。Javaメソッド・シグニチャーは解析が簡単であるので、 IScanner インタフェースの構文解析能力で十分です。しなければならないのは、メソッド宣言の最初の文字からパラメーター宣言の開始括弧の前までに現われる public キーワードを捜して、それを private キーワードに変えることです。もちろん、ソリューションでは、メソッドがもとはpublic、private、protected、package(default)のいずれであったとしても、可能な場合をすべて扱います。


ここからどう進んでいくのか?

この記事のゴールは、生産性を増したEclipseのJava開発環境での重要な拡張をみなさんに提供することでした。正直なところ、私は何度も簡潔さを理由にいくつかの詳細をスキップしてきました。エディターで既に開いているJavaソースを修正できるように、ソリューションはそれ自身いくつか簡単な想定をしています。おそらくより完全な実装によって、その制約を緩めたいと思われるかもしれません。

それにもかかわらず、私は、みなさんが何が可能なのかを学び、それがあまり困難ではないと確信していることを望んでいます。私たちがこの記事でカバーしたものは、The Java Developer's Guide to Eclipse という本の高度な章のうちの1部です。11の基本的な章では、プラグイン開発の基本をカバーしています。ほとんどの章ではこの記事で見てきたものと同じスタイルで、学んできたものを補足する文書化されたソリューションを含んでいます(これほど早足では解説されていません!)。

この本に加えて、 developerWorks やEclipseのホーム・ページ eclipse.org (下の 参考文献 に多くのリストがあります)で素晴らしい記事を参照することができるでしょう。


ソリューションについてもっと詳しく知り、ダウンロードしてください

この記事でカバーされたソリューションの抜粋の詳細についてはリンクの 参考文献 を参照してください。ソリューションの抜粋には、The Java Developer's Guide to Eclipse に付属しているCD-ROMに含まれているいくつかのJDTの拡張が記載されています。ソリューション抜粋をインストールするためには、まずダウンロードをして、ワークスペース(例えば c:¥eclipse2.1¥eclipse\workspace )にプロジェクトを解凍し、 File > Import > Existing Project into Workspace を選択して、現在のEclipseワークスペースへプロジェクトをインポートしてください。

注: ソリューションをコンパイルし実行するためには、ワークスペースに必要なプラグインを加える必要があるかもしれません。 Window > Preferences > Plug-in Development > Target Platform を選択して、 Not in Workspace を選択してください。これで、ソリューションが依存する基本プラグインがインポートおよび再コンパイル・プロセス中に利用可能になります。

インポートしたら、プラグイン開発パースペクティブに切り替える必要があります。そして com.ibm.lab.soln.jdt.excerpt プロジェクトで plugin.xml を選択し、 Update Classpath を選択してください。これで、Eclipseインストール・パスとソリューション・パスの違いによって引き起こされたコンパイル・エラーを修正することができます。

参考文献

  • この記事で使用した ソース・コード をダウンロードするか、それに関する多くの 詳細 を参照してください。
  • eclipse.org Web site はEclipseのホーム・ページです。
  • この記事のソリューションは、Sherry Shavor、Jim D'Anjou、Dan Kehn、Scott Fairbrother、John Kellerman、Pat McCarthyによる、 The Java Developer's Guide to Eclipse (Addison Wesley Professional, 2003; ISBN 0321159640)の26章の中のコンパニオン・ソリューションを基にしています。
  • developerWorks では、 Eclipseユーザー 向けにさらに詳細な情報を提供しています。

コメント

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, Java technology
ArticleID=236398
ArticleTitle=EclipseでのJava Development Toolsの拡張
publish-date=07222003