デジャヴ

次回はこれらのアノテーションを使ったバリデータの実装を考えてみようと思う。
JSR303のアノテーションを利用する

ということでバリデータのクラスを書いているのだが。

public final class TestModel {
    @AssertFalse
    boolean item1;
    
    @AssertTrue
    boolean item2;
    
    @DecimalMax(value="123456789.1235")
    double item3;

    @NotNull
    @Size(min=5, max=10)
    @Pattern(regexp="^[A-Z0-9_]+$")
    String item4;

    @NotNull
    @Size(min=11, max=11)
    @Pattern(regexp="^[0-9]+$")
    @Min(value=0)
    @Digits(integer=11, fraction=0)
    String item5;
}

開発者がモデル・オブジェクトをバリデーションする際、その順序として直感的なのはフィールドの定義順だろう。

item1 -> item2 -> item3 -> item4 -> item5

この順でバリデーションを実施して制約違反が発生した際は処理を中断してエラー処理を実行する。

@ValidatorClassアノテーションに記述した制約毎に用意されたバリデータを順に起動していくには、以下のようなコードを書くだろう。

public boolean validateModel(Object model, List errors) {
    if ( errors != null ) errors.clear();
    
    boolean result = true;
    
    Field fields = model.getClass().getDeclaredFields();
    for (Field f : fields) {
        f.setAccessible(true);
        IValidator validators = AbstractValidator.getValidator(f);
        for ( IValidator v : validators ) {
            result &= v.validate(f.get(model));
            errors.add(v.getMessage());
        }
    }
    
    return result;
}

長くなるのでソースコードを示さないが、AbstractValidator.getValidatorメソッドはフィールドに記述されたアノテーションから適切なバリデータの配列を取得する処理である。

この処理の問題はリフレクションで取得したFieldが定義順にはならないため、既に書いた通りフィールドの定義順にバリデーションが実行されないことだ。

Field[] fields = model.getClass().getDeclaredFields();
//定義順に格納される訳ではない

どうして定義順で取れないんだろう。確か.NET C#は定義順で取れたような。
と、ここで以前に同じ愚痴を書いていたことを思い出した。

getMethods()と順序

今回はJavaではなくてAndroidだし、実装が違う可能性もあるかもと調べてみたのだが、結果は同じだった。
(当時のコメントでDocletAPIを使うことで定義順にメソッドを取れるとアドバイスを頂いていたが、AndroidでDocletAPIは使えない)

メソッドで定義通りで取得できない以上、フィールドに序列を付けるしか無い訳で、今回もアノテーションに登場願うことになった。

public final class TestModel {
    @Order(1)
    @AssertFalse
    boolean item1;
    
    @Order(2)
    @AssertTrue
    boolean item2;
    
    @Order(3)
    @DecimalMax(value="123456789.1235")
    double item3;

    @Order(4)
    @NotNull
    @Size(min=5, max=10)
    @Pattern(regexp="^[A-Z0-9_]+$")
    String item4;

    @Order(5)
    @NotNull
    @Size(min=11, max=11)
    @Pattern(regexp="^[0-9]+$")
    @Min(value=0)
    @Digits(integer=11, fraction=0)
    String item5;
}

制約アノテーションは多重に定義する可能性が高く、ただでさえ種類が多いのに、そこに@Orderまで追加すると、上記のように甚だしつこい様子になってしまう。

何か考えないとな。