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// if( debug ) { println( arg.toString() ); } // 5.7.3.0 (2014/02/07) デバッグ情報 133 134 if( infile == null ) { 135 String errMsg = "ファイル名が指定されていません。" ; 136 throw new RuntimeException( errMsg ); 137 } 138 139 File file = new File( infile ); 140 141 if( ! file.exists() ) { 142 if( existCheck ) { 143 String errMsg = "ファイルが存在しません。File=[" + file + "]" ; 144 throw new RuntimeException( errMsg ); 145 } 146 else { 147 nameNull = true; return ; 148 } 149 } 150 151 if( ! file.isFile() ) { 152 String errMsg = "ファイル名を指定してください。File=[" + file + "]" ; 153 throw new RuntimeException( errMsg ); 154 } 155 156 reader = FileUtil.getBufferedReader( file,encode ); 157 158 // 5.2.2.0 (2010/11/01) names の外部指定の処理を先に行う。 159// String[] clmNames = readName( reader ); // ファイルのカラム名配列 160// if( clmNames == null || clmNames.length == 0 ) { nameNull = true; return ; } 161 162 final String[] names ; 163 if( clms != null ) { 164 names = StringUtil.csv2Array( clms ); // 指定のカラム名配列 165 } 166 else { 167 // 5.2.2.0 (2010/11/01) names の外部指定の処理を先に行う。 168 String[] clmNames = readName( reader ); // ファイルのカラム名配列 169 if( clmNames == null || clmNames.length == 0 ) { nameNull = true; return ; } 170 names = clmNames; 171 } 172 173 model = new LineModel(); 174 model.init( names ); 175 176 if( display ) { println( model.nameLine() ); } 177 178// clmNos = new int[names.length]; 179// for( int i=0; i<clmNames.length; i++ ) { 180// int no = model.getColumnNo( clmNames[i] ); 181// if( no >= 0 ) { clmNos[no] = i+1; } // 行番号分を+1しておく。 182// } 183 clmNos = new int[names.length]; 184 for( int i=0; i<names.length; i++ ) { 185 int no = model.getColumnNo( names[i] ); 186 // 5.2.2.0 (2010/11/01) useNumber="true"の場合は、行番号分を+1しておく。 187 if( no >= 0 ) { clmNos[no] = (useNumber) ? i+1 : i ; } 188 } 189 } 190 191 /** 192 * プロセスの終了を行います。最後に一度だけ、呼び出されます。 193 * 終了処理(ファイルクローズ、DBクローズ等)に使用します。 194 * 195 * @param isOK トータルで、OKだったかどうか[true:成功/false:失敗] 196 */ 197 public void end( final boolean isOK ) { 198 Closer.ioClose( reader ); 199 reader = null; 200 } 201 202 /** 203 * このデータの処理において、次の処理が出来るかどうかを問い合わせます。 204 * この呼び出し1回毎に、次のデータを取得する準備を行います。 205 * 206 * @og.rev 5.2.2.0 (2010/11/01) ""で囲われているデータに改行が入っていた場合の対応 207 * 208 * @return 処理できる:true / 処理できない:false 209 */ 210 public boolean next() { 211 if( nameNull ) { return false; } 212 213 boolean flag = false; 214 try { 215 while((line = reader.readLine()) != null) { 216 inCount++ ; 217 if( line.length() == 0 || line.charAt( 0 ) == '#' ) { continue; } 218 else { 219 // 5.2.2.0 (2010/11/01) findbugs 対策(文字列の + 連結と、奇数判定ロジック) 220 int quotCount = StringUtil.countChar( line, '"' ); 221 if( quotCount % 2 != 0 ) { 222 String addLine = null; 223 StringBuilder buf = new StringBuilder( line ); 224 while(quotCount % 2 != 0 && (addLine = reader.readLine()) != null) { 225 if( addLine.length() == 0 || addLine.charAt( 0 ) == '#' ) { continue; } 226 buf.append( CR ).append( addLine ); 227 quotCount += StringUtil.countChar( addLine, '"' ); 228 } 229 line = buf.toString(); 230 } 231 flag = true; 232 break; 233 } 234 } 235 } 236 catch (IOException ex) { 237 String errMsg = "ファイル読込みエラー[" + reader.toString() + "]" ; 238 throw new RuntimeException( errMsg,ex ); 239 } 240 if( debug ) { println( line ); } // 5.7.3.0 (2014/02/07) デバッグ情報 241 return flag; 242 } 243 244 /** 245 * 最初に、 行データである LineModel を作成します 246 * FirstProcess は、次々と処理をチェインしていく最初の行データを 247 * 作成して、後続の ChainProcess クラスに処理データを渡します。 248 * 249 * ファイルより読み込んだ1行のデータを テーブルモデルに 250 * セットするように分割します 251 * なお、読込みは,NAME項目分を読み込みます。データ件数が少ない場合は、 252 * "" をセットしておきます。 253 * 254 * @param rowNo 処理中の行番号 255 * 256 * @return 処理変換後のLineModel 257 */ 258 public LineModel makeLineModel( final int rowNo ) { 259 outCount++ ; 260 String[] vals = StringUtil.csv2Array( line ,separator.charAt(0) ); 261 262 int len = vals.length; 263 for( int clmNo=0; clmNo<model.size(); clmNo++ ) { 264 int no = clmNos[clmNo]; 265 if( len > no ) { 266 model.setValue( clmNo,vals[no] ); 267 } 268 else { 269 // EXCEL が、終端TABを削除してしまうため、少ない場合は埋める。 270 model.setValue( clmNo,"" ); 271 } 272 } 273 model.setRowNo( rowNo ) ; 274 275 if( display ) { println( model.dataLine() ); } 276 277 return model; 278 } 279 280 /** 281 * BufferedReader より、#NAME 行の項目名情報を読み取ります。 282 * データカラムより前に、項目名情報を示す "#Name" が存在する仮定で取り込みます。 283 * この行は、ファイルの形式に無関係に、TAB で区切られています。 284 * 285 * @param reader PrintWriterオブジェクト 286 * 287 * @return カラム名配列(存在しない場合は、サイズ0の配列) 288 */ 289 private String[] readName( final BufferedReader reader ) { 290 try { 291 // 4.0.0 (2005/01/31) line 変数名変更 292 String line1; 293 while((line1 = reader.readLine()) != null) { 294 inCount++ ; 295 if( line1.length() == 0 ) { continue; } 296 if( line1.charAt(0) == '#' ) { 297 String key = line1.substring( 0,5 ); 298 if( key.equalsIgnoreCase( "#NAME" ) ) { 299 // 超イレギュラー処理 最初の TAB 以前の文字は無視する。 300 String line2 = line1.substring( line1.indexOf( TAB )+1 ); 301 return StringUtil.csv2Array( line2 ,TAB.charAt(0) ); 302 } 303 else { continue; } 304 } 305 else { 306 String errMsg = "#NAME が見つかる前にデータが見つかりました。"; 307 throw new RuntimeException( errMsg ); 308 } 309 } 310 } 311 catch (IOException ex) { 312 String errMsg = "ファイル読込みエラー[" + reader.toString() + "]" ; 313 throw new RuntimeException( errMsg,ex ); 314 } 315 return new String[0]; 316 } 317 318 /** 319 * プロセスの処理結果のレポート表現を返します。 320 * 処理プログラム名、入力件数、出力件数などの情報です。 321 * この文字列をそのまま、標準出力に出すことで、結果レポートと出来るような 322 * 形式で出してください。 323 * 324 * @return 処理結果のレポート 325 */ 326 public String report() { 327 String report = "[" + getClass().getName() + "]" + CR 328 + TAB + "Input File : " + infile + CR 329 + TAB + "Input Count : " + inCount + CR 330 + TAB + "Output Count : " + outCount ; 331 332 return report ; 333 } 334 335 /** 336 * このクラスの使用方法を返します。 337 * 338 * @og.rev 5.2.2.0 (2010/11/01) useNumber属性のコメント追加 339 * 340 * @return このクラスの使用方法 341 */ 342 public String usage() { 343 StringBuilder buf = new StringBuilder(); 344 345 buf.append( "Process_TableReaderは、ファイルから読み取った内容を、LineModel に設定後、" ).append( CR ); 346 buf.append( "下流に渡す、FirstProcess インターフェースの実装クラスです。" ).append( CR ); 347 buf.append( CR ); 348 buf.append( "DBTableModel 形式のファイルを読み取って、各行を LineModel にセットして、" ).append( CR ); 349 buf.append( "下流(プロセスチェインのデータは上流から下流に渡されます。)に渡します。" ).append( CR ); 350 buf.append( CR ); 351 buf.append( "columns 属性は、#NAME で列カラムを外部から指定する場合に使用します。" ).append( CR ); 352 buf.append( "この属性とuseNumber属性は独立していますが、一般には、#NAME を指定" ).append( CR ); 353 buf.append( "する場合は、useNumber=\"true\"として、行番号欄は使用しますし、外部から" ).append( CR ); 354 buf.append( "指定する場合は、useNumber=\"false\"にして先頭から読み取ります。" ).append( CR ); 355 buf.append( "(自動セットではないので、必要に応じて設定してください)" ).append( CR ); 356 buf.append( "useNumber の初期値は、\"true\" です。" ).append( CR ); 357 buf.append( CR ); 358 buf.append( "引数文字列中に空白を含む場合は、ダブルコーテーション(\"\") で括って下さい。" ).append( CR ); 359 buf.append( "引数文字列の 『=』の前後には、空白は挟めません。必ず、-key=value の様に" ).append( CR ); 360 buf.append( "繋げてください。" ).append( CR ); 361 buf.append( CR ).append( CR ); 362 363 buf.append( getArgument().usage() ).append( CR ); 364 365 return buf.toString(); 366 } 367 368 /** 369 * このクラスは、main メソッドから実行できません。 370 * 371 * @param args コマンド引数配列 372 */ 373 public static void main( final String[] args ) { 374 LogWriter.log( new Process_TableReader().usage() ); 375 } 376}