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.HybsEntry ; 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.PrintWriter; 029 030/** 031 * Process_TableWriter は、上流から受け取ったデータをファイルに書き込む 032 * CainProcess インターフェースの実装クラスです。 033 * 034 * 上流(プロセスチェインのデータは上流から下流へと渡されます。)から 035 * 受け取ったLineModel を元に、DBTableModel 形式ファイルを出力します。 036 * 037 * 引数文字列中にスペースを含む場合は、ダブルコーテーション("") で括って下さい。 038 * 引数文字列の 『=』の前後には、スペースは挟めません。必ず、-key=value の様に 039 * 繋げてください。 040 * 041 * @og.formSample 042 * Process_TableWriter -outfile=OUTFILE -sep=, -encode=UTF-8 -append=true 043 * 044 * -outfile=出力ファイル名 :出力ファイル名 045 * [-sep=セパレータ文字 ] :区切り文字(初期値:タブ) 046 * [-encode=文字エンコード ] :出力ファイルのエンコードタイプ 047 * [-append=[false/true] ] :出力ファイルを、追記する(true)か新規作成する(false)か。 048 * [-useHeader=[true/false] ] :ヘッダー情報(#NAME行)を出力する(true)か出力しない(false)か。 049 * [-useNumber=[true/false] ] :行番号を出力する(true)か出力しない(false)か。 050 * [-useWquot=[false/true] ] :出力データをダブルクオーテーションで括る(true)かそのまま(false)か。 051 * [-useDataWquot=[false/true]]:出力データ上のダブルクオーテーションを2重にする(true)かそのまま(false)か。 052 * [-omitCTRL=[false/true] ] :コントロール文字を削除する(true)かそのまま(false)か。 053 * [-const_XXXX=固定値 ] :-const_FGJ=1 054 * LineModel のキー(const_ に続く文字列)の値に、固定値を設定します。 055 * キーが異なれば、複数のカラム名を指定できます。 056 * [-display=[false/true] ] :結果を標準出力に表示する(true)かしない(false)か(初期値:false[表示しない]) 057 * [-debug=[false/true] ] :デバッグ情報を標準出力に表示する(true)かしない(false)か(初期値:false[表示しない]) 058 * 059 * @version 4.0 060 * @author Kazuhiko Hasegawa 061 * @since JDK5.0, 062 */ 063public class Process_TableWriter extends AbstractProcess implements ChainProcess { 064 private static final String CNST_KEY = "const_" ; 065 066 private String outfile = null; 067 private PrintWriter writer = null; 068 private String separator = TAB; // 項目区切り文字 069 070 private String[] cnstClm = null; // 固定値を設定するカラム名 071 private int[] cnstClmNos = null; // 固定値を設定するのカラム番号 072 private String[] constVal = null; // カラム番号に対応した固定値 073 private File file = null; // 出力ファイル 074 private String encode = System.getProperty("file.encoding"); // 出力ファイルエンコード 075 private boolean append = false; // ファイル追加(true:追加/false:通常) 076 private boolean useHeader = true; // ヘッダー情報(#NAME行)を出力する(true)か出力しない(false)か。 077 private boolean useNumber = true; // 行番号を出力する(true)か出力しない(false)か。 078 private boolean useWquot = false; // 出力データをダブルクオーテーションで括る(true)かそのまま(false)か。 079 private boolean useDataWquot = true; // データ上のダブルクオーテーションを重ねる(true)かそのまま(false)か。 080 private boolean omitCTRL = false; // コントロール文字を削除する(true)かそのまま(false)か。 081 private boolean display = false; // 表示しない 082 private boolean debug = false; // 5.7.3.0 (2014/02/07) デバッグ情報 083 084 private boolean firstRow = true; // 最初の一行目 085 private int count = 0; 086 087 private static final Map<String,String> mustProparty ; // [プロパティ]必須チェック用 Map 088 private static final Map<String,String> usableProparty ; // [プロパティ]整合性チェック Map 089 090 static { 091 mustProparty = new LinkedHashMap<String,String>(); 092 mustProparty.put( "outfile", "出力ファイル名 (必須)" ); 093 094 usableProparty = new LinkedHashMap<String,String>(); 095 usableProparty.put( "sep", "区切り文字(初期値:タブ)" ); 096 usableProparty.put( "encode", "出力ファイルのエンコードタイプ" ); 097 usableProparty.put( "append", "出力ファイルを、追記する(true)か新規作成する(false)か。" ); 098 usableProparty.put( "useHeader", "ヘッダー情報(#NAME行)を出力する(true)か出力しない(false)か。" ); 099 usableProparty.put( "useNumber", "行番号を出力する(true)か出力しない(false)か。" ); 100 usableProparty.put( "useWquot", "出力データをダブルクオーテーションで括る(true)かそのまま(false)か。" ); 101 usableProparty.put( "useDataWquot", "出力データ中のダブルクオーテーションを重ねる(true)かそのまま(false)か。" ); 102 usableProparty.put( "omitCTRL", "コントロール文字を削除する(true)かそのまま(false)か。" ); 103 usableProparty.put( "const_", "LineModel のキー(const_ に続く文字列)の値に、固定値を" + 104 CR + "設定します。キーが異なれば、複数のカラム名を指定できます。" + 105 CR + "例: -const_FGJ=1" ); 106 usableProparty.put( "display", "結果を標準出力に表示する(true)かしない(false)か" + 107 CR + " (初期値:false:表示しない)" ); 108 usableProparty.put( "debug", "デバッグ情報を標準出力に表示する(true)かしない(false)か" + 109 CR + "(初期値:false:表示しない)" ); // 5.7.3.0 (2014/02/07) デバッグ情報 110 } 111 112 /** 113 * デフォルトコンストラクター。 114 * このクラスは、動的作成されます。デフォルトコンストラクターで、 115 * super クラスに対して、必要な初期化を行っておきます。 116 * 117 */ 118 public Process_TableWriter() { 119 super( "org.opengion.fukurou.process.Process_TableWriter",mustProparty,usableProparty ); 120 } 121 122 /** 123 * プロセスの初期化を行います。初めに一度だけ、呼び出されます。 124 * 初期処理(ファイルオープン、DBオープン等)に使用します。 125 * 126 * @og.rev 5.9.10.3 (2016/07/15) useDatWquot 127 * 128 * @param paramProcess データベースの接続先情報などを持っているオブジェクト 129 */ 130 public void init( final ParamProcess paramProcess ) { 131 Argument arg = getArgument(); 132 133 outfile = arg.getProparty("outfile"); 134 encode = arg.getProparty("encode",encode); 135 separator = arg.getProparty("sep",separator ); 136 append = arg.getProparty("append",append); 137 useHeader = arg.getProparty("useHeader",useHeader); 138 useNumber = arg.getProparty("useNumber",useNumber); 139 useWquot = arg.getProparty("useWquot",useWquot); 140 useDataWquot = arg.getProparty("useDataWquot",useDataWquot); 141 omitCTRL = arg.getProparty("omitCTRL",omitCTRL); 142 HybsEntry[] cnstKey = arg.getEntrys( CNST_KEY ); // 配列 143 display = arg.getProparty("display",display); 144 debug = arg.getProparty("debug",debug); // 5.7.3.0 (2014/02/07) デバッグ情報 145// if( debug ) { println( arg.toString() ); } // 5.7.3.0 (2014/02/07) デバッグ情報 146 147 int size = cnstKey.length; 148 cnstClm = new String[size]; 149 constVal = new String[size]; 150 for( int i=0; i<size; i++ ) { 151 cnstClm[i] = cnstKey[i].getKey(); 152 constVal[i] = cnstKey[i].getValue(); 153 } 154 155 if( outfile == null ) { 156 String errMsg = "ファイル名が指定されていません。" ; 157 throw new RuntimeException( errMsg ); 158 } 159 160 file = new File( outfile ); 161 File dir = file.getParentFile() ; 162 163 // ディレクトリが存在しない場合の処理 164 if( ! dir.exists() && ! dir.mkdirs() ) { 165 String errMsg = "ディレクトリが作成できませんでした。[" + dir + "]" ; 166 throw new RuntimeException( errMsg ); 167 } 168 } 169 170 /** 171 * プロセスの終了を行います。最後に一度だけ、呼び出されます。 172 * 終了処理(ファイルクローズ、DBクローズ等)に使用します。 173 * 174 * @param isOK トータルで、OKだったかどうか[true:成功/false:失敗] 175 */ 176 public void end( final boolean isOK ) { 177 if( writer != null ) { 178 writer.flush(); 179 Closer.ioClose( writer ); 180 writer = null; 181 } 182 } 183 184 /** 185 * 引数の LineModel を処理するメソッドです。 186 * 変換処理後の LineModel を返します。 187 * 後続処理を行わない場合(データのフィルタリングを行う場合)は、 188 * null データを返します。つまり、null データは、後続処理を行わない 189 * フラグの代わりにも使用しています。 190 * なお、変換処理後の LineModel と、オリジナルの LineModel が、 191 * 同一か、コピー(クローン)かは、各処理メソッド内で決めています。 192 * ドキュメントに明記されていない場合は、副作用が問題になる場合は、 193 * 各処理ごとに自分でコピー(クローン)して下さい。 194 * 195 * @param data オリジナルのLineModel 196 * 197 * @return 処理変換後のLineModel 198 */ 199 public LineModel action( final LineModel data ) { 200 count++ ; 201// if( display ) { println( data.dataLine() ); } 202 if( firstRow ) { 203 writer = FileUtil.getPrintWriter( file,encode,append ); 204 if( useHeader && useNumber ) { writeName( data ); } 205 206 int size = cnstClm.length; 207 cnstClmNos = new int[size]; 208 for( int i=0; i<size; i++ ) { 209 cnstClmNos[i] = data.getColumnNo( cnstClm[i] ); 210 } 211 212 firstRow = false; 213 if( display ) { println( data.nameLine() ); } // 5.7.3.0 (2014/02/07) デバッグ情報 214 } 215 216 // 固定値置き換え処理 217 for( int j=0; j<cnstClmNos.length; j++ ) { 218 data.setValue( cnstClmNos[j],constVal[j] ); 219 } 220 221 writeData( data ); 222 223 if( display ) { println( data.dataLine() ); } // 5.1.2.0 (2010/01/01) display の条件変更 224 return data; 225 } 226 227 /** 228 * PrintWriter に LineModelの項目名情報を書き込みます。 229 * 第一カラム目は、項目名情報を示す "#Name" を書き込みます。 230 * この行は、出力形式に無関係に、TAB で区切られます。 231 * 232 * @param data ラインモデル 233 */ 234 private void writeName( final LineModel data ) { 235 int size = data.size(); 236 writer.print( "#Name" ); 237 for( int clm=0; clm<size; clm++ ) { 238 writer.print( TAB ); 239 writer.print( data.getName(clm) ); 240 } 241 writer.println(); 242 } 243 244 /** 245 * PrintWriter に LineModelのテーブル情報を書き込みます。 246 * 247 * @og.rev 5.2.2.0 (2010/11/01) 改行を含む場合は、ダブルクオートを強制的に前後に追加する。 248 * @og.rev 5.2.2.0 (2010/11/01) ダブルクオートを含む場合は、その直前にダブルクオートを強制的に追加する。 249 * @og.rev 5.9.10.3 (2016/07/15) ダブルクオートを重ねるかどうかの判定useDataWquot追加 250 * 251 * @param data ラインモデル 252 */ 253 private void writeData( final LineModel data ) { 254 int size = data.size(); 255 256 if( useNumber ) { writer.print( data.getRowNo() ); } // 行番号 257 for( int clm=0; clm<size; clm++ ) { 258 if( useNumber || clm!=0 ) { writer.print( separator ); } 259 Object val = data.getValue(clm); 260 if( val == null ) { val = ""; } 261 262 String sval = String.valueOf( val ); 263 // 5.2.2.0 (2010/11/01) ダブルクオートを含む場合は、その直前にダブルクオートを強制的に追加する。 264// if( sval.indexOf( '"' ) >= 0 ) { sval = sval.replaceAll( "\"" ,"\"\"" ) ; } 265 if( useDataWquot && sval.indexOf( '"' ) >= 0 ) { sval = sval.replaceAll( "\"" ,"\"\"" ) ; } // 5.9.10.3 266 if( omitCTRL ) { sval = sval.replaceAll( "\\s" ," " ) ; } 267// if( useWquot ) { sval = "\"" + sval + "\"" ; } 268 // 5.2.2.0 (2010/11/01) 改行を含む場合は、ダブルクオートを強制的に前後に追加する。 269 if( useWquot || ( !omitCTRL && sval.indexOf( CR ) >= 0 ) ) { 270 sval = "\"" + sval + "\"" ; 271 } 272 writer.print( sval ); 273 } 274 writer.println(); 275 } 276 277 /** 278 * プロセスの処理結果のレポート表現を返します。 279 * 処理プログラム名、入力件数、出力件数などの情報です。 280 * この文字列をそのまま、標準出力に出すことで、結果レポートと出来るような 281 * 形式で出してください。 282 * 283 * @return 処理結果のレポート 284 */ 285 public String report() { 286 String report = "[" + getClass().getName() + "]" + CR 287 + TAB + "Output File : " + outfile + CR 288 + TAB + "Output Count : " + count ; 289 290 return report ; 291 } 292 293 /** 294 * このクラスの使用方法を返します。 295 * 296 * @return このクラスの使用方法 297 */ 298 public String usage() { 299 StringBuilder buf = new StringBuilder(); 300 301 buf.append( "Process_TableWriter は、上流から受け取ったデータをファイルに書き込む" ).append( CR ); 302 buf.append( "CainProcess インターフェースの実装クラスです。" ).append( CR ); 303 buf.append( CR ); 304 buf.append( "上流(プロセスチェインのデータは上流から下流へと渡されます。)から" ).append( CR ); 305 buf.append( "受け取ったLineModel を元に、DBTableModel 形式ファイルを出力します。" ).append( CR ); 306 buf.append( CR ); 307 buf.append( "引数文字列中に空白を含む場合は、ダブルコーテーション(\"\") で括って下さい。" ).append( CR ); 308 buf.append( "引数文字列の 『=』の前後には、空白は挟めません。必ず、-key=value の様に" ).append( CR ); 309 buf.append( "繋げてください。" ).append( CR ); 310 buf.append( CR ).append( CR ); 311 312 buf.append( getArgument().usage() ).append( CR ); 313 314 return buf.toString(); 315 } 316 317 /** 318 * このクラスは、main メソッドから実行できません。 319 * 320 * @param args コマンド引数配列 321 */ 322 public static void main( final String[] args ) { 323 LogWriter.log( new Process_TableWriter().usage() ); 324 } 325}