17
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

IBMメインフレームのファイルをJavaで入出力するには?

Last updated at Posted at 2017-04-15

IBMメインフレームのファイルをJavaで入出力するには?

IBMメインフレームのファイルをJavaで入出力するには、下記のようにいくつか面倒なことがあります。

  • 文字コードは、半角文字は、EBCDIC。
  • 全角文字はIBM漢字コード。シフトアウト(0x0E)、シフトイン(0x0F)で括られた範囲のバイトが全角文字。
  • 改行文字がないので、レコード区切りはレコードのバイト数を意識して区切る必要あり。
  • 符号付きパック10進数、符号無しパック10進数のいわゆる2進化10進数をBigDecimalにするのは、そこそこ面倒。

で、自作するかどうか考えていたところ、非常に便利なユーティリティクラスが、オープンソースとして提供されているのを見つけたので、その使い方のご紹介です。
※便利なのに、日本語の情報が少ない。もったいない。

IBM Toolbox for Java

名前からして、THEメインフレームの感じです。

インストールとセットアップ

  • 下記のダウンロードページから、OSSバージョンのjarが格納されたZIPファイル(例えば、jtopen_9_3.zip)をダウンロードします。
  • 「jtopen_9_3.zip」ファイルを解凍し、「lib\java8\jt400.jar」のjarファイルを取り出して、クラスパスに通します。

プログラミングに関する情報源など

IBMメインフレームのファイルをJavaで入出力するための要件定義

ツールの紹介の前に、簡単にこのツールに期待している事、つまり要件を定義し、この要件に従って、ツールの使い方を紹介します。

  1. IBMメインフレームのファイルの入出力に使うレコードフォーマット定義は、Javaプログラムの外部に定義し、その定義に従って、レコードの区切りや、項目の区切りを行いたい。
  2. 文字コード変換も行いたい。
  3. 符号付きパック10進数、符号無しパック10進数のいわゆる2進化10進数をBigDecimalとして扱いたい。

例題:IBMメインフレームファイルをJavaで入出力

次のようなIBMメインフレームファイルのレコードフォーマをJavaで入出力する前提とします。

レベル データ項目名称 型  桁数 精度 繰返 バイト 備考
01 ORDER-REC 124 受注レコード
03 REC-NO B 4 2 レコード番号
03 ORDER-NO C 10 10 受注番号
03 ZIP-CD C 8 8 郵便番号
03 ADDRESS C 20 20 住所(IBM漢字コードで18バイトに、シフトコードで合計20バイト)
03 PRODUCT-GR S 3 84 商品グループ項目(OCCCURS 3回)
05 PRODUCT-CD C 15 15 商品コード
05 PRODUCT-AMT Z 5 2 5 商品重量(ゾーン10進数)
05 UNIT-PRC P 7 2 4 単価(パック10進数)
05 DISCOUNT-PRC P 7 2 4 割引額(パック10進数)

型の凡例:

説明
B バイナリ型。上記の「レコード番号」をCOBOLで書くと、「S9(4) COMP-4」。バイナリの4桁としていますので、バイト数は2バイトになる。
C 文字型。上記の「受注番号」をCOBOLで書くと、「X(10)」。
S グループ項目。複数のデータ項目を括っている。
P パック10進数型。上記の「単価」をCOBOLで書くと、「S9(7)V9(2) COMP-3」。
Z ゾーン10進数型。上記の「商品重量」をCOBOLで書くと、「9(5)V9(2)」。

ゾーン10進数(外部10進数、アンパック10進数とも言う)、パック10進数(内部10進数)などの解説は、次のURL等を参考。
https://ja.wikipedia.org/wiki/%E3%83%91%E3%83%83%E3%82%AF10%E9%80%B2%E6%95%B0
https://www.ibm.com/support/knowledgecenter/ja/SSQ2R2_9.1.0/com.ibm.etools.cbl.win.doc/topics/cpari20e.htm#header_19

レコードフォーマット定義ファイル(RFMLファイル)を作り、クラスパス上に置きます。

メインフレームファイルのレコードフォーマット定義ファイルを作り、それに従ってファイルの入出力できるようにします。
IBM ToolBox for Javaでは、レコードフォーマット定義ファイルとして、XML形式のRFMLファイルを元にファイルの入出力ができますので、
まずは、それを作り、クラスパス上に置きます。後で、Javaプログラムから、この定義ファイルをクラスパスから取得するためです。

「プログラミング IBM Toolbox for Java 」にこのXMLファイルのDTDが書いてあります。
「ccsid="930"」と書いてあるところに注目してください。
ルートレベルの所に書いてあるのと、「ADRESS」の所に書いてあります。
これは、メインフレームの文字コードを指定しているのですが、全体の文字コード指定をする場合は、このルートレベルの所に書き、
個別に指定する場合は、各データ項目に指定することができます。

ORDER-FILE.rfml(メインフレームのレコードフォーマット定義ファイル)
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE rfml SYSTEM "rfml.dtd">
<rfml version="4.0" ccsid="930">
  <struct name="PRODUCT-GR">
    <data name="PRODUCT-CD" type="char" length="15"/>
    <data name="PRODUCT-AMT" type="zoned" length="5" precision="2"/>
    <data name="UNIT-PRC" type="packed" length="7" precision="2"/>
    <data name="DISCOUNT-PRC" type="packed" length="7" precision="2"/>
  </struct>
  <recordformat name="ORDER01-REC">
    <data name="REC-NO" type="int" length="2"/>
    <data name="ORDER-NO" type="char" length="10"/>
    <data name="ZIP-CD" type="char" length="8"/>
    <data name="ADRESS" type="char" length="20" ccsid="930"/>
    <data name="PRODUCT-GR" type="struct" struct="PRODUCT-GR" count="3"/>
  </recordformat>
</rfml>

メインフレームファイル出力その1(失敗例)

下記は、失敗例。StructureとArrayはサポート外とのこと。つまり、グループ項目とOCCURSは使えないということ。どうするか?

