/*
* Copyright (c) 2007 NTT DATA Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jp.terasoluna.fw.file.dao.standard;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import jp.terasoluna.fw.file.annotation.FileFormat;
import jp.terasoluna.fw.file.dao.FileException;
/**
* 可変長ファイルファイル用のファイルアクセス(データ取得)クラス。
* <p>
* 可変長ファイルからデータを読み込み、1行分のデータをファイル行オブジェクトに 格納する。<br>
* CSVファイルでは区切り文字がカンマで固定されているが、可変長ファイルでは カンマ以外を利用することが可能。
* </p>
* <b>※利用するファイル行オブジェクトのアノテーション項目</b><br>
* ⅰ.@{@link FileFormat}の設定項目<br>
* <div align="center">
* <table width="90%" border="1" bgcolor="#FFFFFF">
* <tr>
* <td><b>論理項目名</b></td>
* <td><b>物理項目名</b></td>
* <td><b>デフォルト値</b></td>
* <td><b>必須性</b></td>
* </tr>
* <tr>
* <td> <code>行区切り文字</code></td>
* <td> <code>lineFeedChar</code></td>
* <td> <code>システムの行区切り文字</code></td>
* <td> <code>オプション</code></td>
* </tr>
* <tr>
* <td> <code>区切り文字</code></td>
* <td> <code>delimiter</code></td>
* <td> <code>','</code></td>
* <td> <code>オプション</code></td>
* </tr>
* <tr>
* <td> <code>囲み文字</code></td>
* <td> <code>encloseChar</code></td>
* <td> <code>なし('\u0000')</code></td>
* <td> <code>オプション</code></td>
* </tr>
* <tr>
* <td> <code>ファイルエンコーディング</code></td>
* <td> <code>fileEncodeing</code></td>
* <td> <code>システムのファイルエンコーディング</code></td>
* <td> <code>オプション</code></td>
* </tr>
* <tr>
* <td> <code>ヘッダ行数</code></td>
* <td> <code>headerLineCount</code></td>
* <td> <code>0</code></td>
* <td> <code>オプション</code></td>
* </tr>
* <tr>
* <td> <code>トレイラ行数</code></td>
* <td> <code>trailerLineCount</code></td>
* <td> <code>0</code></td>
* <td> <code>オプション</code></td>
* </tr>
* </table>
* </div> <br>
* ⅱ.@{@link jp.terasoluna.fw.file.annotation.InputFileColumn}、@{@link jp.terasoluna.fw.file.annotation.OutputFileColumn}の設定項目<br>
* <div align="center">
* <table width="90%" border="1" bgcolor="#FFFFFF">
* <tr>
* <td><b>論理項目名</b></td>
* <td><b>物理項目名</b></td>
* <td><b>デフォルト値</b></td>
* <td><b>必須性</b></td>
* </tr>
* <tr>
* <td> <code>カラムインデックス</code></td>
* <td> <code>columnIndex</code></td>
* <td>-</td>
* <td> <code>必須</code></td>
* </tr>
* <tr>
* <td> <code>フォーマット</code></td>
* <td> <code>columnFormat</code></td>
* <td> <code>""</code></td>
* <td> <code>オプション</code></td>
* </tr>
* <tr>
* <td> <code>バイト長</code></td>
* <td> <code>bytes</code></td>
* <td> <code>-1</code></td>
* <td> <code>オプション</code></td>
* </tr>
* <tr>
* <td> <code>パディング種別</code></td>
* <td> <code>paddingType</code></td>
* <td> <code>パディングなし</code></td>
* <td> <code>オプション</code></td>
* </tr>
* <tr>
* <td> <code>パディング文字</code></td>
* <td> <code>paddingChar</code></td>
* <td> <code>' '</code></td>
* <td> <code>オプション</code></td>
* </tr>
* <tr>
* <td> <code>トリム種別</code></td>
* <td> <code>trimType</code></td>
* <td> <code>トリムなし</code></td>
* <td> <code>オプション</code></td>
* </tr>
* <tr>
* <td> <code>トリム文字</code></td>
* <td> <code>trimChar</code></td>
* <td> <code>' '</code></td>
* <td> <code>オプション</code></td>
* </tr>
* <tr>
* <td> <code>文字変換種別</code></td>
* <td> <code>stringConverter</code></td>
* <td> <code>NullStringConverter.class</code></td>
* <td> <code>オプション</code></td>
* </tr>
* </table>
* </div> <br>
* <b>※注意事項</b><br>
* <ul>
*
* <li>区切り文字にCaracter.MIN_VALUEを設定することは出来ない。(エラー発生)</li>
* </ul>
* @param <T> ファイル行オブジェクト
*/
public class VariableFileLineIterator<T> extends AbstractFileLineIterator<T> {
/**
* 区切り文字。
*/
private char delimiter = ',';
/**
* 囲み文字。
*/
private char encloseChar = Character.MIN_VALUE;
/**
* コンストラクタ。
* @param fileName ファイル名
* @param clazz ファイル行オブジェクトクラス
* @param columnParserMap テキスト設定ルール
*/
public VariableFileLineIterator(String fileName, Class<T> clazz,
Map<String, ColumnParser> columnParserMap) {
super(fileName, clazz, columnParserMap);
FileFormat fileFormat = clazz.getAnnotation(FileFormat.class);
// 区切り文字がCharacter.MIN_VALUEの場合、例外をスローする。
if (fileFormat.delimiter() == Character.MIN_VALUE) {
throw new FileException("Delimiter can not use '\\u0000'.",
new IllegalStateException(), fileName);
}
// 改行文字内に区切り文字が含まれている場合、例外をスローする。
if (fileFormat.lineFeedChar().indexOf(fileFormat.delimiter()) >= 0) {
throw new FileException(
"delimiter is the same as lineFeedChar and is no use.",
new IllegalStateException(), fileName);
}
// 囲み文字を設定する。
this.encloseChar = fileFormat.encloseChar();
// 区切り文字を設定する。
this.delimiter = fileFormat.delimiter();
// 初期化処理を行う。
super.init();
}
/**
* 読み込んだファイルのレコードを、区切り文字、 囲み文字に従って 文字配列に変換する。<br>
* 引数<code>fileLineString</code>が<code>null</code>もしくは 空文字の場合は、要素を持たない<code>String</code>配列を返します。
* @param fileLineString 可変長ファイルの1レコード分の文字列
* @return 文字配列
*/
@Override
protected String[] separateColumns(String fileLineString) {
if (fileLineString == null || "".equals(fileLineString)) {
return new String[0];
}
// 1カラム分の文字列を格納する文字シーケンス
StringBuilder columnBuilder = new StringBuilder();
// チェック対象文字の直前の文字
char previousChar = Character.MIN_VALUE;
// 文字列を格納するための配列
List<String> columnList = new ArrayList<String>();
boolean isEnclosed = true;
boolean isEscaped = false;
int fieldCount = 0;
char[] columnEncloseChar = getColumnEncloseChar();
if (!isEnclosed()) {
return StringUtils.splitByWholeSeparatorPreserveAllTokens(
fileLineString, Character.toString(delimiter));
} else {
for (char currentChar : fileLineString.toCharArray()) {
if (previousChar == Character.MIN_VALUE) {
previousChar = currentChar;
}
if (previousChar == getEncloseCharcter(columnEncloseChar,
fieldCount)) {
if (isEnclosed) {
if (currentChar == getEncloseCharcter(
columnEncloseChar, fieldCount)) {
isEnclosed = false;
}
} else {
if (currentChar == getEncloseCharcter(
columnEncloseChar, fieldCount)) {
if (isEscaped) {
columnBuilder.append(currentChar);
isEscaped = false;
} else {
isEscaped = true;
}
} else if (currentChar == getDelimiter()) {
if (isEscaped) {
columnList.add(columnBuilder.toString());
previousChar = Character.MIN_VALUE;
columnBuilder.delete(0, columnBuilder.length());
isEnclosed = true;
isEscaped = false;
fieldCount++;
} else {
columnBuilder.append(currentChar);
isEscaped = false;
}
} else {
columnBuilder.append(currentChar);
}
}
} else {
if (currentChar != getDelimiter()) {
columnBuilder.append(currentChar);
} else {
columnList.add(columnBuilder.toString());
previousChar = Character.MIN_VALUE;
columnBuilder.delete(0, columnBuilder.length());
fieldCount++;
}
}
}
columnList.add(columnBuilder.toString());
return columnList.toArray(new String[columnList.size()]);
}
}
/**
* カラムに対応する囲み文字を取得する。
* @param index カラムのインデックス
* @return 囲み文字
*/
private char getEncloseCharcter(char[] columnEncloseChar, int index) {
if (columnEncloseChar.length == 0 || index >= columnEncloseChar.length) {
return this.encloseChar;
} else {
return columnEncloseChar[index];
}
}
/**
* 区切り文字を取得する。
* @return 区切り文字
*/
@Override
public char getDelimiter() {
return delimiter;
}
/**
* 囲み文字を取得する。
* @return 囲み文字
*/
@Override
public char getEncloseChar() {
return encloseChar;
}
}