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.system.OgRuntimeException ; // 6.4.2.0 (2016/01/29) 019import org.opengion.fukurou.util.Argument; 020import org.opengion.fukurou.util.HybsEntry ; 021import org.opengion.fukurou.util.FileUtil; 022import org.opengion.fukurou.system.Closer ; 023import org.opengion.fukurou.system.LogWriter; 024import org.opengion.fukurou.model.Formatter; // 6.3.2.0 (2015/07/10) 025 026import java.util.Map ; 027import java.util.LinkedHashMap ; 028 029import java.io.File; 030import java.io.PrintWriter; 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 public LineModel action( final LineModel data ) { 216 count++ ; 217 // if( display ) { println( data.dataLine() ); } 218 if( firstRow ) { 219 writer = FileUtil.getPrintWriter( file,encode,append ); 220 if( useHeader && useNumber ) { writeName( data ); } 221 222 final int size = cnstClm.length; 223 cnstClmNos = new int[size]; 224 for( int i=0; i<size; i++ ) { 225 cnstClmNos[i] = data.getColumnNo( cnstClm[i] ); 226 } 227 228 // 6.3.2.0 (2015/07/10) 1行分のフォーマット(lineFormat属性)対応 229 if( lineFormat != null ) { 230 format = new Formatter( data,lineFormat ); // 6.4.3.4 (2016/03/11) 231 } 232 233 firstRow = false; 234 if( display ) { println( data.nameLine() ); } // 5.7.3.0 (2014/02/07) デバッグ情報 235 } 236 237 // 固定値置き換え処理 238 for( int j=0; j<cnstClmNos.length; j++ ) { 239 data.setValue( cnstClmNos[j],constVal[j] ); 240 } 241 242 // 6.3.2.0 (2015/07/10) 1行分のフォーマット(lineFormat属性)対応 243 if( lineFormat == null ) { 244 writeData( data ); 245 } 246 else { 247 final String fmtVal = format.getLineFormatString( data ); 248 writer.println( fmtVal ); 249 } 250 251 if( display ) { println( data.dataLine() ); } // 5.1.2.0 (2010/01/01) display の条件変更 252 return data; 253 } 254 255 /** 256 * PrintWriter に LineModelの項目名情報を書き込みます。 257 * 第一カラム目は、項目名情報を示す "#Name" を書き込みます。 258 * 259 * seHeader=true && useNumber=true の場合のみ、このメソッドが呼ばれます。 260 * 261 * @og.rev 6.0.4.0 (2014/11/28) #NAME 行の区切り文字は、指定の区切り文字を利用する。 262 * 263 * @param data ラインモデル 264 */ 265 private void writeName( final LineModel data ) { 266 final int size = data.size(); 267 writer.print( "#Name" ); 268 for( int clm=0; clm<size; clm++ ) { 269 writer.print( separator ); // 6.0.4.0 (2014/11/28) #NAME 行の区切り文字 270 writer.print( data.getName(clm) ); 271 } 272 writer.println(); 273 } 274 275 /** 276 * PrintWriter に LineModelのテーブル情報を書き込みます。 277 * 278 * @og.rev 5.2.2.0 (2010/11/01) 改行を含む場合は、ダブルクオートを強制的に前後に追加する。 279 * @og.rev 5.2.2.0 (2010/11/01) ダブルクオートを含む場合は、その直前にダブルクオートを強制的に追加する。 280 * @og.rev 5.9.10.3 (2016/07/15) ダブルクオートを重ねるかどうかの判定useDataWquot追加 281 * 282 * @param data ラインモデル 283 */ 284 private void writeData( final LineModel data ) { 285 final int size = data.size(); 286 287 if( useNumber ) { writer.print( data.getRowNo() ); } // 行番号 288 for( int clm=0; clm<size; clm++ ) { 289 if( useNumber || clm!=0 ) { writer.print( separator ); } 290 Object val = data.getValue(clm); 291 if( val == null ) { val = ""; } 292 293 String sval = String.valueOf( val ); 294 // 5.2.2.0 (2010/11/01) ダブルクオートを含む場合は、その直前にダブルクオートを強制的に追加する。 295 if( useDataWquot && sval.indexOf( '"' ) >= 0 ) { sval = sval.replaceAll( "\"" ,"\"\"" ) ; } // 5.9.10.3 296 if( omitCTRL ) { sval = sval.replaceAll( "\\s" ," " ) ; } 297 // 5.2.2.0 (2010/11/01) 改行を含む場合は、ダブルクオートを強制的に前後に追加する。 298 if( !omitCTRL && sval.indexOf( CR ) >= 0 || useWquot ) { // 6.9.7.0 (2018/05/14) PMD Useless parentheses. 299 sval = "\"" + sval + "\"" ; 300 } 301 writer.print( sval ); 302 } 303 writer.println(); 304 } 305 306 /** 307 * プロセスの処理結果のレポート表現を返します。 308 * 処理プログラム名、入力件数、出力件数などの情報です。 309 * この文字列をそのまま、標準出力に出すことで、結果レポートと出来るような 310 * 形式で出してください。 311 * 312 * @return 処理結果のレポート 313 */ 314 public String report() { 315 final String report = "[" + getClass().getName() + "]" + CR 316 + TAB + "Output File : " + outfile + CR 317 + TAB + "Output Count : " + count ; 318 319 return report ; 320 } 321 322 /** 323 * このクラスの使用方法を返します。 324 * 325 * @return このクラスの使用方法 326 * @og.rtnNotNull 327 */ 328 public String usage() { 329 final StringBuilder buf = new StringBuilder( BUFFER_LARGE ) 330 .append( "Process_TableWriter は、上流から受け取ったデータをファイルに書き込む" ).append( CR ) 331 .append( "CainProcess インターフェースの実装クラスです。" ).append( CR ) 332 .append( CR ) 333 .append( "上流(プロセスチェインのデータは上流から下流へと渡されます。)から" ).append( CR ) 334 .append( "受け取ったLineModel を元に、DBTableModel 形式ファイルを出力します。" ).append( CR ) 335 .append( CR ) 336 .append( "引数文字列中に空白を含む場合は、ダブルコーテーション(\"\") で括って下さい。" ).append( CR ) 337 .append( "引数文字列の 『=』の前後には、空白は挟めません。必ず、-key=value の様に" ).append( CR ) 338 .append( "繋げてください。" ).append( CR ) 339 .append( CR ).append( CR ) 340 .append( getArgument().usage() ).append( CR ); 341 342 return buf.toString(); 343 } 344 345 /** 346 * このクラスは、main メソッドから実行できません。 347 * 348 * @param args コマンド引数配列 349 */ 350 public static void main( final String[] args ) { 351 LogWriter.log( new Process_TableWriter().usage() ); 352 } 353}