M12i.

学術書・マンガ・アニメ・映画の消費活動とプログラミングについて

JP1/AJS2ユニット定義パーサにDate-Time APIライクな機能追加

以前何度か紹介したJP1/AJS2ユニット定義パーサに、Java 8で導入されたDate-Time APIをまねた機能を追加しました。これにともない古いAPIのかなりの部分を破棄しています。

Date-Time APIはその名の通り日付・時間をあつかうためのクラス/インターフェース群で、従来からJava SEの標準ライブラリに含まれていたjava.util.Datejava.util.Calendarjava.text.DateFormatに代わるものです。

このAPIの特徴はいろいろありますが、今回とくに参考にしたのはインタフェースTemporalQuery<R>を中核とした非常に柔軟性の高いアクセサーの提供です。詳細はOracleの公式ドキュメントや各種情報サイトの記事などを見ていただきたいですが、このインターフェースを使うことでDate-Time APIのユーザは日付や時間を表わすオブジェクトから任意の型のデータを取り出す(問い合わせる)ことができます:

final LocalDate ld = LocalDate.of(2016, 2, 24);
final DayOfWeek dow = ld.query(DayOfWeek::from);
System.out.println(dow);

注目すべきはもちろん2行目です。DayOfWeek::fromという「メソッド参照」(これもJava 8で導入されたもの)によりTemporalQuery<DayOfWeek>インスタンスqueryメソッドに渡されています。

TemporalQuery<R>R queryFrom(TemporalAccessor)という単一のメソッドを宣言した関数型インターフェースです。日付や時間を表わすオブジェクトが持つ<R> R query(TemporalQuery<R>)TemporalQuery<R>を受け取るとレシーバ・インスタンス自身を引数にしてR queryFrom(TemporalAccessor)を呼び出します。そしてクエリが返したオブジェクトをそのまま返します。したがって先程のサンプルコードのメソッド呼び出しに型注釈をつけるとこんな感じになります:

DayOfWeek LocalDate#<DayOfWeek>query(TemporalQuery<DayOfWeek>)

若干繰り返しになりますが、利用するTemporalQuery<R>インスタンス次第で任意の型のオブジェクトを得ることができるわけです。これにより日付や時間(あるいはそこから導出される何らかの情報)をあらわすオブジェクトに相互変換のためのメソッドを個別実装する必要がなくなります。個々のオブジェクトは<R> R query(TemporalQuery<R>)の実装さえ持っていればよく(これまたJava 8で導入された「defaultメソッド」のおかげでこのqueryメソッドすら個別実装は不要になっています)、○○から△△への変換はこのメソッド、○○から××への変換は別のメソッド・・・といった個別目的ごとのメソッドは不要です。データの変換や抽出のロジックはクエリ・オブジェクトというかたちで一般化・外部化されているわけです。DOMのようにキャストのコーディングをユーザに強いることもありません。結果としてこの新しいAPIの「概念的な重み」は小さく抑えられています。これはAPIの開発者にとってもユーザにとっても歓迎すべきことでしょう。

Java 8ではここに「メソッド参照」と「ラムダ式」という2つの補助技術が加わってAPIの利便性がさらに高められるわけですが、それがなくてもこのクエリ・パターンはなかなかに便利です。というわけで、JP1/AJS2ユニット定義パーサでもこのモノマネをしてみました:

package ...;

import org.unclazz.jp1ajs2.unitdef.parameter.AnteroposteriorRelationship;
import org.unclazz.jp1ajs2.unitdef.parameter.FixedDuation;
import org.unclazz.jp1ajs2.unitdef.Unit;
import org.unclazz.jp1ajs2.unitdef.Units;
import static org.unclazz.jp1ajs2.unitdef.UnitQueries.*;
import static java.lang.System.*;

public final class Usage {

    private static final String sampleDef = ""
            + "unit=XXXX0000,AAAAA,BBBBB,CCCCC;\r\n"
            + "{\r\n"
            + " ty=n;\r\n"
            + " el=XXXX0001,g,+80 +48;\r\n" 
            + " el=XXXX0002,g,+240 +144;\r\n"
            + " ar=(f=XXXX0001,t=XXXX0002);\r\n" 
            + " cm=\"これはコメントです。\";\r\n"
            + " fd=30;\r\n"
            + " unit=XXXX0001,AAAAA,BBBBB,CCCCC;\r\n"
            + " {\r\n"
            + "     ty=pj;\r\n"
            + "     sc=\"hello.exe\";\r\n"
            + " }\r\n"
            + " unit=XXXX0002,AAAAA,BBBBB,CCCCC;\r\n"
            + " {\r\n"
            + "     ty=pj;\r\n" 
            + "     sc=\"bonjour.exe\";\r\n"
            + " }\r\n"
            + "}\r\n";

    public static void main(String[] args) {
        // ユニット定義を文字列から読み取ります
        // Units.from...系メソッドは文字列・ストリーム・ファイルに対応しています
        final Unit u = Units.fromCharSequence(sampleDef).get(0);

        // Unitオブジェクトはユニット定義情報にアクセスするローレベルのAPIを提供します
        out.println(u.getName()); // => "XXXX0000"
        out.println(u.getType()); // => "JOBNET"
        out.println(u.getSubUnits().size()); // => 2

        // UnitQuery<T>とそのユーティリティを使ってユニットから各種の情報を取得します
        // 実行所要時間の取得
        final List<FixedDuration> p0 = u.query(fd());
        // 下位ユニット間の実行順序関係を取得
        final List<AnteroposteriorRelationship> p1 = u.query(ar());
    }
}