    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.system.OgCharacterException;                                                // 6.5.0.1 (2016/10/21)
    020import org.opengion.fukurou.system.Closer;
    021import org.opengion.fukurou.system.LogWriter;
    022import org.opengion.fukurou.util.Argument;
    023import org.opengion.fukurou.util.FileUtil;
    024import org.opengion.fukurou.util.StringUtil;
    025import org.opengion.fukurou.util.CommentLineParser;                                                             // 6.3.1.1 (2015/07/10)
    026import org.opengion.fukurou.util.FileInfo;                                                                              // 6.4.0.2 (2015/12/11)
    027import org.opengion.hayabusa.common.HybsSystem;                                                                 // 8.1.3.0 (2022/06/03)
    028
    029import java.util.Arrays;
    030import java.util.Enumeration;                                                                                                   // 8.1.3.0 (2022/06/03)
    031import java.util.jar.JarFile;                                                                                                   // 8.1.3.0 (2022/06/03)
    032import java.util.jar.JarEntry;                                                                                                  // 8.1.3.0 (2022/06/03)
    033import java.util.LinkedHashMap;
    034import java.util.Locale;                                                                                                                // 8.1.3.0 (2022/06/03)
    035import java.util.Map;
    036import java.util.regex.Pattern;
    037import java.util.regex.Matcher;
    038
    039import java.io.File;
    040import java.io.PrintWriter;
    041import java.io.BufferedReader;
    042import java.io.IOException;
    043import java.io.InputStreamReader;                                                                                               // 8.1.3.0 (2022/06/03)
    044import java.io.InputStream;                                                                                                             // 8.1.3.0 (2022/06/03)
    045import java.nio.charset.CharacterCodingException;                                                               // 6.3.1.0 (2015/06/28)
    046
    047/**
    048 * Process_Grep は、上流から受け取った FileLineModelから、文字列を見つけ出す
    049 * ChainProcess ｲﾝﾀｰﾌｪｰｽの実装ｸﾗｽです｡
    050 *
    051 * 正規表現の keyword を上流から受け取った FileLineModel から検索します｡
    052 * 見つかった対象ﾌｧｲﾙから、指定の文字列を置換する場合は、-change か
    053 * -changeFile で、keyword を置換する文字列を指定して下さい｡
    054 * 置換する文字列には、\t と \n の特殊文字が使用できます｡
    055 *
    056 * 処理対象は、通常は、１行づつ読み取りながら処理を行います｡存在ﾁｪｯｸの場合は、
    057 * 見つかった時点で処理を中止します｡これは、該当箇所をﾋﾟｯｸｱｯﾌﾟするのではなく、
    058 * 存在しているかどうかを判断して、あれば、下流に流すというのが目的だからです｡
    059 * keyword を、改行を含む正規表現で、検索･置換する場合は、-useBulkRead 属性を
    060 * true に設定してください｡これは、入力ﾌｧｲﾙを一括して読み込みます｡
    061 * -ignoreCase は、検索時にｷｰの大文字小文字を無視するように指定します｡
    062 * -notEquals は、結果(見つかればtrue)を反転(見つからなければtrue)します｡
    063 * これは、行単位ではなく、ﾌｧｲﾙ単位に判定しますので、change 指定した場合
    064 * でも、対象行は、見つかった行です｡ただし、下流に対して、見つからない
    065 * 場合だけ処理を継続させます｡
    066 * -inEncode は、入力ﾌｧｲﾙのｴﾝｺｰﾄﾞ指定になります｡
    067 * -outEncode は、出力ﾌｧｲﾙのｴﾝｺｰﾄﾞや、changeFileで指定の置換文字列ﾌｧｲﾙの
    068 * ｴﾝｺｰﾄﾞ指定になります｡(changeFile は、必ず 出力ﾌｧｲﾙと同じｴﾝｺｰﾄﾞです｡)
    069 * これらのｴﾝｺｰﾄﾞが無指定の場合は、System.getProperty("file.encoding") で
    070 * 求まる値を使用します｡
    071 * -changeFile を使用することで、複数行の文字列に置換することが可能です｡
    072 * -outfile では、処理を行ったﾌｧｲﾙ名一覧をｾｰﾌﾞします｡
    073 *
    074 * 上流(ﾌﾟﾛｾｽﾁｪｲﾝのﾃﾞｰﾀは上流から渡されます｡)からのLineModel の
    075 * ﾌｧｲﾙｵﾌﾞｼﾞｪｸﾄより、指定の文字列が含まれているか検索します｡
    076 * 上流ﾌﾟﾛｾｽでは、Name 属性として、『File』を持ち、値は、Fileｵﾌﾞｼﾞｪｸﾄ
    077 * である、Process_FileSearch を使用するのが、便利です｡それ以外のｸﾗｽを
    078 * 使用する場合でも、Name属性と、File ｵﾌﾞｼﾞｪｸﾄを持つ LineModel を受け渡し
    079 * できれば、使用可能です｡
    080 *
    081 * ※ 6.3.1.1 (2015/07/10) useOmitCmnt、useAllFind 機能追加
    082 *
    083 * 引数文字列中に空白を含む場合は、ﾀﾞﾌﾞﾙｺｰﾃｰｼｮﾝ("") で括って下さい｡
    084 * 引数文字列の 『=』の前後には、空白は挟めません｡必ず、-key=value の様に
    085 * 繋げてください｡
    086 *
    087 * ※ 8.1.3.0 (2022/06/03) jarPrefix、jarSuffix、jarInstr、useRegexp、saveFile 追加
    088 *    jar ﾌｧｲﾙの中身も検索します｡その際、jarﾌｧｲﾙに圧縮されているﾌｧｲﾙ名での
    089 *    絞り込みができるように、指定できる属性を追加します｡
    090 *    ただし、jarﾌｧｲﾙ内の検索は、useAllFind=true(置換ではなく検索だけ最後まで行う)のみです｡
    091 *    上流から jar ﾌｧｲﾙが指定された場合は、常に検索対象になります｡
    092 *
    093 * @og.formSample
    094 *  Process_Grep -keyword=検索文字列 -ignoreCase=true -outfile=OUTFILE -encode=UTF-8
    095 *
    096 *    -keyword=ｷｰﾜｰﾄﾞ        ：検索する語句
    097 *   [-ignoreCase=大文字小文字 ] ：検索時に大文字小文字を区別しない(true)かどうか(初期値:区別する[false])
    098 *   [-notEquals=判定結果の反転] ：判定結果を反転させる(true)かどうか(初期値:反転させない[false])
    099 *   [-inEncode=入力ｴﾝｺｰﾄﾞ     ] ：入力ﾌｧｲﾙのｴﾝｺｰﾄﾞﾀｲﾌﾟ
    100 *   [-outEncode=出力ｴﾝｺｰﾄﾞ    ] ：出力ﾌｧｲﾙや置換ﾌｧｲﾙのｴﾝｺｰﾄﾞﾀｲﾌﾟ
    101 *   [-change=置換文字列       ] ：-change="ABCD" \t や \n などの特殊文字が使用できます｡
    102 *   [-changeFile=置換ﾌｧｲﾙ     ] ：-changeFile=change.txt このﾌｧｲﾙの記述すべてと置換します｡
    103 *                                     -change と、-changeFile は、同時に指定できません｡
    104 *                                     置換機能使用時は、必ず、_backup というﾌｧｲﾙが作成されます｡
    105 *   [-insert=[HEAD/CHANGE/BEFORE/AFTER/TAIL]   ]
    106 *                               : 置換でなく挿入する場合の位置を指定します(初期値:CHANGE)
    107 *                                 ｽﾍﾟｰｽで区切って数字を記述すると、挿入位置にｵﾌｾｯﾄできます｡
    108 *   [-delete=[false/true]     ] : 置換でなく削除します(初期値:false)
    109 *   [-skipRowCount=ｽｷｯﾌﾟ行数  ] : 先頭行から、ｽｷｯﾌﾟする行数を指定します(useBulkRead時には使用されません)
    110 *   [-useBackup=[false/true]  ] ：trueは、backupﾌｧｲﾙを作成します(初期値:false)
    111 *   [-useBulkRead=[false/true]] ：trueは、入力ﾌｧｲﾙを一括読込します(初期値:false)
    112 *   [-useAllFind=[false/true] ] ：置換ではなく検索だけ最後まで行う場合、trueを指定します(初期値:false)
    113 *   [-useOmitCmnt=[false/true]] ：ｺﾒﾝﾄ部分を削除したﾌｧｲﾙでgrep処理を行うかどうかを指定(初期値:false)
    114 *   [-errAbend=[true/false]   ] ：異常発生時に、処理を中断(true)するか、継続(false)するかを指定する(初期値:true[中断する])
    115 *   [-jarPrefix=接頭辞        ] ：File････,View････,など、指定の接頭辞で始まるjarﾌｧｲﾙを中を検索 8.1.3.0 (2022/06/03)
    116 *   [-jarSuffix=接尾辞        ] ：.txt|.java|.jsp.... など、指定の接尾辞で終わるjarﾌｧｲﾙを中を検索 8.1.3.0 (2022/06/03)
    117 *   [-jarInstr=部分文字列     ] ：jarﾌｧｲﾙを中と一致する部分文字列を指定 8.1.3.0 (2022/06/03)
    118 *   [-useRegexp=[false/true]  ] ：trueは、正規表現で検索します(初期値:false) 8.1.3.0 (2022/06/03)
    119 *   [-saveFile=保存ﾌｧｲﾙ       ] ：検索結果を指定ﾌｧｲﾙに保存します 8.1.3.0 (2022/06/03)
    120 *   [-display=[false/true]    ] ：trueは、検索状況を表示します(初期値:false)
    121 *   [-debug=[false/true]      ] ：ﾃﾞﾊﾞｯｸﾞ情報を標準出力に表示する(true)かしない(false)か(初期値:false[表示しない])
    122 *
    123 * @version  4.0
    124 * @author   Kazuhiko Hasegawa
    125 * @since    JDK5.0,
    126 */
    127public class Process_Grep extends AbstractProcess implements ChainProcess {
    128        private static final String[] INSERT_LIST = { "HEAD","CHANGE","BEFORE","AFTER","TAIL" };        // 6.2.4.0 (2015/05/15)
    129        private static final String ENCODE              = "UTF-8";                                                                                      // 8.1.3.0 (2022/06/03)
    130        private static final String AUTO_ENCODE = "autoEncode";                                                                         // 8.1.3.0 (2022/06/03)
    131
    132        /** 8.1.3.0 (2022/06/03) 拡張子をｴﾝｺｰﾄﾞに変換するMap */
    133        private static final Map<String,String> EXT2ENC = Map.ofEntries(
    134                Map.entry("bat"  , "Windows-31J"),      Map.entry("ken"  , "Windows-31J"),
    135                Map.entry("sql"  , "Windows-31J"),      Map.entry("vbs"  , "Windows-31J"),
    136                Map.entry("css"  , "UTF-8"),            Map.entry("html" , "UTF-8"),
    137                Map.entry("java" , "UTF-8"),            Map.entry("js"   , "UTF-8"),
    138                Map.entry("jsp"  , "UTF-8"),            Map.entry("xml"  , "UTF-8"),
    139                Map.entry("jar"  , "UTF-8")                                                                                             // 除外されない為に追記
    140        );
    141
    142        private Pattern pattern;
    143        private String  keyword;
    144        private boolean ignoreCase;
    145        private boolean notEquals;
    146        private String  inEncode;
    147        private String  outEncode;
    148        private String  change;
    149        private String  insert          = "CHANGE";                                                                             // "HEAD","CHANGE","BEFORE","AFTER","TAIL" のどれか
    150        private int             insOffset;                                                                                                      // "BEFORE","AFTER" 時のｵﾌｾｯﾄ
    151        private boolean useBackup;
    152        private boolean useBulkRead;                                                                                            // 4.0.1.0 (2007/12/14) 一括読込
    153        private boolean delete;
    154        private boolean useAllFind;                                                                                                     // 6.3.1.1 (2015/07/10) 最後まで検索
    155        private boolean useOmitCmnt;                                                                                            // 6.3.1.1 (2015/07/10) ｺﾒﾝﾄ除外
    156        private boolean errAbend        = true;                                                                                 // 6.3.1.0 (2015/06/28) 中断する
    157        private String  jarPrefix;                                                                                                      // 8.1.3.0 (2022/06/03)
    158        private String  jarSuffix;                                                                                                      // 8.1.3.0 (2022/06/03)
    159        private String  jarInstr;                                                                                                       // 8.1.3.0 (2022/06/03)
    160        private boolean useRegexp;                                                                                                      // 8.1.3.0 (2022/06/03)
    161        private String  saveFile;                                                                                                       // 8.1.3.0 (2022/06/03)
    162        private boolean display;
    163        private boolean debug;                                                                                                          // 5.1.2.0 (2010/01/01)
    164
    165        private int             inCount;
    166        private int             findCount;
    167        private int             cngCount;
    168        private int             skipRowCount;                                                                                           // 6.2.4.0 (2015/05/15) 行ｽｷｯﾌﾟ
    169        private PrintWriter             outWriter;                                                                                      // 8.1.3.0 (2022/06/03) PrintWriterｵﾌﾞｼﾞｪｸﾄ
    170        private final String    fileURL = HybsSystem.sys( "FILE_URL" );                         // 8.1.3.0 (2022/06/03) ﾌｧｲﾙURL
    171
    172
    173        /** staticｲﾆｼｬﾗｲｻﾞ後、読み取り専用にするので、ConcurrentHashMap を使用しません｡ */
    174        private static final Map<String,String> MUST_PROPARTY   ;               // ［ﾌﾟﾛﾊﾟﾃｨ］必須ﾁｪｯｸ用 Map
    175        /** staticｲﾆｼｬﾗｲｻﾞ後、読み取り専用にするので、ConcurrentHashMap を使用しません｡ */
    176        private static final Map<String,String> USABLE_PROPARTY ;               // ［ﾌﾟﾛﾊﾟﾃｨ］整合性ﾁｪｯｸ Map
    177
    178        static {
    179                MUST_PROPARTY = new LinkedHashMap<>();
    180                MUST_PROPARTY.put( "keyword",   "検索する語句(必須)" );
    181
    182                USABLE_PROPARTY = new LinkedHashMap<>();
    183                USABLE_PROPARTY.put( "ignoreCase",      "検索時に大文字小文字を区別しない(true)かどうか｡" +
    184                                                                                CR + "(初期値:区別する[false])" );
    185                USABLE_PROPARTY.put( "notEquals",       "検索時に判定結果を反転させる(true)かどうか｡" +
    186                                                                                CR + "(初期値:反転させない[false])" );
    187                USABLE_PROPARTY.put( "inEncode",        "入力ﾌｧｲﾙのｴﾝｺｰﾄﾞﾀｲﾌﾟ" );
    188                USABLE_PROPARTY.put( "outEncode",       "出力ﾌｧｲﾙや置換ﾌｧｲﾙのｴﾝｺｰﾄﾞﾀｲﾌﾟ" );
    189                USABLE_PROPARTY.put( "change",          "置換文字列 例: -change=\"ABCD\" \\t や \\n などの特殊文字が使用できます｡" );
    190                USABLE_PROPARTY.put( "changeFile",      "置換文字列ﾌｧｲﾙ 例: -changeFile=change.txt" +
    191                                                                                CR + "-change と、-changeFile は、同時に指定できません｡" +
    192                                                                                CR + "置換機能使用時は、必ず、_backup というﾌｧｲﾙが作成されます｡" );
    193                USABLE_PROPARTY.put( "insert",          "[HEAD/CHANGE/BEFORE/AFTER/TAIL]:置換でなく挿入する場合の位置を指定します(初期値:CHANGE)"  +
    194                                                                                CR + "ｽﾍﾟｰｽで区切って数字を記述すると、挿入位置にｵﾌｾｯﾄできます｡" );
    195                USABLE_PROPARTY.put( "delete",          "[false/true]:trueは、置換でなく削除します(初期値:false)" );
    196                USABLE_PROPARTY.put( "skipRowCount","先頭行から、ｽｷｯﾌﾟする行数を指定します｡" );                                                                 // 6.2.4.0 (2015/05/15)
    197                USABLE_PROPARTY.put( "useBackup",       "[false/true]:trueは、backupﾌｧｲﾙを作成します(初期値:false)" );
    198                USABLE_PROPARTY.put( "useBulkRead",     "[false/true]:trueは、入力ﾌｧｲﾙを一括読込します(初期値:false)" );
    199                USABLE_PROPARTY.put( "useAllFind",      "置換ではなく検索だけ最後まで行う場合、trueを指定します(初期値:false)" );   // 6.3.1.1 (2015/07/10)
    200                USABLE_PROPARTY.put( "useOmitCmnt",     "ｺﾒﾝﾄ部分を削除したﾌｧｲﾙでgrep処理を行うかどうかを指定(初期値:false)" );                // 6.3.1.1 (2015/07/10)
    201                USABLE_PROPARTY.put( "jarPrefix",       "File････,View････,など、指定の接頭辞で始まるjarﾌｧｲﾙを中を検索" );                        // 8.1.3.0 (2022/06/03)
    202                USABLE_PROPARTY.put( "jarSuffix",       ".txt|.java|.jsp.... など、指定の接尾辞で終わるjarﾌｧｲﾙを中を検索" );              // 8.1.3.0 (2022/06/03)
    203                USABLE_PROPARTY.put( "jarInstr",        "jarﾌｧｲﾙを中と一致する部分文字列を指定" );                                                                     // 8.1.3.0 (2022/06/03)
    204                USABLE_PROPARTY.put( "useRegexp",       "[false/true]:trueは、正規表現で検索します(初期値:false)" );                           // 8.1.3.0 (2022/06/03)
    205                USABLE_PROPARTY.put( "errAbend",        "異常発生時に、処理を中断(true)するか、継続(false)するか" +
    206                                                                                CR + "(初期値:true:中断する)" );                                                                                               // 6.3.1.0 (2015/06/28)
    207                USABLE_PROPARTY.put( "saveFile",        "検索結果を指定ﾌｧｲﾙに保存します" );                                                                                  // 8.1.3.0 (2022/06/03)
    208                USABLE_PROPARTY.put( "display",         "[false/true]:trueは、検索状況を表示します(初期値:false)" );
    209                USABLE_PROPARTY.put( "debug",           "ﾃﾞﾊﾞｯｸﾞ情報を標準出力に表示する(true)かしない(false)か" +
    210                                                                                        CR + "(初期値:false:表示しない)" );
    211        }
    212
    213        /**
    214         * ﾃﾞﾌｫﾙﾄｺﾝｽﾄﾗｸﾀｰ｡
    215         * このｸﾗｽは、動的作成されます｡ﾃﾞﾌｫﾙﾄｺﾝｽﾄﾗｸﾀｰで、
    216         * super ｸﾗｽに対して、必要な初期化を行っておきます｡
    217         *
    218         */
    219        public Process_Grep() {
    220                super( "org.opengion.fukurou.process.Process_Grep",MUST_PROPARTY,USABLE_PROPARTY );
    221        }
    222
    223        /**
    224         * ﾌﾟﾛｾｽの初期化を行います｡初めに一度だけ、呼び出されます｡
    225         * 初期処理(ﾌｧｲﾙｵｰﾌﾟﾝ、ＤＢｵｰﾌﾟﾝ等)に使用します｡
    226         *
    227         * @og.rev 6.3.1.0 (2015/06/28) errAbend属性追加｡
    228         * @og.rev 6.3.1.1 (2015/07/10) useOmitCmnt、useAllFind 機能追加
    229         * @og.rev 8.1.3.0 (2022/06/03) jarﾌｧｲﾙ内の検索、ｵｰﾄｴﾝｺｰﾄﾞ対応
    230         *
    231         * @param       paramProcess    ﾃﾞｰﾀﾍﾞｰｽの接続先情報などを持っているｵﾌﾞｼﾞｪｸﾄ
    232         */
    233        public void init( final ParamProcess paramProcess ) {
    234                final Argument arg = getArgument();
    235
    236                keyword                 = arg.getProparty( "keyword");
    237                ignoreCase              = arg.getProparty( "ignoreCase" ,ignoreCase     );
    238                notEquals               = arg.getProparty( "notEquals"  ,notEquals      );
    239                inEncode                = arg.getProparty( "inEncode"   ,System.getProperty("file.encoding"));
    240                outEncode               = arg.getProparty( "outEncode"  ,System.getProperty("file.encoding"));
    241                useBackup               = arg.getProparty( "useBackup"  ,useBackup      );
    242                useBulkRead             = arg.getProparty( "useBulkRead",useBulkRead);          // 4.0.1.0 (2007/12/14)
    243                delete                  = arg.getProparty( "delete"             ,delete         );
    244                insert                  = arg.getProparty( "insert"             ,insert         );
    245                change                  = arg.getFileProparty( "change" ,"changeFile",outEncode,false );
    246                skipRowCount    = arg.getProparty( "skipRowCount",0                     );              // 6.2.4.0 (2015/05/15)
    247                useAllFind              = arg.getProparty( "useAllFind" ,useAllFind);           // 6.3.1.1 (2015/07/10)
    248                useOmitCmnt             = arg.getProparty( "useOmitCmnt",useOmitCmnt);          // 6.3.1.1 (2015/07/10)
    249                errAbend                = arg.getProparty( "errAbend"   ,errAbend       );              // 6.3.1.0 (2015/06/28) errAbend属性追加
    250                jarPrefix               = arg.getProparty( "jarPrefix"  ,jarPrefix      );              // 8.1.3.0 (2022/06/03)
    251                jarSuffix               = arg.getProparty( "jarSuffix"  ,jarSuffix      );              // 8.1.3.0 (2022/06/03)
    252                jarInstr                = arg.getProparty( "jarInstr"   ,jarInstr       );              // 8.1.3.0 (2022/06/03)
    253                useRegexp               = arg.getProparty( "useRegexp"  ,useRegexp      );              // 8.1.3.0 (2022/06/03)
    254                saveFile                = arg.getProparty( "saveFile"   ,saveFile       );              // 8.1.3.0 (2022/06/03)
    255                display                 = arg.getProparty( "display"    ,display        );
    256                debug                   = arg.getProparty( "debug"              ,debug          );              // 5.1.2.0 (2010/01/01)
    257
    258                if( change != null ) {
    259                        final int adrs = insert.indexOf( ' ' ); // ｵﾌｾｯﾄ数字の有無
    260                        if( adrs > 0 ) {
    261                                insOffset = Integer.parseInt( insert.substring( adrs+1 ) );
    262                                insert    = insert.substring( 0,adrs );
    263                        }
    264
    265                        boolean isOK = false;
    266                        for( int i=0; i<INSERT_LIST.length; i++ ) {
    267                                if( insert.equalsIgnoreCase( INSERT_LIST[i] ) ) {
    268                                        isOK = true; break;
    269                                }
    270                        }
    271                        if( !isOK ) {
    272                                // 実行時ｴﾗｰではないので、errAbend 対象外
    273                                final String errMsg = "insert は、" + Arrays.toString( INSERT_LIST )
    274                                                                        + " から指定してください｡" + CR
    275                                                                        + "-insert=[" + insert + "]" ;
    276                                throw new OgRuntimeException( errMsg );
    277                        }
    278
    279                        change = StringUtil.replace( change,"\\n",CR );
    280                        change = StringUtil.replace( change,"\\t","\t" );
    281                }
    282
    283                if( delete ) { change = ""; }   // 削除は、"" 文字列と置換します｡
    284
    285                // 8.1.3.0 (2022/06/03) Modify
    286//              if( ignoreCase ) {
    287//                      pattern = Pattern.compile( keyword,Pattern.CASE_INSENSITIVE );
    288//              }
    289//              else {
    290//                      pattern = Pattern.compile( keyword );
    291//              }
    292                // 大文字小文字を区別しない
    293                if( ignoreCase ) {
    294                        if( useRegexp ) { pattern = Pattern.compile( keyword,Pattern.CASE_INSENSITIVE ); }      // 正規表現を使用する
    295                        else { keyword = keyword.toLowerCase( Locale.JAPAN ); }
    296                }
    297                // 大文字小文字を区別する
    298                else {
    299                        if( useRegexp ) { pattern = Pattern.compile( keyword ); }                                                       // 正規表現を使用しない
    300                }
    301
    302                // jarﾌｧｲﾙの接頭辞／接尾辞／部分文字列
    303                if( StringUtil.isNotNull(jarPrefix) ){ jarPrefix = jarPrefix.toLowerCase( Locale.JAPAN ); }
    304                if( StringUtil.isNotNull(jarSuffix) ){ jarSuffix = jarSuffix.toLowerCase( Locale.JAPAN ); }
    305                if( StringUtil.isNotNull(jarInstr)  ){ jarInstr = jarInstr.toLowerCase( Locale.JAPAN ); }
    306
    307                // 8.1.3.0 (2022/06/03) 保存ﾌｧｲﾙ指定有り
    308                if( StringUtil.isNotNull(saveFile) ){
    309                        final String filename = HybsSystem.url2dir( StringUtil.urlAppend( fileURL, saveFile ) );
    310                        outWriter = FileUtil.getPrintWriter( new File( filename ), ENCODE, true );
    311                }
    312        }
    313
    314        /**
    315         * ﾌﾟﾛｾｽの終了を行います｡最後に一度だけ、呼び出されます｡
    316         * 終了処理(ﾌｧｲﾙｸﾛｰｽﾞ、DBｸﾛｰｽﾞ等)に使用します｡
    317         *
    318         * @og.rev 8.1.3.0 (2022/06/03) jarﾌｧｲﾙ内の検索、ｵｰﾄｴﾝｺｰﾄﾞ対応
    319         *
    320         * @param       isOK    ﾄｰﾀﾙで、OKだったかどうか[true:成功/false:失敗]
    321         */
    322        public void end( final boolean isOK ) {
    323                // 8.1.3.0 (2022/06/03) 保存ﾌｧｲﾙ指定有り
    324                if( StringUtil.isNotNull(saveFile) ){
    325                        Closer.ioClose( outWriter );
    326                }
    327        }
    328
    329        /**
    330         * 引数の LineModel を処理するﾒｿｯﾄﾞです｡
    331         * 変換処理後の LineModel を返します｡
    332         * 後続処理を行わない場合(ﾃﾞｰﾀのﾌｨﾙﾀﾘﾝｸﾞを行う場合)は、
    333         * null ﾃﾞｰﾀを返します｡つまり、null ﾃﾞｰﾀは、後続処理を行わない
    334         * ﾌﾗｸﾞの代わりにも使用しています｡
    335         * なお、変換処理後の LineModel と、ｵﾘｼﾞﾅﾙの LineModel が、
    336         * 同一か、ｺﾋﾟｰ(ｸﾛｰﾝ)かは、各処理ﾒｿｯﾄﾞ内で決めています｡
    337         * ﾄﾞｷｭﾒﾝﾄに明記されていない場合は、副作用が問題になる場合は、
    338         * 各処理ごとに自分でｺﾋﾟｰ(ｸﾛｰﾝ)して下さい｡
    339         *
    340         * @og.rev 4.0.1.0 (2007/12/14) ﾌｧｲﾙの一括処理対応｡
    341         * @og.rev 5.7.2.2 (2014/01/24) ｴﾗｰ時にﾃﾞｰﾀも出力します｡
    342         * @og.rev 6.3.1.0 (2015/06/28) errAbend属性追加｡
    343         * @og.rev 8.1.3.0 (2022/06/03) jarﾌｧｲﾙ内の検索、ｵｰﾄｴﾝｺｰﾄﾞ対応
    344         *
    345         * @param       data    ｵﾘｼﾞﾅﾙのLineModel
    346         *
    347         * @return      処理変換後のLineModel
    348         */
    349        @Override       // ChainProcess
    350        public LineModel action( final LineModel data ) {
    351                inCount++ ;
    352
    353                final FileLineModel fileData ;
    354                if( data instanceof FileLineModel ) {
    355                        fileData = (FileLineModel)data ;
    356                }
    357                else {
    358                        // これは、ﾌﾟﾛｸﾞﾗﾏｰの問題なので、errAbend 対象外
    359                        final String errMsg = "ﾃﾞｰﾀが FileLineModel ｵﾌﾞｼﾞｪｸﾄではありません｡" + CR ;
    360                        throw new OgRuntimeException( errMsg );
    361                }
    362
    363                final File file = fileData.getFile() ;
    364                if( !file.isFile() ) {
    365                        if( display ) { println( data.dataLine() ); }           // 5.1.2.0 (2010/01/01) display の条件変更
    366                        return data;
    367                }
    368
    369                boolean isFind = false ;        // 6.3.1.0 (2015/06/28) errAbend属性追加に伴う、初期化漏れ対応
    370                try {
    371                        String fileLine = null;
    372                        int firstLineNo = -1;
    373
    374                        // 8.1.3.0 (2022/06/03) 拡張子によるｴﾝｺｰﾄﾞ指定
    375                        final String mapEnc = getEncode( file );
    376                        // 8.1.3.0 (2022/06/03) 該当するｴﾝｺｰﾄﾞ無し
    377                        if( !"-".equals(mapEnc) ) {
    378                                // 8.1.3.0 (2022/06/03) jarﾌｧｲﾙ内の検索
    379//                              if( useBulkRead ) { fileLine    = findKeywordAsBulk( file ); }
    380//                              else                      { firstLineNo = findKeyword( file ); }
    381                                if( useBulkRead ) { fileLine = findKeywordAsBulk( file,mapEnc ); }
    382                                else {
    383                                        if( file.getName().endsWith( ".jar" ) ) { findJarKeyword( file ); }             // firstLineNo は、常に -1
    384                                        else                                                                    { firstLineNo = findKeyword( file,mapEnc ); }
    385                                }
    386                        }
    387
    388                        isFind = fileLine != null || firstLineNo >= 0 ;
    389
    390                        // 置換処理 ただし、見つかったときのみ実行
    391                        if( change != null && isFind ) {
    392                                // 入力ﾌｧｲﾙは、ｵﾘｼﾞﾅﾙ_backup ﾌｧｲﾙとする｡過去のﾌｧｲﾙを削除
    393                                final File inFile = new File( file.getPath() + "_backup" );
    394                                if( inFile.exists() && !inFile.delete() ) {
    395                                        final String errMsg = "過去のBKUPﾌｧｲﾙを削除できませんでした｡[" + inFile + "]" + CR
    396                                                                +       "data=[" + data.dataLine() + "]" + CR ;         // 5.7.2.2 (2014/01/24) ｴﾗｰ時にﾃﾞｰﾀも出力します｡
    397                                        // try の中から throw するのは、行儀がよくないが、catch ﾌﾞﾛｯｸで errAbend処理する｡
    398                                        throw new OgRuntimeException( errMsg );
    399                                }
    400
    401                                // ｵﾘｼﾞﾅﾙのﾌｧｲﾙを、_backup ﾌｧｲﾙ名に先に変換する｡
    402                                final File fromFile = new File( file.getPath() );
    403                                if( !fromFile.renameTo( inFile ) ) {
    404                                        final String errMsg = "所定のﾌｧｲﾙをﾘﾈｰﾑできませんでした｡[" + fromFile + "]" + CR
    405                                                                +       "data=[" + data.dataLine() + "]" + CR ;         // 5.7.2.2 (2014/01/24) ｴﾗｰ時にﾃﾞｰﾀも出力します｡
    406                                        // try の中から throw するのは、行儀がよくないが、catch ﾌﾞﾛｯｸで errAbend処理する｡
    407                                        throw new OgRuntimeException( errMsg );
    408                                }
    409
    410                                // 変換処理 本体
    411                                if( useBulkRead ) { changeKeywordAsBulk( fileLine,file ); }
    412//                              else                      { changeKeyword( inFile,file,firstLineNo ); }
    413                                else                      { changeKeyword( inFile,file,firstLineNo,mapEnc ); }  // 8.1.3.0 (2022/06/03)
    414
    415                                // backup を使わない場合は、削除する｡
    416                                // 4.0.0.0 (2007/11/29) 入れ子if の統合
    417                                if( !useBackup && !inFile.delete() ) {
    418                                        final String errMsg = "所定のﾌｧｲﾙを削除できませんでした｡[" + inFile + "]" + CR
    419                                                                +       "data=[" + data.dataLine() + "]" + CR ;         // 5.7.2.2 (2014/01/24) ｴﾗｰ時にﾃﾞｰﾀも出力します｡
    420                                        // try の中から throw するのは、行儀がよくないが、catch ﾌﾞﾛｯｸで errAbend処理する｡
    421                                        throw new OgRuntimeException( errMsg );
    422                                }
    423                        }
    424                }
    425                catch( final RuntimeException ex ) {
    426                        final String errMsg = "処理中にｴﾗｰが発生しました｡[" + data.getRowNo() + "]件目" + CR
    427                                                +       "data=[" + data.dataLine() + "]" + CR ;         // 5.7.2.2 (2014/01/24) ｴﾗｰ時にﾃﾞｰﾀも出力します｡
    428                        // 6.3.1.0 (2015/06/28) errAbend属性追加｡
    429                        throwException( errMsg,ex,errAbend );
    430                }
    431
    432//              if( display && ( notEquals ^ isFind ) ) { println( data.dataLine() ); }         // 5.1.2.0 (2010/01/01) display の条件変更
    433                if( notEquals ^ isFind && display ) { println( data.dataLine() ); }                     // 5.1.2.0 (2010/01/01) display の条件変更   // 6.9.7.0 (2018/05/14) PMD Useless parentheses.
    434                return notEquals ^ isFind ? data : null ;
    435        }
    436
    437        /**
    438         * ｷｰﾜｰﾄﾞが存在しているかどうかをﾁｪｯｸします｡
    439         * ここでは、１行づつ読み取りながら、すべてのｷｰﾜｰﾄﾞをﾋﾟｯｸｱｯﾌﾟします。
    440         *
    441         * @og.rev 8.1.3.0 (2022/06/03) jarﾌｧｲﾙ内の検索、ｵｰﾄｴﾝｺｰﾄﾞ対応
    442         *
    443         * @param       file    検索元のﾌｧｲﾙｵﾌﾞｼﾞｪｸﾄ
    444         */
    445        private void findJarKeyword( final File file ) {
    446                JarFile jarFile = null;
    447
    448                try {
    449                        jarFile = new JarFile( file );
    450
    451                        final Enumeration<JarEntry> flEnum = jarFile.entries() ;                        // Generics警告対応
    452                        while( flEnum.hasMoreElements() ) {
    453                                final JarEntry ent = flEnum.nextElement();                                              // Generics警告対応
    454                                if( ent.isDirectory() ) { continue; }
    455
    456                                final String fileName = ent.getName();                                                  // jarﾌｧｲﾙ内のﾌｧｲﾙ
    457
    458                                // 拡張子によるｴﾝｺｰﾄﾞ指定
    459                                final String mapEnc = getEncode( fileName );
    460                                // 該当するｴﾝｺｰﾄﾞ無し
    461                                if( "-".equals(mapEnc) ) { continue; }
    462
    463                                final String lowName = fileName.toLowerCase( Locale.JAPAN );
    464                                if( ( jarPrefix == null || lowName.startsWith( jarPrefix ) )    &&
    465                                        ( jarSuffix == null || lowName.endsWith( jarSuffix )   )        &&
    466                                        ( jarInstr  == null || lowName.contains( jarInstr )    ) ) {
    467
    468                                        InputStream stream = null;
    469                                        try {
    470                                                stream = jarFile.getInputStream( ent ) ;
    471
    472                                                final BufferedReader reader = new BufferedReader( new InputStreamReader( stream, mapEnc ) );
    473
    474                                                final CommentLineParser clp = useOmitCmnt ? new CommentLineParser( FileInfo.getSUFIX( fileName ) ) : null;
    475                                                try {
    476                                                        String line ;
    477                                                        int lineNo = 0;
    478                                                        while((line = reader.readLine()) != null) {
    479                                                                lineNo++ ;                                                                              // 注意：ここで返す行数は、ｺﾒﾝﾄ行を含む行数とする
    480
    481                                                                // useOmitCmnt 機能(ｺﾒﾝﾄ行を削除する処理を入れる)
    482                                                                if( useOmitCmnt ) {
    483                                                                        line = clp.line( line );
    484                                                                        if( line == null ) { continue; }                        // 戻り値が null の場合は、行として不成立
    485                                                                }
    486
    487                                                                // ｷｰﾜｰﾄﾞを含むかどうか判定
    488                                                                if( isKeyword( line ) ) {
    489                                                                        final String msg = file.getPath() + "\\" + fileName + '(' + lineNo + "):" + line ;
    490                                                                        if( debug ) {
    491                                                                                final String buf = "DEBUG:\t" + msg ;
    492                                                                                println( buf );
    493                                                                        }
    494                                                                        // useAllFind=true 相当の処理のみ行う
    495                                                                        println( msg );
    496                                                                        // 保存ﾌｧｲﾙ指定有り
    497                                                                        if( StringUtil.isNotNull(saveFile) ){ outWriter.println( msg ); }
    498
    499                                                                        // useAllFind 機能(最後まで検索を続ける)
    500                                                                        if( !useAllFind ) { break; }
    501                                                                }
    502                                                        }
    503                                                }
    504                                                // nioを使用すると UTF-8とShuft-JISで、ｴﾗｰになる
    505                                                catch( final CharacterCodingException ex ) {
    506                                                        final String errMsg = "文字のｴﾝｺｰﾄﾞ･ｴﾗｰが発生しました｡" + CR
    507                                                                                                +       "  ﾌｧｲﾙのｴﾝｺｰﾄﾞが指定のｴﾝｺｰﾄﾞと異なります｡" + CR
    508                                                                                                +       " [" + file.getPath() + "] , Encode=[" + mapEnc + "]" ;
    509                                                        // 呼出元で errAbend 処理するので、そのまま throw しておく
    510                                                        throw new OgCharacterException( errMsg,ex );
    511                                                }
    512                                                catch( final IOException ex ) {
    513                                                        final String errMsg = "ｷｰﾜｰﾄﾞﾌｧｲﾙ読取ｴﾗｰが発生しました｡" + CR
    514                                                                                                +       " [" + file.getPath() + "] , Encode=[" + mapEnc + "]" ;
    515                                                        // 呼出元で errAbend 処理するので、そのまま throw しておく
    516                                                        throw new OgRuntimeException( errMsg,ex );
    517                                                }
    518                                                finally {
    519                                                        Closer.ioClose( reader );
    520                                                }
    521                                        }
    522                                        finally {
    523                                                Closer.ioClose( stream );
    524                                        }
    525                                }
    526                        }
    527                }
    528                catch( final IOException ex ) {
    529                        final String errMsg = "ｷｰﾜｰﾄﾞﾌｧｲﾙ読取ｴﾗｰが発生しました｡" + CR
    530                                                                +       " [" + file.getPath() + "] , Encode=[" + inEncode + "]" ;
    531                        // 呼出元で errAbend 処理するので、そのまま throw しておく
    532                        throw new OgRuntimeException( errMsg,ex );
    533                }
    534                finally {
    535                        Closer.zipClose( jarFile );
    536                }
    537        }
    538
    539        /**
    540         * 拡張子をｴﾝｺｰﾄﾞに変換します。
    541         *
    542         * @og.rev 8.1.3.0 (2022/06/03) jarﾌｧｲﾙ内の検索、ｵｰﾄｴﾝｺｰﾄﾞ対応
    543         *
    544         * @param       file    検索元のﾌｧｲﾙ
    545         * @return      ｴﾝｺｰﾄﾞ
    546         */
    547        private String getEncode( final File file ) {
    548                return getEncode( file.getName() );
    549        }
    550
    551        /**
    552         * 拡張子をｴﾝｺｰﾄﾞに変換します。
    553         *
    554         * @og.rev 8.1.3.0 (2022/06/03) jarﾌｧｲﾙ内の検索、ｵｰﾄｴﾝｺｰﾄﾞ対応
    555         *
    556         * @param       fileName        検索元のﾌｧｲﾙ名
    557         * @return      ｴﾝｺｰﾄﾞ
    558         */
    559        private String getEncode( final String fileName ) {
    560                // 自動判定
    561                if( AUTO_ENCODE.equalsIgnoreCase( inEncode ) ) {
    562                        final String sufix = FileInfo.getSUFIX( fileName );                                     // 拡張子取得
    563                        return StringUtil.nval( EXT2ENC.get( sufix ), "-" );
    564                }
    565                else {
    566                        return StringUtil.nval( inEncode, "-" );
    567                }
    568        }
    569
    570        /**
    571         * 該当の行にｷｰﾜｰﾄﾞを含むかどうか判定します。
    572         *
    573         * @og.rev 8.1.3.0 (2022/06/03) jarﾌｧｲﾙ内の検索、ｵｰﾄｴﾝｺｰﾄﾞ対応
    574         *
    575         * @param       line    検索元のﾌｧｲﾙの行
    576         * @return      ｷｰﾜｰﾄﾞを含むかどうか[true/false]
    577         */
    578        private boolean isKeyword( final String line ) {
    579                // 正規表現を使用する
    580                if( useRegexp ){
    581                        final Matcher mach = pattern.matcher( line );
    582                        return mach.find();
    583                }
    584                // 正規表現を使用しない
    585                else {
    586                        // 大文字小文字を区別しない
    587                        if( ignoreCase ) {
    588                                return line.toLowerCase( Locale.JAPAN ).contains( keyword );
    589                        }
    590                        // 大文字小文字を区別する
    591                        else {
    592                                return line.contains( keyword );
    593                        }
    594                }
    595        }
    596
    597        /**
    598         * ｷｰﾜｰﾄﾞが存在しているかどうかをﾁｪｯｸします｡
    599         * ここでは、１行づつ読み取りながら、最初に見つかった時点で制御を返します｡
    600         * よって、複数行にまたがる keyword でのﾏｯﾁﾝｸﾞは出来ませんが、大きな
    601         * ﾌｧｲﾙ等での検索には、効率的です｡
    602         *
    603         * @og.rev 4.0.1.0 (2007/12/14) 新規追加
    604         * @og.rev 6.3.1.0 (2015/06/28) nioを使用すると UTF-8とShuft-JISで、ｴﾗｰになる｡
    605         * @og.rev 6.3.1.0 (2015/06/28) errAbend属性追加｡
    606         * @og.rev 6.3.1.1 (2015/07/10) useOmitCmnt、useAllFind 機能追加
    607         * @og.rev 6.4.0.2 (2015/12/11) CommentLineParser 改造｡
    608         * @og.rev 6.5.0.1 (2016/10/21) CharacterCodingException は、OgCharacterException に変換する｡
    609         * @og.rev 8.1.3.0 (2022/06/03) jarﾌｧｲﾙ内の検索、ｵｰﾄｴﾝｺｰﾄﾞ対応
    610         *
    611         * @param       file    検索元のﾌｧｲﾙｵﾌﾞｼﾞｪｸﾄ
    612         * @param       mapEnc  検索元のﾌｧｲﾙｴﾝｺｰﾄﾞ
    613         *
    614         * @return      最初に見つかった行番号(見つからなければ、-1 を返す)
    615         */
    616//      private int findKeyword( final File file ) {
    617        private int findKeyword( final File file ,final String mapEnc ) {                               // 8.1.3.0 (2022/06/03)
    618                int firstLineNo = -1;
    619//              final BufferedReader reader = FileUtil.getBufferedReader( file,inEncode );
    620                final BufferedReader reader = FileUtil.getBufferedReader( file,mapEnc );        // 8.1.3.0 (2022/06/03)
    621
    622                // 6.4.0.2 (2015/12/11) CommentLineParser 改造
    623                final CommentLineParser clp = useOmitCmnt ? new CommentLineParser( FileInfo.getSUFIX( file ) ) : null;
    624                try {
    625                        String line ;
    626                        int lineNo = 0;
    627                        while((line = reader.readLine()) != null) {
    628                                lineNo++ ;              // 注意：ここで返す行数は、ｺﾒﾝﾄ行を含む行数とする｡
    629
    630                                // 6.3.1.1 (2015/07/10) useOmitCmnt 機能｡ｺﾒﾝﾄ行を削除する処理を入れる｡
    631                                if( useOmitCmnt ) {
    632                                        line = clp.line( line );
    633                                        if( line == null ) { continue; }        // 戻り値が null の場合は、行として不成立
    634                                }
    635                                // 8.1.3.0 (2022/06/03) Modify
    636//                              final Matcher mach = pattern.matcher( line );
    637//                              if( mach.find() ) {
    638                                // ｷｰﾜｰﾄﾞを含むかどうか判定
    639                                if( isKeyword( line ) ) {
    640                                        if( debug ) {
    641                                                final String buf = "DEBUG:\t" + file.getPath() + "(" + lineNo + "): " + line ;
    642                                                println( buf );
    643                                        }
    644
    645                                        // 6.3.1.1 (2015/07/10) useAllFind 機能｡最後まで検索を続けます｡
    646                                        if( useAllFind ) {
    647                                                final String msg = file.getAbsolutePath() + '(' + lineNo + "):" + line ;
    648                                                println( msg );
    649                                                // 8.1.3.0 (2022/06/03) 保存ﾌｧｲﾙ指定有り
    650                                                if( StringUtil.isNotNull(saveFile) ){ outWriter.println( msg ); }
    651                                        }
    652                                        else {
    653                                                firstLineNo = lineNo;
    654                                                break;
    655                                        }
    656                                }
    657                        }
    658                }
    659                // 6.3.1.0 (2015/06/28) nioを使用すると UTF-8とShuft-JISで、ｴﾗｰになる｡
    660                catch( final CharacterCodingException ex ) {
    661                        final String errMsg = "文字のｴﾝｺｰﾄﾞ･ｴﾗｰが発生しました｡" + CR
    662                                                                +       "  ﾌｧｲﾙのｴﾝｺｰﾄﾞが指定のｴﾝｺｰﾄﾞと異なります｡" + CR
    663//                                                              +       " [" + file.getPath() + "] , Encode=[" + inEncode + "]" ;
    664                                                                +       " [" + file.getPath() + "] , Encode=[" + mapEnc + "]" ; // 8.1.3.0 (2022/06/03)
    665                        // 呼出元で、errAbend処理するので、そのまま、throw しておく｡
    666                        throw new OgCharacterException( errMsg,ex );    // 6.5.0.1 (2016/10/21)
    667                }
    668                catch( final IOException ex ) {
    669                        final String errMsg = "ｷｰﾜｰﾄﾞﾌｧｲﾙ読取ｴﾗｰが発生しました｡" + CR
    670//                                                              +       " [" + file.getPath() + "] , Encode=[" + inEncode + "]" ;
    671                                                                +       " [" + file.getPath() + "] , Encode=[" + mapEnc + "]" ; // 8.1.3.0 (2022/06/03)
    672                        // 呼出元で、errAbend処理するので、そのまま、throw しておく｡
    673                        throw new OgRuntimeException( errMsg,ex );
    674                }
    675                finally {
    676                        Closer.ioClose( reader );
    677                }
    678
    679                return firstLineNo;
    680        }
    681
    682        /**
    683         * ｷｰﾜｰﾄﾞが存在しているかどうかをﾁｪｯｸします｡
    684         * ここでは、ﾌｧｲﾙをすべて読み取ってから、ﾁｪｯｸします｡
    685         * よって、複数行にまたがる keyword でのﾏｯﾁﾝｸﾞが可能です｡
    686         *
    687         * @og.rev 4.0.1.0 (2007/12/14) 新規追加
    688         * @og.rev 6.4.5.1 (2016/04/28) FileStringのｺﾝｽﾄﾗｸﾀｰ変更
    689         * @og.rev 6.4.5.2 (2016/05/06) fukurou.util.FileString から、fukurou.util.FileUtil に移動｡
    690         * @og.rev 8.1.3.0 (2022/06/03) jarﾌｧｲﾙ内の検索、ｵｰﾄｴﾝｺｰﾄﾞ対応
    691         *
    692         * @param       file    検索元のﾌｧｲﾙｵﾌﾞｼﾞｪｸﾄ
    693         * @param       mapEnc  検索元のﾌｧｲﾙｴﾝｺｰﾄﾞ
    694         *
    695         * @return      検索元のﾌｧｲﾙの文字列化情報(ただし、見つからなければ、null)
    696         */
    697//      private String findKeywordAsBulk( final File file ) {
    698        private String findKeywordAsBulk( final File file,final String mapEnc ) {       // 8.1.3.0 (2022/06/03)
    699
    700                boolean isFind = false;
    701
    702                // 6.4.5.1 (2016/04/28) FileStringのｺﾝｽﾄﾗｸﾀｰ変更
    703//              final String line = FileUtil.getValue( file.getPath() , inEncode );             // 6.4.5.2 (2016/05/06)
    704                final String line = FileUtil.getValue( file.getPath() , mapEnc );               // 8.1.3.0 (2022/06/03)
    705
    706                final Matcher mach = pattern.matcher( line );
    707                if( mach.find() ) {
    708                        if( debug ) { println( "DEBUG:\t" + file.getPath() ); }
    709                        isFind = true;
    710                }
    711
    712                return isFind ? line : null;
    713        }
    714
    715        /**
    716         * ｷｰﾜｰﾄﾞを指定の文字列に置き換えます｡
    717         * useBackup 属性に true を指定した場合、置き換え後の、backup ﾌｧｲﾙは、
    718         * ｵﾘｼﾞﾅﾙ_backup という名称に変わります｡
    719         * ここでは、１行づつ読み取りながら、変換処理を行います｡
    720         * よって、複数行にまたがる keyword でのﾏｯﾁﾝｸﾞは出来ませんが、大きな
    721         * ﾌｧｲﾙ等での置換でも、ﾒﾓﾘの使用量は抑えられます｡
    722         *
    723         * @og.rev 4.0.1.0 (2007/12/14) 置換処理を独立させます｡
    724         * @og.rev 6.2.4.0 (2015/05/15) HEAD,TAIL 追加
    725         * @og.rev 6.3.1.0 (2015/06/28) nioを使用すると UTF-8とShuft-JISで、ｴﾗｰになる｡
    726         * @og.rev 6.5.0.1 (2016/10/21) CharacterCodingException は、OgCharacterException に変換する｡
    727         * @og.rev 8.1.3.0 (2022/06/03) jarﾌｧｲﾙ内の検索、ｵｰﾄｴﾝｺｰﾄﾞ対応
    728         *
    729         * @param       inFile  検索元の入力ﾌｧｲﾙｵﾌﾞｼﾞｪｸﾄ
    730         * @param       outFile 変換後の出力ﾌｧｲﾙｵﾌﾞｼﾞｪｸﾄ
    731         * @param       firstLineNo     ｷｰﾜｰﾄﾞが存在した場合の最初の行番号
    732         * @param       mapEnc  検索元のﾌｧｲﾙｴﾝｺｰﾄﾞ
    733         */
    734//      private void changeKeyword( final File inFile,final File outFile,final int firstLineNo ) {
    735        private void changeKeyword( final File inFile,final File outFile,final int firstLineNo,final String mapEnc ) {  // 8.1.3.0 (2022/06/03)
    736
    737//              final BufferedReader reader = FileUtil.getBufferedReader( inFile,inEncode );
    738                final BufferedReader reader = FileUtil.getBufferedReader( inFile,mapEnc );      // 8.1.3.0 (2022/06/03)
    739                final PrintWriter    writer = FileUtil.getPrintWriter( outFile,outEncode );
    740
    741                String line = null;
    742                try {
    743                        // 6.2.4.0 (2015/05/15) HEAD,TAIL 追加
    744                        if( "HEAD".equals( insert ) ) {
    745                                writer.println( change );
    746                        }
    747
    748                        int lineNo = 0;
    749                        while((line = reader.readLine()) != null) {
    750                                lineNo++ ;
    751                                if( lineNo <= skipRowCount ) { continue; }              // 6.2.4.0 (2015/05/15)
    752
    753                                if( lineNo >= firstLineNo ) {
    754                                        final Matcher mach = pattern.matcher( line );
    755
    756                                        String chnStr = null;
    757                                        if( "CHANGE".equals( insert ) ) {
    758                                                chnStr = strChange( mach );
    759                                        }
    760                                        else if( "BEFORE".equals( insert ) ) {
    761                                                chnStr = strBefore( line,mach );
    762                                        }
    763                                        else if( "AFTER".equals( insert ) ) {
    764                                                chnStr = strAfter( line,mach );
    765                                        }
    766
    767                                        if( chnStr != null ) {
    768                                                line = chnStr;
    769                                                cngCount++ ;    // 変換されれば ｶｳﾝﾄ
    770                                        }
    771                                }
    772                                writer.println( line ); // readLine() してるので、最後に改行が必要｡
    773                        }
    774
    775                        // 6.2.4.0 (2015/05/15) HEAD,TAIL 追加
    776                        if( "TAIL".equals( insert ) ) {
    777                                writer.println( change );
    778                        }
    779                }
    780                // 6.3.1.0 (2015/06/28) nioを使用すると UTF-8とShuft-JISで、ｴﾗｰになる｡
    781                catch( final CharacterCodingException ex ) {
    782                        final String errMsg = "文字のｴﾝｺｰﾄﾞ･ｴﾗｰが発生しました｡" + CR
    783                                                                +       "  ﾌｧｲﾙのｴﾝｺｰﾄﾞが指定のｴﾝｺｰﾄﾞと異なります｡" + CR
    784//                                                              +       " [" + inFile + "] , Encode=[" + inEncode + "]" ;
    785                                                                +       " [" + inFile + "] , Encode=[" + mapEnc + "]" ; // 8.1.3.0 (2022/06/03)
    786                        // 呼出元で、errAbend処理するので、そのまま、throw しておく｡
    787                        throw new OgCharacterException( errMsg,ex );    // 6.5.0.1 (2016/10/21)
    788                }
    789                catch( final IOException ex ) {
    790                        final String errMsg = "処理中にｴﾗｰが発生しました｡[" + line + "]" + CR
    791//                                                              +       " [" + inFile + "] , Encode=[" + inEncode + "]" ;
    792                                                                +       " [" + inFile + "] , Encode=[" + mapEnc + "]" ; // 8.1.3.0 (2022/06/03)
    793                        // 呼出元で、errAbend処理するので、そのまま、throw しておく｡
    794                        throw new OgRuntimeException( errMsg,ex );
    795                }
    796                finally {
    797                        Closer.ioClose( reader );
    798                        Closer.ioClose( writer );
    799                }
    800        }
    801        /**
    802         * ｷｰﾜｰﾄﾞを指定の文字列に置き換えます｡
    803         * useBackup 属性に true を指定した場合、置き換え後の、backup ﾌｧｲﾙは、
    804         * ｵﾘｼﾞﾅﾙ_backup という名称に変わります｡
    805         * ここでは、ﾌｧｲﾙをすべて読み取ってから、ﾁｪｯｸします｡
    806         * よって、複数行にまたがる keyword でのﾏｯﾁﾝｸﾞが可能です｡
    807         *
    808         * @og.rev 4.0.1.0 (2007/12/14) 置換処理を独立させます｡
    809         * @og.rev 6.2.4.0 (2015/05/15) HEAD,TAIL 追加
    810         *
    811         * @param       fileLine        検索元の行文字列
    812         * @param       outFile 出力ﾌｧｲﾙｵﾌﾞｼﾞｪｸﾄ
    813         */
    814        private void changeKeywordAsBulk( final String fileLine,final File outFile ) {
    815                final PrintWriter writer = FileUtil.getPrintWriter( outFile,outEncode );
    816
    817                String line = fileLine ;
    818                try {
    819                        // 6.2.4.0 (2015/05/15) HEAD,TAIL 追加
    820                        if( "HEAD".equals( insert ) ) {
    821                                writer.println( change );
    822                        }
    823
    824                        final Matcher mach = pattern.matcher( line );
    825
    826                        String chnStr = null;
    827                        if( "CHANGE".equals( insert ) ) {
    828                                chnStr = strChange( mach );
    829                        }
    830                        else if( "BEFORE".equals( insert ) ) {
    831                                chnStr = strBefore( line,mach );
    832                        }
    833                        else if( "AFTER".equals( insert ) ) {
    834                                chnStr = strAfter( line,mach );
    835                        }
    836
    837                        if( chnStr != null ) {
    838                                line = chnStr;
    839                                cngCount++ ;    // 変換されれば ｶｳﾝﾄ
    840                        }
    841
    842                        writer.print( line );   // 注意：改行ｺｰﾄﾞは、不要
    843
    844                        // 6.2.4.0 (2015/05/15) HEAD,TAIL 追加
    845                        if( "TAIL".equals( insert ) ) {
    846                                writer.println( change );
    847                        }
    848                }
    849                catch( final RuntimeException ex ) {
    850                        final String errMsg = "処理中にｴﾗｰが発生しました｡[" + outFile.getPath() + "]" ;
    851                        // 呼出元で、errAbend処理するので、そのまま、throw しておく｡
    852                        throw new OgRuntimeException( errMsg,ex );
    853                }
    854                finally {
    855                        Closer.ioClose( writer );
    856                }
    857        }
    858
    859        /**
    860         * insert が、"CHANGE" の場合の処理結果を求めます｡
    861         * 変換しなかった場合は、null を返します｡
    862         * これは、変換ｶｳﾝﾄを算出する為のﾌﾗｸﾞ代わりに使用しています｡
    863         *
    864         * @param       mach    ｷｰﾜｰﾄﾞの正規表現
    865         *
    866         * @return      変換結果(対象行で無い場合は、null)
    867         */
    868        private String strChange( final Matcher mach ) {
    869                String line = null;
    870                if( mach.find() ) {
    871                        line = mach.replaceAll( change );
    872                }
    873                return line ;
    874        }
    875
    876        /**
    877         * insert が、"BEFORE" の場合の処理結果を求めます｡
    878         * 変換しなかった場合は、null を返します｡
    879         * これは、変換ｶｳﾝﾄを算出する為のﾌﾗｸﾞ代わりに使用しています｡
    880         *
    881         * @param       line    検索行
    882         * @param       mach    ｷｰﾜｰﾄﾞの正規表現
    883         *
    884         * @return      変換結果(対象行で無い場合は、null)
    885         */
    886        private String strBefore( final String line , final Matcher mach ) {
    887                boolean isChng = false;
    888                final StringBuilder buf = new StringBuilder( line.length() );
    889                int indx = 0;
    890                while( mach.find() ) {
    891                        isChng = true;
    892                        final int strt = mach.start() + insOffset;
    893                        buf.append( line.substring( indx,strt ) );
    894                        buf.append( change );
    895                        indx = strt;
    896                }
    897
    898                String rtn = null;
    899                if( isChng ) {
    900                        buf.append( line.substring( indx ) );
    901                        rtn = buf.toString();
    902                }
    903
    904                return rtn ;
    905        }
    906
    907        /**
    908         * insert が、"AFTER" の場合の処理結果を求めます｡
    909         * 変換しなかった場合は、null を返します｡
    910         * これは、変換ｶｳﾝﾄを算出する為のﾌﾗｸﾞ代わりに使用しています｡
    911         *
    912         * @param       line    検索行
    913         * @param       mach    ｷｰﾜｰﾄﾞの正規表現
    914         *
    915         * @return      変換結果(対象行で無い場合は、null)
    916         */
    917        private String strAfter( final String line , final Matcher mach ) {
    918                boolean isChng = false;
    919                final StringBuilder buf = new StringBuilder( line.length() );
    920                int indx = 0;
    921                while( mach.find() ) {
    922                        isChng = true;
    923                        final int end = mach.end() + insOffset;
    924                        buf.append( line.substring( indx,end ) );
    925                        buf.append( change );
    926                        indx = end;
    927                }
    928                String rtn = null;
    929                if( isChng ) {
    930                        buf.append( line.substring( indx ) );
    931                        rtn = buf.toString();
    932                }
    933
    934                return rtn ;
    935        }
    936
    937        /**
    938         * ﾌﾟﾛｾｽの処理結果のﾚﾎﾟｰﾄ表現を返します｡
    939         * 処理ﾌﾟﾛｸﾞﾗﾑ名、入力件数、出力件数などの情報です｡
    940         * この文字列をそのまま、標準出力に出すことで、結果ﾚﾎﾟｰﾄと出来るような
    941         * 形式で出してください｡
    942         *
    943         * @return   処理結果のﾚﾎﾟｰﾄ
    944         */
    945        public String report() {
    946                if( findCount < cngCount ) { findCount = cngCount; }
    947
    948                // 7.2.9.5 (2020/11/28) PMD:Consider simply returning the value vs storing it in local variable 'XXXX'
    949                return "[" + getClass().getName() + "]" + CR
    950//              final String report = "[" + getClass().getName() + "]" + CR
    951                                + TAB + "Search Keyword    : " + keyword    + CR
    952                                + TAB + "Search File Count : " + inCount    + CR
    953                                + TAB + "Key Find    Count : " + findCount  + CR
    954                                + TAB + "Key Change  Count : " + cngCount ;
    955
    956//              return report ;
    957        }
    958
    959        /**
    960         * このｸﾗｽの使用方法を返します｡
    961         *
    962         * @return      このｸﾗｽの使用方法
    963         * @og.rtnNotNull
    964         */
    965        public String usage() {
    966                final StringBuilder buf = new StringBuilder( 1400 )
    967                        .append( "Process_Grep は、上流から受け取った FileLineModelから、文字列を見つけ出す"   ).append( CR )
    968                        .append( "ChainProcess ｲﾝﾀｰﾌｪｰｽの実装ｸﾗｽです｡"                                                                                       ).append( CR )
    969                        .append( CR )
    970                        .append( "正規表現の keyword を上流から受け取った FileLineModel から検索します｡"              ).append( CR )
    971                        .append( "見つかった対象ﾌｧｲﾙから、指定の文字列を置換する場合は、-change か"                               ).append( CR )
    972                        .append( "-changeFile で、keyword を置換する文字列を指定して下さい｡"                                      ).append( CR )
    973                        .append( "置換する文字列には、\t と \n の特殊文字が使用できます｡"                                              ).append( CR )
    974                        .append( CR )
    975                        .append( "処理対象は、通常は、１行づつ読み取りながら処理を行います｡存在ﾁｪｯｸの場合は、"     ).append( CR )
    976                        .append( "見つかった時点で処理を中止します｡これは、該当箇所をﾋﾟｯｸｱｯﾌﾟするのではなく、"   ).append( CR )
    977                        .append( "存在しているかどうかを判断して、あれば、下流に流すというのが目的だからです｡"       ).append( CR )
    978                        .append( "keyword を、改行を含む正規表現で、検索･置換する場合は、-useBulkRead 属性を"     ).append( CR )
    979                        .append( "true に設定してください｡これは、入力ﾌｧｲﾙを一括して読み込みます｡"                                ).append( CR )
    980                        .append( "-ignoreCase は、検索時にｷｰの大文字小文字を無視するように指定します｡"                    ).append( CR )
    981                        .append( "-notEquals は、結果(見つかればtrue)を反転(見つからなければtrue)します｡"              ).append( CR )
    982                        .append( "これは、行単位ではなく、ﾌｧｲﾙ単位に判定しますので、change 指定した場合"             ).append( CR )
    983                        .append( "でも、対象行は、見つかった行です｡ただし、下流に対して、見つからない"                   ).append( CR )
    984                        .append( "場合だけ処理を継続させます｡"                                                                                                       ).append( CR )
    985                        .append( "-inEncode は、入力ﾌｧｲﾙのｴﾝｺｰﾄﾞ指定になります｡"                                                            ).append( CR )
    986                        .append( "-outEncode は、出力ﾌｧｲﾙのｴﾝｺｰﾄﾞや、changeFileで指定の置換文字列"                              ).append( CR )
    987                        .append( "ﾌｧｲﾙのｴﾝｺｰﾄﾞ指定になります｡(changeFile は、必ず 出力ﾌｧｲﾙと)"                                 ).append( CR )
    988                        .append( "同じｴﾝｺｰﾄﾞです｡"                                                                                                                          ).append( CR )
    989                        .append( "これらのｴﾝｺｰﾄﾞが無指定の場合は、System.getProperty(\"file.encoding\") "            ).append( CR )
    990                        .append( "で求まる値を使用します｡"                                                                                                                 ).append( CR )
    991                        .append( "-changeFile を使用することで、複数行の文字列に置換することが可能です｡"            ).append( CR )
    992                        .append( CR )
    993                        .append( "上流(ﾌﾟﾛｾｽﾁｪｲﾝのﾃﾞｰﾀは上流から渡されます｡)からのLineModel の"                                  ).append( CR )
    994                        .append( "ﾌｧｲﾙｵﾌﾞｼﾞｪｸﾄより、指定の文字列が含まれているか検索します｡"                                  ).append( CR )
    995                        .append( "上流ﾌﾟﾛｾｽでは、Name 属性として、『File』を持ち、値は、Fileｵﾌﾞｼﾞｪｸﾄ"                       ).append( CR )
    996                        .append( "である、Process_FileSearch を使用するのが、便利です｡それ以外のｸﾗｽを"                        ).append( CR )
    997                        .append( "使用する場合でも、Name属性と、File ｵﾌﾞｼﾞｪｸﾄを持つ LineModel を受け渡し"            ).append( CR )
    998                        .append( "できれば、使用可能です｡"                                                                                                         ).append( CR )
    999                        .append( CR )
    1000                        .append( "引数文字列中に空白を含む場合は、ﾀﾞﾌﾞﾙｺｰﾃｰｼｮﾝ(\"\") で括って下さい｡"                  ).append( CR )
    1001                        .append( "引数文字列の 『=』の前後には、空白は挟めません｡必ず、-key=value の様に"           ).append( CR )
    1002                        .append( "繋げてください｡"                                                                                                                             ).append( CR )
    1003                        .append( CR ).append( CR )
    1004                        .append( getArgument().usage() ).append( CR );
    1005
    1006                return buf.toString();
    1007        }
    1008
    1009        /**
    1010         * このｸﾗｽは、main ﾒｿｯﾄﾞから実行できません｡
    1011         *
    1012         * @param       args    ｺﾏﾝﾄﾞ引数配列
    1013         */
    1014        public static void main( final String[] args ) {
    1015                LogWriter.log( new Process_Grep().usage() );
    1016        }
    1017}