    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         * 初期処理(ファイルオープン、ＤＢオープン等)に使用します。
    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         * 終了処理(ファイルクローズ、ＤＢクローズ等)に使用します。
    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}