デベロッパー2007年8月28日 11:00
文字サイズ文字サイズ小文字サイズ中文字サイズ大

Eclipse RCPを好きなスクリプト言語で拡張する

この記事のURLhttp://japan.internet.com/developer/20070828/27.html
著者:Riccardo Govoni
海外internet.com発の記事

はじめに

 Java Specification Request (JSR) 223は、Javaプラットフォームとスクリプト言語を連係させるための一連のAPIと関連フレームワークを定義します。このAPIは、Java SE 6に標準装備されている標準ライブラリの一部であるため、SE6 JVM上でアプリケーションを実行する場合にはスクリプトサポートを無償で受けられます。これは、Eclipseプラットフォーム上で構築されたアプリケーションにもあてはまります。

 JSR-223は、スクリプト言語とJavaプラットフォームとの間で行われるさまざまな対話を定義します。たとえば次のような対話があります。

  • インタプリタ型スクリプトをJavaアプリケーションに埋め込む
  • Javaオブジェクトをスクリプトコンテキスト内から変更およびコントロールする
  • Java言語を使用してスクリプトインタプリタを書き、公開する

 この記事では、Eclipseプラットフォームと、このプラットフォーム上で構築されるアプリケーションを、これらの新しいJava SE 6機能とスクリプト言語のメリットを利用して拡張する方法を紹介します。Eclipseプラットフォームをスクリプトで拡張する方法を覚えると、以下のことが可能になります。

  • 統合開発環境内でよく行う繰り返しの操作を自動化する
  • ユーザーインターフェイスとコントロールロジックの両方をその場で変更することにより、ユーザーインターフェイスのプロトタイプを短時間で作成する
  • アプリケーションユーザーが一般的な環境設定の域を超えてアプリケーションをカスタマイズできるようにする。たとえばユーザーがお気に入りのスクリプトまたはドメイン固有言語(DSL)にロジックを追加できるようにする

 これらの利点の多くは、スクリプト言語の性質に由来しています。スクリプト言語は、一般的には動的に型付けされ、場合によっては特定の問題分野に固有のものであることもあります。また、通常はコンパイラ型言語よりも読み書きが簡単で、JavaやCなどの古い言語のように「記述→コンパイル→実行」といったサイクルに縛られません。

前提条件

 この記事は、JSR-223に関する基本的な知識のみがあれば理解できます。実際のところ、理解しておく必要があるのは次のコードだけです。

Map vars =
    getScriptVariables(); // fictional method
String scriptBody = getScriptBody(); // fictional method
ScriptEngineManager sem = new ScriptEngineManager();
ScriptEngine engine = sem.getEngineByExtension("js");

for (String key : vars.keySet()) 
   engine.put(key, vars.get(key));

engine.eval(scriptBody); 
   // ScriptException handling code omitted

 このコードを実行すると、基本的には次の3つの処理が行われます。

  1. javax.script.ScriptEngineManagerを作成する(サポートされている拡張で検索)
  2. このjavax.script.ScriptEngineManagerは、スクリプトエンジンを見つけてインスタンス化します。スクリプトエンジンとは、スクリプトを評価して、効率よく実行するコンポーネントのことです。
  1. ScriptEngine.put(String,Object)メソッドで、一連のスクリプト変数を設定する
  2. これにより、Javaオブジェクトとスクリプト環境間のバインディングが定義され、このようなオブジェクトをスクリプトで処理できるようになります。一般的には、Java環境をスクリプトでコントロールできます。
  1. 指定されたスクリプトを、eval(String script)メソッドで評価する

 JVMは、Service Providerメカニズムを使って使用可能なエンジンを探します。このとき、アプリケーションで使用できるjarファイルのMETA-INF/サービスディレクトリをスキャンして、特定の構成ファイルを検索します。スクリプトエンジンをアプリケーションで使用できるようにするには、そのスクリプトエンジンを含む、正しく構成されたjarファイルをクラスパスに追加します。この記事では、dev.java.netのスクリプトプロジェクトで提供されているスクリプトエンジンのいくつか、たとえばRubyGroovyのエンジンを使用します。

 今回のサンプルではスクリプトAPIの使用をかなり抑えていますが、チュートリアルとしての目的は十分に果たしているのではないかと思います。詳細については、最後に紹介する参考資料を参照してください。

