    001/*
    002 * Copyright (c) 2017 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.fileexec;
    017
    018import java.util.Locale;
    019import java.util.Date;
    020import java.text.DateFormat;
    021import java.text.SimpleDateFormat;
    022
    023/**
    024 * StringUtilは、共通的に使用される文字列処理に関する、ユーティリティークラスです。
    025 *
    026 * @og.rev 7.0.0.0 (2017/07/07) 新規作成
    027 *
    028 * @version  7.0
    029 * @author   Kazuhiko Hasegawa
    030 * @since    JDK1.8,
    031 */
    032public final class StringUtil {
    033        /** システム依存の改行記号(String)。        */
    034        public static final String CR = System.getProperty("line.separator");
    035
    036        private static final int        BUFFER_MIDDLE    = 200 ;        // 7.2.9.5 (2020/11/28)
    037
    038        /**
    039         * デフォルトコンストラクターをprivateにして、
    040         * オブジェクトの生成をさせないようにする。
    041         */
    042        private StringUtil() {}
    043
    044        /**
    045         * 指定の文字列が nullか、ゼロ文字列 の場合は、２番目以降の引数から、null でない最初の値を返します。
    046         * ２番目以降の引数には、ゼロ文字列の指定も可能です。
    047         *
    048         * @param       val 判定する文字列
    049         * @param       def 初期値の可変長引数
    050         * @return      指定の値で、最初にnullでない値。最後まで、無ければ、val を返します。
    051         */
    052        public static String nval( final String val , final String... def ) {
    053                if( val == null || val.trim().isEmpty() ) {
    054                        for( final String str : def ) {
    055                                if( str != null ) { return str.trim(); }                        // ゼロ文字列 の場合も、返します。
    056                        }
    057                }
    058
    059                return val;             // 最後まで null以外の値が見つからない場合なので、val が null の場合もありうる。
    060        }
    061
    062        /**
    063         * 指定の数値型文字列が nullか、ゼロ文字列 の場合は、２番目の初期値を返します。
    064         *
    065         * そうでない場合は、指定の文字列を、Integer.parseInt( String ) した値を返します。
    066         *
    067         * @param       val 判定する数値型文字列
    068         * @param       def 初期値の数値
    069         * @return      指定の文字列が nullか、ゼロ文字列でない場合は、数値に変換し、そうでなければ、初期値の数値を返します。
    070         * @throws      NumberFormatException   文字列が解析可能な整数型を含まない場合。
    071         */
    072        public static int nval( final String val , final int def ) {
    073                return val == null || val.trim().isEmpty() ? def : Integer.parseInt( val.trim() );
    074        }
    075
    076        /**
    077         * LOGファイルのフォーマットに対応した日付、時刻文字列を作成します。
    078         *
    079         * yyyyMMddHHmmss 形式のフォーマットで、返します。
    080         *
    081         * @return      現在の日付、時刻文字列
    082         */
    083        public static String getTimeFormat() {
    084                return getTimeFormat( "yyyyMMddHHmmss" );
    085        }
    086
    087        /**
    088         * 指定のフォーマットに対応した日付、時刻文字列を作成します。
    089         *
    090         * 例えば、LOGフォーマットの場合は、yyyyMMdd HH:mm:ss 形式です。
    091         *
    092         * @param format        日付、時刻文字列のフォーマット
    093         * @return      指定のフォーマットに対応した現在の日付、時刻文字列
    094         */
    095        public static String getTimeFormat( final String format ) {
    096                final DateFormat formatter = new SimpleDateFormat( format , Locale.getDefault() );
    097                return formatter.format( new Date() );
    098        }
    099
    100        /**
    101         * 指定のフォーマットに対応した日付、時刻文字列を作成します。
    102         *
    103         * 例えば、LOGフォーマットの場合は、yyyyMMdd HH:mm:ss 形式です。
    104         *
    105         * @param date          フォーマットする日付情報
    106         * @param format        日付、時刻文字列のフォーマット
    107         * @return      指定のフォーマットに対応した指定の日付、時刻文字列
    108         */
    109        public static String getTimeFormat( final long date , final String format ) {
    110                final DateFormat formatter = new SimpleDateFormat( format , Locale.getDefault() );
    111                return formatter.format( new Date( date ) );
    112        }
    113
    114        /**
    115         * EUROMAPの日付、時間文字列から、openGion系日付時刻文字列を作成します。
    116         *
    117         * 日付は、yyyyMMdd 形式で、時間は、HH:mm:ss 形式を標準としますが、
    118         * 数字以外の文字列を削除して、連結した文字列を作成します。
    119         * その場合、１４桁数字文字列 になります。
    120         *
    121         * その形式に合わない場合は、現在時刻 を返します。
    122         * ただし、日付の整合性チェックは、行っていません。
    123         *
    124         * @og.rev 7.0.5.1 (2019/09/27) 日付指定がない場合に、無条件に１秒待つ仕様を廃止。ロジックミス修正
    125         * @og.rev 7.2.5.0 (2020/06/01) 日付と時間を分けて取得します。
    126         *
    127         * @param       ymd EUROMAPの日付(yyyyMMdd形式の８桁数字)
    128         * @param       hms EUROMAPの時間(HH:mm:ss形式の８桁数字)
    129         * @return      openGion系日付時刻文字列(yyyyMMddHHmmss形式の１４桁数字)
    130         */
    131        public static String getTime( final String ymd , final String hms ) {
    132                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
    133
    134                // yyyyMMdd形式のはずだが、数字以外を削除する。
    135                if( ymd != null && ymd.length() == 8 ) {
    136                        for( int i=0; i<ymd.length(); i++ ) {
    137                                final char ch = ymd.charAt( i );
    138                                if( '0' <= ch && ch <= '9' ) { buf.append( ch ); }      // 数字のみ許可
    139                        }
    140                }
    141                else {
    142                        buf.append( getTimeFormat( "yyyyMMdd" ) );      // 現在の年月日
    143                }
    144
    145                // HH:mm:ss形式のはずだが、数字以外を削除する。
    146                if( hms != null && hms.length() == 8 ) {
    147                        for( int i=0; i<hms.length(); i++ ) {
    148                                // HH:mm:ss形式から、':'を取り除く
    149                                final char ch = hms.charAt( i );
    150                                if( '0' <= ch && ch <= '9' ) { buf.append( ch ); }      // 数字のみ許可
    151                        }
    152                }
    153                else {
    154                        buf.append( getTimeFormat( "HHmmss" ) );        // 現在の時分秒
    155                }
    156
    157                // 14桁でなければ、現在時刻を設定します。日付の整合性チェックは行いません。
    158                if( buf.length() != 14 ) {
    159                        buf.append( getTimeFormat() );                          // 現在時刻
    160                }
    161
    162                // １秒待たないので、時刻が重複するとエラーになる可能性があります。
    163                return buf.toString();
    164        }
    165
    166//      public static String getTime( final String ymd , final String hms ) {
    167//              if( ymd != null && hms != null ) {
    168//                      if( ymd.length() == 8 && hms.length() == 8 ) {
    169//                              return ymd + hms.substring(0,2) + hms.substring(3,5) + hms.substring(6);
    170//                      }
    171//                      else {
    172//                              final StringBuilder buf = new StringBuilder();
    173//                              for( int i=0; i<ymd.length(); i++ ) {
    174//                                      final char ch = ymd.charAt( i );
    175////                                    if( '0' <= ch && ch < '9' ) { buf.append( ch ); }       // 数字のみ許可
    176//                                      if( '0' <= ch && ch <= '9' ) { buf.append( ch ); }      // 数字のみ許可               7.0.5.1 (2019/09/27)
    177//                              }
    178//
    179//                              for( int i=0; i<hms.length(); i++ ) {
    180//                                      final char ch = hms.charAt( i );
    181////                                    if( '0' <= ch && ch < '9' ) { buf.append( ch ); }       // 数字のみ許可
    182//                                      if( '0' <= ch && ch <= '9' ) { buf.append( ch ); }      // 数字のみ許可               7.0.5.1 (2019/09/27)
    183//                              }
    184//
    185//                              if( buf.length() == 14 ) {
    186//                                      return buf.toString();
    187//                              }
    188//                      }
    189//              }
    190//
    191//              // 引数が存在しない場合は、無条件に１秒待ちます。
    192//              // これは、テスト用に日付無しのDATデータを用意して登録する場合に使用します。
    193////            try{ Thread.sleep( 1000 ); } catch( final InterruptedException ex ){}
    194//
    195//              return getTimeFormat() ;                // 現在時刻
    196//      }
    197
    198        /**
    199         * すべての引数をｽﾍﾟｰｽで連結してtrim()した文字列を返します。
    200         *
    201         * 各引数は、それぞれ、trim()した後、ｽﾍﾟｰｽで連結します。
    202         * ただし、引数が、nullの場合は、ゼロ文字列に変換します。
    203         * すべてを連結して、trim() した結果が、ゼロ文字列の場合は、defVal を返します。
    204         *
    205         * @og.rev 7.2.5.0 (2020/06/01) 日付と時間を分けて取得します。
    206         *
    207         * @param       defVal 結果が、ゼロ文字列の場合に返す文字列
    208         * @param       keys 連結したいキーの可変長文字列配列
    209         * @return      合成した文字列。
    210         */
    211        public static String keyAppend( final String defVal , final String... keys ) {
    212                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
    213                for( final String key : keys ) {
    214//                      buf.append( nval( key , "" ).trim() ).append( ' ' );
    215                        final String key2 = nval( key , "" ).trim();
    216                        if( !key2.isEmpty() ) { buf.append( key2 ).append( ' ' ); }             // keyがゼロ文字列でなければ連結する。
    217                }
    218
    219                return nval( buf.toString().trim() , defVal );          // 最後のスペースを削除
    220        }
    221
    222        /**
    223         * 指定の文字列を、引数の文字で、前後に分割します。
    224         * キー=値 や、キー,値 などの文字列を分割して、キーと値の配列を返します。
    225         * １番目の配列[0] は、キー(chの左側)、２番目の配列[1] は、値(chの右側) です。
    226         * キーも値も、前後のスペースを削除:trim() します。
    227         * キー(１番目の配列[0])は、空文字列を許可しません。
    228         *
    229         * 分割文字を含まない場合や、キーが、空文字列の場合は、null を返します。
    230         * nullで無い場合は、配列は、必ず２個存在しそのどちらの値も、null を含みません。
    231         *
    232         * @param       line    １行分の文字列
    233         * @param       ch      分割する文字
    234         * @return      キーと値の配列。無ければ、null
    235         */
    236        public static String[] split( final String line , final char ch ) {
    237                final int ad = line.indexOf( ch );
    238                if( ad > 0 ) {          // ch が先頭は無視
    239                        final String key = line.substring( 0,ad ).trim();
    240
    241                        if( !key.isEmpty() ) {
    242                                final String val = line.substring( ad+1 ).trim();
    243                                return new String[] { key , val };
    244                        }
    245                }
    246                return null;
    247        }
    248
    249        /**
    250         * 数字型文字列の一致をチェックします。
    251         *
    252         * 処理としては、数字型文字列以外を判定条件に入れても、同一文字列の場合は、
    253         * true になります。
    254         * ここでは、どちらかの文字列の末尾が、".0" などの場合に、同じかどうかを
    255         * 数値に変換して、判定します。
    256         * どちらかが、null の場合は、必ず false になります。両方とも、null でも false です。
    257         *
    258         * @param       val1 判定用の引数１
    259         * @param       val2 判定用の引数２
    260         * @return      判定結果(一致する場合は、true)
    261         */
    262        public static boolean numEquals( final String val1 , final String val2 ) {
    263                if( val1 == null || val2 == null ) { return false; }
    264
    265                if( val1.equals( val2 ) ) {
    266                        return true;
    267                }
    268                else {
    269                        try {
    270//                              return Double.parseDouble( val1 ) == Double.parseDouble( val2 );
    271
    272                                // 6.9.8.0 (2018/05/28) FindBugs:浮動小数点の等価性のためのテスト
    273                                // FindBugs では、等価性のための比較は、if (Math.abs(x - y) < .0000001) が推奨されています。
    274                                // return Math.abs( Double.parseDouble( val1 ) - Double.parseDouble( val2 ) ) < .0000001 ;
    275                                return Double.compare( Double.parseDouble( val1 ) , Double.parseDouble( val2 ) ) == 0 ;
    276                        }
    277                        catch( final NumberFormatException ex ) {
    278                                ;       // 数値変換できなかった場合は、無視します。
    279                        }
    280                }
    281
    282                return false;
    283        }
    284
    285        /**
    286         * 変数の置き換え処理を行います。
    287         *
    288         * これは、org.opengion.fukurou.util.StringUtil#replaceText( String ,String ,String ,UnaryOperator&lt;String&gt; ) の簡易版です。
    289         *
    290         * これは、単純な変数ではなく、${env.XXX}または、{&#064;ENV..XXX} の XXX を環境変数に置き換えたり、
    291         * {&#064;DATE.XXXX} を、日付文字列に置き換えたりする場合に、使用できます。
    292         *
    293         * fukurou.util.StringUtil#replaceText 版との違いは、1回のみの変換と、ENV と DATE を決め打ちで処理していることです。
    294         * fileexec パッケージに、util  パッケージと同じクラス名のStringUtil を作ったのがそもそもの間違いで、
    295         * fileexec パッケージ単独で使用していたものを、統合するにあたり、統合しきれなかったメソッドが
    296         * 悪さしています。
    297         *
    298         * @og.rev 7.2.5.0 (2020/06/01) 新規追加
    299         *
    300         * @param  orgTxt 変換元の文字列
    301         * @return  置換処理したテキスト
    302         */
    303        public static String replaceText( final String orgTxt ) {
    304                // null か、ゼロ文字列か、'{' を含んでいない場合は、そのまま返します。
    305                if( orgTxt == null || orgTxt.isEmpty() || orgTxt.indexOf( '{' ) < 0 ) { return orgTxt; }
    306
    307                final StringBuilder buf = new StringBuilder( orgTxt );
    308
    309                // 環境変数の処理
    310                int st1 = buf.indexOf( "${env." );
    311                if( st1 < 0 ) { st1 = buf.indexOf( "{@ENV." ); }                                // どちらか
    312                if( st1 >= 0 ) {                                                                                                // 環境変数の処理がある
    313                        final int ed1 = buf.indexOf( "}",st1+6 );
    314                        final String envKey = buf.substring( st1+6,ed1 );
    315                        final String envVal = nval( System.getenv( envKey ), "" );      // 未定義の場合は、ゼロ文字列
    316                        buf.replace( st1,ed1+1,envVal );
    317                }
    318                else {                                                                                                                  // どちらか
    319                        // 日付文字列の処理
    320                        st1 = buf.indexOf( "{@DATE." );
    321                        if( st1 >= 0 ) {                                                                                        // 日付文字列の処理がある
    322                                final int ed1 = buf.indexOf( "}",st1+7 );
    323                                final String dateKey = buf.substring( st1+7,ed1 );
    324                                final String dateVal = getTimeFormat( dateKey );                // 未定義の場合は、ゼロ文字列
    325                                buf.replace( st1,ed1+1,dateVal );
    326                        }
    327                }
    328
    329                return buf.toString();
    330        }
    331}