All Downloads are FREE. Search and download functionalities are using the official Maven repository.

nablarch.test.core.file.DataFileFragment Maven / Gradle / Ivy

There is a newer version: 2.2.0
Show newest version
package nablarch.test.core.file;

import static nablarch.core.util.Builder.concat;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

import nablarch.core.dataformat.DataRecord;
import nablarch.core.dataformat.FieldDefinition;
import nablarch.core.dataformat.FileRecordWriter;
import nablarch.core.dataformat.FixedLengthDataRecordFormatter;
import nablarch.core.dataformat.RecordDefinition;
import nablarch.core.dataformat.convertor.ConvertorFactorySupport;
import nablarch.core.dataformat.convertor.datatype.DataType;
import nablarch.core.repository.SystemRepository;
import nablarch.core.util.Builder;
import nablarch.test.NablarchTestUtils;
import nablarch.test.core.util.MapCollector;

/**
 * ファイルの断片を表すクラス。
* 1つのレコード種別とそのレコード種別に適合する複数のレコードにより構成される。 * * @author T.Kawasaki */ public abstract class DataFileFragment { /** この断片を包含するファイル */ protected final DataFile container; // SUPPRESS CHECKSTYLE サブクラスに対してカプセル化の必要がないため /** データ型のマッピング表 */ private static final String DATATYPE_MAPPING = "dataTypeMapping"; /** フィールド名称 */ protected List names = null; // SUPPRESS CHECKSTYLE サブクラスに対してカプセル化の必要がないため /** データ型のシンボル */ protected List types = null; // SUPPRESS CHECKSTYLE サブクラスに対してカプセル化の必要がないため /** フィールド長 */ protected List lengths = null; // SUPPRESS CHECKSTYLE サブクラスに対してカプセル化の必要がないため /** レコード追加時に文字列長を計算するか否かフラグ */ private List isOndemandCalcFieldSizeList = null; /** レコード種別 */ private String recordType = null; /** フィールド名称と{@link FieldDefinition}のペア */ private Map fields = new HashMap(); /** レコードのデータ */ protected List> values = new ArrayList>(); // SUPPRESS CHECKSTYLE サブクラスに対してカプセル化の必要がないため /** レコードフォーマッタ */ private RecordDefinition recordDefinition; /** 最初のフィールドの値(連番)が格納されるキー */ public static final String FIRST_FIELD_NO = "DataFileFragment:firstFieldKey"; /** テスト用のデータ型シンボルのプレフィクス */ private static final String TEST_SYMBOL_PREFIX = "TEST_"; /** フィールドサイズをデータ追加時に動的計算する文字列 */ private static final String ONDEMAND_CALC_FIELD_SIZE = "-"; /** 改行および空白除去時のパターン */ private static final Pattern REMOVE_LS_SP_PATTERN = Pattern.compile("\\s*[\\r\\n]\\s*"); /** * コンストラクタ。 * * @param container 本インスタンスが所属するファイル */ protected DataFileFragment(DataFile container) { this.container = container; } /** * レコード種別を設定する。 * * @param recordType レコード種別 */ public void setRecordType(String recordType) { this.recordType = recordType; } /** * 値を追加する。 * * @param line 行データ */ public void addValue(List line) { Map map = new LinkedHashMap(); for (int i = 0; i < names.size(); i++) { String name = names.get(i); String value = i < line.size() ? line.get(i) : ""; if (isOndemandCalcFieldSize(i)) { value = removeLineSeparatorWithTrim(value); replaceFieldSize(i, value); } map.put(name, value); } values.add(map); } /** * フィールドサイズをデータ追加時に動的計算するかを返却する * @param idx インデックス * @return フィールドサイズをデータ追加時に動的計算する場合true */ private boolean isOndemandCalcFieldSize(int idx) { if (isOndemandCalcFieldSizeList == null) { return false; } return isOndemandCalcFieldSizeList.get(idx); } /** * フィールドデータからフィールドサイズを計算し、置換する * * ※すでに設定されているフィールドサイズが今回のフィールドデータ長より長い場合には何もしない。 * * @param idx インデックス * @param data フィールドデータ */ private void replaceFieldSize(int idx, String data) { int currentLength = data.getBytes(container.getEncodingFromDirectives()).length; int originalLength; try { originalLength = Integer.parseInt(lengths.get(idx)); } catch (NumberFormatException ignore) { originalLength = 0; } if (originalLength > currentLength) { return; } String size = Integer.toString(currentLength); lengths.set(idx, size); } /** * 各行についてトリムを行い、改行コードも除去する * @param data フィールドデータ * @return トリムおよび改行が除去されたフィールドデータ */ private String removeLineSeparatorWithTrim(String data) { return REMOVE_LS_SP_PATTERN.matcher(data).replaceAll(""); } /** * 最初のフィールドの値(連番)を追加する。 * * @param line 行データ * @param no 最初のフィールドの値(連番) */ public void addValueWithId(List line, String no) { Map map = new LinkedHashMap(); map.put(FIRST_FIELD_NO, no); for (int i = 0; i < names.size(); i++) { String name = names.get(i); String value = i < line.size() ? line.get(i) : ""; if (isOndemandCalcFieldSize(i)) { value = removeLineSeparatorWithTrim(value); replaceFieldSize(i, value); } map.put(name, value); } values.add(map); } /** * フィールド名称を設定する。 * * @param names フィールド名称 */ public void setNames(List names) { assertNotNullOrEmpty(names, "names"); assertNotContainDuplicateNames(names); this.names = names; } /** * データ型のシンボルを設定する。
* データ型の要素数はフィールド名と同じでなければならない。 * * @param types データ型のシンボル */ public void setTypes(List types) { assertSameSizeAsNames(types, "types"); List t = new ArrayList(types.size()); for (String dataType : types) { t.add(convertToFrameworkExpression(dataType)); } this.types = t; } /** * フィールドに対するテスト用のデータ型シンボルを取得する。
*

*

テスト用のデータ型シンボルは、通常typesのデータ型シンボルであるが、 * "TEST_" というデータ型シンボルが存在した場合はこのデータ型シンボルを使用する。 *

*

*

* 例えば下記の様なデータ型がフィールド定義に存在していた場合を考える。 *

*

* * * *
X
X9
TEST_X9
*

* この場合、このメソッドの返す値は下記のようになる。 *

*

* * *
typesに入ったデータ型XX9
戻り値のデータ型シンボルXTEST_X9
*

* * @param fieldIndex 対象となるフィールドのインデックス * @return テスト用のデータ型シンボル */ protected String getTypeForTest(int fieldIndex) { String baseType = types.get(fieldIndex); String testTypeName = TEST_SYMBOL_PREFIX + baseType; ConvertorFactorySupport support = getConvertorFactorySupport(); boolean typeExists = support.getConvertorTable().containsKey(testTypeName); return typeExists ? testTypeName : baseType; } /** * コンバータの生成を行うクラスを取得する。 * * @return コンバータの生成を行うファクトリクラス */ protected abstract ConvertorFactorySupport getConvertorFactorySupport(); /** * 外部インタフェース設計書のデータ型記法から * フレームワークのデータ型シンボルへ変換する。 * * @param dataTypeInDesign 外部インタフェース設計書のデータ型 * @return フレームワークのデータ型シンボル */ private String convertToFrameworkExpression(String dataTypeInDesign) { DataTypeMapping mapping = null; if (container.getEncodingFromDirectives() != null) { //文字コード別マッピングの取得を試みる String encoding = container.getEncodingFromDirectives().name(); mapping = SystemRepository.get(DATATYPE_MAPPING + "_" + encoding); } if (mapping == null) { //文字コード別マッピングが定義されていなければ、標準的な名前で定義されたマッピングの取得を試みる。 mapping = SystemRepository.get(DATATYPE_MAPPING); } if (mapping == null) { //通常のマッピングも定義されていなければ、組み込みのマッピングを使用する。 mapping = BasicDataTypeMapping.getDefault(); } return mapping.convertToFrameworkExpression(dataTypeInDesign); } /** * フィールド長を設定する。
* フィールド長の要素数はフィールド名と同じでなければならない。 * * @param lengths フィールド長 */ public void setLengths(List lengths) { assertSameSizeAsNames(lengths, "lengths"); this.lengths = lengths; isOndemandCalcFieldSizeList = new ArrayList(); for (String length : lengths) { isOndemandCalcFieldSizeList.add(ONDEMAND_CALC_FIELD_SIZE.equals(length)); } } /** * この断片が持つレコード長を取得する。 * * @return レコード長 */ int getRecordLength() { return calcRecordLength(lengths); } /** * レコード長を計算する。
* 与えられたフィールド長を合算しレコード長とする。 * * @param fieldLengths フィールド長のリスト * @return レコード長 */ int calcRecordLength(List fieldLengths) { int sum = 0; for (String length : fieldLengths) { sum += NablarchTestUtils.parseInt(length); } return sum; } /** * リストがnullまたは空でないことを表明する。 * * @param list 検査対象のList * @param name 名称 * @throws IllegalArgumentException 引数のListがnullまたは空の時 */ private void assertNotNullOrEmpty(List list, String name) throws IllegalArgumentException { if (NablarchTestUtils.isNullOrEmpty(list)) { throw new IllegalArgumentException(name + " must not be null or empty."); } } /** * 与えられたリストの要素数が、フィールド名の要素数と合致することを表明する。 * * @param list 調査対象のリスト * @param name そのリストの名称(例外メッセージ出力に使用) * @throws IllegalArgumentException 要素数が合致しなかった場合 */ private void assertSameSizeAsNames(List list, String name) throws IllegalArgumentException { assertNotNullOrEmpty(list, name); if (list.size() != names.size()) { throw new IllegalArgumentException(Builder.concat( "field name size is ", names.size(), ". but ", name, " size is ", list.size(), ". ", toString())); } } /** * 指定されたフィールド名に重複がないことを表明する。 * * @param names フィールド名 * @throws IllegalArgumentException フィールド名に重複が存在する場合 */ private void assertNotContainDuplicateNames(List names) throws IllegalArgumentException { Set duplicate = extractDuplicateElement(names); if (!duplicate.isEmpty()) { throw new IllegalArgumentException( "Duplicate field names are not permitted in a record. duplicate field=" + duplicate + " . " + "file=[" + container.getPath() + "]" ); } } /** * コレクション内の重複した要素を抽出する。 * * @param target 対象となるコレクション * @param コレクションの要素の型 * @return 重複した要素一覧(重複がない場合、空のリスト) */ private Set extractDuplicateElement(Collection target) { List orig = new ArrayList(target); // コピーを作成 Set unique = new HashSet(target); // 重複を取り除いたSet for (T e : unique) { orig.remove(e); // 元のコレクションから消しこみ } Set duplicate = new LinkedHashSet(orig); // 残った要素が重複した要素(Assert用にLinkedHashSetを使用) return duplicate; } /** * {@link DataRecord}型に変換する。 * * @return DataRecordのリスト */ List toDataRecords() { List dataRecords = new ArrayList(values.size()); for (Map value : values) { DataRecord record = toDataRecord(value); dataRecords.add(record); } return dataRecords; } /** * {@link DataRecord}型に変換する。 * * @param value 変換対象フィールドの値 * @return {@link DataRecord} */ private DataRecord toDataRecord(Map value) { DataRecord record = new DataRecord().setRecordType(recordType); Map converted = convertForDataRecord(value); record.putAll(converted); return record; } /** * テストデータ(文字列)から{@link DataRecord}用に値を変換する。 * * @param value 元の値(文字列) * @return 変換後の値 */ protected abstract Map convertForDataRecord(Map value); /** * 値の型変換を行う。
* * @param fieldName フィールド名称 * @param stringExpression 処理対象フィールドの文字列表現 * @return 型変換された値 */ protected abstract Object convertValue(String fieldName, String stringExpression); /** * フィールド名から、そのフィールドのレコードタイプを取得する。 * * @param fieldName フィールド名 * @return レコードタイプ */ protected final String getTypeOf(String fieldName) { return getTypeForTest(getIndexOf(fieldName)); } /** * フィールド名からそのフィールドが格納されている位置(インデックス)を取得する。 * * @param fieldName フィールド名 * @return インデックス */ protected final int getIndexOf(String fieldName) { int index = names.indexOf(fieldName); if (index == -1) { throw new IllegalArgumentException("no such field [" + fieldName + "]. " + this); } return index; } /** * フィールド定義を取得する。 * * @param fieldName フィールド名 * @return フィールド定義 */ protected FieldDefinition getFieldDefinition(String fieldName) { return fields.get(fieldName); } /** * パディングを取り除く。 * * @param fieldName フィールド名称 * @param value 処理対象となる値 * @param formatter バディング除去用のフォーマッタ * @return パディングを取り除いた値 */ protected Object removePadding(String fieldName, Object value, FixedLengthDataRecordFormatter formatter) { if (FIRST_FIELD_NO.equals(fieldName)) { // No列は実際のデータではないのでパディング除去対象外 return value; } FieldDefinition field = getFieldDefinition(fieldName); DataType dataType = field.getDataType(); if (dataType == null) { String typeSymbol = getTypeOf(fieldName); int length = getLengthOf(fieldName); // しょうがなく dataType = getDataType(typeSymbol, field, length); formatter.setDataTypeProperty(dataType); } return dataType.removePadding(value); } /** * データタイプを取得する。 * * @param typeSymbol データタイプ名 * @param field フィールド定義 * @param length フィールド長 * @return データタイプ */ protected DataType getDataType(String typeSymbol, FieldDefinition field, int length) { return getConvertorFactorySupport().typeOf(typeSymbol, field, length); } /** * この断片が持つレコード定義を取得する。 * * @return レコード定義 */ RecordDefinition getRecordDefinition() { if (recordDefinition != null) { return recordDefinition; } recordDefinition = prepareRecordDefinition(); return recordDefinition; } /** * {@link RecordDefinition}を生成する。
* 設定されたデータから{@link FieldDefinition}を生成し、それらを組み合わせて * {@link RecordDefinition}を作成する。 * * @return 生成したRecordDefinition */ private RecordDefinition prepareRecordDefinition() { checkSize(); RecordDefinition record = new RecordDefinition(); for (int fieldIdx = 0; fieldIdx < names.size(); fieldIdx++) { FieldDefinition field = createFieldDefinition(fieldIdx); fields.put(field.getName(), field); record.addField(field); } record.setTypeName(recordType); return record; } /** * {@link FieldDefinition}を生成する。
* 設定されたデータから{@link FieldDefinition}を生成する。 * * @param fieldIndex 対象となるフィールドのインデックス * @return 生成したFieldDefinition */ protected abstract FieldDefinition createFieldDefinition(int fieldIndex); /** データの要素数が妥当であることを確認する。 */ private void checkSize() { if (isSizeValid()) { throw new IllegalStateException("invalid data. " + toString()); } } /** * 各要素のサイズが妥当であるかどうか判定する。 * * @return 妥当である場合、真 */ protected abstract boolean isSizeValid(); /** {@inheritDoc} */ @Override public String toString() { return concat(getClass().getSimpleName(), "{", "recordType=", recordType, ", values=", values, ", types=", types, ", lengths=", lengths, '}'); } /** * ライターに書きだす。 * * @param writer ライター */ void writeWith(FileRecordWriter writer) { for (Map row : values) { // 文字列からObjectに変換 Map converted = new MapCollector() { @Override protected Object evaluate(String key, String value) { return convertValue(key, value); } } .collect(row); // 書き出し writer.write(recordType, converted); } } /** * このインスタンスがもつ要素数(レコード数)を取得する。 * * @return レコード数 */ int getNumberOfRecords() { return values.size(); } /** * フィールド名から、そのフィールドのフィールド長を取得する。 * * @param fieldName フィールド名 * @return フィールド長 */ protected final int getLengthOf(String fieldName) { return Integer.parseInt(lengths.get(getIndexOf(fieldName))); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy