プログラムdeタマゴ

nodamushiの著作物は、文章、画像、プログラムにかかわらず全てUnlicenseです

Eclipseプラグイン開発: バンドルリソース関連

Eclipse プラグイン開発 目次



 IPath,IResourceなどで、Eclipseプラットフォームが管理するリソースにアクセスが出来ます。

 しかし、プラグインがアクセスするのは何もユーザーが準備するファイルだけではありません。プラグイン自身が始めから持っているファイル達も利用する場面があります。今回の話題はそういったファイルの扱い方です。

バンドルリソースの場所

 プラグイン開発をし、実際に一般に使えるようにした場合、プラグイン用に用意したリソースが配置される場所は2通りあります。

  • 配布jarの中に入ったまま
  • インストール時に自動的に展開される


 インストール後、展開されるかどうかはfeatureの設定によりますが、基本的には後者は「exe」などの実行ファイルや、外部ライブラリの「jar」等を格納するために使い、画像などのファイルはjarの中に入れたままにすることが多いようです。


 展開されたリソースはjava.io.Fileやjava.nio.Pathで処理可能ですが、jarに同梱されているリソースはFileやPathでは処理できません。どちらのインストール方法でも対応できるように基本的にはバンドルのリソースはURLで操作することになります。


バンドルリソースの取得

 では実際にプラグインに梱包されたリソースを読み出してみましょう。例として次の様な内容のプラグインを作成しました。

f:id:nodamushi:20170405023654p:plain

 mytest.Activatorはプラグインを作ったら出てくるコードそのままです。ブログ掲載のためにコメントと空白を消しただけで、特にプログラムを弄ってはいません。

package mytest;

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;

public class Activator implements BundleActivator {
  private static BundleContext context;
  static BundleContext getContext() {return context;}
  public void start(BundleContext bundleContext) throws Exception {
    Activator.context = bundleContext;
  }
  public void stop(BundleContext bundleContext) throws Exception {
    Activator.context = null;
  }
}

 


 一番単純なリソースの取得方法は、このActivatorに渡されるBundleから直接リソースURLを取得する方法です。存在しないリソースを取得しようとした場合はnullが返ります。

  //ResourceTest.javaの中身
  Bundle bundle = mytest.Activator.getContext().getBundle();

  @Test public void getResourceTest(){
    assertThat( bundle.getEntry("/resource/alphabet.txt"),notNullValue());
    //存在しないときはnull
    assertThat( bundle.getEntry("/resource/hoge.txt"),nullValue());
  }

 


 URLを取得できてしまえば、中身を読み出すには普通にopenStreamしてしまえば良いので簡単ですね。

  //ResourceTest.javaの中身
  private BufferedReader reader(InputStream i){
    return new BufferedReader(new InputStreamReader(i));
  }

  @Test public void readResourceTest() throws IOException{
    URL entry = bundle.getEntry("/resource/alphabet.txt");
    try(BufferedReader r= reader(entry.openStream())){
      assertThat(r.readLine(),is("abcdefghijklmnopqrstuvwxyz"));
    }
  }

 


 これでも基本的には問題ないのですが、フラグメント・プロジェクトを扱うと、これではファイルが取得できないという問題があります。そういった問題も考慮し、お行儀良くURLやInputStreamを取得する場合は、FileLocatorを使います。

 とりあえず、今はこれだと困ることがある、ということだけ理解してください。詳しくはフラグメント・プロジェクトの項で解説します。

  //ResourceTest.javaの中身
  Path path = new Path("/resource/alphabet.txt");
  @Test public void getResourceTest2(){
    URL url = FileLocator.find(bundle, path, null);
    assertThat(url,notNullValue());
  }

  @Test public void readResourceTest2() throws IOException{
    try(BufferedReader r=
        reader(FileLocator.openStream(bundle,path, false))){
      assertThat(r.readLine(),is("abcdefghijklmnopqrstuvwxyz"));
    }
  }

 

