-Project RTTI

Eclipseのプロジェクト内は、そのままでは普通のクラスロードやリフレクション、オブジェクトの生成ができません。JDTのJavaモデル操作APIを用いてエディタで人間が読み書きするのと同様の操作を行えるのですが、Javaリフレクション(java.lang.reflect.*)が使えないために、XML等に書かれたOGNLやGroovy等のスクリプト言語は参照するプロジェクト内で、まさに今エディット中のJavaオブジェクトの型が解決できないのです。SpindleでもOGNL式まで来たらまったく何もバリデーションしてくれないのはそのためです。そこで昨日の風呂で思いついたプロジェクト内のJavaクラスのリフレクションモデルの第一歩として(というか、ほとんど終わってますが)プロジェクト内JavaクラスのRTTI(Runtime Type Information)を実装しました。基本は5/8-5/10に、温泉につかりながら作っていたJDT操作ユーティリティです。プロジェクト内OGNLから使えるように、細かい機能をまとめ整理しました。APIインターフェイスは以下のとおりです。

package org.seasar.eclipse.core.rtti;
public interface ProjectRTTI {
  public boolean isPrimitive();
  public boolean isArray();
  public boolean isTypeAvailable();
  public String getFullyQualifiedName();
  public boolean isInterface();
  public boolean isAssignableFrom(ProjectRTTI testRTTI);
  public ProjectRTTI getArrayItemClass() throws RTTIException;
  public ProjectRTTI[] getSuperInterfaces() throws RTTIException;
  public ProjectRTTI getSuperClass() throws RTTIException;
  public ProjectRTTI  invoke(String methodName) throws RTTIException;
  public boolean hasSetterMethod(String property, String argType) throws RTTIException;
  public boolean hasGetterMethod(String property) throws RTTIException;
  public ProjectRTTI getProperty(String property) throws RTTIException;
  public ProjectRTTI getMember(String member) throws RTTIException;
}

これの実装クラスが一つあって、それのコンストラクタは以下のとおり。

public ProjectRTTIImpl(IJavaProject project, String name)

動くサンプルはテストしかありませんので、そのテストケースで使い方を示します。実際のソースでは今日現在で36個のテストケースがありますので、読めばJDTやEclipseプラグインを知らなくても、およそ使い方がわかると思います。

  protected void setUp() throws Exception {
    project = new TestProject();
    IPackageFragment pack = project.createPackage("test");
    project.createType(pack, "IPerson.java", "public interface IPerson {}");
    project.createType(pack, "AbstractPerson.java",
      "public abstract class AbstractPerson {"+
      "  public int integer;" +
      "}");
    project.createType(pack, "Person.java", 
      "public class Person extends AbstractPerson implements IPerson" +
      "  public Person parent;" +
      "  public char[ ] array;" +
      "  public Person createPerson() {" +
      "    return new Person();" +
      "  }" +
      "  public Person getParent() {" +
      "    return parent;" +
      "  }" +
      "}"
    );
    i_person = new ProjectRTTIImpl(project.getJavaProject(), "test.IPerson");
    a_person = new ProjectRTTIImpl(project.getJavaProject(), "test.AbstractPerson");
    person = new ProjectRTTIImpl(project.getJavaProject(), "test.Person");
  }

以上のようにお膳だてしているところで、以下のようなユニットテストが通ります。特に難しい処理をしているものを5つ抽出しました。まず、testIsAssignableFrom3()は、アサインというだけで難しい。これが無いとプロパティSetterの解決ができません。setProp(int) とsetProp(Prop)ではまったく違うのですが、動かすことができないので実行時の例外でチェックするわけにいかないのです。メンバーアクセス2つは、スーパークラスのメンバーアクセスと、配列型の処理です。メソッドはJavaの言語仕様で引数型に関わらず、戻値は一定というルールに気が付くまで無駄な実装をしてました。最後のプロパティは、複合技です。内部的にはとにかく面倒な処理が連続します。

  public void testIsAssignableFrom3() {
    assertTrue(i_person.isAssignableFrom(person));
  }

  public void testGetMember3() throws Exception {
    ProjectRTTI member = person.getMember("integer");
    assertEquals(member.getFullyQualifiedName(), "int");
  }

  public void testGetMember4() throws Exception {
    ProjectRTTI member = person.getMember("array");
    assertEquals(member.getFullyQualifiedName(), "char[]");
  }

  public void testInvoke3() throws Exception {
    ProjectRTTI ret3 = person.invoke("createPerson");
    assertEquals(ret3.getFullyQualifiedName(), "test.Person");
  }

  public void testGetProperty() throws Exception {
    ProjectRTTI prop = person.getProperty("parent");
    assertEquals(prop.getFullyQualifiedName(), "test.Person");
  }

いつもの、http://www.kjps.net/user/kurihara/ に全実装をアップしておきました。動作環境はスペシャルにPDEユニットテストが速くなったEclipse3.0I20040518(build:200405181622)以降としておきます。Eclipseプロジェクト中の特殊環境に対応するものなので、PDEユニットテストプラグインの上で実行します。2.1系はコンパイルおよび動作未確認です。
今後OGNL式でこのProjectRTTIオブジェクトに対する、PropertyAccessor/MethodAccessor等を作ります。今の既知の実装漏れは、staticなメソッド、メンバーのアクセス。これも早期にできるでしょう。あとは、hasSetterMethod(String, String)じゃなく、hasSetterMethod(String, ProjectRTTI)に直します。今日は遅いのでまた明日。