しいしせねっとわーく
[映像編] [色規格] [JPEG] [QRコード]

Javaで使えるABNF

JavaでABNFを扱うためのライブラリをなんとなく作ってみたので使えるように公開? JSONもABNFでできているので作ってみました。簡易マニュアル的なページです。自己流なので特にきるようなものはありません。

ABNFとは

RFC でよく使われるバッカス・ナウア記法(Backus-Naur form : BNF)のひとつ(Augmented Backus-Naur form)でIETFがRFCを記述する際によく利用される。BNFは、メタ言語などといわれていて、言語を作るための言語のようなものです。他にはEBNFなどが一般的ですがEBNFにもいろいろあるようです。RFC 5234 とRFC 7405で定義されています。RFC 7405の拡張はあまり使われていません。

ABNF(Augmented Backus-Naur form)
定義
RFC 5234, RFC 7405
他の実装
少数?
使用例
IMF, URL, URI, URN, IRI, HTTP, IPv6
ダウンロード
SoftLibABNF (GitHub JDK8,Mavenくらい想定), SoftLibが必要, SoftLibRFC サンプル的なもの

ABNFはメール(CMS)、HTML、IPv6アドレスなどの定義に使われています。XMLなども別のBNFで定義されています。

自作ツールはSoftLibABNFという名で、各種定義はSoftLibRFCとして別途まとめています。しばらく更新せず安定版ですが、現状バージョン付与していないソースコードのみです。

JSONとは

XMLに代わってRESTだとかWeb APIの主役になっているあれです。JavaScript Object Notation という名のとおりJavaScriptではよく使われている形式がWebの標準的なものとして拡大したものですが、Javaで直接扱うのは難しそうなので作ってみました。JavaでJSONを扱えるのは、他に標準API、Jakson、Json in Javaなどがあるようですが、いろいろ機能を盛っているので標準APIよりは便利かどうか。RFC 8259とECMA-404 2nd Editionが最終的なJSONの標準として落ち着いているようです。

JSON(JavaScript Object Notation)
定義
RFC 8259, ECMA-404 2nd Edition
他の実装
Java, Jakson, Json in Java,Gson など各種
使用例
REST
ダウンロード
SoftLibJSON (GitHub JDK8,Mavenくらい想定), SoftLibABNFが必要

自作ツールはSoftLibJSONという名です。現状バージョン付与していないソースコードのみです。

ABNF作り方

ABNFの説明はどこかのを見てください。SoftLibABNFではRFC 5234とRFC 7405に対応しています。

SoftLibABNFでは、ABNFReg というクラスをひとつの名前空間として構築します。ABNFではrulelist相当です。class中にstaticで定義して、下に1行ずつ並べてもいいですし、テキスト形式で流し込んでもかまいません。rule, elementなどはABNF型で対応します。

class Test {
    static ABNFReg REG = new ABNFReg();
}

classひとつでひとつの名前空間を作るくらいの感じで利用しています。

ABNFReg();

ABNFReg(ABNFReg 引き継ぐ名前空間);

ABNFReg(ABNFReg 引き継ぐ名前空間, ABNFReg ABNF定義);

ABNFReg(URL abnfのURL, ABNFReg 引き継ぐ名前空間);

ABNFReg(URL abnfのURL, ABNFReg 引き継ぐ名前空間, ABNFReg ABNF定義);

初期化パラメータは3種類

引き継ぐ名前空間

ABNFにはALPHA,DIGITなどのcore rulesがありますが、それを引き継ぐにはnew ABNFReg(ABNFReg.BASE) とします。他のABNFRegを引き継ぐのも同様です。複製するのでループなどに配慮された複製可能なものがいいです。

ABNFReg.BASEには特殊なパーサは埋め込まれていないのでパース後もPacket形式です。

ABNF定義

2つめのパラメータはABNFパーサ自体のABNFで、省略時はRFC 5234ですが差し替えが可能です。RFC 7405やHTTPなどABNF構文自体に特殊な定義がある場合に差し替えます。普段は気にしなくてもいいかもしれません。EBNFなどに差し替えができるようになるかもしれません。

