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 Toolbox for Java
IBM提供の概要説明。
URL:https://www.ibm.com/support/knowledgecenter/ja/ssw_ibm_i_73/rzahh/page1.htm
- プログラミング IBM Toolbox for Java
プログラミングお作法などの概要説明したPDFファイル。これがプログラミングの手助けになる。今回の投稿もこれを参考にしながら書いています(このPDFファイルはエッセンスだけしか書いていないので、試行錯誤が必要でした)。
URL:https://www.ibm.com/support/knowledgecenter/ja/ssw_ibm_i_73/rzahh/rzahhpdf.pdf
- IBM Toolbox for Java API Document
URL:https://www.ibm.com/support/knowledgecenter/ssw_ibm_i_72/rzahh/javadoc/index.html
- JTOpen: The Open Source version of the IBM Toolbox for Java
オープンソース版の概要説明。
URL:http://jt400.sourceforge.net/
- JTOpen: The Open Source version of the IBM Toolbox for Javaのダウンロードページ
URL:https://sourceforge.net/projects/jt400/
- その最新版のダウンロードURL
URL:https://sourceforge.net/projects/jt400/files/latest/download?source=files
- JTOpenとIBM Toolbox for Javaのライセンス
どちらも「IBM Public License Version 1.0」。
URL:https://opensource.org/licenses/ibmpl.php
IBMメインフレームのファイルをJavaで入出力するための要件定義
ツールの紹介の前に、簡単にこのツールに期待している事、つまり要件を定義し、この要件に従って、ツールの使い方を紹介します。
- IBMメインフレームのファイルの入出力に使うレコードフォーマット定義は、Javaプログラムの外部に定義し、その定義に従って、レコードの区切りや、項目の区切りを行いたい。
- 文字コード変換も行いたい。
- 符号付きパック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」の所に書いてあります。
これは、メインフレームの文字コードを指定しているのですが、全体の文字コード指定をする場合は、このルートレベルの所に書き、
個別に指定する場合は、各データ項目に指定することができます。
<?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は使えないということ。どうするか?
/**
* メインフレームファイル出力. <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.にセットします。
<?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>
/**
* メインフレームファイル出力. <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している。
//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で行う方が全体としての統一感がでる。
その他確認につかったコード全体
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) {
;
}
}
}
}
}