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}