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