[Eclipseプラグイン開発] Resource関連


# 目的

- Eclipseプラグインに関連する知識を（自分的に）再整理したい
- 主にResource関連について整理する
- 関連する主なプラグインは
    - プラグインID : `org.eclipse.core.resources`
    - プラグイン本体 : `ResourcesPlugin`
- 環境は Eclipse 4.5 (3.2 以降ならあまり変わらないはず）

# 要素

Eclipse は Java をはじめとしたさまざまな言語の開発環境をサポートしています。また開発環境以外の目的で使われることもあります。
そしてそれぞれ用の独自のプロジェクトなどがあります。

`IResource` があらわすのは、特定の開発環境やプロジェクトなどに依存しない、共通的なリソース（ファイルやフォルダ、汎用的なプロジェクトなど）です。

## IResource

`IResource` は、Eclipse で管理しているリソース（ファイルやフォルダ）をあらわすインタフェースです。
逆に言えば、Eclipse が管理していないファイルやフォルダを表すことはできません。

また `IResource` は、これから作られるリソースを示すこともあります。つまり `IResource` のオブジェクトはあっても、それが指し示す実体のファイルやフォルダがまだ作られていないことがあります。
（java.io.File を new したからといって、ファイルができるわけでは無いことと同じです）

表す対象に応じて、以下のインタフェースが用意されています。
また、子を持つインタフェースは `IContainer` も拡張しています。

| インタフェース名 | 説明 | IContainer の拡張 |
|:--|:--|:--:|
| IWorkspaceRoot | ワークスペース | ○ |
| IProject | プロジェクト | ○ |
| IFolder | フォルダ | ○ |
| IFile | ファイル | × |

## IContainer

ワークスペース、プロジェクト、フォルダなどの「子要素を持つことのできるリソース」を表します。

子要素は `IContainer#findMember()` や `IContainer#members()` などで取得することができます。

## IWorkspaceRoot

Eclipse のワークベンチは、１つのワークスペースを対象としています。

ワークスペースを表すクラスは２種類あり、１つは `IWorkspaceRoot` であり、もう１つは `IWorkspace` です。

位置づけ的に `IWorkspace` がワークスペースの「機能的」側面を表しており、 `IWorkspaceRoot` はワークスペースの「リソース的」側面を表すものだと思います（たぶん）。

`IWorkspace` は `ResourcesPlugin#getWorkspace()` から取得できます。
`IWorkspaceRoot` は `IWorkspace#getRoot()` から取得することができます。

```java
IWorkspace workspace = ResourcesPlugin.getWorkspace();
IWorkspaceRoot workspaceRoot = workspace.getRoot();
```

また `IResource` は自分の属する `IWorkspace` を覚えており、 `IResource#getWorkspace()` で取得することもできます。

## IProject

ワークスペース内には複数のプロジェクトを持つことができます。

`IWorkspaceRoot#getProjects()` ですべてのプロジェクトを取得できます。
名前がわかっていれば `IWorkspaceRoot#getProject(String)` でプロジェクトを取得することができます。

また `IResource` は自分の属する `IProject` を覚えていますので `IResource#getProject()` で取得することができます。

## IFolder

フォルダを表します。フォルダ内にはサブフォルダやファイルを持つことができます。

フォルダは、以下のメソッドなどで取得可能です。

- `IProject#getFolder(String)`
- `IContainer#getFolder(IPath)`
- `IContainer#findMember()`  - フォルダ以外のこともある
- `IResource#getParent()` - フォルダ以外のこともある
- その他、いろいろ

基本的にメソッド名が find から始まるものは、存在するリソースを見つけます。しかし getFolder() メソッドなどはフォルダが存在しなくても IFolder オブジェクトを返します。必要であれば create() メソッドで作成してください。

## IFile

ファイルを表します。

以下のようなメソッドなどで取得できます。

- `IWorkspaceRoot#getFileForLocation(IPath)`
- `IProject#getFile(String)`
- `IContainer#getFile(IPath)`
- `IContainer#findMember()`  - ファイル以外のこともある
- その他、いろいろ

基本的にメソッド名が find から始まるものは、存在するファイルを見つけます。しかし getFile() メソッドなどはファイルが存在しなくても IFile オブジェクトを返してきます。
そのためファイルが存在する場合には更新、ファイルが存在しない場合にはファイルを新規作成といった判断が必要です。

```java
try (InputStream in = new ByteArrayInputStream(bytes)) {
    IFile file = IFolder.getFile(name);
    if (file.exists()) {
        file.setContents(in, false, true, null);

    } else {
        file.create(in, false, null);
    }
}
```

## IPath

リソースそのものを示すのは `IResource` ですが、リソースの場所を示すのは `IPath` です。
（正確には `IPath` は Eclipse で管理しているリソース以外も表せます）
`IPath` には相対パスや絶対パスがあります。

`IResource` のメソッドでいくつかの `IPath` を取得できます。

たとえば `C:\Workspace` にワークスペースがあるとして、Project `project` 内の `data/file.txt` のファイルの場合の取得例を示します。

| メソッド名 | 取得できる内容 | 取得例 |
|:--|:--|:--|
| `IResource#getFullPath()` | ワークスペースからの相対パス | `/project/data/file.txt` |
| `IResource#getProjectRelativePath()` | プロジェクトからの相対パス | `data/file.txt` |
| `IResource#getLocation()` | 絶対パス | `C:/Workspace/data/file.txt` |
| `IResource#getRawLocation()` | 絶対パス（リンク対応） | （同上） |

※文字列化は `IPath#toPortableString()` を使用した例です。
※プロジェクトはワークスペース直下にあることを想定しています。別の場所にあることもあるので注意してください。
※ `getRawLocation()` はリンクされたリソースの場合に異なる結果を返すことがあります。またリソースの種類によっては `null` を返すことがあります。

また `IPath` を文字列化する方法は以下のメソッドで可能です。

| メソッド名 | 取得できる内容 | 取得例 |
|:--|:--|:--|
| `IPath#toOSString()` | プラットフォーム個別パス | `C:\Workspace\data\file.txt` |
| `IPath#toPortableString()` | プラットフォーム共通パス | `C:/Workspace/data/file.txt` |
| `IPath#toString()` | 文字列化 | （同上） |

相対パスは `IPath#append()` などで結合した新しい `IPath` を作ることができます。元の `IPath` は書きかわらずに、新しいインスタンスが生成されます。

```java
IPath projectPath = project.getLocation();
IPath newPath = projectPath.append("data/file2.txt");
```

また `IPath` の具象クラスとして `org.eclipse.core.runtime.Path` があります。

```java
String pathName = "C:/Workspace/project/data/file3.txt";
IPath path = new Path(pathName);
```

## IAdaptable

前述のとおり Eclipse には、独自の開発環境用のプロジェクトや、それ専用のリソースを表すクラスもあります。

例えば、Java の開発環境(Java development tools; JDT)では `IResource` の代わりに `org.eclipse.jdt.core.IJavaElement` というインタフェースでリソースを表します。
そして、プラグインで選択項目( `org.eclipse.jface.viewers.IStructuredSelection` )を取得したりすれば、それらを実装したクラスが返ってくることがあります。

もちろん `IJavaElement` から、共通のリソースである `IResource` に変換するメソッドはあります( `IJavaElement#getCorrespondingResource()` )。
そして JDT だけではなく他の開発環境などでも同じようなメソッドは用意されていると思います。

しかし、汎用的な「選択されたファイルの文字コードを変換する」というプラグインを作る際に、JDTやその他のプラグインのすべてのクラスに対応することは、現実的ではありません（プラグインの依存関係はどうする？　今後追加された別の開発環境用のクラスはどうする？　など）。

そこで、Eclipse では `IAdaptable` というインタフェースを用意しています。

これは「指定のクラスに変換できるなら、変換したオブジェクトを返す」というメソッドが定義されたインタフェースです。

```java:IAdaptable.java
package org.eclipse.core.runtime;

public interface IAdaptable {
	public <T> T getAdapter(Class<T> adapter);
}
```

これを使えば、未知のクラスであっても `IResource` に変換して扱うことができます。もちろん、そのオブジェクトが `IResource` に変換が可能な場合のみですが。

```java:SampleHandler.java
public class SampleHandler extends AbstractHandler {

	@Override
	public Object execute(ExecutionEvent event) throws ExecutionException {
		ISelection selection = HandlerUtil.getCurrentSelection(event);
		if (selection instanceof IStructuredSelection) {
			IStructuredSelection structuredSelection = (IStructuredSelection) selection;
			Iterator<?> iterator = structuredSelection.iterator();
			while (iterator.hasNext()) {
				Object value = iterator.next();
				if (value instanceof IAdaptable) {
					IResource resource = ((IAdaptable) value).getAdapter(IResource.class);
					// resource を使う処理
				}
			}
		}

		return null;
	}
}
```

【言い訳】
上記ソースを見ると `IStructuredSelection` を拡張forループでまわせば良いのにと思うかもしれません。
しかし  `IStructuredSelection` はこんなメソッド名を持っているのに、なぜか `Iterable` になっていません。仕方が無いので愚直に `Iterator` を扱っています。
Java8 以降限定でよければ、メソッド参照を使って以下のように書くことは可能です。

```java
for (Object value : (Iterable<?>) structuredSelection::iterator) {
    // ...
}
```