可変長バイナリデータをJavaでつくる

## この記事は

可変長データ（改行コード付き）を、長さフィールド（LL）付き可変長データ（改行コードなし）に変換するJavaプログラムを作ったメモです。

## 自己紹介

Java（Spring Boot）のWebアプリ開発を主にやってます。

## 成果物

ソースコードと入力ファイルは[GitHub](https://github.com/mochimoching/EditBinary)にあります。

## 背景

- おしごとで関わったレガシーマイグレーション案件で、データ移行ツール開発を担当しました。
  - 移行元はIMS（階層型DB）のセグメントデータです
  - 移行先はPostgreSQLのbytea型カラムです
- テスト用データをテキストエディタで作るのが難しかったので、プログラムを作りました。

## テスト用データの仕様

- 長さフィールド（LL）2バイト付可変長バイナリデータファイルです。
- バイナリデータの中身はなんでもかまいません。
  - 厳密には、移行元のIMSからやってくるデータは、文字コードEBCDICのバイナリ表現ですが、移行ツール内でデータの中身をテキスト処理することはありません。
  - なので、移行ツールのテスト用としてはデータの中身にこだわりはありません。Javaのテキスト入力処理で扱いやすいUTF-8のテキストファイルを元ネタにすることにします。

## 元ネタファイルの準備

- 元ネタとしてUTF-8の可変長テキストファイルを用意します。
  - [input.txt](https://github.com/mochimoching/EditBinary/blob/master/src/input.txt)
- 以下のようなイメージです。
  - 1レコード目：100文字（11111...）
  - 2レコード目：200文字（22222...）
  - 3レコード目：300文字（33333...）
  - 4レコード目：400文字（44444...）
  - 5レコード目：500文字（55555...）
- macのバイナリエディタ「Hex Fiend」でinput.txtの中身を見てみると、レコード末尾に改行コードLF（0x0A）があるのがわかります。
![input.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33137/b268284e-5feb-a3a6-5843-b8b6b8e84f73.png)

## ソースコード解説

```java:EditBinary.java
// 略
    public static void main(String[] args) {
        // (1)
        BufferedReader br = null;
        BufferedOutputStream bos = null;
        int LLSIZE = 2; 

        try {
            br = new BufferedReader(new FileReader("input.txt"));
            bos = new BufferedOutputStream(new FileOutputStream("output.txt"));
            String line = null；
            // (2)
            while ((line = br.readLine()) != null) {
                int recSize = line.length()；
                // (3)
                byte ll[] = ByteBuffer.allocate(LLSIZE).putShort((short)recSize).array();
                // (4)
                byte data[] = ByteBuffer.allocate(recSize).put(line.getBytes("UTF-8")).array();
                // (5)
                ByteBuffer lineBuf = ByteBuffer.allocate(LLSIZE + recSize);
                lineBuf.put(ll);
                lineBuf.put(data);
                // (6)
                bos.write(lineBuf.array());
            }
            bos.flush();
// 略
```

- (1) 入力ストリームはBufferedReaderで、出力ストリームはBufferedOutputStreamを使います。
  - 入力ファイルはテキストデータ、出力ファイルはバイナリデータとして処理します。
- (2) BufferedReaderで1レコードづつ読み込みます（readLine）。
- (3) readLineで読み込んだレコードサイズを長さフィールドLLにバイトデータとして保持します。
  - LLには自分自身のサイズ（2バイト）は含まないことにします
  - recSizeはint型（4バイト）でLL（2バイト）に収まらないので、short型（2バイト）にキャストします
- (4) readLineで読み込んだレコードデータをUTF-8のバイトデータとして保持します。
- (5) LLとレコードデータを1レコードに結合するためにByteBufferで領域を確保し、LLとレコードデータをセットします。
- (6) LLとレコードデータのバイナリ配列をファイルに出力します。

## コンパイル、実行

コンパイル、実行は単純です。

- コンパイル

```bash
javac EditBinary.java
```

- 実行

```bash
java EditBinary
```

## 実行結果

「Hex Fiend」でoutput.txtの中身を見てみましょう。
![result.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33137/61f4866b-bb86-1df1-f2cf-b7589954a2a5.png)
各レコード末の改行コードが除去され、代わりに先頭にLLが付与されました。
LLの値が以下のようになっているのがわかります。
カッコ内は10進数のバイト数、すなわちレコード長です。

- 1レコード目のLL：0x0064（100）
- 2レコード目のLL：0x00C8（200）
- 3レコード目のLL：0x012C（300）
- 4レコード目のLL：0x0190（400）
- 5レコード目のLL：0x01F4（500）

ということで、LL付き可変長バイナリデータをつくることができました。

## おまけ

### 区切り文字としての改行コード

- バイナリデータの区切り文字として改行コードを使うケースってあまりないと思いますが（本記事でも使ってませんし）、もし使う場合、2進数表現が環境によって変わったりするので注意が必要ですよねって話です。

- 改行コードは、WinではCRLF、Unix/LinuxではLFとなります。
  - CRLFの2進数（16進数）表現は（0x0D 0x0A）
  - LFの2進数（16進数）表現は（0x0A）
  - 文字コードによって2バイトだったり4バイトだったりするようです

  

## 参考

- [入力メッセージの形式と内容](https://www.ibm.com/support/knowledgecenter/ja/SSEPH2_13.1.0/com.ibm.ims13.doc.apg/ims_inputmessagesformat.htm)
- [改行コード](https://ja.wikipedia.org/wiki/%E6%94%B9%E8%A1%8C%E3%82%B3%E3%83%BC%E3%83%89)