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