スクリプトプラグインとそのフラグメント

 この記事のサンプルコードでは、スクリプト言語とEclipse(補足説明1「EclipseのRich Client Platform」を参照。補足説明はこの記事の最後にあります)を、com.devx.scriptingプラグイン(補足説明2「Eclipseプラグインのしくみ」を参照)を介して接続しています。これにより、プラットフォームの他の部分が、スクリプトリソースと一連のプラグインフラグメントに共通アクセスできるようになります。図1で示すように、これは、アプリケーションがサポートするすべてのスクリプト言語に対応します。

図1 com.devx.scriptingプラグインアーキテクチャ
図1 com.devx.scriptingプラグインアーキテクチャ

 すべてのフラグメントは指定のインタプリタ(Ruby、JavaScript、AppleScriptなど)とそのJSR-223エンジンを提供しており、そのインタプリタをjavax.scriptスクリプトAPIを通じて公開します。インタプリタとJSR-223ラッパーは、セットにすることも、個別に開発して提供することもできます。

 このプラグインセットアップにはさまざまなメリットがあり、たとえば次のようなことが考えられます。

  • 分散フラグメントの数を制限し、プラットフォームに追加する言語を明確にコントロールできる。
  • ユーザーが必要なスクリプト言語のみを更新サイトから選択できるため、アプリケーションのインストールに伴う自分の作業を軽減できる。

 プラグインは次のようなIScriptインターフェイスを定義します。

public interface IScript {
   // @return the URI which points to the script code.
   public String getURI();
   // @return the script extension 
   public String getExtension();
   // @return a Reader which points to the script code
   public Reader getReader() throws IOException;
   // @return the script unique id
   public String getId();
   // @return the namespace (plug-in id) of the script
   public String getNamespace();
   // @return run the script in a modal context?
   public boolean isModal();
}

 スクリプトプラグインはcom.devx.scripting.ScriptSupportクラスを公開し、Eclipseプラットフォームで発生するスクリプト関連の一般的なニーズに対応するためにパブリックメソッドを定義します。一般的なニーズとしては、たとえば、進捗状況のモニタ(Eclipseでソースをコンパイルしているときに表示されるモニタなど)に照らしてスクリプトを実行する、ScriptEngineManagerに対してクエリを実行してサポート言語の一覧を取得する、などの処理が考えられます。次のコードは、クラスのパブリックインターフェイスの一部を示しています(実装についてはダウンロードサンプル中のソースコードを参照)。

public void runScript(final IScript script,
   Map params) throws ScriptException;
public List getSupportedLanguages();

外部スクリプトの実行

 これらの基本エレメントだけで、既にEclipseアプリケーション内でカスタムスクリプトを実行する手段を実現できます。たとえば、ユーザーがファイルシステムからスクリプトを選択し、プラットフォーム内で実行するためのEclipseアクションを提供できます。図2と図3は、これを実装した場合の画面例です。

図2 「Run Script」アクション
図2 「Run Script」アクション
図3 サポートされているすべての種類のスクリプトを選択できるファイルセレクタ
図3 サポートされているすべての種類のスクリプトを選択できるファイルセレクタ

 「Run Script」アクションは、ファイルセレクタを表示します。このファイルセレクタは、com.devx.scripting.ScriptSupportクラスに対してクエリを実行することで、使用可能なスクリプト言語のみをフィルタを使って抽出します。クエリを受け取ったcom.devx.scripting.ScriptSupportクラスは、javax.script.ScriptEngineManagerに対してサポート言語を要求します。最後に、javax.script.ScriptEngineManagerが、Service Providerメカニズムを使ってプラグインクラスパスとそのフラグメントをスキャンします。

 図のような結果を得るためには、org.eclipse.ui.actionSets拡張ポイントに対して拡張を定義する必要があります。この拡張ポイントは、リスト1のcom.devx.scripting.actions.RunScriptActionクラスによって実装されている、アプリケーションに対する追加メニューアクションを提供します。

 必要な作業はこれだけです。これで、アプリケーション内でRuby、Groovyなどのスクリプトを実行して、それぞれのスクリプトの性能や特性を利用することができます。たとえば、ワークスペースの一部で一括変更を行うスクリプトを作成したり、ビルドおよび配備プロセスの一部をスクリプト言語で書いて、必要に応じて開発者がプラットフォームから呼び出せるようにしたりできます。