メインフレームファイル出力その1(失敗例)
    /**
     * メインフレームファイル出力. <BR>
     * RFMLファイルのレコードフォーマット定義に従ってメインフレームファイルを出力します。<BR>
     * {@code
      rec.setField("PRODUCT-GR", null);}の所で、 {@code
    com.ibm.as400.access.ExtendedIllegalArgumentException: name (PRODUCT-GR): Field was not found.
    }<BR>
     * 例外が発生します。 次のAPIドキュメントに、Structureはサポート外とある。Arrayもサポート外とある。<BR>
     * <a href=
     * "https://www.ibm.com/support/knowledgecenter/ssw_ibm_i_71/rzahh/javadoc/com/ibm/as400/data/RecordFormatDocument.html">
     * https://www.ibm.com/support/knowledgecenter/ssw_ibm_i_71/rzahh/javadoc/com/ibm/as400/data/RecordFormatDocument.html</a>
     */
    @Test
    public void test01MfFileOutput() {
        // レコードフォーマット定義をクラスパスから探して読み込む。
        RecordFormatDocument rfd = null;
        try {
            rfd = new RecordFormatDocument("jp.golden.cat.util.ORDER-FILE.rfml");
        } catch (XmlException e) {
            throw new RuntimeException(e);
        }

        // メインフレームの文字コードでファイル出力。
        File ioFile = new File("./data/ORDER_FILE.dat");
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(ioFile);
            Record rec = rfd.toRecordFormat("ORDER01-REC").getNewRecord();
            for (short i = 1; i < 11; i++) {
                rec.setField("REC-NO", new Short(i));
                rec.setField("ORDER-NO", "170423-123");
                rec.setField("ZIP-CD", "103-0028");
                rec.setField("ADRESS", "東京都中央区八重洲");
                // ↓ここで例外が発生するので、コメントアウト。
                // rec.setField("PRODUCT-GR", null);
                // バイト配列化して、ファイルに出力。
                byte[] buf = rec.getContents();
                StringUtil.dump("DEBUG:", buf, "");
                fos.write(buf);
            }
            fos.flush();
        } catch (XmlException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

メインフレームファイル出力その2(成功例)

グループ項目とOCCURSが使えないので、次のように処理します。

1.グループ項目とOCCURSが意図した挙動にならないので、その部分を一つのバイト配列と見做します。

2.グループ項目部分を別のレコードフォーマット定義で定義して、そこにJavaオブジェクトを設定して、MF文字コードのバイト配列にします。

3.上記2を上記1.にセットします。

ORDER-FILE.rfml(メインフレームのレコードフォーマット定義ファイル。グループ項目部分を別のレコード定義にしている。)
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE rfml SYSTEM "rfml.dtd">
<rfml version="4.0" ccsid="930">
  <recordformat name="PRODUCT-GR">
    <data name="PRODUCT-CD" type="char" length="15"/>
    <data name="PRODUCT-AMT" type="zoned" length="5" precision="2"/>
    <data name="UNIT-PRC" type="packed" length="7" precision="2"/>
    <data name="DISCOUNT-PRC" type="packed" length="7" precision="2"/>
  </recordformat>
  <recordformat name="ORDER01-REC">
    <data name="REC-NO" type="int" length="2"/>
    <data name="ORDER-NO" type="char" length="10"/>
    <data name="ZIP-CD" type="char" length="8"/>
    <data name="ADRESS" type="char" length="20" ccsid="930"/>
    <data name="PRODUCT-B" type="byte" length="84"/>
  </recordformat>
</rfml>
メインフレームファイル出力その2(成功例)
    /**
     * メインフレームファイル出力. <BR>
     * RFMLファイルのレコードフォーマット定義に従ってメインフレームファイルを出力します。<BR>
     * 1.グループ項目とOCCURSが意図した挙動にならないので、その部分を一つのバイト配列と見做します。<BR>
     * 2.グループ項目部分を別のレコードフォーマット定義で定義して、そこにJavaオブジェクトを設定して、MF文字コードのバイト配列にします。<br>
     * 3.上記2を上記1.にセットします。
     */
    @Test
    public void test02MfFileOutput() {
        // レコードフォーマット定義をクラスパスから探して読み込む。
        RecordFormatDocument rfd = null;
        try {
            rfd = new RecordFormatDocument("jp.golden.cat.util.ORDER-FILE.rfml");
        } catch (XmlException e) {
            throw new RuntimeException(e);
        }

        // メインフレームの文字コードでファイル出力。
        File ioFile = new File("./data/ORDER_FILE.dat");
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(ioFile);
            Record rec = rfd.toRecordFormat("ORDER01-REC").getNewRecord();
            Record grp = rfd.toRecordFormat("PRODUCT-GR").getNewRecord();
            for (int i = 1; i < 11; i++) {
                rec.setField("REC-NO", new Short((short) i));
                rec.setField("ORDER-NO", "170423-123");
                rec.setField("ZIP-CD", "103-0028");
                rec.setField("ADRESS", "東京都中央区八重洲");

                // グループ項目のOCCURS部分のコード。
                // 項目移送先のグループ項目全体のバイトストリーム。
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                // 項目移送先のバイト長÷1 OCCURS分のバイト長(PRODUCT-GR)=OCCCURS数
                int occursCount = rec.getFieldAsBytes("PRODUCT-B").length / grp.getRecordLength();
                // OCCURS数分繰り返す
                for (int j = 0; j < occursCount; j++) {
                    grp.setField("PRODUCT-CD", "ABCDEFGHIJ-000" + j);
                    grp.setField("PRODUCT-AMT", new BigDecimal("123.45"));
                    grp.setField("UNIT-PRC", new BigDecimal("+12345.67"));
                    grp.setField("DISCOUNT-PRC", new BigDecimal("-45.6"));

                    baos.write(grp.getContents());
                }
                // グループ項目のバイト配列にセット。
                rec.setField("PRODUCT-B", baos.toByteArray());

                // バイト配列化して、ファイルに出力。
                byte[] buf = rec.getContents();
                StringUtil.dump("DEBUG:", buf, "");
                fos.write(buf);
            }
            fos.flush();
        } catch (XmlException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

メインフレームファイル入力

グループ項目とOCCURSが使えないので、上記のファイル出力でやったようなイメージで対応します。

メインフレームファイル入力
    /**
     * メインフレームファイル入力. <BR>
     * RFMLファイルのレコードフォーマット定義に従ってメインフレームファイルを入力します。<BR>
     * 1.グループ項目とOCCURSが意図した挙動にならないので、その部分を一つのバイト配列と見做します。<BR>
     * 2.グループ項目部分を別のレコードフォーマット定義で定義して、そこにMF文字コードのバイト配列をセットして、Javaオブジェクトにします。<br>
     */
    @Test
    public void test03MfFileInput() {
        // メインフレームファイルを作る。
        this.test02MfFileOutput();

        // レコードフォーマット定義をクラスパスから探して読み込む。
        RecordFormatDocument rfd = null;
        try {
            rfd = new RecordFormatDocument("jp.golden.cat.util.ORDER-FILE.rfml");
        } catch (XmlException e) {
            throw new RuntimeException(e);
        }

        // メインフレームの文字コードでファイル入力。
        File ioFile = new File("./data/ORDER_FILE.dat");
        FileInputStream fis = null;
        try {
            Record rec = rfd.toRecordFormat("ORDER01-REC").getNewRecord();
            Record grp = rfd.toRecordFormat("PRODUCT-GR").getNewRecord();
            fis = new FileInputStream(ioFile);
            // 1レコード分のバイト数を取得し、読み込みバッファのバイト配列を生成する。
            byte[] recordBuf = new byte[rec.getRecordLength()];
            // MFファイルには、改行文字がない。従って、1レコードのバイト数分バッファに読み込んでいく。
            while (fis.read(recordBuf) != -1) {
                // バッファのバイト配列からレコード化。
                rec.setContents(recordBuf);

                System.out.print("[" + rec.getField("REC-NO"));
                System.out.print("],[" + rec.getField("ORDER-NO"));

                // グループ項目のOCCURS部分のコード。
                // 項目取得元のグループ項目全体のバイトストリーム。
                ByteArrayInputStream bais = new ByteArrayInputStream((byte[]) rec.getField("PRODUCT-B"));
                // 1 OCCURS分の読み込みバッファを作る。
                byte[] groupBuf = new byte[grp.getRecordLength()];
                while (bais.read(groupBuf) != -1) {
                    grp.setContents(groupBuf);
                    System.out.print("],[" + grp.getField("PRODUCT-CD"));
                    System.out.print("],[" + grp.getField("PRODUCT-AMT"));
                    System.out.print("],[" + grp.getField("UNIT-PRC"));
                    System.out.print("],[" + grp.getField("DISCOUNT-PRC"));
                }
                System.out.println("]");
            }
        } catch (XmlException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

2017/04/19(水):新たに分かったこと:

rfd.toRecord("ORDER-REC").getRecordLength()は、つぎの例外が発生します。
「com.ibm.as400.data.PcmlException: Value is not set. Processing element 'ORDER-REC.REC-NO'.」
おそらく、RecordFormatDocumentオブジェクトの中にRecordオブジェクトが生成されていなため、Lengthを取得することができないと思われます。getNewRecord()でオブジェクトを生成してからLengthを取得するようにサンプルコードを修正しています。

2017/04/19(水):バグ修正:

バッファに読み込んだ後に、RecordFormatDocumentオブジェクトにバッファをセットしていなかった。
while (fis.read(buffer) != -1) {
rfd.setValues("ORDER-REC", buffer);

2017/04/19(水):新たに分かったこと(getValue()メソッドは勝手にtrimする):

次のコードは、どちらもORDER-NOTEを取り出すメソッドであるが、結果が異なる。
(1) rfd.getValue("ORDER-REC.ORDER-NOTE")
(2) rec.getField("ORDER-NOTE")
(1)は、Stringの末尾スペースが勝手に削除される。(2)は、末尾スペースがあれば、もとのまま残る。
(1)は、内部でPcmlDataValues.toObject()を呼び出している模様。その中で、trimStringしている。

PcmlDataValues.toObject()
//For Strings, trim blanks and nulls appropriately
if (dataType == PcmlData.CHAR) {
    newVal = trimString((String)newVal, getTrim());
}

getValue()は、勝手なことをしているので、getField()メソッドを使った方が良い。

2017/04/23(日):新たに分かったこと:

AS400Arrayは、RFMLファイル書き込みことはできるが、RFMLファイルに読み込むと、そのOCCURS数が再現されない。
AS400Structreは、RFMLファイル書き込みできない。RFMLファイルで読み込んで使おうとすると例外が起きる。

2017/04/26(水):グループ項目の入出力見直し:

ファイルの入出力にStreamを使っているなら、グループ項目のバイト配列の入出力もStreamで行う方が全体としての統一感がでる。

その他確認につかったコード全体

IBMToolboxTest.java
package jp.golden.cat.util;

import static org.junit.Assert.*;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.CharConversionException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import com.ibm.as400.access.AS400Array;
import com.ibm.as400.access.AS400Bin4;
import com.ibm.as400.access.AS400ByteArray;
import com.ibm.as400.access.AS400DataType;
import com.ibm.as400.access.AS400PackedDecimal;
import com.ibm.as400.access.AS400Structure;
import com.ibm.as400.access.AS400Text;
import com.ibm.as400.access.ArrayFieldDescription;
import com.ibm.as400.access.BinaryFieldDescription;
import com.ibm.as400.access.CharacterFieldDescription;
import com.ibm.as400.access.HexFieldDescription;
import com.ibm.as400.access.PackedDecimalFieldDescription;
import com.ibm.as400.access.Record;
import com.ibm.as400.access.RecordFormat;
import com.ibm.as400.data.RecordFormatDocument;
import com.ibm.as400.data.XmlException;

/**
 * IBM Toolbox for Javaのテスト.<BR>
 *
 * <H1>インストールとセットアップ</H1><BR>
 * 下記のダウンロードページから、OSSバージョンのjarが格納されたZIPファイル(例えば、jtopen_9_3.zip)をダウンロードします。<br>
 * 「jtopen_9_3.zip\lib\java8\jt400.jar」をクラスパスに通します。<br>
 * <br>
 * <H1>情報源など</H1> <BR>
 * IBM Toolbox for Java。IBM提供の概要説明。 URL:<a href=
 * "https://www.ibm.com/support/knowledgecenter/ja/ssw_ibm_i_73/rzahh/page1.htm">https://www.ibm.com/support/knowledgecenter/ja/ssw_ibm_i_73/rzahh/page1.htm</a><BR>
 * プログラミング IBM Toolbox for Java。PDFファイル。プログラミングお作法などの概要説明。 URL:<a href=
 * "https://www.ibm.com/support/knowledgecenter/ja/ssw_ibm_i_73/rzahh/rzahhpdf.pdf">https://www.ibm.com/support/knowledgecenter/ja/ssw_ibm_i_73/rzahh/rzahhpdf.pdf</a><BR>
 * IBM Toolbox for Java API Document。URL:<a href=
 * "https://www.ibm.com/support/knowledgecenter/ssw_ibm_i_72/rzahh/javadoc/index.html">https://www.ibm.com/support/knowledgecenter/ssw_ibm_i_72/rzahh/javadoc/index.html</a><BR>
 * JTOpen: The Open Source version of the IBM Toolbox for
 * Java。オープンソース版の概要説明。URL:<a href=
 * "http://jt400.sourceforge.net/">http://jt400.sourceforge.net/</a><BR>
 * JTOpen: The Open Source version of the IBM Toolbox for
 * Javaのダウンロードページ。URL:<a href=
 * "https://sourceforge.net/projects/jt400/">https://sourceforge.net/projects/jt400/</a><BR>
 * 最新版のダウンロードURL。URL:<a href=
 * "https://sourceforge.net/projects/jt400/files/latest/download?source=files">https://sourceforge.net/projects/jt400/files/latest/download?source=files</a><BR>
 * JTOpenとIBM Toolbox for Javaのライセンス。どちらも「IBM Public License Version
 * 1.0」。URL:<a href=
 * "https://opensource.org/licenses/ibmpl.php">https://opensource.org/licenses/ibmpl.php</a><BR>
 */
public class IBMToolboxTest {
    @Rule
    public ExpectedException thrown = ExpectedException.none();

    /**
     * メインフレームファイル出力. <BR>
     * RFMLファイルのレコードフォーマット定義に従ってメインフレームファイルを出力します。<BR>
     * {@code
      rec.setField("PRODUCT-GR", null);}の所で、 {@code
    com.ibm.as400.access.ExtendedIllegalArgumentException: name (PRODUCT-GR): Field was not found.
    }<BR>
     * 例外が発生します。 次のAPIドキュメントに、Structureはサポート外とある。Arrayもサポート外とある。<BR>
     * <a href=
     * "https://www.ibm.com/support/knowledgecenter/ssw_ibm_i_71/rzahh/javadoc/com/ibm/as400/data/RecordFormatDocument.html">
     * https://www.ibm.com/support/knowledgecenter/ssw_ibm_i_71/rzahh/javadoc/com/ibm/as400/data/RecordFormatDocument.html</a>
     */
    @Test
    public void test01MfFileOutput() {
        // レコードフォーマット定義をクラスパスから探して読み込む。
        RecordFormatDocument rfd = null;
        try {
            rfd = new RecordFormatDocument("jp.golden.cat.util.ORDER-FILE.rfml");
        } catch (XmlException e) {
            throw new RuntimeException(e);
        }

        // メインフレームの文字コードでファイル出力。
        File ioFile = new File("./data/ORDER_FILE.dat");
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(ioFile);
            Record rec = rfd.toRecordFormat("ORDER01-REC").getNewRecord();
            for (short i = 1; i < 11; i++) {
                rec.setField("REC-NO", new Short(i));
                rec.setField("ORDER-NO", "170423-123");
                rec.setField("ZIP-CD", "103-0028");
                rec.setField("ADRESS", "東京都中央区八重洲");
                // ↓ここで例外が発生するので、コメントアウト。
                // rec.setField("PRODUCT-GR", null);
                // バイト配列化して、ファイルに出力。
                byte[] buf = rec.getContents();
                StringUtil.dump("DEBUG:", buf, "");
                fos.write(buf);
            }
            fos.flush();
        } catch (XmlException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    /**
     * メインフレームファイル出力. <BR>
     * RFMLファイルのレコードフォーマット定義に従ってメインフレームファイルを出力します。<BR>
     * 1.グループ項目とOCCURSが意図した挙動にならないので、その部分を一つのバイト配列と見做します。<BR>
     * 2.グループ項目部分を別のレコードフォーマット定義で定義して、そこにJavaオブジェクトを設定して、MF文字コードのバイト配列にします。<br>
     * 3.上記2を上記1.にセットします。
     */
    @Test
    public void test02MfFileOutput() {
        // レコードフォーマット定義をクラスパスから探して読み込む。
        RecordFormatDocument rfd = null;
        try {
            rfd = new RecordFormatDocument("jp.golden.cat.util.ORDER-FILE.rfml");
        } catch (XmlException e) {
            throw new RuntimeException(e);
        }

        // メインフレームの文字コードでファイル出力。
        File ioFile = new File("./data/ORDER_FILE.dat");
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(ioFile);
            Record rec = rfd.toRecordFormat("ORDER01-REC").getNewRecord();
            Record grp = rfd.toRecordFormat("PRODUCT-GR").getNewRecord();
            for (int i = 1; i < 11; i++) {
                rec.setField("REC-NO", new Short((short) i));
                rec.setField("ORDER-NO", "170423-123");
                rec.setField("ZIP-CD", "103-0028");
                rec.setField("ADRESS", "東京都中央区八重洲");

                // グループ項目のOCCURS部分のコード。
                // 項目移送先のグループ項目全体のバイトストリーム。
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                // 項目移送先のバイト長÷1 OCCURS分のバイト長(PRODUCT-GR)=OCCCURS数
                int occursCount = rec.getFieldAsBytes("PRODUCT-B").length / grp.getRecordLength();
                // OCCURS数分繰り返す
                for (int j = 0; j < occursCount; j++) {
                    grp.setField("PRODUCT-CD", "ABCDEFGHIJ-000" + j);
                    grp.setField("PRODUCT-AMT", new BigDecimal("123.45"));
                    grp.setField("UNIT-PRC", new BigDecimal("+12345.67"));
                    grp.setField("DISCOUNT-PRC", new BigDecimal("-45.6"));

                    baos.write(grp.getContents());
                }
                // グループ項目のバイト配列にセット。
                rec.setField("PRODUCT-B", baos.toByteArray());

                // バイト配列化して、ファイルに出力。
                byte[] buf = rec.getContents();
                StringUtil.dump("DEBUG:", buf, "");
                fos.write(buf);
            }
            fos.flush();
        } catch (XmlException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    /**
     * メインフレームファイル入力. <BR>
     * RFMLファイルのレコードフォーマット定義に従ってメインフレームファイルを入力します。<BR>
     * 1.グループ項目とOCCURSが意図した挙動にならないので、その部分を一つのバイト配列と見做します。<BR>
     * 2.グループ項目部分を別のレコードフォーマット定義で定義して、そこにMF文字コードのバイト配列をセットして、Javaオブジェクトにします。<br>
     */
    @Test
    public void test03MfFileInput() {
        // メインフレームファイルを作る。
        this.test02MfFileOutput();

        // レコードフォーマット定義をクラスパスから探して読み込む。
        RecordFormatDocument rfd = null;
        try {
            rfd = new RecordFormatDocument("jp.golden.cat.util.ORDER-FILE.rfml");
        } catch (XmlException e) {
            throw new RuntimeException(e);
        }

        // メインフレームの文字コードでファイル入力。
        File ioFile = new File("./data/ORDER_FILE.dat");
        FileInputStream fis = null;
        try {
            Record rec = rfd.toRecordFormat("ORDER01-REC").getNewRecord();
            Record grp = rfd.toRecordFormat("PRODUCT-GR").getNewRecord();
            fis = new FileInputStream(ioFile);
            // 1レコード分のバイト数を取得し、読み込みバッファのバイト配列を生成する。
            byte[] recordBuf = new byte[rec.getRecordLength()];
            // MFファイルには、改行文字がない。従って、1レコードのバイト数分バッファに読み込んでいく。
            while (fis.read(recordBuf) != -1) {
                // バッファのバイト配列からレコード化。
                rec.setContents(recordBuf);

                System.out.print("[" + rec.getField("REC-NO"));
                System.out.print("],[" + rec.getField("ORDER-NO"));

                // グループ項目のOCCURS部分のコード。
                // 項目取得元のグループ項目全体のバイトストリーム。
                ByteArrayInputStream bais = new ByteArrayInputStream((byte[]) rec.getField("PRODUCT-B"));
                // 1 OCCURS分の読み込みバッファを作る。
                byte[] groupBuf = new byte[grp.getRecordLength()];
                while (bais.read(groupBuf) != -1) {
                    grp.setContents(groupBuf);
                    System.out.print("],[" + grp.getField("PRODUCT-CD"));
                    System.out.print("],[" + grp.getField("PRODUCT-AMT"));
                    System.out.print("],[" + grp.getField("UNIT-PRC"));
                    System.out.print("],[" + grp.getField("DISCOUNT-PRC"));
                }
                System.out.println("]");
            }
        } catch (XmlException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * COMP-4からJavaオブジェクト(Integer)に変換するテスト.
     */
    @Test
    public void test01() {
        // 入力値(MFバイトデータ)
        // IBMメインフレームもJavaもエンディアンはビッグエンディアンなので、バイトオーダーは気にしなくてよい。
        byte[] mfData = new byte[] { 0x0A, 0x0B, 0x0C, 0x0D };
        StringUtil.dump("test01:input   :", mfData, "");

        // COMP-4からJavaオブジェクト(Integer)に変換する。
        AS400Bin4 comp4 = new AS400Bin4();
        int actual = ((Integer) comp4.toObject(mfData, 0)).intValue();
        System.out.println("test01:actual  :" + actual);

        // 期待値作成。
        int expected = 168496141;
        System.out.println("test01:expected:" + expected);

        // 0x0A0B0C0Dは、10進数の168496141。
        assertEquals(expected, actual);
    }

    /**
     * Javaオブジェクト(Integer)からCOMP-4に変換するテスト.
     */
    @Test
    public void test02() {
        // 入力値(Javaオブジェクト)
        Integer javaObject = new Integer(168496141);
        System.out.println("test02:input   :" + javaObject);

        // Javaオブジェクト(Integer)からCOMP-4に変換する。
        AS400Bin4 comp4 = new AS400Bin4();
        byte[] actual = comp4.toBytes(javaObject);
        StringUtil.dump("test02:actual  :", actual, "");

        // 期待値作成。
        byte[] expected = new byte[] { 0x0A, 0x0B, 0x0C, 0x0D };
        StringUtil.dump("test02:expected:", expected, "");

        // test
        assertArrayEquals(expected, actual);
    }

    /**
     * PIC X形式のEBCIDICからMS932(SJIS)に変換するテスト. <BR>
     *
     * IBM Globalization - Coded character set identifiers.UR:<a href=
     * "https://www-01.ibm.com/software/globalization/ccsid/ccsid_registered.html">https://www-01.ibm.com/software/globalization/ccsid/ccsid_registered.html</a><br>
     *
     * IBM Globalization - Coded character set identifiers - CCSID
     * 5035。URL:<a href=
     * "https://www-01.ibm.com/software/globalization/ccsid/ccsid5035.html">https://www-01.ibm.com/software/globalization/ccsid/ccsid5035.html</a><br>
     *
     * CCSID 5035 の代替 Unicode 変換表.URL:<a href=
     * "https://www.ibm.com/support/knowledgecenter/ja/SSEPGG_9.5.0/com.ibm.db2.luw.admin.nls.doc/doc/c0022611.html">https://www.ibm.com/support/knowledgecenter/ja/SSEPGG_9.5.0/com.ibm.db2.luw.admin.nls.doc/doc/c0022611.html</a><br>
     *
     * 79.英小文字を入力するには CCSID 5035.URL:<a href=
     * "http://www.as400-net.com/tips/environment/79.html">http://www.as400-net.com/tips/environment/79.html</a><br>
     *
     * CCSID=930 「4370 UDC
     * 文字を含む日本語カタカナ漢字、5026のスーパーセット。半角カナ優先(半角英小文字を混在できない)。<BR>
     * CCSID=939 「4370 UDC
     * 文字を含む日本語ラテン文字漢字、5035のスーパーセット。半角英小文字優先(半角カナ文字を混在できない)。<BR>
     */
    @Test
    public void test03() {
        // MFの文字コード、CCSID=930 「4370 UDC
        // 文字を含む日本語カタカナ漢字、5026のスーパーセット。半角カナ優先(半角英小文字を混在できない)のバイト配列。
        // "123AαBア"=0xF1F2F3C10E41410FC281。
        // 半角カタカナの"ア"は、CCSID=930は、0x81。これが、CCSID=939だ0x56。
        // 全角文字の"α"は、MFの文字コードで0X4141。これをシフトアウト(0x0E)とシフトイン(0x0F)でくくり、0x0E41410F。
        byte[] mfData = new byte[] { (byte) 0xF1, (byte) 0xF2, (byte) 0xF3, (byte) 0xC1, 0x0E, 0x41, 0x41, 0x0F,
                (byte) 0xC2, (byte) 0x81 };
        StringUtil.dump("test03:input   :", mfData, "");
        System.out.println("test03:input   バイト数:" + mfData.length);

        // CCSID=930 「4370 UDC
        // 文字を含む日本語カタカナ漢字、5026のスーパーセット。半角カナ優先(半角英小文字を混在できない)のバイト配列を
        // Unicodeに変換します。
        int ccsid = 930;
        AS400Text xtype = new AS400Text(mfData.length, ccsid);
        String unicodeString = (String) xtype.toObject(mfData);
        System.out.println("test03:unicodeStringバイト数:" + unicodeString.length());
        StringUtil.dump("test03:unicodeString:", unicodeString.getBytes(), "");

        // UnicodeをMS932(SJIS)に変換します。
        String encoding = "MS932";
        String actual = this.convertString(unicodeString, encoding);
        System.out.println("test03:actual  :" + actual);
        // シフトアウト(0x0E)とシフトイン(0x0F)がないので、2バイト少なくなる。
        System.out.println("test03:actual  バイト数:" + this.getStringByteLength(actual, encoding));
        StringUtil.dump("test03:actual  :", this.getStringBytes(actual, encoding), "");

        // 期待値を作ります。
        String expected = this.convertString("123AαBア", encoding);
        System.out.println("test03:expected:" + expected);
        // シフトアウト(0x0E)とシフトイン(0x0F)がないので、2バイト少なくなる。
        System.out.println("test03:expectedバイト数:" + this.getStringByteLength(expected, encoding));
        StringUtil.dump("test03:expected:", this.getStringBytes(expected, encoding), "");

        // test
        assertEquals(expected, actual);
    }

    /**
     * 文字コード変換.
     *
     * @param from 変換元の文字列。
     * @param encoding 変換後の文字コード。
     * @return 変換後の文字列。
     */
    String convertString(String from, String encoding) {
        String res = null;
        try {
            res = new String(getStringBytes(from, encoding), encoding);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
        return res;
    }

    /**
     * 文字コード変換し、バイト配列で返します.
     *
     * @param from 変換元の文字列。
     * @param encoding 変換後の文字コード。
     * @return 変換後のバイト配列。
     */
    byte[] getStringBytes(String from, String encoding) {
        byte[] res = null;
        try {
            res = from.getBytes(encoding);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
        return res;
    }

    /**
     * 文字コード変換後のバイト数を返します.
     *
     * @param from 変換元の文字列。
     * @param encoding 変換後の文字コード。
     * @return 文字コード変換後のバイト数。
     */
    int getStringByteLength(String from, String encoding) {
        return getStringBytes(from, encoding).length;
    }

    /**
     * 複合データタイプの変換テスト.
     */
    @Test
    public void test04() {
        // Java複合オブジェクト
        Object[] javaObjects = {
                new Integer(88),
                new byte[] { 0x41 },
                new BigDecimal("-12.34"),
                "ABCDE"
        };

        // MF複合データ定義
        AS400DataType[] mfDataType = {
                new AS400Bin4(),
                new AS400ByteArray(4),
                new AS400PackedDecimal(4, 2),
                new AS400Text(10)
        };

        // 引数で指定されたMF複合データ定義でコンバータを生成します。
        AS400Structure converter = new AS400Structure(mfDataType);

        // Java複合オブジェクトからMF文字コード変換したバイト配列を生成します。
        byte[] outAS400 = converter.toBytes(javaObjects);
        StringUtil.dump("test04:", outAS400, "");

        Object[] outJavaObjects = (Object[]) converter.toObject(outAS400, 0);
        System.out.println("[" + outJavaObjects[0] + "]");
        StringUtil.dump("[", (byte[]) outJavaObjects[1], "]");    // 受け側4byteに対して、0x41をセットすると、左詰めでセットされる。残りは0x00で埋めらる。
        System.out.println("[" + outJavaObjects[2] + "]");
        System.out.println("[" + outJavaObjects[3] + "]");  // 受け側10byteに対して、ABCDEの5文字をセットすると、左爪でセットされる。残りは、スペース。

        // test
        assertEquals(88, outJavaObjects[0]);
        assertArrayEquals(new byte[] { 0x41, 0x00, 0x00, 0x00 }, (byte[]) outJavaObjects[1]);
        assertEquals(new BigDecimal("-12.34"), outJavaObjects[2]);
        assertEquals("ABCDE     ", outJavaObjects[3]);
    }

    /**
     * MFレコードフォーマット定義オブジェクトを生成し、それに従って値をセットするテスト.
     */
    @Test
    public void test05() {
        // 項目定義
        BinaryFieldDescription bfd = new BinaryFieldDescription(new AS400Bin4(), "REC-NO");
        CharacterFieldDescription cfd1 = new CharacterFieldDescription(new AS400Text(10), "ORDER-NO");
        CharacterFieldDescription cfd2 = new CharacterFieldDescription(new AS400Text(20), "ORDER-NOTE");

        // レコードフォーマット定義
        RecordFormat rf = new RecordFormat();
        rf.addFieldDescription(bfd);
        rf.addFieldDescription(cfd1);
        rf.addFieldDescription(cfd2);

        // レコード生成し、Javaオブジェクトの値をセット。
        Record rec = rf.getNewRecord();
        rec.setField("REC-NO", new Integer(1));
        rec.setField("ORDER-NO", "170415-123");
        rec.setField("ORDER-NOTE", "HELLO WORLD");

        // MFバイト配列を取得。
        byte[] actual = null;
        try {
            actual = rec.getContents();
        } catch (CharConversionException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        // 期待値。
        byte[] expected = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0xF1, (byte) 0xF7,
                (byte) 0xF0, (byte) 0xF4, (byte) 0xF1, (byte) 0xF5, (byte) 0x60, (byte) 0xF1, (byte) 0xF2, (byte) 0xF3,
                (byte) 0xC8, (byte) 0xC5, (byte) 0xD3, (byte) 0xD3, (byte) 0xD6, (byte) 0x40, (byte) 0xE6, (byte) 0xD6,
                (byte) 0xD9, (byte) 0xD3, (byte) 0xC4, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,
                (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40 };
        StringUtil.dump("test05:actual  :", actual, "");
        StringUtil.dump("test05:expected:", expected, "");

        // test
        assertArrayEquals(expected, actual);
    }

    /**
     * MFレコードフォーマット定義オブジェクトを生成し、それに従ってEBCIDICバイナリファイルの入出力テスト.
     */
    @Test
    public void test06() {
        RecordFormat rf = this.createOrderRecordFormat();
        Record rec = rf.getNewRecord();

        File ioFile = new File("./data/AS400.dat");

        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(ioFile);
            // 10レコード生成
            for (int i = 1; i < 11; i++) {
                rec.setField("REC-NO", new Integer(i));
                rec.setField("AMOUNT", new BigDecimal("-12.34"));
                rec.setField("ORDER-NO", "170415-123");
                rec.setField("ORDER-NOTE", "HELLOαWORLD");
                // getContentsの引数にOutputStreamを指定できる。
                // イディオム的にはos.write(rec.getContents())をよく使うので、これで行く。
                fos.write(rec.getContents());
            }
            fos.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (CharConversionException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }

        FileInputStream fis = null;
        try {
            fis = new FileInputStream(ioFile);
            rec = rf.getNewRecord();
            // 1レコード分のバイト数を取得し、バイト配列を生成する。
            int len = rec.getRecordLength();
            byte[] buffer = new byte[len];

            // MFファイルには、改行文字がない。従って、1レコードのバイト数分バッファに読み込んでいく
            while (fis.read(buffer) != -1) {
                rec.setContents(buffer);
                StringUtil.dump("test06:", buffer, "");
                System.out.print(rec.getField("REC-NO"));
                System.out.print("," + rec.getField("ORDER-NO"));
                System.out.print("," + rec.getField("AMOUNT"));
                System.out.println("," + rec.getField("ORDER-NOTE"));
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * MFレコードフォーマット定義ファイルからMFレコードフォーマット定義オブジェクトを生成し、それに従ってEBCDICバイナリファイル出力テスト.
     *
     * @throws XmlException レコードフォーマット例外
     * @throws IOException ファイルIO例外
     */
    @Test
    public void test07() throws XmlException, IOException {
        // MFレコードフォーマット定義を生成する。
        RecordFormat rf = this.createOrderRecordFormat();

        // MFレコードフォーマット定義をクラスパス上に保存する。
        this.saveRecordFormat(rf, new File("src/test/java/jp/golden/cat/util/ORDER.rfml"));

        // MFレコードフォーマット定義をクラスパスから探して読み込む。
        RecordFormatDocument rfd = this.loadRecordFormatDocument("jp.golden.cat.util.ORDER.rfml");

        // EBCDICで書く。
        File ioFile = new File("./data/AS400_2.dat");
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(ioFile);
            // 10レコード生成
            for (int i = 1; i < 11; i++) {
                // 項目の指定の仕方に癖がある。「レコードフォーマット定義名.項目名」で項目を指定する。
                rfd.setValue("ORDER-REC.REC-NO", new Integer(i));
                rfd.setValue("ORDER-REC.AMOUNT", new BigDecimal("-12.34"));
                rfd.setValue("ORDER-REC.ORDER-NO", "170415-123");
                rfd.setValue("ORDER-REC.ORDER-NOTE", "HELLOαWORLD");
                fos.write(rfd.toByteArray("ORDER-REC"));
            }
            fos.flush();
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    /**
     * MFレコードフォーマット定義ファイルからMFレコードフォーマット定義オブジェクトを生成し、それに従ってEBCDICバイナリファイル入力テスト.
     *
     * @throws XmlException レコードフォーマット例外
     * @throws IOException ファイルIO例外
     */
    @Test
    public void test08() throws XmlException, IOException {
        // MFレコードフォーマット定義をクラスパスから探して読み込む。
        RecordFormatDocument rfd = this.loadRecordFormatDocument("jp.golden.cat.util.ORDER.rfml");

        // EBCDICファイルを読む。
        File ioFile = new File("./data/AS400_2.dat");
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(ioFile);

            // rfd.toRecord("ORDER-REC").getRecordLength()は、つぎの例外が発生する。
            // com.ibm.as400.data.PcmlException: Value is not set. Processing
            // <data> element 'ORDER-REC.REC-NO'.
            // おそらく、RecordFormatDocumentオブジェクトの中にRecordオブジェクトが生成されていなため、Lengthを取得することができないと思われる。
            // よって、getNewRecord()でオブジェクトを生成してからLengthを取得する。
            // int len = rfd.toRecord("ORDER-REC").getRecordLength();

            // 1レコード分のバイト数を取得し、バイト配列を生成する。
            int len = rfd.toRecordFormat("ORDER-REC").getNewRecord().getRecordLength();
            byte[] buffer = new byte[len];

            // MFファイルには、改行文字がない。従って、1レコードのバイト数分バッファに読み込んでいく
            while (fis.read(buffer) != -1) {
                rfd.setValues("ORDER-REC", buffer);

                StringUtil.dump("test08:", rfd.toByteArray("ORDER-REC"), "");

                System.out.print("[" + rfd.getValue("ORDER-REC.REC-NO"));
                System.out.print("],[" + rfd.getValue("ORDER-REC.ORDER-NO"));
                System.out.print("],[" + rfd.getValue("ORDER-REC.AMOUNT"));
                System.out.print("],[" + rfd.getValue("ORDER-REC.ORDER-NOTE"));
                System.out.println("]");

                Record rec = rfd.toRecord("ORDER-REC");
                System.out.print("[" + rec.getField("REC-NO"));
                System.out.print("],[" + rec.getField("ORDER-NO"));
                System.out.print("],[" + rec.getField("AMOUNT"));

                // 次のコードは、どちらもORDER-NOTEを取り出すメソッドであるが、結果が異なる。
                // (1) rfd.getValue("ORDER-REC.ORDER-NOTE")
                // (2) rec.getField("ORDER-NOTE")
                // (1)は、Stringの末尾スペースが勝手に削除される。(2)は、末尾スペースがあれば、もとのまま残る。
                // (1)は、内部でPcmlDataValues.toObject()を呼び出している模様。その中で、trimStringしている。
                /*
                 * //For Strings, trim blanks and nulls appropriately if
                 * (dataType == PcmlData.CHAR) { newVal = trimString((String)
                 * newVal, getTrim()); }
                 */
                // getValue()は、勝手なことをしているので、getField()メソッドを使った方が良い。
                System.out.print("],[" + rec.getField("ORDER-NOTE"));
                System.out.println("]");
            }
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    /**
     * 構造体のOCCURSはレコードフォーマット定義にできるか?=>できない。
     *
     * @throws XmlException レコードフォーマット例外
     * @throws IOException ファイルIO例外
     */
    @Test
    public void test09() throws XmlException, IOException {
        // AS400データタイプ定義
        AS400DataType[] as400Types = new AS400DataType[] {
                new AS400Text(10),
                new AS400PackedDecimal(4, 2),
        };
        AS400Structure as400Structure = new AS400Structure(as400Types);
        AS400Array as400Array = new AS400Array(as400Structure, 3);

        // Javaオブジェクト=>AS400データバイト配列
        Object[] inJavaObject = new Object[as400Array.getNumberOfElements()];
        System.out.println("test09:AS400Array elements:" + as400Array.getNumberOfElements());
        for (int i = 0; i < inJavaObject.length; i++) {
            inJavaObject[i] = new Object[] {
                    new String("170415-123"),
                    new BigDecimal("-12.34"),
            };
        }
        byte[] as400bytes = as400Array.toBytes(inJavaObject);
        StringUtil.dump("test09:", as400bytes, "");

        // AS400データタイプ(Structure+Array)レコードフォーマット定義をXMLで保存できるか?=>できない。
        ArrayFieldDescription afd = new ArrayFieldDescription(as400Array, "AMOUNTS");
        RecordFormat rf = new RecordFormat("TEST09-REC");
        rf.addFieldDescription(afd);
        File rfml = new File("src/test/java/jp/golden/cat/util/TEST09.rfml");
        FileOutputStream fos = null;
        try {
            // RecordFormatの中に、AS400Structureは、次の例外が発生し、RFMLファイルとして保存できない。
            // com.ibm.as400.data.XmlException: Data type AS400Structure is not
            // supported by RFML.
            // APIドキュメントにもサポート外と書いてあった。多次元のAS400Arrayもダメ見たい。
            // https://www.ibm.com/support/knowledgecenter/ssw_ibm_i_71/rzahh/javadoc/com/ibm/as400/data/RecordFormatDocument.html
            thrown.expect(com.ibm.as400.data.XmlException.class);
            thrown.expectMessage("Data type AS400Structure is not supported by RFML.");
            RecordFormatDocument rfd = new RecordFormatDocument(rf);
            fos = new FileOutputStream(rfml);
            rfd.toXml(fos);
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }

    }

    /**
     * AS400Array+AS400StructureはRFMLファイルにできないので、バイト数をカウントするまでにとどめておいて、RFMLファイルに保存する。
     *
     */
    @Test
    public void test10() {
        // AS400データタイプ定義
        AS400DataType[] types = new AS400DataType[] {
                new AS400Text(10),
                new AS400PackedDecimal(4, 2),
        };
        AS400Structure a400Structure = new AS400Structure(types);
        AS400Array as400Array = new AS400Array(a400Structure, 3);
        System.out.println("test10:AS400Structure byte length:" + a400Structure.getByteLength());
        System.out.println("test10:AS400Arrat byte length:" + as400Array.getByteLength());
        // AS400Array+AS400Structureのバイト数分のAS400ByteArrayの定義を作る。
        AS400ByteArray as400ByteArray = new AS400ByteArray(as400Array.getByteLength());

        // レコードフォーマット定義を作る。
        BinaryFieldDescription bfd = new BinaryFieldDescription(new AS400Bin4(), "REC-NO");
        CharacterFieldDescription cfd1 = new CharacterFieldDescription(new AS400Text(10), "ORDER-NO");
        HexFieldDescription hfd = new HexFieldDescription(as400ByteArray, "AMOUNTS");   // AS400Array+AS400Structureのバイト数分のAS400ByteArray
        CharacterFieldDescription cfd2 = new CharacterFieldDescription(new AS400Text(20), "ORDER-NOTE");
        RecordFormat rf = new RecordFormat("ORDER3-REC");
        rf.addFieldDescription(bfd);
        rf.addFieldDescription(cfd1);
        rf.addFieldDescription(hfd);
        rf.addFieldDescription(cfd2);

        // RFMLファイルに保存する。
        File rfml = new File("src/test/java/jp/golden/cat/util/ORDER3.rfml");
        this.saveRecordFormat(rf, rfml);
    }

    /**
     * AS400Array+AS400StructureはRFMLファイルにできないので、その部分をバイト配列で表現したRFMLファイルでレコードを出力.
     *
     * @throws XmlException レコードフォーマット例外
     * @throws IOException ファイルIO例外
     */
    @Test
    public void test11() throws XmlException, IOException {
        // AS400データタイプ定義
        AS400DataType[] types = new AS400DataType[] {
                new AS400Text(10),
                new AS400PackedDecimal(4, 2),
        };
        AS400Structure as400structure = new AS400Structure(types);
        AS400Array as400array = new AS400Array(as400structure, 3);

        // RFMLファイルからレコードオブジェクトを生成。
        RecordFormatDocument rfd = this.loadRecordFormatDocument("jp.golden.cat.util.ORDER3.rfml");
        Record rec = rfd.toRecordFormat("ORDER3-REC").getNewRecord();

        // MFファイル出力
        File ioFile = new File("./data/AS4003.dat");
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(ioFile);
            // 10レコード生成
            for (int i = 1; i < 11; i++) {
                rec.setField("REC-NO", new Integer(i));
                rec.setField("ORDER-NO", "170415-123");

                // AS400Array+AS400Structureの部分のバイト配列を作る
                // まずは、セットするJavaオブジェクトを作る
                Object[] inJavaObject = new Object[as400array.getNumberOfElements()];
                for (int j = 0; j < inJavaObject.length; j++) {
                    inJavaObject[j] = new Object[] {
                            new String("170415-AA" + j),
                            new BigDecimal("-12.34"),
                    };
                }
                // 2次元のJavaオブジェクトからバイト配列を作るイメージ
                byte[] as400bytes = as400array.toBytes(inJavaObject);
                System.out.println("test11:" + as400bytes.length);
                // そのバイト配列をセット
                rec.setField("AMOUNTS", as400bytes);

                rec.setField("ORDER-NOTE", "HELLOαWORLD");
                fos.write(rec.getContents());
            }
            fos.flush();
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    /**
     * AS400Array+AS400StructureはRFMLファイルにできないので、AS400Structureを1レコードと見做して、RFMLファイルにして、読んでみる。
     *
     * @throws XmlException レコードフォーマット例外
     * @throws IOException ファイルIO例外
     */
    @Test
    public void test12() throws IOException, XmlException {
        // RFMLファイルからレコードオブジェクトを生成。
        RecordFormatDocument rfd = this.loadRecordFormatDocument("jp.golden.cat.util.ORDER4.rfml");
        Record rec1 = rfd.toRecordFormat("ORDER4-REC").getNewRecord();
        // 1 OCCURS分のレコード定義。本当はAS400Structureを使いたいが、サポート外なので。
        Record rec2 = rfd.toRecordFormat("AMOUNTS").getNewRecord();

        // MFファイル入力
        File ioFile = new File("./data/AS4003.dat");
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(ioFile);
            // 1レコード分のバイト数を取得し、バイト配列を生成する。
            int len = rec1.getRecordLength();
            byte[] buffer = new byte[len];
            // MFファイルには、改行文字がない。従って、1レコードのバイト数分バッファに読み込んでいく
            while (fis.read(buffer) != -1) {
                rec1.setContents(buffer);

                System.out.print("[" + rec1.getField("REC-NO"));
                System.out.print("],[" + rec1.getField("ORDER-NO"));

                // OCCURS全体のバイト配列を取得
                byte[] amounts = (byte[]) rec1.getField("AMOUNTS");
                // OCCURS全体のバイト長÷1 OCCURS分のバイト長=OCCCURS数
                int occursCount = amounts.length / rec2.getRecordLength();
                // OCCURS数分繰り返す
                for (int i = 0; i < occursCount; i++) {
                    // amountsバイト配列から1 OCCURS分切り出す。
                    rec2.setContents(amounts, i * rec2.getRecordLength());
                    // 1 OCCURSを構成しているORDER-CNOとAMOUNTを標準出力
                    System.out.print("],[" + rec2.getField("ORDER-CNO"));
                    System.out.print("],[" + rec2.getField("AMOUNT"));
                }
                System.out.print("],[" + rec1.getField("ORDER-NOTE"));
                System.out.println("]");
            }
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    /**
     * AS400Arrayの挙動テスト.
     *
     * AS400Arrayの内訳は、Xタイプ10byte×3 OCCURSの合計30byteのデータ構造。
     * これを、RFMLファイルに保存。XMLにはcount="3"で出力されることを期待。 ここまでは、うまくいった。
     * しかし、RFMLファイルを読み込んでRecordオブジェクトを作って、長さを取得すると、10byte。 OCCURSが再現されない。
     *
     * 結論:RFMLファイルにcountを書き込むことは可能だが、読み込んだ時にcountがなくなるので、RFMLファイルでは、countは使わないこと。
     *
     * @throws XmlException レコードフォーマット例外
     * @throws IOException ファイルIO例外
     */
    @Test
    public void test13() throws IOException, XmlException {
        // 項目定義
        AS400Array as400Array = new AS400Array(new AS400Text(10), 3);
        ArrayFieldDescription afd = new ArrayFieldDescription(as400Array, "ORDER-NOTE");

        // レコードフォーマット定義をファイルで保存。
        RecordFormat rf = new RecordFormat("TEST13-REC");
        rf.addFieldDescription(afd);
        // 期待値:10*3=30byte.
        int expected = rf.getNewRecord().getRecordLength();
        // count="3"で保存される。
        this.saveRecordFormat(rf, new File("src/test/java/jp/golden/cat/util/TEST13.rfml"));

        // RFMLファイルからレコードオブジェクトを生成。
        RecordFormatDocument rfd = this.loadRecordFormatDocument("jp.golden.cat.util.TEST13.rfml");
        // 10*3=30byteを期待しているが、なぜか10が返ってくる。countが無視されている。
        int actual = rfd.toRecordFormat("TEST13-REC").getNewRecord().getRecordLength();
        // afterとして保存してみると、countがなくなって保存される。
        this.saveRecordFormat(rfd.toRecordFormat("TEST13-REC"),
                new File("src/test/java/jp/golden/cat/util/TEST13_after.rfml"));

        System.out.println("test13:actual:" + actual);
        System.out.println("test13:expected:" + expected);
        // assertEquals(expected, actual);
        assertEquals(10, actual);

    }

    /**
     * 注文レコードフォーマット定義作成.
     *
     * @return 注文レコードフォーマット定義作成.
     */
    RecordFormat createOrderRecordFormat() {
        // 項目定義
        BinaryFieldDescription bfd = new BinaryFieldDescription(new AS400Bin4(), "REC-NO");
        CharacterFieldDescription cfd1 = new CharacterFieldDescription(new AS400Text(10), "ORDER-NO");
        PackedDecimalFieldDescription pfd = new PackedDecimalFieldDescription(new AS400PackedDecimal(4, 2), "AMOUNT");
        CharacterFieldDescription cfd2 = new CharacterFieldDescription(new AS400Text(20), "ORDER-NOTE");

        // レコードフォーマット定義
        RecordFormat rf = new RecordFormat("ORDER-REC");
        rf.addFieldDescription(bfd);
        rf.addFieldDescription(cfd1);
        rf.addFieldDescription(pfd);
        rf.addFieldDescription(cfd2);

        return rf;
    }

    /**
     * MFレコードフォーマット定義をクラスパスから取得します。
     *
     * @param rfmlClassPath。
     * @return MFレコードフォーマット定義。
     */
    RecordFormatDocument loadRecordFormatDocument(String rfmlClassPath) {
        RecordFormatDocument rfd = null;
        try {
            rfd = new RecordFormatDocument(rfmlClassPath);
        } catch (XmlException e) {
            throw new RuntimeException(e);
        }
        return rfd;
    }

    /**
     * MFレコードフォーマット定義をファイルとして保存する。
     *
     * @param rf MFレコード定義フォーマット。
     * @param rfml
     *            MFレコードフォーマット定義ファイルパス。クラスパス上に定義ファイルがないと後で、定義ファイルが読み込めないので注意のこと。
     */
    void saveRecordFormat(RecordFormat rf, File rfml) {
        FileOutputStream fos = null;
        try {
            RecordFormatDocument rfd = new RecordFormatDocument(rf);
            fos = new FileOutputStream(rfml);
            rfd.toXml(fos);
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (XmlException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    /**
     * エンディアンの確認.
     *
     * ネイティブバイトオーダー:LITTLE_ENDIAN<br>
     * 初期値:0A0B0C0D12345678<br>
     * LITTLE_ENDIAN:0D0C0B0A BIG_ENDIAN:0A0B0C0D<br>
     * LITTLE_ENDIAN:78563412 BIG_ENDIAN:12345678<br>
     */
    @Test
    public void testEndian() {
        System.out.println("testEndian:");
        // ネイティブのバイトオーダーを標準出力。
        // Javaの内部処理では、ビッグエンディアンであるが、Intel系CPUはリトルエンディアンなので、次のメソッドをIntel系CPUのJavaVMで動かすと、
        // 「ネイティブバイトオーダー:LITTLE_ENDIAN」と表示される。
        System.out.println("ネイティブバイトオーダー:" + ByteOrder.nativeOrder());

        byte[] mfData = new byte[] { 0xA, 0xB, 0xC, 0xD, 0x12, 0x34, 0x56, 0x78 };
        StringUtil.dump("初期値:", mfData, "");

        ByteArrayInputStream bis = null;
        try {
            bis = new ByteArrayInputStream(mfData);

            byte[] buffer = new byte[4];
            int len = -1;
            while ((len = bis.read(buffer)) != -1) {
                if (len != buffer.length) {
                    throw new IOException("読み込みバッファの長さと入力値の関係を見直してください。");
                }
                // リトルエンディアンで変換。
                ByteBuffer byteBuff = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN);
                int i = byteBuff.getInt();
                System.out.print(ByteOrder.LITTLE_ENDIAN);
                System.out.print(":" + StringUtil.toHexString(BigInteger.valueOf(i).toByteArray()));
                System.out.print("\t");

                // ビッグエンディアンで変換。
                byteBuff = ByteBuffer.wrap(buffer).order(ByteOrder.BIG_ENDIAN);
                i = byteBuff.getInt();
                System.out.print(ByteOrder.BIG_ENDIAN);
                System.out.print(":" + StringUtil.toHexString(BigInteger.valueOf(i).toByteArray()));
                System.out.println();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (bis != null) {
                try {
                    bis.close();
                } catch (IOException e) {
                    ;
                }
            }
        }
    }
}
17
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?