ある特定なプラグインをEclipseで使いたいとしましょう。あなたはそのプラグインを自分のプラグイン・マニフェスト・ファイルに含めるためにあらゆる努力をし、そのファイルを依存関係として宣言します。ところがシステムはそのプラグインをロードせず、あなたのソフトウェア開発は、そこから先に進めなくなってしまいました。
おなじみの話に聞こえるでしょうか。もしそうであれば、あなたは数多くのplugin.xmlファイルを調べ、Eclipseがロードし損なったそのプラグインを特定するために、多くの時間と手間をかけてきたはずです。また、あなたは、Eclipse PDEプロジェクトに付属しているPlug-in Dependenciesビューを試して見たかも知れません。ところが、このビューは、無事ロードできたプラグインをリストアップすることしかできません。残念ながら、問題のプラグインは、無事ロードされたものの中には含まれていないのです。
Eclipseが見つけ損なった、あるいはロードし損なったプラグインを特定するためには、どんな方法があるのでしょう。plugin.xmlファイルを1つ1つ手動で調べるよりも、検索を自動化する方法を考えましょう。そのためには、Eclipseがどのようにプラグインを保持するのか、ディスク中のどこかにある他のプラグインとのリンクをどうやって見つけるのか、について知る必要があります。それが分かれば、自分で独自のプラグイン依存関係ウォーカーを書くか、あるいは単純に、私の書いた汎用のDependency Walkerプラグインを使えばよいのです。この記事のダウンロードのセクションには、そのソースコードを挙げてあります。
最初に: プラグイン依存関係とEclipseのプラグイン・リンクを理解する
Eclipseのプラグインはソフトウェア・モジュールであり、他のプラグインが利用できるような、ある機能を提供します。プラグインAが機能するためにプラグインBが必要な場合、AはBに依存していると言われます。この依存関係はまた、プラグインBが無事ロードされないと、プラグインAが動作しないことも意味します。場合によると、プラグインBがプラグインCやプラグインD、プラグインEにも依存し、(・・・気持ちが悪くなりそうです)しかもそれぞれがまた、別のプラグインに依存している可能性があるのです。こうした依存関係の連鎖によって、簡単に何百というプラグインがリンクされることになります。当然ながら、このプラグイン連鎖の中のどれかがうまくロードされないと、それに依存したプラグインには問題が起きてきます。
plugin.xmlという名前のプラグイン・マニフェスト・ファイルは、プラグインのそれぞれを記述します。このXMLファイルには、そのプラグインが他のプラグインに依存していること、あるいは他のプラグインにとって必要であることを宣言する部分があります。リスト1のplugin.xmlファイルでは、依存関係を宣言する部分を太字で示しています。
リスト1. plugin.xmlファイル
<?xml version="1.0" encoding="UTF-8" ?>
<?eclipse version="3.0"?>
<plugin id="org.eclipse.draw2d" name="Draw2d" version="3.0.0"
provider-name="Eclipse.org">
<runtime>
<library name="draw2d.jar">
<export name="*" />
<packages prefixes="org.eclipse.draw2d" />
</library>
</runtime>
<requires>
<import plugin="org.eclipse.swt" export="true" />
<import plugin="org.eclipse.core.runtime" />
</requires>
</plugin>
|
ここで、<requires> </requires>セクションの内側に埋め込まれた、<import plugin="plugin id"/>宣言に注意してください。リスト1のサンプルは、プラグインID、org.eclipse.draw2dが、org.eclipse.swtというIDを持つプラグインとorg.eclipse.core.runtimeというIDを持つプラグインに依存していることを示しています。
Eclipseで、Java™技術プラットフォーム・パースペクティブを使ってソフトウェアを開発すると、システムは、ターゲットとするプラットフォームに対してコードをコンパイルします。ターゲット・プラットフォームの場所は、Window > Preferences > Plug-in Development > Target Platformで規定することができます。ターゲット・プラットフォームは自分自身のEclipseのコピーを、<targetPlatform>\eclipseに持ちます。自分のコードの中にある依存関係を解決するためには、必要なプラグインがあるかどうかを、次の2つの場所で探します。
- <targetPlatform>\eclipse\pluginsフォルダーの下にあるEclipseプラグイン
- <targetPlatform>\eclipse\linksフォルダーの中の .linkファイルが指す、リンク付きプラグイン
プログラマーは通常、2番目の場所を『リンク』フォルダーと呼びます。このリンク・フォルダーは、ゼロ以上のファイルを含み、ファイルは通常「.link」拡張子で終わっています。これらのファイルはリンク情報を含み、この情報が、リンク対象のプラグインを見つけるべきディスク上の場所を指します。
それぞれの.linkファイルには、path=locationというフォーマットを持つ、鍵と値の対があります。(例えばリンク・フォルダー、C:\eclipse\linksは任意の数の .linkファイルがあり、その中の1つがcom.ibm.indiver.dependencywalker.linkという名前、そして、このファイルの中に含まれる1つのラインがpath=c:\myPlugins\dependencyWalker、など。)この.linkファイルがEclipseに対して、指定された場所に行き、そこにある\eclipse\pluginsフォルダーの中にあるプラグインも探すように指示するのです。
依存関係ウォーカーを書くためのプロセスは、すべてのプラグインをリストアップするプロセスと、ユーザーが選択したプラグインの依存関係をリストアップするプロセス、という2つの部分から成ります。
最初のプロセスでは、Eclipseシステムの下に物理的に存在する全プラグインを探し出し、何らかの単純なUI(ユーザー・インターフェース、例えばテーブル)を使って、そのリストをエンドユーザーに提供します。このUIは、あるプラグインに関する依存関係の連鎖をユーザーが見たいと思った場合に、そのプラグインをユーザーが選択できるような方法を、何かの形で提供する必要があります。
2番目のプロセスでは、ユーザーが選択したプラグインのplugin.xmlファイルを構文解析し、そのplugin.xmlファイルの<requires> </requires>セクションの中に埋め込まれている<import plugin="plugin id"/>宣言を探します。プラグインの依存関係連鎖を完全にたどるためには、すべてのプラグイン・マニフェスト・ファイルを再帰的に検索する必要があります。プラグイン同士の依存関係が、親-兄弟-子というような場合、それを表現するためのUIとしては、ツリー・ビューが最適でしょう。また、物理的に存在するプラグインが、Eclipseプラグイン・レジストリーに実際にロードされているかどうかも、視覚的に記述する必要もあります。
第1部: Eclipseシステム内の全プラグインをリストアップする
ディスク上に物理的に存在するプラグインすべてをリストアップするコードを書くには、次のようなことを知っておく必要があります。
プラグインは主に、<targetPlatform>\eclipse\pluginsフォルダーの中にあります。
その他に、複数の<someLinkedPath>\eclipse\pluginsフォルダーの中にある場合もあります。
<targetPlatform>\eclipse\linksフォルダーの中にある .linkファイルから、すべての<someLinkedPath>へのパスを得ることができます。
Eclipseシステムにあるすべてのプラグインをリストアップするための手順は、下記の通りです。
- ターゲット・プラットフォームの場所を見つけます。
- リンク・フォルダーのパスを用意します。リンク・フォルダーは\eclipseフォルダーの中にあります。
- \eclipse\linksフォルダーにあるファイルのリストを取得します。ソースコードの中にある、Utilities.getLinkedPaths() 関数を参照してください。
- 各 .linkファイルを調べ、リンクされたEclipseフォルダーのパスを取得します。
- 全プラグイン・ルート・フォルダー(つまり<targetPlatform>\eclipse\pluginsフォルダーと、可能性のあるすべての<someLinkedPath>\eclipse\pluginsフォルダー)のリストを用意します。
- プラグイン・ルート・フォルダーそれぞれに対して、各プラグイン・ディレクトリーに行き、plugin.xmlファイルのパスを取得します。
- plugin.xmlファイルを構文解析してプラグインIDとプラグインのバージョンを取得し、その情報をデータ構造の中に保存します。
- ステップ6に戻り、次のプラグイン・ディレクトリーに進みます。
リスト2. Eclipseシステムの下に物理的に存在する全プラグインのリストを用意する
/**
*
* @return returns a Vector containing PluginData objects.
* Each PluginData object represents a Plugin found under any of the following
* plugin directories
* a. the targetPlatformLocation\eclipse\plugins directory,
* b. other plugin directories as specified by *.link files under
* targetPlatform\eclipse\links directory
**/
public static Vector getPluginsInTargetPlatform(){
/**
//step1: Get path of target platform.
//step2: Prepare path of links folder.
//step3: Get list of files in links folder.
//step4: Parse each link file and get the path of linked Eclipse folder.
//step5: Prepare a list of all plugin root folders
// (Eclipse plugins and linked Eclipse plugins).
//step6: 6a. For each plugin root folder,
// 6b. go to each plugin directory and get path of plugin.xml.
//step7: Parse the plugin.xml file to get plugin id, plugin version,
// and store in vectors, lists, etc.
//step8: Go back to step 6 to continue with next plugin directory.
**/
//step1: Get path of target platform.
//Fall back to Eclipse install location if targetplatform in not set.
URL platFormURL = Platform.getInstallLocation().getURL();
Location location = Platform.getInstallLocation();
IPath eclipsePath = null ;
//Get path of target platform against which the users of this tool
//will compile their code.
IPath targetPlatFormLocation = new Path(getTargetPlatformPath(true));
if(_useTargetPlatform == false)
eclipsePath = new Path(platFormURL.getPath());
else
eclipsePath = targetPlatFormLocation;
showMessage("Considering target platform to be: " +
eclipsePath.toString());
//step2: Prepare path of links folder.
//step3: Get list of files in links folder.
//step4: Parse each link file and get the path of linked Eclipse folder.
IPath linksPath = new Path( eclipsePath.toString() ).append("/links");
String linkedPaths[] = getLinkedPaths(linksPath.toString());
int linkedPathLength = 0;
if(null != linkedPaths){
linkedPathLength = linkedPaths.length;
}
//step5: Prepare a list of all plugin root folders
// (Eclipse plugins and linked Eclipse plugins).
IPath eclipsePluginRootFolders[] = new IPath[linkedPathLength + 1];
eclipsePluginRootFolders[0] =
new Path( eclipsePath.toString() ).append("/plugins");
if(null != linkedPaths){
for(int i=0; i<linkedPaths.length; i++){
eclipsePluginRootFolders[i+1] =
new Path(linkedPaths[i]).append("/eclipse/plugins");
}
}
//step6: 6a. For each plugin root folder,
// 6b. go to each plugin directory and get path of plugin.xml.
//step7: Parse the plugin.xml file to get plugin id, plugin version,
// and store in vectors, lists, etc.
Vector vectorsInThisVector = new Vector();
for(int i=0; i<eclipsePluginRootFolders.length; i++){
System.out.println("\n========plugin IDs and Versions in " +
eclipsePluginRootFolders[i] + "========");
Vector pluginDataObjs =
getPluginDataForAllPlugins(
eclipsePluginRootFolders[i].toString());
vectorsInThisVector.add(pluginDataObjs);
System.out.println(pluginDataObjs);
System.out.println("\n===========|||=== end ===|||===========");
}
Vector pluginData = new Vector();
Iterator outerIterator = vectorsInThisVector.iterator();
while(outerIterator.hasNext()){
Vector pluginDataObjs = (Vector)outerIterator.next();
Iterator innerIterator = pluginDataObjs.iterator();
while(innerIterator.hasNext()){
PluginData pd = (PluginData)innerIterator.next();
String pluginIdKey = pd.getPluginID();
String versionValue = pd.getPluginVersion();
String pluginPath = pd.getPluginLocation();
pluginData.add(pd);
}
}
int breakpoint=0;
return pluginData;
}
|
すべてのプラグインが手に入ったら、その情報を見える形にするために、プラグインIDとバージョン、場所などを、SWT(Standard Widget Toolkit)テーブルの中に表示します。オプションとして、サンプル・コードにあるように、プラグインIDでカラムをソートするコードを書くこともできます。また、見つかったプラグインの数を、どこかのカラムに表示すべきでしょう。結果は次のようになるはずです。
図1. Target-Platformビューで見た、すべてのプラグイン
第2部: 依存関係連鎖をウォークするための、plugin.xmlファイルに対する再帰検索
あるプラグインに対する依存関係連鎖を見ようとユーザーがプラグインを選択したら、そのプラグインのplugin.xmlファイルを構文解析して、依存関係を探さなければなりません。依存関係はそれぞれ別のplugin.xmlファイルを指しており、そのファイルはまた、独自の依存関係を持っています。この依存関係の連鎖は、すぐに膨大な数のplugin.xmlファイルになります。これらを、ユーザーが選択したプラグインを開始点として、構文解析しなければなりません。(同じシステム中にプラグインが重複している場合もあるため)最新バージョンのプラグインを見つけ、またその依存関係すべてを見つけるには、再帰関数を書くことによって、こうした依存関係をウォークします。
そうした再帰関数を書くために必要なステップは、下記の通りです。リスト3は、この関数自体のソースコードです。再帰関数は非常にリソースを浪費しがちであり、ユーザーがしびれを切らすほど結果の戻りが遅い場合もあります。この問題に対処するには、ユーザーが選択したプラグインに直接つながる依存関係だけをフェッチするのが一つの方法です。サンプル・コードの中にある関数、loadImmediateDependencies() は、この方法をとっています。
- ユーザーが選択したプラグインのパス位置を取得します。
- その場所に、plugin.xmlファイルあるいはfragment.xmlファイルが存在するかどうかをチェックします。
- plugin.xmlファイルあるいはfragment.xmlファイルを構文解析し、このプラグインが必要とする全プラグインIDのリストを取得します。
- このリストにあるプラグインIDそれぞれに対して、それに対応するプラグインを見つけます。
- もし、複数のプラグインが同じIDを持っている場合には、一度だけユーザーにレポートし、最新バージョンを持つプラグインを使うように自動決定します。リスト4は、プラグインをプログラム的に比較し、新しい方のバージョンを見つける方法の例です。
- そのプラグイン(上記のステップ4または4aで規定されるもの)をツリー・ビューアーに追加して、同じ関数を再帰的に呼び、ステップ1から繰り返します。この場合だけは、ユーザーはプラグインを選択せず、ステップ4あるいは4aに対応するコードが選択を行います。
リスト3. recursivePluginDependencyWalker() 関数
private Vector alreadyNotified = new Vector();
private boolean firstCall = true;
private TreeParent root = null;
private void recursivePluginDependencyWalker(PluginData pdObject,
TreeParent parentNode){
try {
String path = pdObject.getPluginLocation();
PluginParser pp = null;
File pluginDotXmlFile = new File(path + "/plugin.xml");
if(pluginDotXmlFile.exists()){
pp = new PluginParser(pluginDotXmlFile);
}else{
File fragmentDotXmlFile = new File(path +
"/fragment.xml");
if(fragmentDotXmlFile.exists()){
pp = new PluginParser(fragmentDotXmlFile);
}else{
return;//no plugin.xml or fragment.xml found
}
}
String displayName = pdObject.getDisplayName();
System.out.println("\nPlugin ["+ displayName + "]
requires" + "\n");
String requires[] = pp.getDependencyList();
if(0 != requires.length ){
for(int i=0; i<requires.length; i++){
System.out.println("\t" + requires[i] );
PluginData pd[] =
getPluginDataObjectsFromPluginID(requires[i]);
PluginData nextPlugin = null;
switch(pd.length){
case 0:
//great, we know there is
//something missing
nextPlugin = null;
break;
case 1:
//best case, everything will be smooth
nextPlugin = pd[0];
break;
default:
//worst case, there must be more
//than 1 plugin with the same id
//at different locations.
String msgLine1 =
"Plugin " + displayName +
" requires " +
requires[i] + "\n";
String msgLine2 =
"Duplicate plug-ins found for ID: \
" " + requires[i] +
"\"" +
"\n Continuing with higher version...
" ;
//it is bad to give repeated
//reminders,
//so remind only once per plugin id.
if(! alreadyNotified.contains(
new String(requires[i]))){
MessageDialog.openInformation(null,
"Dependency Walker",
msgLine1 +
msgLine2);
alreadyNotified.add(
new String(requires[i]));
}
//always take the better
//version anyway
nextPlugin =
getBetterVersionPlugin(pd);
break;
}//end of switch
if( null != nextPlugin ){
TreeParent nextinLine =
new TreeParent(
nextPlugin.getDisplayName(),
nextPlugin.isPlugin
LoadedInRegistry()
);
parentNode.addChild(nextinLine);
recursivePluginDependencyWalker(
nextPlugin,
nextinLine);
}else{
TreeParent nextinLine =
new TreeParent(
requires[i] +
" [This plug-in is missing]
",
false);
parentNode.addChild(nextinLine);
//obviously we can't recurse
//into a missing plugin...
}
}//end of for
}else{
System.out.println("\t NOTHING:
No further dependency \n" );
//no further dependency
}
} catch (Exception e) {
e.printStackTrace();
}
} |
時には、同じIDを持つプラグインが別々の2つの場所にあることもあります。例えば、IDとしてorg.eclipse.xsdを持つプラグインが、<targetPlatform>\eclipse\pluginsフォルダーと<someLinkedPath>\eclipse\pluginsフォルダーの両方にある可能性もあります。
そうした場合、ディスク上に2つ以上あるプラグインのコピーの中から、どれを考慮すべきかを決めなければなりません。当然ですが、皆さんに興味があるのは最新のプラグイン、つまり最新バージョンを持つプラグインでしょう。Eclipseプラグインのバージョン比較には、既存の関数も幾つか利用できます。あるいは、リスト4のサンプル・コードを基にして、プラグインのバージョンを比較する簡単な関数を書くこともできます。
リスト4. プラグインのバージョンを比較する
private PluginData getBetterVersionPlugin(PluginData pdo[]){
PluginData _pdObjs[] = pdo;
int len = pdo.length;
if(len==0)
return null;
Arrays.sort(_pdObjs,new Comparator() {
/**Compares its two arguments for order.
* Returns a negative integer, zero, or a positive integer
* as the first argument is less than, equal to, or greater than
* the second.
**/
public int compare(Object leftObj, Object riteObj) {
String leftPID = ((PluginData)leftObj).
getPluginVersion().replace('.', ':');
String ritePID = ((PluginData)riteObj).
getPluginVersion().replace('.', ':');
String leftID[] = leftPID.split(":");
String riteID[] = ritePID.split(":");
int maxlen = leftID.length > riteID.length ?
leftID.length : riteID.length;
for(int i=0; i<maxlen; i++){
int left = 0;
int rite = 0;
try {
left = new Integer(leftID[i]).intValue();
} catch (NullPointerException e) { left = 0; }
try {
rite = new Integer(riteID[i]).intValue();
} catch (NullPointerException e) { rite = 0; }
if(left==rite){
continue;
}else{
int bigger = left > rite ? left : rite;
if(bigger==left)
return 1;
if(bigger==rite)
return -1;
}
}
return 0;
}
public boolean equals(Object arg0) {
return false;
}
});
return _pdObjs[len-1];
}
|
皆さんのコードが依存関係のリンクの連鎖を完全にウォークできたら、ツリー・ビューアーを使って視覚的に表示することができます。また、ロード失敗を引き起こした特定なプラグインを、視覚的に示すこともできます(次の図で、斜線入りの赤丸が付いているのを見てください)。
検索の結果は、次のようなものになります。
図2. 依存関係ウォーカーのツリー・ビュー
未解決のプラグイン依存関係、(行方不明のプラグイン、あるいは何らかの理由でEclipseがロードし損なったプラグイン)を見つけるには、まずEclipse PDEのPlug-in Dependenciesビューを使って、プラグインの依存関係を見てみます。その中に探しているプラグインがなかった場合は、この記事で取り上げたツールを使って、リンクされたプラグインを持つフォルダーすべてを網羅する自動検索を行うことができます。特定なプラグインのみが対象の場合には、必要に応じてソースコードを修正すればよいのです。
このツールのソースコードは、下記のダウンロード・セクションからダウンロードすることができます。ソースをブラウズするには、このプラグインをEclipseプロジェクトとして解凍し、開きます。このツールを使うには、このプラグインを解凍して\eclipse\pluginsフォルダーの中に置き、次のようにします。
- Eclipseで、Window > Show View > Others > DependencyWalker Category と進み、All Plugins in Target-Platformビューを選択します。
- このビューは、指定されたターゲット・プラットフォーム中に物理的に存在するプラグインを、すべて表示します。プラグインを選択してダブル・クリックします。
- DependencyWalkerTreeViewは、選択したプラグインの依存関係をリストアップします。終わる度に、このビューを閉じます。
| 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|---|---|---|
| Unzip and open this plug-in as an Eclipse project | os-DependencyWalker.jar.zip | 54KB | HTTP |
学ぶために
- 記事、「Eclipse Platform入門」(developerWorks, 2002年11月)は、Eclipseとプラグインのインストール方法の詳細を含めて、Eclipseの歴史と概要を解説しています。
- JARファイルが見つからない場合、あるいはNoClassDefFound例外が出た場合には、Eclipseベースのプラグイン・ユーティリティーである、JAR Class Finderをちょっと見てください(alphaWorks, 2003年8月)。
- PDEが初めての人、ターゲット・プラットフォームとは何かに戸惑っている人は、記事、introduction to PDEを読むことをお勧めします。
- developerWorksのOpen sourceゾーンには、オープンソース技術を利用して開発を行うための、そしてIBM製品でそれを利用するための、ハウツー情報やツール、プロジェクト・アップデートなどが豊富に用意されています。
製品や技術を入手するために
- 皆さんの次期オープンソース開発プロジェクトを、IBM trial softwareを使って革新してください。ダウンロード、あるいはDVDで入手することができます。
議論するために
-
developerWorks blogsに参加して、developerWorksのコミュニティーに加わってください。

Indiver Dwivediは、IBM India Software LabsのPune研究所に勤務するソフトウェア・エンジニアです。2000年にIBMに入社し、Lotus SmartSuiteや、IBM Workplace用のデータ・アクセス・ツールなどのプロジェクトに従事してきました。Ladder LogicやC、C++などで幅広いプログラミング経験があり、IAC(industrial automation and control、工業用自動制御)用のロジック・コントローラーや、PCベースのSCADA(supervisory control and data acquisition、監督制御およびデータ取得)ソフトウェアなどのプログラムを行ってきました。関心を持っている領域は、デバイス通信や、IAC領域でのデータ取得システムが保存した履歴データのチャート化/プロット化などです。現在はIBM Pune研究所のチームで作業を行っており、IBM Workplace Designerプロジェクトに貢献しています。