ABNF定義として ABNF5234.REG や ABNF7405.REG が使えます。これ自体がABNFRegでできているため改変も可能です。RFC 7230ではABNFの拡張がされているため、SoftLibRFC のHTTP7230.PAR として拡張しています。

メモ : ABNFの構文解析で必要ですが、ABNFの定義をABNFで書くと無限ループになってしまいます。Javaで記述する場合は参照しないため、コアな部分は後述するJavaの形で記述しています。

abnfのURL

rulelist(abnfのURL) で読み込むための値を初期値として渡しているだけです。

ABNFReg REGの使い方

登録はREG#rule() または REG#rulelist() で。

rule() はnameとvalue を分けてもあわせても記述できます。valueはABNF型でもよいです。nameとvalueの間にparser classを指定することで特定のデータ型で返すこともできるので拡張が簡単にできます。

参照は REG.ref(String name) または REG.href(String name) で。

name() は、REGへの登録には反映されない?のでruleで名付けた方がいいかもしれません。

登録と参照

static ABNF a = REG.rule("a","b"); // rulename と elements を分ける
static ABNF b = REG.rule("b := c");  // rule として記述(改行省略)
static ABNF c = REG.rule("c", b.or(a)); // elements を javaで記述

ABNFの rule をひとつ作るのは、名前空間(ABNFReg)からrule(String name,value) か、rule(rule) で可能です。ABNF自身の定義では厳密にはruleには改行を含まなくてはいけませんが、rule()では最後の改行は省略できます。

ABNF型は名前がついているrule も名前のついていないelements も扱えます。名前がないものは式そのままっぽいもの(仮)を名前相当で扱います。

一括登録するには、rulelist(定義一覧) が使えます。参照には ref(name) または href(name) が使えます。

定義にABNF変数を使うこともできます。書式ではなくオブジェクトで構築することもできます。(後述)

ABNFReg#ref(String name)では未定義のルールを参照してもかまいません。循環ルールの定義も可能です。

文字コードはutf-8、改行コードはCRLFです。CRのみやLFのみでは読み込めません。

書式の代用

ABNFの書式で書けるものは、Javaの構文で記述することもできます。パースする手間がないぶん速いかもしれません。

name := value
name = value;
名前
a = b.name("a");
択一 a / b / c
rule = a.or(b,c)
連結 a b c
rule = a.pl(b,c)
選択肢追加 b / c / d
a = b.or(c,d)
値の範囲指定 %##-##
ABNF.range(32,64)
反復 a := 2*3b
a = b.x(2,3)
a := 2b
a = b.x(2)
a := *b
a = b.x()
a := 1*b
a = b.ix()
オプション a := b [ d ]
a = b.pl(d.c())
文字
a = ABNF.text('a')
テキスト
a = ABNF.text("text")
バイナリ
a = ABNF.bin(int) または ABNF.bin(String 文字列)
text列 "a" / "b" / "c" / "d"
a = ABNF.list("abcd")

だいたいこんな感じで参照も組み合わせていくとABNFをJavaの構文で構築できます。x()やix(),c()などに慣れるとABNFから気軽に書き換えられるはずです。

要素のコンストラクタは使わない方向でABNF.xxxx() という感じでまとめています。

比較

各ABNFは、is() と eq() という比較演算子とfind()を用意してみました。Packetというクラスを使うと便利ですが、Stringでもかまいません。バイナリを直接読ませるパターンはまだ作っていないのでPacketをかませてください。

eq()とis(Striing)はtrueまたはfalseを返します。 a.is("data") というふうな。Packetの場合は一致した長さが返せたりします。

find() は先頭がABNFに一致した場合に構造の部分を抽出します。名前をつけた要素を抽出したい場合に使うのですが、返す構造に癖があるので直すかもしれません。

構文に対応するパーサ?を指定する パースとマッピングがつよいといろいろ作れる

一般的なABNFパーサの機能はここで終わりですが、ABNFの定義にいろいろなパーサというかビルダというのか組み立て方法を埋め込むことができるので以下簡単に。

static ABNF d = REG.rule( String name, Class<ABNFParser>.cls, Strubg value);