URLをFileやPathに変換する際の要注意事項

 URLをjava.io.Fileやjava.nio.file.Pathに変換する場合に注意する点があります。 (以降この節ではFile,Pathと省略。EclipseのPath (org.eclipse.core.runtime.Path)ではない)

 ネイティブ実行ファイル(exeなど)をプラグインに含む場合、インストール方法を展開にします。こうすると、実行ファイルがファイルとして展開されて保存される為、「jarから取得→一時ファイルとして保存→実行」をしなくても、パスさえ取得出来れば即座に実行出来ます。この時、FileやPathとしてパスを取得すると、JavaのAPIと関連させる場合に便利です。

 一般的には、URLからURIに変換し、そのURIからFileやPathを得ます。その時にはtoURIを使わずに以下の様にしてください。

URL  url  = FileLocator.find(bundle, path, null);
URI  uri  = new URI(url.getProtocol(), null, url.getPath(), null);
File file = new File(uri);
Path path = Paths.get(uri);


 URL.toURIを使うと、URLが特定の文字を含む場合、例外が発生します。
 問題の核は、URLがRFC2396に従って適切なエスケープがされている必要があるという点です。例えば空白文字は%20、%そのものは%25などの様にエスケープする必要があります。このエスケープ処理はURLを生成するクラスが責任を持ちます。これらの詳細はURLのJavaDocを読んでください。

 Eclipseが作成するURLは、空白文字列を含む場合は空白をそのまま空白として出力します。従って、toURIをそのまま使うと、例外が発生する可能性があります。



バンドルリソースの画像ファイルの取得

 Eclipseプラグイン開発において、画像はImageのまま保持するのではなく、ImageDescriptorの形で保持しておいて、データを使い回すと言うことが多いです。

 このImageDescriptorを作るのは割と面倒なんですが、AbstractUIPluginというクラスのstaticメソッドとして便利関数が定義されているのでこれを使います。URLのときと同様に、存在しない画像ファイルの場合はnullが返ります。

  //ResourceTest.javaの中身
  @Test public void getImageTest(){
    ImageDescriptor img = AbstractUIPlugin.imageDescriptorFromPlugin(bundle.getSymbolicName(), "/resource/test.jpg");
    assertThat(img,notNullValue());

    ImageDescriptor img2 = AbstractUIPlugin.imageDescriptorFromPlugin(bundle.getSymbolicName(), "/resource/hoge.jpg");
    assertThat(img2,nullValue());
  }


 といっても、AbstractUIPlugin~~~~って毎度書くのも面倒くさいし、何のプラグインの画像読み出してるのかわかりにくいので、私はこうしています。もっと真面目に実装する場合はリソースマネージャーとか自作するみたいよ。私はしてないけど。

  //Activator.javaに追加
  /** このプラグインのID */
  public static final String PLUGIN_ID="mytest";

  /**
   * このプラグインのリソースからImageDescriptorを取得
   * @param path 画像ファイルのパス
   * @return
   */
  public static ImageDescriptor getImageDescriptor(String path){
    return AbstractUIPlugin.imageDescriptorFromPlugin(PLUGIN_ID, path);
  }

 

共通画像の取得

 Eclipseプラグインを開発する上で、大概必要になるフォルダアイコンやらを一々用意するのは面倒です。むろん、AbstractUIPlugin.imageDescriptorFromPluginで他のプラグインの持っている画像を取得することは出来ることは出来ますが、画像ファイルのパスが分からんし、調べたとしても(調べるプラグインがあります)、そのパスがずっと有効かどうかなんて分かりません。

 そんな開発者のために、一般によく用いられるアイコンに関しては、取得出来るようにしてくれてある場合があります。

ISharedImages shared = PlatformUI.getWorkbench().getSharedImages();
//ファイルアイコン
shared.getImageDescriptor(ISharedImages.IMG_OBJ_FILE);

//Java画像も公開されています
JavaUI.getSharedImages().getImageDescriptor(org.eclipse.jdt.ui.ISharedImages.IMG_OBJS_CLASS);

//CDTも公開しています
CDTSharedImages.getImageDescriptor(CDTSharedImages.IMG_OBJS_BUILD);