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か、ゼロ文字列 の場合は、2番目以降の引数から、null でない最初の値を返します。 046 * 2番目以降の引数には、ゼロ文字列の指定も可能です。 047 * 048 * @param val 判定する文字列 049 * @param def 初期値の可変長引数 050 * @return 指定の値で、最初にnullでない値。最後まで、無ければ、val を返します。 051 */ 052 public static String nval( final String val , final String... def ) { 053 // 8.5.5.1 (2024/02/29) PMD 7.0.0 OnlyOneReturn メソッドには終了ポイントが 1 つだけ必要 054 String rtn = val; 055 056 // 8.5.4.2 (2024/01/12) PMD 7.0.0 InefficientEmptyStringCheck 対応 057// if( val == null || val.trim().isEmpty() ) { 058 if( val == null || val.isBlank() ) { // fukurou.util.StringUtil#isNull(String) を使いたくなかった。 059 for( final String str : def ) { 060// if( str != null ) { return str.trim(); } // ゼロ文字列 の場合も、返します。 061 if( str != null ) { // ゼロ文字列 の場合も、返します。 062// return str.trim(); 063 rtn = str.trim(); 064 break; 065 } 066 } 067 } 068 069// return val; // 最後まで null以外の値が見つからない場合なので、val が null の場合もありうる。 070 return rtn; // 最後まで null以外の値が見つからない場合なので、val が null の場合もありうる。 071 } 072 073 /** 074 * 指定の数値型文字列が nullか、ゼロ文字列 の場合は、2番目の初期値を返します。 075 * 076 * そうでない場合は、指定の文字列を、Integer.parseInt( String ) した値を返します。 077 * 078 * @param val 判定する数値型文字列 079 * @param def 初期値の数値 080 * @return 指定の文字列が nullか、ゼロ文字列でない場合は、数値に変換し、そうでなければ、初期値の数値を返します。 081 * @throws NumberFormatException 文字列が解析可能な整数型を含まない場合。 082 */ 083 public static int nval( final String val , final int def ) { 084 // 8.5.4.2 (2024/01/12) PMD 7.0.0 InefficientEmptyStringCheck 対応 085// return val == null || val.trim().isEmpty() ? def : Integer.parseInt( val.trim() ); 086 // fukurou.util.StringUtil#isNull(String) を使いたくなかった。 087 return val == null || val.isBlank() ? def : Integer.parseInt( val.trim() ); 088 } 089 090 /** 091 * LOGファイルのフォーマットに対応した日付、時刻文字列を作成します。 092 * 093 * yyyyMMddHHmmss 形式のフォーマットで、返します。 094 * 095 * @return 現在の日付、時刻文字列 096 */ 097 public static String getTimeFormat() { 098 return getTimeFormat( "yyyyMMddHHmmss" ); 099 } 100 101 /** 102 * 指定のフォーマットに対応した日付、時刻文字列を作成します。 103 * 104 * 例えば、LOGフォーマットの場合は、yyyyMMdd HH:mm:ss 形式です。 105 * 106 * @param format 日付、時刻文字列のフォーマット 107 * @return 指定のフォーマットに対応した現在の日付、時刻文字列 108 */ 109 public static String getTimeFormat( final String format ) { 110 final DateFormat formatter = new SimpleDateFormat( format , Locale.getDefault() ); 111 return formatter.format( new Date() ); 112 } 113 114 /** 115 * 指定のフォーマットに対応した日付、時刻文字列を作成します。 116 * 117 * 例えば、LOGフォーマットの場合は、yyyyMMdd HH:mm:ss 形式です。 118 * 119 * @param date フォーマットする日付情報 120 * @param format 日付、時刻文字列のフォーマット 121 * @return 指定のフォーマットに対応した指定の日付、時刻文字列 122 */ 123 public static String getTimeFormat( final long date , final String format ) { 124 final DateFormat formatter = new SimpleDateFormat( format , Locale.getDefault() ); 125 return formatter.format( new Date( date ) ); 126 } 127 128 /** 129 * EUROMAPの日付、時間文字列から、openGion系日付時刻文字列を作成します。 130 * 131 * 日付は、yyyyMMdd 形式で、時間は、HH:mm:ss 形式を標準としますが、 132 * 数字以外の文字列を削除して、連結した文字列を作成します。 133 * その場合、14桁数字文字列 になります。 134 * 135 * その形式に合わない場合は、現在時刻 を返します。 136 * ただし、日付の整合性チェックは、行っていません。 137 * 138 * @og.rev 7.0.5.1 (2019/09/27) 日付指定がない場合に、無条件に1秒待つ仕様を廃止。ロジックミス修正 139 * @og.rev 7.2.5.0 (2020/06/01) 日付と時間を分けて取得します。 140 * @og.rev 7.4.1.0 (2021/04/23) 1秒待つ仕様を復活。あくまで現在時刻を設定した場合のみ待ちます。 141 * 142 * @param ymd EUROMAPの日付(yyyyMMdd形式の8桁数字) 143 * @param hms EUROMAPの時間(HH:mm:ss形式の8桁数字) 144 * @return openGion系日付時刻文字列(yyyyMMddHHmmss形式の14桁数字) 145 */ 146 public static String getTime( final String ymd , final String hms ) { 147 final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE ); 148 149 // yyyyMMdd形式のはずだが、数字以外を削除する。 150 // 7.4.1.0 (2021/04/23) 仕様変更。null 出なければ、数字項目を削除してから、桁数を判定する。 151// if( ymd != null && ymd.length() == 8 ) { 152 if( ymd != null ) { 153 for( int i=0; i<ymd.length(); i++ ) { 154 final char ch = ymd.charAt( i ); 155 if( '0' <= ch && ch <= '9' ) { buf.append( ch ); } // 数字のみ許可 156 } 157 } 158// else { 159// buf.append( getTimeFormat( "yyyyMMdd" ) ); // 現在の年月日 160// } 161 162 final int ymdLen = buf.length(); 163 // 8桁以上の場合は、8桁に切る。 164 if( ymdLen > 8 ) { 165 buf.setLength( 8 ); 166 } 167 // 8桁未満の場合は、現在の年月日を後ろから埋める。つまり、年月しか指定されていなければ、日だけ現在になる。 168 else if( ymdLen < 8 ) { 169 buf.append( getTimeFormat( "yyyyMMdd" ).substring( ymdLen ) ); 170 } 171 // 8桁の場合は、日付として不正でも無条件で信頼する。 172 173 // HH:mm:ss形式のはずだが、数字以外を削除する。 174// if( hms != null && hms.length() == 8 ) { 175 if( hms != null ) { 176 for( int i=0; i<hms.length(); i++ ) { 177 // HH:mm:ss形式から、':'を取り除く 178 final char ch = hms.charAt( i ); 179 if( '0' <= ch && ch <= '9' ) { buf.append( ch ); } // 数字のみ許可 180 } 181 } 182// else { 183// buf.append( getTimeFormat( "HHmmss" ) ); // 現在の時分秒 184// } 185 186 final int ymdhmsLen = buf.length(); 187 // 14桁以上の場合は、14桁に切る。 188 if( ymdhmsLen > 14 ) { 189 buf.setLength( 14 ); 190 } 191 // 14桁未満の場合は、現在の現在時刻を後ろから埋める。日付は埋めているので、日時の不足分を補完する。 192 else if( ymdhmsLen < 14 ) { 193 buf.append( getTimeFormat().substring( ymdhmsLen ) ); 194 195 // 現在時刻を後ろから埋めた場合は、無条件に1秒待ちます。 196 // これは、テスト用に日付無しのDATデータを用意して登録する場合に使用します。 197// try{ Thread.sleep( 1000 ); } catch( final InterruptedException ex ){} 198// try{ Thread.sleep( 1100 ); } catch( final InterruptedException ex ){} // 7.4.4.0 (2021/06/30) 何となく重複エラーが出るので 199 try { Thread.sleep( 1100 ); } catch( final InterruptedException ignored ) {} // 8.5.4.2 (2024/01/12) PMD 7.0.0 EmptyCatchBlock 200 } 201 202// // 14桁でなければ、現在時刻を設定します。日付の整合性チェックは行いません。 203// if( buf.length() != 14 ) { 204// buf.append( getTimeFormat() ); // 現在時刻 205// } 206 207// // 1秒待たないので、時刻が重複するとエラーになる可能性があります。 208 return buf.toString(); 209 } 210 211// public static String getTime( final String ymd , final String hms ) { 212// if( ymd != null && hms != null ) { 213// if( ymd.length() == 8 && hms.length() == 8 ) { 214// return ymd + hms.substring(0,2) + hms.substring(3,5) + hms.substring(6); 215// } 216// else { 217// final StringBuilder buf = new StringBuilder(); 218// for( int i=0; i<ymd.length(); i++ ) { 219// final char ch = ymd.charAt( i ); 220//// if( '0' <= ch && ch < '9' ) { buf.append( ch ); } // 数字のみ許可 221// if( '0' <= ch && ch <= '9' ) { buf.append( ch ); } // 数字のみ許可 7.0.5.1 (2019/09/27) 222// } 223// 224// for( int i=0; i<hms.length(); i++ ) { 225// final char ch = hms.charAt( i ); 226//// if( '0' <= ch && ch < '9' ) { buf.append( ch ); } // 数字のみ許可 227// if( '0' <= ch && ch <= '9' ) { buf.append( ch ); } // 数字のみ許可 7.0.5.1 (2019/09/27) 228// } 229// 230// if( buf.length() == 14 ) { 231// return buf.toString(); 232// } 233// } 234// } 235// 236// // 引数が存在しない場合は、無条件に1秒待ちます。 237// // これは、テスト用に日付無しのDATデータを用意して登録する場合に使用します。 238//// try{ Thread.sleep( 1000 ); } catch( final InterruptedException ex ){} 239// 240// return getTimeFormat() ; // 現在時刻 241// } 242 243 /** 244 * すべての引数をスペースで連結してtrim()した文字列を返します。 245 * 246 * 各引数は、それぞれ、trim()した後、スペースで連結します。 247 * ただし、引数が、nullの場合は、ゼロ文字列に変換します。 248 * すべてを連結して、trim() した結果が、ゼロ文字列の場合は、defVal を返します。 249 * 250 * @og.rev 7.2.5.0 (2020/06/01) 日付と時間を分けて取得します。 251 * 252 * @param defVal 結果が、ゼロ文字列の場合に返す文字列 253 * @param keys 連結したいキーの可変長文字列配列 254 * @return 合成した文字列。 255 */ 256 public static String keyAppend( final String defVal , final String... keys ) { 257 final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE ); 258 for( final String key : keys ) { 259// buf.append( nval( key , "" ).trim() ).append( ' ' ); 260 final String key2 = nval( key , "" ).trim(); 261 if( !key2.isEmpty() ) { buf.append( key2 ).append( ' ' ); } // keyがゼロ文字列でなければ連結する。 262 } 263 264 return nval( buf.toString().trim() , defVal ); // 最後のスペースを削除 265 } 266 267 /** 268 * 指定の文字列を、引数の文字で、前後に分割します。 269 * キー=値 や、キー,値 などの文字列を分割して、キーと値の配列を返します。 270 * 1番目の配列[0] は、キー(chの左側)、2番目の配列[1] は、値(chの右側) です。 271 * キーも値も、前後のスペースを削除:trim() します。 272 * キー(1番目の配列[0])は、空文字列を許可しません。 273 * 274// * 分割文字を含まない場合や、キーが、空文字列の場合は、null を返します。 275 * 分割文字を含まない場合や、キーが、空文字列の場合は、長さゼロの配列 を返します。 276 * nullで無い場合は、配列は、必ず2個存在しそのどちらの値も、null を含みません。 277 * 278 * @og.rev 7.3.0.0 (2021/01/06) SpotBugs:null ではなく長さが0の配列を返すことを検討する 279 * 280 * @param line 1行分の文字列 281 * @param ch 分割する文字 282 * @return キーと値の配列。無ければ、長さゼロの配列 283 * @og.rtnNotNull 284 */ 285 public static String[] split( final String line , final char ch ) { 286 // 8.5.5.1 (2024/02/29) PMD 7.0.0 OnlyOneReturn メソッドには終了ポイントが 1 つだけ必要 287 String[] rtns = new String[0]; 288 289 final int ad = line.indexOf( ch ); 290 if( ad > 0 ) { // ch が先頭は無視 291 final String key = line.substring( 0,ad ).trim(); 292 293 if( !key.isEmpty() ) { 294 final String val = line.substring( ad+1 ).trim(); 295// return new String[] { key , val }; 296 rtns = new String[] { key , val }; 297 } 298 } 299// return null; 300// return new String[0]; 301 return rtns; 302 } 303 304 /** 305 * 数字型文字列の一致をチェックします。 306 * 307 * 処理としては、数字型文字列以外を判定条件に入れても、同一文字列の場合は、 308 * true になります。 309 * ここでは、どちらかの文字列の末尾が、".0" などの場合に、同じかどうかを 310 * 数値に変換して、判定します。 311 * どちらかが、null の場合は、必ず false になります。両方とも、null でも false です。 312 * 313 * @param val1 判定用の引数1 314 * @param val2 判定用の引数2 315 * @return 判定結果(一致する場合は、true) 316 */ 317 public static boolean numEquals( final String val1 , final String val2 ) { 318 if( val1 == null || val2 == null ) { return false; } 319 if( val1.equals( val2 ) ) { return true; } 320// else { 321 // 8.5.5.1 (2024/02/29) PMD 7.0.0 OnlyOneReturn メソッドには終了ポイントが 1 つだけ必要 322 boolean flag = false; 323 try { 324// return Double.parseDouble( val1 ) == Double.parseDouble( val2 ); 325 326 // 6.9.8.0 (2018/05/28) FindBugs:浮動小数点の等価性のためのテスト 327 // FindBugs では、等価性のための比較は、if (Math.abs(x - y) < .0000001) が推奨されています。 328 // return Math.abs( Double.parseDouble( val1 ) - Double.parseDouble( val2 ) ) < .0000001 ; 329// return Double.compare( Double.parseDouble( val1 ) , Double.parseDouble( val2 ) ) == 0 ; 330 flag = Double.compare( Double.parseDouble( val1 ) , Double.parseDouble( val2 ) ) == 0 ; 331 } 332 catch( final NumberFormatException ex ) { 333 System.err.println( ex.getMessage() ); // 8.0.0.0 (2021/07/31) 334// ; // 数値変換できなかった場合は、無視します。 335 } 336// } 337 338// return false; 339 return flag; 340 } 341 342 /** 343 * 変数の置き換え処理を行います。 344 * 345 * これは、org.opengion.fukurou.util.StringUtil#replaceText( String ,String ,String ,UnaryOperator<String> ) の簡易版です。 346 * 347 * これは、単純な変数ではなく、${env.XXX}または、{@ENV..XXX} の XXX を環境変数に置き換えたり、 348 * {@DATE.XXXX} を、日付文字列に置き換えたりする場合に、使用できます。 349 * 350 * fukurou.util.StringUtil#replaceText 版との違いは、1回のみの変換と、ENV と DATE を決め打ちで処理していることです。 351 * fileexec パッケージに、util パッケージと同じクラス名のStringUtil を作ったのがそもそもの間違いで、 352 * fileexec パッケージ単独で使用していたものを、統合するにあたり、統合しきれなかったメソッドが 353 * 悪さしています。 354 * 355 * @og.rev 7.2.5.0 (2020/06/01) 新規追加 356 * 357 * @param orgTxt 変換元の文字列 358 * @return 置換処理したテキスト 359 */ 360 public static String replaceText( final String orgTxt ) { 361 // null か、ゼロ文字列か、'{' を含んでいない場合は、そのまま返します。 362 if( orgTxt == null || orgTxt.isEmpty() || orgTxt.indexOf( '{' ) < 0 ) { return orgTxt; } 363 364 final StringBuilder buf = new StringBuilder( orgTxt ); 365 366 // 環境変数の処理 367 int st1 = buf.indexOf( "${env." ); 368 if( st1 < 0 ) { st1 = buf.indexOf( "{@ENV." ); } // どちらか 369 if( st1 >= 0 ) { // 環境変数の処理がある 370 final int ed1 = buf.indexOf( "}",st1+6 ); 371 final String envKey = buf.substring( st1+6,ed1 ); 372 final String envVal = nval( System.getenv( envKey ), "" ); // 未定義の場合は、ゼロ文字列 373 buf.replace( st1,ed1+1,envVal ); 374 } 375 else { // どちらか 376 // 日付文字列の処理 377 st1 = buf.indexOf( "{@DATE." ); 378 if( st1 >= 0 ) { // 日付文字列の処理がある 379 final int ed1 = buf.indexOf( "}",st1+7 ); 380 final String dateKey = buf.substring( st1+7,ed1 ); 381 final String dateVal = getTimeFormat( dateKey ); // 未定義の場合は、ゼロ文字列 382 buf.replace( st1,ed1+1,dateVal ); 383 } 384 } 385 386 return buf.toString(); 387 } 388}