思いついたこと (3)

メタモデル記述をJavaで書いて解釈するってのが前回の主張ですが、GAE以外のケースでいくらかうまくいかないものを発見。
たとえば、HogeがFooを参照していて、それぞれのメタモデルがHogeMetaとFooMetaであるとすると、HogeMetaは次のように書かなきゃいけない。

public class HogeMeta {
    // FooはFooMetaからあとで生成されるので、いまは参照できない
    public static final Property<Foo> foo = ...;
}

そうすると、FooじゃなくてFooMetaを参照しないといけない。

public class HogeMeta {
    public static final Property<FooMeta> foo = ...;
}

もしくは遅延を掛けておく。

public class HogeMeta {
    public static final Property<?> foo = Property.modelOf(FooMeta.class, ...);
}

普通にモデルオブジェクトを作るくらいなら問題ないのですが、S2JDBCGenくらい高度なことやろうとすると問題としては大きい。単にHogeMeta/FooMetaを高階メタモデルとして、その次に一階メタモデルと実モデルを生成するという手はあるけどわざわざJavaメタモデルの宣言する利点が半減する(Validationの層をメタモデルでやりたかった)。

この時点では確かにFooがどのようなクラスになるか分かってないのでFooMetaしか参照できないのは仕方がないのだけど、非常にいまいちではある。
それならいっそProperty<Integer>とかProperty<String>の部分もJavaの型をそのまま使うんじゃなくてメタ型を使ってしまうのもいいのかな…

とりあえずこの問題は保留して、なんか作ってみるかな。

思いついたこと(2)

  • JDOはどう考えてもDatastore Low Level APIと相性が悪い
  • Low Level APIはEntityを生で扱うあたりがありえない
  • Javaはかなり厳格にnominal typeを扱うので、Pythonのような無茶がしにくい

というあたりで、モデル記述からエンティティモデルを自動生成するスタイルが妥当かなーと思っていましたが、モデル記述の記法で悩んでました。DSL使えばいいんですが、俺俺言語すぎるのもアレだなと。

ということでJavaの言語内DSLっぽくやればいいんですけど、これはこれで面倒。

  • Beanに規約ベース/注釈ベースで属性書いていくのはあまり好きじゃない
    • getter/setterのドキュメント書くのがめんどくさい
    • getter/setterにミスマッチが紛れ込むかも
  • public fieldに属性書いていくのも好きじゃない
    • リフレクションを経由しないとメタ情報を取得できない

なんとなく思いついたのが、実際に使うエンティティクラスを装飾するんじゃなくて、エンティティ飲めた情報をクラスで定義して、それを装飾すればいいんじゃないかと。

こんな感じ。

@Meta
public class HogeEntityMeta {
    
    public static final Property<Integer> foo = Property.of(Integer.class);

    public static final Property<String> bar = Property.of(String.class, new Constraint<String>() {
        public void validate(String value) throws ValidationException {
            if (value == null) {
                throw new ValidationException("is null");
            }
            if (value.length() > 500) {
                throw new ValidationException("is too long");
            }
        }
    });
}

これをaptで読んで、メタ情報からエンティティクラスを生成する。

public class HogeEntity {
    private Integer foo;
    private String bar;
    public Integer getFoo() { return foo; }
    public void setFoo(Integer foo) { this.foo = foo; }
    // ...
}

実際はドキュメントの生成もしたいので、メタ情報のクラスフィールドにJavadocを書いておけば、エンティティクラスのgetter/setterにも反映されるとかにしておく。

Low Level APIのエンティティからHogeEntityに変換する場合は、data exchange object classもついでに生成してやればいい。逆変換も同様。

これまでは、エンティティクラスのほうに注釈つけるのがよくある手法でしたが、こういうようにメタ情報を注釈そのものにしておいて、そこからエンティティクラス生成するのがよさそうな気がしてきた。

思いついたこと

寝ようと思った瞬間になんか思いつくのはもはや呪い。

@Dao(Hoge.class)
public interface HogeDao {
    @Read
    Hoge get( // WHERE email = :email AND ANCESTOR = :parentKey
        @Parent String parentKey,
        String email
    );

    @Read
    @OrderBy("date asc")
    List<Hoge> get( // WHERE date >= :from AND date <= :to
        @GreaterThanOrEquals("date") from, // @GEでいいか
        @LessThanOrEquals("date") to // @LEでいいか
    );

    @Append
    @Transactional
    Foo insert( // (WHERE email = :email).fooList.add(Foo)
        @Property("email") String email,
        @Child("foo_list") Foo
    );
}

BigTableはモデルが複雑じゃないし、クエリもたいしたことかけないので上記で十分だと思う。

他の手法に対する優位性は、

  • 静的解析できる
    • モデルとの整合性
    • GQLとしての正当性
    • インデククスの自動生成
  • JDOに縛られない
    • JDOのクエリがいまだに好きになれない
    • PersistentManager.retrieve 周りがいまだに怪しげ

未検証なのは、Entity Groupを使ったトランザクション内での更新を宣言的にカバーできるかどうか。まだ実例を積んでないからなんともいえない。

最悪の場合、GQLにINSERT/UPDATEの構文作ってそれっぽい動きをさせればよさげ。書いててそっちのほうがよさそうだと思いつつある。

Least Containing Type Argument の計算がおかしい?

めも。

where lcta() is the the least containing type argument function defined (assuming U and V are type expressions) as:

lcta(U, V) = U if U = V, ? extends lub(U, V) otherwise
lcta(U, ? extends V) = ? extends lub(U, V)
lcta(U, ? super V) = ? super glb(U, V)
lcta(? extends U, ? extends V) = ? extends lub(U, V)
lcta(? extends U, ? super V) = U if U = V, ? otherwise
lcta(? super U, ? super V) = ? super glb(U, V)

15.12.2.7 Inferring Type Arguments Based on Actual Arguments

上記の赤くした部分て、U = V であっても lctaの定義から ? じゃないとおかしくね?と思った。

現行のコンパイラの実装はすべて?(境界なしのワイルドカード)になるように調整されてるっぽい。

SunのコンパイラとEclipse JDTで動きが違う(7)

下記はJDTでエラー。

static void f() {
    int[][] a = null;
    int[][][] b = null;
    Object[] tt = tt(a, b); // error@JDT
}
static <T> T tt(T a, T b) {
    return a;
}

次元数が違うプリミティブの配列型を渡したときにエラーになる。

SunのコンパイラとEclipse JDTで動きが違う(6)

f()の中身はJDTではすべてOK、javacでは全滅。なんか代入コンテキストの判定がおかしくない?

private void f() {
    if (t()) {}
    assert t();
    int a = t() ? 1 : 2;
    for (;t();) {}
    while (t()) {}
    do {} while(t());
    System.out.println((new int[t()])[t()]);
}

<T> T t() {
    return null;
}