リスト1 RunScriptActionクラス
public class RunScriptAction
   implements IWorkbenchWindowActionDelegate {

   // unneeded methods
   public void dispose() {}
   public void init(IWorkbenchWindow window) {}
   public void selectionChanged(IAction action,
      ISelection selection) {}

   public void run(IAction action) {
      FileDialog dlg = new FileDialog(
         PlatformUI.getWorkbench().getActiveWorkbenchWindow().
         getShell(), SWT.OPEN);

      List filterNames = new ArrayList();
      List filterExtensions = new ArrayList();
      ScriptSupport support = new ScriptSupport();
      for (ScriptSupport.Language l :
         support.getSupportedLanguages()) {
         filterNames.add(
            l.getName() + " (*." + l.getExtension().get(0) + ")");
         filterExtensions.add("*." +l.getExtension().get(0));
      }

      filterNames.add("All files (*.*)");
      filterExtensions.add("*.*");

      dlg.setFilterNames(
         filterNames.toArray(new String[filterNames.size()]));
      dlg.setFilterExtensions(
         filterExtensions.toArray(
            new String[filterExtensions.size()]));

      String f = dlg.open();
      if (f != null) {
         BufferedReader br = null;

         try {
            br = new BufferedReader(new FileReader(f));
            support.runScript(br, getExtension(f),null,true );
         }
         catch(IOException ioe) {
                // exception handling
         }
         catch(ScriptException se) {
            // exception handling
         }
         finally {
            if (br != null)
               try { br.close(); }
               catch(Exception ex) {}
         }
      }
   }

   private String getExtension(String f) {
      if (f.lastIndexOf(’.’) != -1) {
         return f.substring(f.lastIndexOf(’.’)+1);
      }
      else
         return f;
   }

}

プラットフォームでスクリプトコントリビューションを活用する

 これで、次の段階に進む準備ができました。今度は、スクリプト言語を使用してEclipseプラットフォームを直接操作して変更する方法、そしてスクリプトを使ってプラットフォームにコントリビューションを追加する方法を見ていきましょう。まずは、スクリプトコントリビューションとプラットフォームとの間を結ぶバインディングレイヤーの定義が必要です。このレイヤーには、以下のものが含まれます。

  • 標準のビューコントリビューションの代わりとなる、標準のEclipse拡張ポイントを模倣する拡張ポイント(scriptedViewなど)。スクリプトから返されるEclipseビューを提供するために必要。
  • org.eclipse.ui.part.ViewPartなどの標準Eclipseインターフェイスを実装し、メソッド呼び出しを基幹スクリプトに委任するプロキシクラス。
  • org.eclipse.ui.startup拡張ポイントへの拡張。スタートアップ時にスクリプトコントリビューションとプラットフォームとの間のすべてのプラミングとバインディングを行う。

 JavaScript実装に支えられているEclipseビューの例を見てみます。図4は、この例のサイクル全体を表しています。これを見ると、構成/スタートアップ時に実行されるアクションと、実行時に実行されるアクションの違いがわかります(Eclipseアクションセットなど、他の種類のコントリビューションについては、サンプルコードを参照してください)。

図4 スクリプトコントリビューションの処理サイクル
図4 スクリプトコントリビューションの処理サイクル

 まず、次のコードを使用して、スクリプトコントリビューションを定義します。

<plugin>
   <extension
      point="com.devx.scripting.scriptedContribution">
      <scriptedView
         allowMultiple="false"
         id="com.devx.scripting.jsCalculator
         name="JavaScript Calculator">
         <script
            extension="js"
            id="com.devx.scripting.jsCalculator.script"
            uri="scripts/jsCalculator.js">
         script>
      scriptedView>
   extension>
plugin>

 この拡張は標準のorg.eclipse.ui.viewsと非常によく似ています。唯一異なるのは追加の