というふうに net.siisise.abnf.parser.ABNFParser インターフェースを継承したものをパラメータとして渡すことで、ABNFでパースした結果を独自の(任意の)型として返すことができるようになります。
ABNFList, ABNFSelect, ABNFSub などいくつかの抽象的実装を用意してありますのでそこから継承します。コンストラクタの形式を統一することで埋め込みを楽にしているので、その周辺は多少癖があります。

public class XXXParser extends ABNFBaseParser<型1, 型2> {
    public XXXParser(ABNF def, ABNFReg reg, ABNFReg base) {
        super(def, reg, base);
    }


    public 型1 parse(FrontPacket pac) {
        // 処理
    } 
}

public class XXXListParser extends ABNFList<型1,型2> {
    public XXXListParser(ABNF def, ABNFReg reg, ABNFReg base) {
        super(def, reg, base, "subのabnf名1", "subのabnf名2" ... );
    }

    public 型1 parse(List<型2> list) {
        // 処理
    }
}

public class XXXSelectParser extends ABNFSelect<型1> {
    public XXXSelectParser(ABNF def, ABNFReg reg, ABNFReg base) {
        super(def, reg, base, "subの選択肢1", "選択肢2", "選択肢3"... );
} public 型1 other(FrontPacket pac) { // 選択肢以外の処理 } }

型1はこのParserが返す型。型2はABNFListなどで使う parseに渡される子の型です。例外もありますがparseで加工したものを上位に渡して組み立てていきます。

コンストラクタの引数 defはこのABNFの定義、regは登録されているABNFReg、baseはRFC 5234準拠, RFC 7405準拠、HTTP拡張など、BNFの種類です。

ABNFBaseParser はそのまま加工する場合。型2は使わないかもしれません。

ABNFListはABNFの*aなど繰り返し要素をリストに分解してから加工する場合。 subは基本的に1つですが、複数指定するとsuperに渡した順で繋いで渡します。登場順ではありません。各1つしかないものを受け取る場合にも使えます。

ABNFSelectは型を持ったParserが複数あってABNFの a / b / c / dなどの場合、aとbはparserがあるが、c, dにはないときc, dはotherで対応します。superにはaとbの名前を渡します。

データはPacketや他のABNFParserで返された値、そのListなどで渡されます。それを組み立てて更に上流に返すことで全体の構造が完成します。

superに他ABNFをREGと名前で渡すのは、ABNFのRFC 5234とRFC 7405のような違いを簡単な組み替えで吸収できるようにするためです。

使うには

Object バイトから = REG.parse(String name, byte[] source);
Object 文字列から = REG.parse(String name, String source);

などでしょうか。name はREGに登録したABNFの名前です。JSONを実装した場合であればJSON-text、ABNFであればrulelistやruleなどの単位で指定するだけで階層的に組み上がった形で期待するものが返ってくるはずです。

ABNFParserは、現状ABNFRegが持つ形になっています。

よくある構文解析の字句解析はABNFがやってくれます。あとは解析されたデータを組み立てるだけ、ということでこういう方向でいいんだと思うのですが、他の形でナニカよいものがあるのかな。SoftLibJSONのJSON Parserは、JSONのABNF定義とABNFParserを基にして構築しています。その他の構文解析は行っていません。

HTTPのヘッダ解析ではABNFを若干拡張しているので、そのような方式にも対応できるように拡張手段を追加しています。拡張自体はABNF Parser本体には含まれません。

初期化と全体の動作例

まずABNFRegのBASEとREGにJava構文で書かれた方のABNF基本構造を登録しています。登録することでParserが使えるようになります。

次に必要なRFCなどのABNFを読み込みます。たとえば JSONならJSONのParseができます。特定の型にする場合はJava内での記述とパース用class埋め込みになります。

JSONをJSON Parserで必要なString,Integer,List/Mapなどの形式に変換して読み込みます。JSON Parser程度のものは各パーツ単位のパーサを用意するだけで作れます。

必要な場合はParser内またはABNFとは別のところでJava Objectなどに変換しますが、これはJSON Parserとオブジェクトマッピングの組み方次第なのでお好みでどちらにでもできます。