001/* 002 * Copyright (c) 2009 The openGion Project. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 013 * either express or implied. See the License for the specific language 014 * governing permissions and limitations under the License. 015 */ 016package org.opengion.fukurou.process; 017 018import org.opengion.fukurou.util.Argument; 019import org.opengion.fukurou.util.StringUtil; 020import org.opengion.fukurou.util.FileUtil; 021import org.opengion.fukurou.util.Closer ; 022import org.opengion.fukurou.util.LogWriter; 023 024import java.util.Map ; 025import java.util.LinkedHashMap ; 026 027import java.io.File; 028import java.io.BufferedReader; 029import java.io.IOException; 030 031/** 032 * Process_TableReaderは、ファイルから読み取った内容を、LineModel に設定後、 033 * 下流に渡す、FirstProcess インターフェースの実装クラスです。 034 * 035 * DBTableModel 形式のファイルを読み取って、各行を LineModel にセットして、 036 * 下流(プロセスチェインのデータは上流から下流に渡されます。)に渡します。 037 * 038 * columns 属性は、#NAME で列カラムを外部から指定する場合に使用します。 039 * この属性とuseNumber属性は独立していますが、一般には、#NAME を指定 040 * する場合は、useNumber="true"として、行番号欄は使用しますし、外部から 041 * 指定する場合は、useNumber="false"にして先頭から読み取ります。 042 * (自動セットではないので、必要に応じて設定してください) 043 * useNumber の初期値は、"true" です。 044 * 045 * ※ 注意 046 * Process_TableReader では、セパレータ文字 で区切って読み込む処理で、前後のスペースを 047 * 削除しています。 048 * 049 * 引数文字列中にスペースを含む場合は、ダブルコーテーション("") で括って下さい。 050 * 引数文字列の 『=』の前後には、スペースは挟めません。必ず、-key=value の様に 051 * 繋げてください。 052 * 053 * @og.formSample 054 * Process_TableReader -infile=INFILE -sep=, -encode=UTF-8 -columns=AA,BB,CC 055 * 056 * -infile=入力ファイル名 :入力ファイル名 057 * [-existCheck=存在確認 ] :ファイルが存在しない場合エラーにする(初期値:true) 058 * [-sep=セパレータ文字 ] :区切り文字(初期値:タブ) 059 * [-encode=文字エンコード ] :入力ファイルのエンコードタイプ 060 * [-columns=読み取りカラム名] :入力カラム名(カンマ区切り) 061 * [-useNumber=[true/false] ] :行番号を使用する(true)か使用しない(false)か。 062 * [-display=[false/true] ] :結果を標準出力に表示する(true)かしない(false)か(初期値:false[表示しない]) 063 * [-debug=[false/true] ] :デバッグ情報を標準出力に表示する(true)かしない(false)か(初期値:false[表示しない]) 064 * 065 * @version 4.0 066 * @author Kazuhiko Hasegawa 067 * @since JDK5.0, 068 */ 069public class Process_TableReader extends AbstractProcess implements FirstProcess { 070 private String separator = TAB; // 項目区切り文字 071 private String infile = null; 072 private BufferedReader reader = null; 073 private LineModel model = null; 074 private String line = null; 075 private int[] clmNos = null; // ファイルのヘッダーのカラム番号 076 private boolean useNumber = true; // 5.2.2.0 (2010/11/01) 行番号を使用する(true)か使用しない(false)か 077 private boolean nameNull = false; // 0件データ時 true 078 private boolean display = false; // 表示しない 079 private boolean debug = false; // 5.7.3.0 (2014/02/07) デバッグ情報 080 081 private int inCount = 0; 082 private int outCount = 0; 083 084 private static final Map<String,String> mustProparty ; // [プロパティ]必須チェック用 Map 085 private static final Map<String,String> usableProparty ; // [プロパティ]整合性チェック Map 086 087 static { 088 mustProparty = new LinkedHashMap<String,String>(); 089 mustProparty.put( "infile", "入力ファイル名 (必須)" ); 090 091 usableProparty = new LinkedHashMap<String,String>(); 092 usableProparty.put( "existCheck", "ファイルが存在しない場合エラーにする(初期値:true)" ); 093 usableProparty.put( "sep", "区切り文字(初期値:タブ)" ); 094 usableProparty.put( "encode", "入力ファイルのエンコードタイプ" ); 095 usableProparty.put( "columns", "入力カラム名(カンマ区切り)" ); 096 usableProparty.put( "useNumber", "行番号を使用する(true)か使用しない(false)か" ); // 5.2.2.0 (2010/11/01) 097 usableProparty.put( "display", "結果を標準出力に表示する(true)かしない(false)か" + 098 CR + " (初期値:false:表示しない)" ); 099 usableProparty.put( "debug", "デバッグ情報を標準出力に表示する(true)かしない(false)か" + 100 CR + "(初期値:false:表示しない)" ); // 5.7.3.0 (2014/02/07) デバッグ情報 101 } 102 103 /** 104 * デフォルトコンストラクター。 105 * このクラスは、動的作成されます。デフォルトコンストラクターで、 106 * super クラスに対して、必要な初期化を行っておきます。 107 * 108 */ 109 public Process_TableReader() { 110 super( "org.opengion.fukurou.process.Process_TableReader",mustProparty,usableProparty ); 111 } 112 113 /** 114 * プロセスの初期化を行います。初めに一度だけ、呼び出されます。 115 * 初期処理(ファイルオープン、DBオープン等)に使用します。 116 * 117 * @og.rev 5.2.2.0 (2010/11/01) useNumber属性の追加 118 * 119 * @param paramProcess データベースの接続先情報などを持っているオブジェクト 120 */ 121 public void init( final ParamProcess paramProcess ) { 122 Argument arg = getArgument(); 123 124 infile = arg.getProparty("infile"); 125 boolean existCheck = arg.getProparty("existCheck",true); 126 String encode = arg.getProparty("encode",System.getProperty("file.encoding")); 127 separator = arg.getProparty("sep",separator ); 128 String clms = arg.getProparty("columns" ); 129 useNumber = arg.getProparty("useNumber",useNumber); // 5.2.2.0 (2010/11/01) 130 display = arg.getProparty("display",display); 131 debug = arg.getProparty("debug",debug); // 5.7.3.0 (2014/02/07) デバッグ情報 132 133 if( infile == null ) { 134 String errMsg = "ファイル名が指定されていません。" ; 135 throw new RuntimeException( errMsg ); 136 } 137 138 File file = new File( infile ); 139 140 if( ! file.exists() ) { 141 if( existCheck ) { 142 String errMsg = "ファイルが存在しません。File=[" + file + "]" ; 143 throw new RuntimeException( errMsg ); 144 } 145 else { 146 nameNull = true; return ; 147 } 148 } 149 150 if( ! file.isFile() ) { 151 String errMsg = "ファイル名を指定してください。File=[" + file + "]" ; 152 throw new RuntimeException( errMsg ); 153 } 154 155 reader = FileUtil.getBufferedReader( file,encode ); 156 157 final String[] names ; 158 if( clms != null ) { 159 names = StringUtil.csv2Array( clms ); // 指定のカラム名配列 160 } 161 else { 162 // 5.2.2.0 (2010/11/01) names の外部指定の処理を先に行う。 163 String[] clmNames = readName( reader ); // ファイルのカラム名配列 164 if( clmNames == null || clmNames.length == 0 ) { nameNull = true; return ; } 165 names = clmNames; 166 } 167 168 model = new LineModel(); 169 model.init( names ); 170 171 if( display ) { println( model.nameLine() ); } 172 173 clmNos = new int[names.length]; 174 for( int i=0; i<names.length; i++ ) { 175 int no = model.getColumnNo( names[i] ); 176 // 5.2.2.0 (2010/11/01) useNumber="true"の場合は、行番号分を+1しておく。 177 if( no >= 0 ) { clmNos[no] = useNumber ? i+1 : i ; } 178 } 179 } 180 181 /** 182 * プロセスの終了を行います。最後に一度だけ、呼び出されます。 183 * 終了処理(ファイルクローズ、DBクローズ等)に使用します。 184 * 185 * @param isOK トータルで、OKだったかどうか[true:成功/false:失敗] 186 */ 187 public void end( final boolean isOK ) { 188 Closer.ioClose( reader ); 189 reader = null; 190 } 191 192 /** 193 * このデータの処理において、次の処理が出来るかどうかを問い合わせます。 194 * この呼び出し1回毎に、次のデータを取得する準備を行います。 195 * 196 * @og.rev 5.2.2.0 (2010/11/01) ""で囲われているデータに改行が入っていた場合の対応 197 * 198 * @return 処理できる:true / 処理できない:false 199 */ 200 public boolean next() { 201 if( nameNull ) { return false; } 202 203 boolean flag = false; 204 try { 205 while((line = reader.readLine()) != null) { 206 inCount++ ; 207 if( line.length() == 0 || line.charAt( 0 ) == '#' ) { continue; } 208 else { 209 // 5.2.2.0 (2010/11/01) findbugs 対策(文字列の + 連結と、奇数判定ロジック) 210 int quotCount = StringUtil.countChar( line, '"' ); 211 if( quotCount % 2 != 0 ) { 212 String addLine = null; 213 StringBuilder buf = new StringBuilder( line ); 214 while(quotCount % 2 != 0 && (addLine = reader.readLine()) != null) { 215 if( addLine.length() == 0 || addLine.charAt( 0 ) == '#' ) { continue; } 216 buf.append( CR ).append( addLine ); 217 quotCount += StringUtil.countChar( addLine, '"' ); 218 } 219 line = buf.toString(); 220 } 221 flag = true; 222 break; 223 } 224 } 225 } 226 catch (IOException ex) { 227 String errMsg = "ファイル読込みエラー[" + reader.toString() + "]" ; 228 throw new RuntimeException( errMsg,ex ); 229 } 230 if( debug ) { println( line ); } // 5.7.3.0 (2014/02/07) デバッグ情報 231 return flag; 232 } 233 234 /** 235 * 最初に、 行データである LineModel を作成します 236 * FirstProcess は、次々と処理をチェインしていく最初の行データを 237 * 作成して、後続の ChainProcess クラスに処理データを渡します。 238 * 239 * ファイルより読み込んだ1行のデータを テーブルモデルに 240 * セットするように分割します 241 * なお、読込みは,NAME項目分を読み込みます。データ件数が少ない場合は、 242 * "" をセットしておきます。 243 * 244 * @param rowNo 処理中の行番号 245 * 246 * @return 処理変換後のLineModel 247 */ 248 public LineModel makeLineModel( final int rowNo ) { 249 outCount++ ; 250 String[] vals = StringUtil.csv2Array( line ,separator.charAt(0) ); 251 252 int len = vals.length; 253 for( int clmNo=0; clmNo<model.size(); clmNo++ ) { 254 int no = clmNos[clmNo]; 255 if( len > no ) { 256 model.setValue( clmNo,vals[no] ); 257 } 258 else { 259 // EXCEL が、終端TABを削除してしまうため、少ない場合は埋める。 260 model.setValue( clmNo,"" ); 261 } 262 } 263 model.setRowNo( rowNo ) ; 264 265 if( display ) { println( model.dataLine() ); } 266 267 return model; 268 } 269 270 /** 271 * BufferedReader より、#NAME 行の項目名情報を読み取ります。 272 * データカラムより前に、項目名情報を示す "#Name" が存在する仮定で取り込みます。 273 * この行は、ファイルの形式に無関係に、TAB で区切られています。 274 * 275 * @param reader PrintWriterオブジェクト 276 * 277 * @return カラム名配列(存在しない場合は、サイズ0の配列) 278 */ 279 private String[] readName( final BufferedReader reader ) { 280 try { 281 // 4.0.0 (2005/01/31) line 変数名変更 282 String line1; 283 while((line1 = reader.readLine()) != null) { 284 inCount++ ; 285 if( line1.length() == 0 ) { continue; } 286 if( line1.charAt(0) == '#' ) { 287 String key = line1.substring( 0,5 ); 288 if( "#NAME".equalsIgnoreCase( key ) ) { 289 // 超イレギュラー処理 最初の TAB 以前の文字は無視する。 290 String line2 = line1.substring( line1.indexOf( TAB )+1 ); 291 return StringUtil.csv2Array( line2 ,TAB.charAt(0) ); 292 } 293 else { continue; } 294 } 295 else { 296 String errMsg = "#NAME が見つかる前にデータが見つかりました。"; 297 throw new RuntimeException( errMsg ); 298 } 299 } 300 } 301 catch (IOException ex) { 302 String errMsg = "ファイル読込みエラー[" + reader.toString() + "]" ; 303 throw new RuntimeException( errMsg,ex ); 304 } 305 return new String[0]; 306 } 307 308 /** 309 * プロセスの処理結果のレポート表現を返します。 310 * 処理プログラム名、入力件数、出力件数などの情報です。 311 * この文字列をそのまま、標準出力に出すことで、結果レポートと出来るような 312 * 形式で出してください。 313 * 314 * @return 処理結果のレポート 315 */ 316 public String report() { 317 String report = "[" + getClass().getName() + "]" + CR 318 + TAB + "Input File : " + infile + CR 319 + TAB + "Input Count : " + inCount + CR 320 + TAB + "Output Count : " + outCount ; 321 322 return report ; 323 } 324 325 /** 326 * このクラスの使用方法を返します。 327 * 328 * @og.rev 5.2.2.0 (2010/11/01) useNumber属性のコメント追加 329 * 330 * @return このクラスの使用方法 331 */ 332 public String usage() { 333 StringBuilder buf = new StringBuilder(); 334 335 buf.append( "Process_TableReaderは、ファイルから読み取った内容を、LineModel に設定後、" ).append( CR ); 336 buf.append( "下流に渡す、FirstProcess インターフェースの実装クラスです。" ).append( CR ); 337 buf.append( CR ); 338 buf.append( "DBTableModel 形式のファイルを読み取って、各行を LineModel にセットして、" ).append( CR ); 339 buf.append( "下流(プロセスチェインのデータは上流から下流に渡されます。)に渡します。" ).append( CR ); 340 buf.append( CR ); 341 buf.append( "columns 属性は、#NAME で列カラムを外部から指定する場合に使用します。" ).append( CR ); 342 buf.append( "この属性とuseNumber属性は独立していますが、一般には、#NAME を指定" ).append( CR ); 343 buf.append( "する場合は、useNumber=\"true\"として、行番号欄は使用しますし、外部から" ).append( CR ); 344 buf.append( "指定する場合は、useNumber=\"false\"にして先頭から読み取ります。" ).append( CR ); 345 buf.append( "(自動セットではないので、必要に応じて設定してください)" ).append( CR ); 346 buf.append( "useNumber の初期値は、\"true\" です。" ).append( CR ); 347 buf.append( CR ); 348 buf.append( "引数文字列中に空白を含む場合は、ダブルコーテーション(\"\") で括って下さい。" ).append( CR ); 349 buf.append( "引数文字列の 『=』の前後には、空白は挟めません。必ず、-key=value の様に" ).append( CR ); 350 buf.append( "繋げてください。" ).append( CR ); 351 buf.append( CR ).append( CR ); 352 353 buf.append( getArgument().usage() ).append( CR ); 354 355 return buf.toString(); 356 } 357 358 /** 359 * このクラスは、main メソッドから実行できません。 360 * 361 * @param args コマンド引数配列 362 */ 363 public static void main( final String[] args ) { 364 LogWriter.log( new Process_TableReader().usage() ); 365 } 366}