    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         * 初期処理(ファイルオープン、ＤＢオープン等)に使用します。
    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         * 終了処理(ファイルクローズ、ＤＢクローズ等)に使用します。
    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                                sval = "\"" + sval + "\"" ;
    301                        }
    302                        writer.print( sval );
    303                }
    304                writer.println();
    305        }
    306
    307        /**
    308         * プロセスの処理結果のレポート表現を返します。
    309         * 処理プログラム名、入力件数、出力件数などの情報です。
    310         * この文字列をそのまま、標準出力に出すことで、結果レポートと出来るような
    311         * 形式で出してください。
    312         *
    313         * @return   処理結果のレポート
    314         */
    315        public String report() {
    316                // 7.2.9.5 (2020/11/28) PMD:Consider simply returning the value vs storing it in local variable 'XXXX'
    317                return "[" + getClass().getName() + "]" + CR
    318//              final String report = "[" + getClass().getName() + "]" + CR
    319                                + TAB + "Output File  : " + outfile + CR
    320                                + TAB + "Output Count : " + count ;
    321
    322//              return report ;
    323        }
    324
    325        /**
    326         * このクラスの使用方法を返します。
    327         *
    328         * @return      このクラスの使用方法
    329         * @og.rtnNotNull
    330         */
    331        public String usage() {
    332                final StringBuilder buf = new StringBuilder( BUFFER_LARGE )
    333                .append( "Process_TableWriter は、上流から受け取ったデータをファイルに書き込む"                 ).append( CR )
    334                .append( "CainProcess インターフェースの実装クラスです。"                                                                ).append( CR )
    335                .append( CR )
    336                .append( "上流(プロセスチェインのデータは上流から下流へと渡されます。)から"                            ).append( CR )
    337                .append( "受け取ったLineModel を元に、DBTableModel 形式ファイルを出力します。"                        ).append( CR )
    338                .append( CR )
    339                .append( "引数文字列中に空白を含む場合は、ダブルコーテーション(\"\") で括って下さい。"    ).append( CR )
    340                .append( "引数文字列の 『=』の前後には、空白は挟めません。必ず、-key=value の様に"           ).append( CR )
    341                .append( "繋げてください。"                                                                                                                             ).append( CR )
    342                .append( CR ).append( CR )
    343                .append( getArgument().usage() ).append( CR );
    344
    345                return buf.toString();
    346        }
    347
    348        /**
    349         * このクラスは、main メソッドから実行できません。
    350         *
    351         * @param       args    コマンド引数配列
    352         */
    353        public static void main( final String[] args ) {
    354                LogWriter.log( new Process_TableWriter().usage() );
    355        }
    356}