    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.FileUtil;
    020import org.opengion.fukurou.util.FileString;
    021import org.opengion.fukurou.util.Closer ;
    022import org.opengion.fukurou.util.StringUtil ;
    023import org.opengion.fukurou.util.LogWriter;
    024
    025import java.util.Map ;
    026import java.util.LinkedHashMap ;
    027import java.util.List ;
    028import java.util.ArrayList ;
    029import java.util.Locale ;                               // 5.7.3.2 (2014/02/28) ignoreCase が実装されていなかった。
    030import java.util.regex.Pattern;                 // 5.7.3.2 (2014/02/28) regexを利用する場合
    031import java.util.regex.Matcher;                 // 5.7.3.2 (2014/02/28) regexを利用する場合
    032
    033import java.io.File;
    034import java.io.PrintWriter;
    035import java.io.BufferedReader;
    036import java.io.IOException;
    037
    038/**
    039 * Process_GrepChange は、上流から受け取った FileLineModelから、語句を
    040 * 置換する、ChainProcess インターフェースの実装クラスです。
    041 *
    042 * Process_Grep との違いは、チェックするファイルのコピーを(キーワードが存在
    043 * しなくとも)作成することと、検索キーに正規表現が使えない、複数行置き換えが
    044 * 出来ないことです。
    045 *
    046 * keywordFile より、置換する語句を含むキーと値のペアー(タブ区切り)を読取り、
    047 * 対象とする語句を置換します。
    048 * keywordFile に、タブが含まれない行や、先頭にタブが存在している場合は、
    049 * その行を読み飛ばします。また、区切りタブは何個存在しても構いません。
    050 * 置換文字(値)は、\t の特殊文字が使用できます。
    051 * この GrepChange では、語句に、正規表現は使用できません。正規表現のキーワード
    052 * や文字列を複数行の文字列と置き換える場合は、Process_Grep を使用してください。
    053 * このプログラムでは、上流から受け取った FileLineModel のファイルに対して、
    054 * 置き換えた結果も、同じファイルにセーブします。
    055 * 元のファイルを保存したい場合は、予めバックアップを取得しておいてください。
    056 * -inEncode は、入力ファイルのエンコード指定になります。
    057 * -outEncode は、出力ファイルのエンコードや、キーワードファイルの
    058 * エンコード指定になります。(keywordFile は、必ず 出力ファイルと同じエンコードです。)
    059 * これらのエンコードが無指定の場合は、System.getProperty("file.encoding") で
    060 * 求まる値を使用します。
    061 *
    062 * 5.7.3.2 (2014/02/28)
    063 * -regex=true で、ｷｰﾜｰﾄﾞに正規表現を利用できます。具体的には、String#replaceAll(String,String)
    064 * を利用して置換します。
    065 * 通常の置換処理は、indexOf で見つけて、StringBuilder#replace(int,int,String) を繰り返して処理しています。
    066 * -ignoreCase=true で、検索ｷｰﾜｰﾄﾞに大文字小文字を区別しない処理が可能です。
    067 *
    068 * 上流プロセスでは、Name 属性として、『File』を持ち、値は、Fileオブジェクト
    069 * である、Process_FileSearch を使用するのが、便利です。それ以外のクラスを
    070 * 使用する場合でも、Name属性と、File オブジェクトを持つ LineModel を受け渡し
    071 * できれば、使用可能です。
    072 *
    073 * 引数文字列中にスペースを含む場合は、ダブルコーテーション("") で括って下さい。
    074 * 引数文字列の 『=』の前後には、スペースは挟めません。必ず、-key=value の様に
    075 * 繋げてください。
    076 *
    077 *  Process_GrepChange -keyword=検索文字列 -ignoreCase=true -outfile=OUTFILE -encode=UTF-8
    078 *
    079 *    -keywordFile=キーワード    ：置換する語句を含むキーと値のペアー(タブ区切り)
    080 *   [-ignoreCase=[false/true] ] ：検索時に大文字小文字を区別しない(true)かどうか(初期値:false[区別する])
    081 *   [-regex=[false/true]      ] ：ｷｰﾜｰﾄﾞに正規表現を利用する(true)かどうか(初期値:false[利用しない])
    082 *   [-isChange=置換可否       ] ：置換処理を実施する(true)かどうか(初期値:置換する[true])
    083 *   [-inEncode=入力エンコード ] ：入力ファイルのエンコードタイプ
    084 *   [-outEncode=出力エンコード] ：出力ファイルやキーワードファイルのエンコードタイプ
    085 *   [-display=[false/true]    ] ：結果を標準出力に表示する(true)かしない(false)か(初期値:false[表示しない])
    086 *   [-debug=[false/true]      ] ：デバッグ情報を標準出力に表示する(true)かしない(false)か(初期値:false[表示しない])
    087 *
    088 * @version  4.0
    089 * @author   Kazuhiko Hasegawa
    090 * @since    JDK5.0,
    091 */
    092public class Process_GrepChange extends AbstractProcess implements ChainProcess {
    093        private String[]        keyword = null;
    094        private String[]        change  = null;
    095        private Pattern[]       pattern = null;                 // 5.7.3.2 (2014/02/28) ｷｰﾜｰﾄﾞに正規表現を利用する場合
    096        private boolean         ignoreCase      = false;
    097        private boolean         regex           = false;        // 5.7.3.2 (2014/02/28) ｷｰﾜｰﾄﾞに正規表現を利用するかどうか
    098        private boolean         isChange        = true;         // 5.1.2.0 (2010/01/01) 置換するかどうかを指定可能にする
    099        private String          inEncode        = null;
    100        private String          outEncode       = null;
    101        private boolean         display         = false;        // 表示しない
    102        private boolean         debug           = false;        // 5.7.3.0 (2014/02/07) デバッグ情報
    103
    104        private int             inCount         = 0;
    105        private int             findCount       = 0;
    106        private int             cngCount        = 0;
    107
    108        private static final Map<String,String> mustProparty   ;          // ［プロパティ］必須チェック用 Map
    109        private static final Map<String,String> usableProparty ;          // ［プロパティ］整合性チェック Map
    110
    111        static {
    112                mustProparty = new LinkedHashMap<String,String>();
    113                mustProparty.put( "keywordFile",        "置換する語句を含むキーと値のペアー(タブ区切り)(必須)" );
    114
    115                usableProparty = new LinkedHashMap<String,String>();
    116                usableProparty.put( "ignoreCase",       "検索時に大文字小文字を区別しない(true)かどうか。" +
    117                                                                                CR + "(初期値:区別する[false])" );
    118                usableProparty.put( "regex",            "ｷｰﾜｰﾄﾞに正規表現を利用する(true)かどうか。" +
    119                                                                                CR + "(初期値:利用しない[false])" );    // 5.7.3.2 (2014/02/28)
    120                usableProparty.put( "isChange",         "置換処理を実施する(true)かどうか" +
    121                                                                                CR + "(初期値:置換する[true])" );
    122                usableProparty.put( "inEncode",         "入力ファイルのエンコードタイプ" );
    123                usableProparty.put( "outEncode",        "出力ファイルやキーワードファイルのエンコードタイプ" );
    124                usableProparty.put( "display",          "結果を標準出力に表示する(true)かしない(false)か" +
    125                                                                                CR + "(初期値:false:表示しない)" );
    126                usableProparty.put( "debug",    "デバッグ情報を標準出力に表示する(true)かしない(false)か" +
    127                                                                                CR + "(初期値:false:表示しない)" );             // 5.7.3.0 (2014/02/07) デバッグ情報
    128        }
    129
    130        /**
    131         * デフォルトコンストラクター。
    132         * このクラスは、動的作成されます。デフォルトコンストラクターで、
    133         * super クラスに対して、必要な初期化を行っておきます。
    134         *
    135         */
    136        public Process_GrepChange() {
    137                super( "org.opengion.fukurou.process.Process_GrepChange",mustProparty,usableProparty );
    138        }
    139
    140        /**
    141         * プロセスの初期化を行います。初めに一度だけ、呼び出されます。
    142         * 初期処理(ファイルオープン、ＤＢオープン等)に使用します。
    143         *
    144         * @og.rev 5.1.2.0 (2010/01/01) 置換するかどうかを指定可能にする(isChange)属性追加
    145         * @og.rev 5.7.3.2 (2014/02/28) debug の表示と、ｷｰﾜｰﾄﾞの \t の使用、trim() 廃止、ignoreCase の実装、regex の追加
    146         *
    147         * @param   paramProcess データベースの接続先情報などを持っているオブジェクト
    148         */
    149        public void init( final ParamProcess paramProcess ) {
    150                Argument arg = getArgument();
    151
    152                String keywordFile = arg.getProparty("keywordFile" );
    153                ignoreCase      = arg.getProparty("ignoreCase",ignoreCase);
    154                regex           = arg.getProparty("regex",regex);                               // 5.7.3.2 (2014/02/28)
    155                isChange        = arg.getProparty("isChange",isChange);                 // 5.1.2.0 (2010/01/01)
    156                inEncode        = arg.getProparty("inEncode",System.getProperty("file.encoding"));
    157                outEncode       = arg.getProparty("outEncode",System.getProperty("file.encoding"));
    158                display         = arg.getProparty("display",display);
    159                debug           = arg.getProparty("debug",debug);                               // 5.7.3.0 (2014/02/07) デバッグ情報
    160//              if( debug ) { println( arg.toString() ); }                                      // 5.7.3.0 (2014/02/07) デバッグ情報
    161
    162                FileString fs = new FileString();
    163                fs.setFilename( keywordFile );
    164                fs.setEncode( outEncode );
    165//              String[] lines = fs.getValue( "\n" );
    166                String[] lines = fs.getValue( CR );                                     // 5.7.3.2 (2014/02/28) \n でなく、CR とします。
    167                int len = lines.length;
    168                if( len == 0 ) {
    169                        String errMsg = "keywordFile の内容が 読み取れませんでした。[" + keywordFile + "]" ;
    170                        throw new RuntimeException( errMsg );
    171                }
    172
    173                println( "keywordFile を、" + len + "件読み取りました。" );
    174                List<String> keyList = new ArrayList<String>( len );
    175                List<String> cngList = new ArrayList<String>( len );
    176
    177                for( int i=0; i<len; i++ ) {
    178        //              String line = lines[i].trim();
    179                        String line = lines[i];
    180                        int indx = line.indexOf( '\t' );
    181                        if( indx <= 0 ) { continue ; }       // TAB が先頭や、存在しない行は読み飛ばす。
    182                        // 5.7.3.2 (2014/02/28) debug の表示と、ｷｰﾜｰﾄﾞの \t の使用、trim() 廃止
    183//                      keyList.add( line.substring( 0,indx ).trim() );
    184//                      String cng = line.substring( indx+1 ).trim();
    185                        String key = line.substring( 0,indx );
    186                        String cng = line.substring( indx+1 );
    187
    188                        if( ignoreCase ) { key = key.toUpperCase(Locale.JAPAN); }       // 5.7.3.2 (2014/02/28) ignoreCase の実装漏れ
    189
    190                        if( debug ) { println( "[" + key + "]⇒[" + cng + "]" ); }
    191
    192//                      key = StringUtil.replace( key,"\\n",CR );               // 5.7.3.2 (2014/02/28) ｷｰﾜｰﾄﾞに \n は使えない。
    193                        key = StringUtil.replace( key,"\\t","\t" );
    194
    195//                      cng = StringUtil.replace( cng,"\\n",CR );               // 5.7.3.2 (2014/02/28) ｷｰﾜｰﾄﾞに \n は使えない。
    196                        cng = StringUtil.replace( cng,"\\t","\t" );
    197
    198                        keyList.add( key );
    199                        cngList.add( cng );
    200                }
    201                keyword = keyList.toArray( new String[keyList.size()] );
    202                change  = cngList.toArray( new String[cngList.size()] );
    203
    204                // 5.7.3.2 (2014/02/28) regex=true の場合の処理
    205                if( regex ) {
    206                        pattern = new Pattern[keyword.length];
    207                        for( int i=0; i<keyword.length; i++ ) {
    208                                pattern[i] = (ignoreCase) ? Pattern.compile( keyword[i],Pattern.CASE_INSENSITIVE )
    209                                                                                  : Pattern.compile( keyword[i] ) ;
    210                        }
    211                }
    212        }
    213
    214        /**
    215         * プロセスの終了を行います。最後に一度だけ、呼び出されます。
    216         * 終了処理(ファイルクローズ、ＤＢクローズ等)に使用します。
    217         *
    218         * @param   isOK トータルで、OKだったかどうか[true:成功/false:失敗]
    219         */
    220        public void end( final boolean isOK ) {
    221                // ここでは処理を行いません。
    222        }
    223
    224        /**
    225         * 引数の LineModel を処理するメソッドです。
    226         * 変換処理後の LineModel を返します。
    227         * 後続処理を行わない場合(データのフィルタリングを行う場合)は、
    228         * null データを返します。つまり、null データは、後続処理を行わない
    229         * フラグの代わりにも使用しています。
    230         * なお、変換処理後の LineModel と、オリジナルの LineModel が、
    231         * 同一か、コピー(クローン)かは、各処理メソッド内で決めています。
    232         * ドキュメントに明記されていない場合は、副作用が問題になる場合は、
    233         * 各処理ごとに自分でコピー(クローン)して下さい。
    234         *
    235         * @og.rev 4.0.0.0 (2007/11/28) メソッドの戻り値をチェックします。
    236         * @og.rev 5.1.2.0 (2010/01/01) 置換するかどうかを指定可能にする(isChange)属性追加
    237         * @og.rev 5.7.2.2 (2014/01/24) エラー時にデータも出力します。
    238         * @og.rev 5.7.3.2 (2014/02/28) debug の表示と、ignoreCase の実装
    239         *
    240         * @param       data    オリジナルのLineModel
    241         *
    242         * @return      処理変換後のLineModel
    243         */
    244        public LineModel action( final LineModel data ) {
    245                inCount++ ;
    246                final FileLineModel fileData ;
    247                if( data instanceof FileLineModel ) {
    248                        fileData = (FileLineModel)data ;
    249                }
    250                else {
    251                        String errMsg = "データが FileLineModel オブジェクトではありません。" + CR ;
    252                        throw new RuntimeException( errMsg );
    253                }
    254
    255
    256                File org = fileData.getFile() ;
    257                String orgName = org.getPath();
    258                if( ! org.isFile() ) { return data; }
    259
    260                if( debug ) { println( "File:" + org ); }               // 5.1.2.0 (2010/01/01) display の条件変更
    261
    262                BufferedReader reader = FileUtil.getBufferedReader( org,inEncode );
    263//              File            tempFile  = new File( org.getPath() + "_temp" );
    264//              PrintWriter     tempWrt   = FileUtil.getPrintWriter( tempFile,outEncode );
    265                File            tempFile  = null;
    266                PrintWriter     tempWrt   = null;
    267
    268                // 5.1.2.0 (2010/01/01) 置換する場合の前処理
    269                if( isChange ) {
    270                        tempFile  = new File( orgName + "_temp" );
    271                        tempWrt   = FileUtil.getPrintWriter( tempFile,outEncode );
    272                }
    273
    274                boolean nextFlag  = false;
    275
    276                try {
    277                        String line ;
    278                        int    lineNo = 0;
    279                        while((line = reader.readLine()) != null) {
    280                                lineNo++ ;
    281                                // 5.7.3.2 (2014/02/28) regex 対応
    282                                if( regex ) {
    283                                        for( int i=0; i<pattern.length; i++ ) {
    284                                                Matcher mt = pattern[i].matcher( line );
    285                                                nextFlag = mt.matches();
    286                                                if( nextFlag ) {
    287                                                        findCount++ ;
    288                                                        if( display ) { println( orgName + ":" + lineNo + ":" + keyword[i] + ":" + line ); }
    289                                                        if( isChange ) {
    290                                                                line = mt.replaceAll( change[i] );
    291                                                                cngCount++ ;
    292                                                        }
    293                                                }
    294                                        }
    295                                }
    296                                else {
    297                                        StringBuilder buf = new StringBuilder( line );
    298        //                              boolean foundFlag = false;              // 行単位に初期化する。
    299                                        for( int i=0; i<keyword.length; i++ ) {
    300                                                // 5.7.3.2 (2014/02/28) ignoreCase 対応。
    301        //                                      int indx = buf.indexOf( keyword[i] );
    302                                                int indx = (ignoreCase) ? buf.toString().toUpperCase(Locale.JAPAN).indexOf( keyword[i] )
    303                                                                                                : buf.indexOf( keyword[i] ) ;
    304
    305                                                // 置換対象発見。行出力用に見つかれば、true にする。
    306                                                if( indx >= 0 ) {
    307        //                                              foundFlag = true;
    308                                                        nextFlag  = true;               // １度でも見つかれば、true にセット
    309                                                        if( display ) { println( orgName + ":" + lineNo + ":" + keyword[i] + ":" + line ); }
    310                                                        findCount++ ;
    311                                                }
    312                                                // 置換対象が見つかっても、isChange=true でなければ、置換処理は行わない。
    313                                                if( isChange ) {
    314                                                        while( indx >= 0 ) {
    315                                                                buf.replace( indx,indx+keyword[i].length(),change[i] );
    316                                                                // 5.7.3.2 (2014/02/28) ignoreCase 対応。
    317        //                                                      indx = buf.indexOf( keyword[i],indx+change[i].length() );
    318                                                                int nxt = indx+change[i].length();
    319                                                                indx = (ignoreCase) ? buf.toString().toUpperCase(Locale.JAPAN).indexOf( keyword[i],nxt )
    320                                                                                                        : buf.indexOf( keyword[i],nxt );
    321
    322                //                                              nextFlag = true;                        // キーワードが存在したファイル。
    323                                                                cngCount++ ;
    324//                                                              findCount++ ;
    325                                                        }
    326                                                }
    327                                        }
    328                                        line = buf.toString();
    329                                }
    330                                // 5.1.2.0 (2010/01/01) 置換する場合の処理
    331                                if( isChange ) {
    332//                                      tempWrt.println( buf.toString() );
    333                                        tempWrt.println( line );                                // 5.7.3.2 (2014/02/28) regexで出力を共有する為。
    334                                }
    335                        }
    336                }
    337                catch ( IOException ex ) {
    338                        String errMsg = "処理中にエラーが発生しました。[" + data.getRowNo() + "]件目" + CR
    339//                                              + data.toString() ;
    340                                                +       "data=[" + data.dataLine() + "]" + CR ;         // 5.7.2.2 (2014/01/24) エラー時にデータも出力します。
    341                        throw new RuntimeException( errMsg,ex );
    342                }
    343                finally {
    344                        Closer.ioClose( reader );
    345                        Closer.ioClose( tempWrt );
    346                }
    347
    348                // 5.1.2.0 (2010/01/01) 置換する場合の処理
    349                if( isChange ) {
    350                        if( nextFlag ) {
    351                                if( !org.delete() ) {
    352                                        String errMsg = "所定のファイルを削除できませんでした。[" + org + "]" + CR
    353                                                        +       "data=[" + data.dataLine() + "]" + CR ;         // 5.7.2.2 (2014/01/24) エラー時にデータも出力します。
    354                                        throw new RuntimeException( errMsg );
    355                                }
    356                                if( !tempFile.renameTo( org ) ) {
    357                                        String errMsg = "所定のファイルをリネームできませんでした。[" + tempFile + "]" + CR
    358                                                        +       "data=[" + data.dataLine() + "]" + CR ;         // 5.7.2.2 (2014/01/24) エラー時にデータも出力します。
    359                                        throw new RuntimeException( errMsg );
    360                                }
    361                        }
    362                        else {
    363                                if( !tempFile.delete() ) {
    364                                        String errMsg = "所定のファイルを削除できませんでした。[" + tempFile + "]" + CR
    365                                                        +       "data=[" + data.dataLine() + "]" + CR ;         // 5.7.2.2 (2014/01/24) エラー時にデータも出力します。
    366                                        throw new RuntimeException( errMsg );
    367                                }
    368                        }
    369                }
    370
    371                return (nextFlag) ? data : null ;
    372        }
    373
    374        /**
    375         * プロセスの処理結果のレポート表現を返します。
    376         * 処理プログラム名、入力件数、出力件数などの情報です。
    377         * この文字列をそのまま、標準出力に出すことで、結果レポートと出来るような
    378         * 形式で出してください。
    379         *
    380         * @return   処理結果のレポート
    381         */
    382        public String report() {
    383                String report = "[" + getClass().getName() + "]" + CR
    384                                + TAB + "Search File Count : " + inCount    + CR
    385                                + TAB + "Key Find    Count : " + findCount  + CR
    386                                + TAB + "Key Change  Count : " + cngCount ;
    387
    388                return report ;
    389        }
    390
    391        /**
    392         * このクラスの使用方法を返します。
    393         *
    394         * @return      このクラスの使用方法
    395         */
    396        public String usage() {
    397                StringBuilder buf = new StringBuilder();
    398
    399                buf.append( "Process_GrepChange は、上流から受け取った FileLineModelから、語句を"                        ).append( CR );
    400                buf.append( "置換する、ChainProcess インターフェースの実装クラスです。"                                               ).append( CR );
    401                buf.append( "Process_Grep との違いは、チェックするファイルのコピーを(キーワードが存在"               ).append( CR );
    402                buf.append( "しなくとも)作成することと、検索キーに正規表現が使えない、複数行置き換えが"             ).append( CR );
    403                buf.append( "出来ないことです。"                                                                                                                 ).append( CR );
    404                buf.append( CR );
    405                buf.append( "keywordFile より、置換する語句を含むキーと値のペアー(タブ区切り)を読取り、"      ).append( CR );
    406                buf.append( "対象とする語句を置換します。"                                                                                                    ).append( CR );
    407                buf.append( "keywordFile に、タブが含まれない行や、先頭にタブが存在している場合は、"         ).append( CR );
    408                buf.append( "その行を読み飛ばします。また、区切りタブは何個存在しても構いません。"                        ).append( CR );
    409                buf.append( "ただし、タブで区切った前(キー)と後ろ(値)は、trim() されますので、スペース"                ).append( CR );
    410                buf.append( "が前後に存在している場合は、ご注意ください。"                                                                    ).append( CR );
    411                buf.append( "置換文字(値)は、\t と \n の特殊文字が使用できます。"                                                    ).append( CR );
    412                buf.append( "この GrepChange では、語句に、正規表現は使用できません。正規表現のキーワード"      ).append( CR );
    413                buf.append( "や文字列を複数行の文字列と置き換える場合は、Process_Grep を使用して下さい。"      ).append( CR );
    414                buf.append( "このプログラムでは、上流から受け取った FileLineModel のファイルに対して、"              ).append( CR );
    415                buf.append( "置き換えた結果も、同じファイルにセーブします。"                                                           ).append( CR );
    416                buf.append( "元のファイルを保存したい場合は、予めバックアップを取得しておいてください。"     ).append( CR );
    417                buf.append( "-inEncode は、入力ファイルのエンコード指定になります。"                                          ).append( CR );
    418                buf.append( "-outEncode は、出力ファイルのエンコードや、キーワードファイルのエンコード"        ).append( CR );
    419                buf.append( "指定になります。(keywordFile は、必ず 出力ファイルと同じエンコードです。)"      ).append( CR );
    420                buf.append( "これらのエンコードが無指定の場合は、System.getProperty(\"file.encoding\") "  ).append( CR );
    421                buf.append( "で求まる値を使用します。"                                                                                                              ).append( CR );
    422                buf.append( CR );
    423                buf.append( "上流プロセスでは、Name 属性として、『File』を持ち、値は、Fileオブジェクト"               ).append( CR );
    424                buf.append( "である、Process_FileSearch を使用するのが、便利です。それ以外のクラスを"             ).append( CR );
    425                buf.append( "使用する場合でも、Name属性と、File オブジェクトを持つ LineModel を受け渡し"   ).append( CR );
    426                buf.append( "できれば、使用可能です。"                                                                                                              ).append( CR );
    427                buf.append( CR );
    428                buf.append( "引数文字列中に空白を含む場合は、ダブルコーテーション(\"\") で括って下さい。" ).append( CR );
    429                buf.append( "引数文字列の 『=』の前後には、空白は挟めません。必ず、-key=value の様に"                ).append( CR );
    430                buf.append( "繋げてください。"                                                                                                                          ).append( CR );
    431                buf.append( CR ).append( CR );
    432
    433                buf.append( getArgument().usage() ).append( CR );
    434
    435                return buf.toString();
    436        }
    437
    438        /**
    439         * このクラスは、main メソッドから実行できません。
    440         *
    441         * @param       args    コマンド引数配列
    442         */
    443        public static void main( final String[] args ) {
    444                LogWriter.log( new Process_GrepChange().usage() );
    445        }
    446}