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.ResourceBundle; 019import java.util.PropertyResourceBundle; 020import java.util.Locale; 021import java.util.Arrays; 022import java.text.MessageFormat; 023 024import java.io.InputStream; 025import java.io.InputStreamReader; 026import java.io.BufferedReader; 027import java.io.IOException; 028import java.net.URL; 029import java.net.URLConnection; 030 031import static java.nio.charset.StandardCharsets.UTF_8; 032 033/** 034 * MsgUtilは、共通的に使用されるリソースからメッセージを作成する、ユーティリティークラスです。 035 * 036 *<pre> 037 * 現状は、{@value F_BS_NM}.properties ファイルをリソースとして使用します。 038 * このリソースファイルを、各言語別に作成することで、アプリケーションのメッセージを国際化できます。 039 * 通常のリソース変換以外に、キーワードと引数で、RuntimeException を返す簡易メソッドも提供します。 040 * 041 *</pre> 042 * @og.rev 7.0.0.0 (2017/07/07) 新規作成 043 * 044 * @version 7.0 045 * @author Kazuhiko Hasegawa 046 * @since JDK1.8, 047 */ 048public final class MsgUtil { 049 private static final XLogger LOGGER= XLogger.getLogger( MsgUtil.class.getName() ); // ログ出力 050 051 /** 初期設定されているリソースバンドルのbaseName {@value} */ 052 public static final String F_BS_NM = "org.opengion.fukurou.message" ; 053 054 private static final int BUFFER_MIDDLE = 200 ; 055 private static final int STACKTRACE_COUNT = 5 ; 056 057 /** 058 * デフォルトコンストラクターをprivateにして、 059 * オブジェクトの生成をさせないようにする。 060 */ 061 private MsgUtil() {} 062 063 /** 064 * "jp.euromap.eu63.message" の、Locale.getDefault() リソースから取得するメッセージを文字列で返します。 065 * 066 * id と引数を受け取り、ResourceBundle と、MessageFormat.format で加工した 067 * 文字列を返します。 068 * baseName は、F_BS_NM で、Locale に、Locale.getDefault() を指定したメッセージを作成します。 069 * 070 * @og.rev 6.4.3.1 (2016/02/12) 新規追加 071 * @og.rev 6.8.1.5 (2017/09/08) LOGGER.debug 情報の追加 072 * 073 * @param id リソースのキーとなるID。 074 * @param args リソースを、MessageFormat.format で加工する場合の引数。 075 * 076 * @return MessageFormat.formatで加工された文字列 077 * @see #F_BS_NM 078 */ 079 public static String getMsg( final String id , final Object... args ) { 080 081 // リソースバンドルのすべてがキャッシュに格納される・・・はず。 082 final ResourceBundle resource = ResourceBundle.getBundle( F_BS_NM , Locale.getDefault() , UTF8_CONTROL ); 083 084 try { 085 return id + ":" + MessageFormat.format( resource.getString( id ) , args ); 086 } 087 catch( final RuntimeException ex ) { 088 final String errMsg = id + "[" + Arrays.toString ( args ) + "]" ; 089 LOGGER.warning( ex , () -> "【WARNING】 " + errMsg ); 090 return errMsg ; 091 } 092 } 093 094 /** 095 * メッセージを作成して、RuntimeExceptionの引数にセットして、throw します。 096 * 097 * @og.rev 6.4.3.1 (2016/02/12) 新規追加 098 * 099 * @param id リソースのキーとなるID。 100 * @param args リソースを、MessageFormat.format で加工する場合の引数。 101 * @return メッセージを書き込んだ、RuntimeException 102 * 103 * @see #getMsg( String,Object... ) 104 * @see #throwException( Throwable,String,Object... ) 105 */ 106 public static RuntimeException throwException( final String id , final Object... args ) { 107 return throwException( null , id , args ); 108 } 109 110 /** 111 * メッセージを作成して、RuntimeExceptionの引数にセットして、throw します。 112 * 113 * @og.rev 6.4.3.1 (2016/02/12) 新規追加 114 * @og.rev 6.8.1.5 (2017/09/08) LOGGER.debug 情報の追加 115 * 116 * @param th 発生元のThrowable( null値は許容されます ) 117 * @param id リソースのキーとなるID。 118 * @param args リソースを、MessageFormat.format で加工する場合の引数。 119 * @return メッセージを書き込んだ、RuntimeException 120 * 121 * @see #getMsg( String,Object... ) 122 * @see #throwException( String,Object... ) 123 */ 124 public static RuntimeException throwException( final Throwable th , final String id , final Object... args ) { 125 final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE ) 126 .append( getMsg( id , args ) ); 127 128 if( th != null ) { 129 buf.append( "\n\t" ).append( th.getMessage() ); 130 } 131 132 // ラムダ式で、Exception が throw された場合、上位にアップされない。(非検査例外(RuntimeException系)なら、スローできる・・・はず) 133 // 原因がわかるまで、とりあえず、printStackTrace しておきます。 134 final String errMsg = buf.toString(); 135 final RuntimeException ex = new RuntimeException( errMsg , th ); 136 LOGGER.warning( ex , () -> "【WARNING】 " + errMsg ); 137 return ex; 138 } 139 140 /** 141 * エラーメッセージを作成して、LOGGER で出力します。 142 * 143 * @og.rev 6.4.3.1 (2016/02/12) 新規追加 144 * 145 * @param id リソースのキーとなるID。 146 * @param args リソースを、MessageFormat.format で加工する場合の引数。 147 * @return 作成されたエラーメッセージ文字列 148 * 149 * @see #getMsg( String,Object... ) 150 */ 151 public static String errPrintln( final String id , final Object... args ) { 152 return errPrintln( null , id , args ); 153 } 154 155 /** 156 * Throwable付きのエラーメッセージを作成して、LOGGER で出力します。 157 * 158 * @og.rev 6.4.3.1 (2016/02/12) 新規追加 159 * 160 * @param th 発生元のThrowable( null値は許容されます ) 161 * @param id リソースのキーとなるID。 162 * @param args リソースを、MessageFormat.format で加工する場合の引数。 163 * @return 作成されたエラーメッセージ文字列 164 * 165 * @see #getMsg( String,Object... ) 166 */ 167 public static String errPrintln( final Throwable th , final String id , final Object... args ) { 168 final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE ) 169 .append( getMsg( id , args ) ); 170 171 if( th != null ) { 172 buf.append( "\n\t" ).append( th.getMessage() ); 173 174 int cnt = 0; 175 for( final StackTraceElement stEle : th.getStackTrace() ) { 176 final String clnNm = stEle.getClassName(); 177 178 if( clnNm.contains( "MsgUtil" ) ) { continue; } 179 180 if( clnNm.contains( "jp.euromap.eu63" ) || cnt < STACKTRACE_COUNT ) { 181 buf.append( "\n\t" ).append( stEle.toString() ); 182 cnt++; 183 } 184 } 185 } 186 187 LOGGER.warning( () -> "【WARNING】 " + buf.toString() ); 188 189 return buf.toString(); 190 } 191 192 /** 193 * ResourceBundle.Controlは、バンドル・ロード処理中にResourceBundle.getBundleファクトリによって呼び出される一連のコールバック・メソッドを定義します。 194 * 195 * @og.rev 6.4.3.1 (2016/02/12) 新規追加 196 */ 197 private static final ResourceBundle.Control UTF8_CONTROL = new ResourceBundle.Control() { 198 /** 199 * 指定された形式とロケールを持つ指定されたバンドル名のリソース・バンドルを、指定されたクラス・ローダーを必要に応じて使用してインスタンス化します。 200 * 201 * 指定されたパラメータに対応する使用可能なリソース・バンドルが存在しない場合、このメソッドはnullを返します。 202 * 予想外のエラーが発生したためにリソース・バンドルのインスタンス化が行えない場合には、単純にnullを返す代わりに、 203 * ErrorまたはExceptionをスローすることでエラーを報告する必要があります。 204 * reloadフラグがtrueの場合、それは、以前にロードされたリソース・バンドルの有効期限が切れたためにこのメソッドが呼び出されたことを示します。 205 * 206 * @og.rev 6.4.3.1 (2016/02/12) 新規追加 207 * 208 * @param baseName リソース・バンドルの基底バンドル名。完全指定クラス名 209 * @param locale リソース・バンドルのインスタンス化対象となるロケール 210 * @param format ロードされるリソース・バンドルの形式 211 * @param loader バンドルをロードするために使用するClassLoader 212 * @param reload バンドルの再ロードを示すフラグ。有効期限の切れたリソース・バンドルを再ロードする場合はtrue、それ以外の場合はfalse 213 * 214 * @return ResourceBundle.Controオブジェクト 215 * 216 * @throws NullPointerException bundleName、locale、format、またはloaderがnullの場合、またはtoBundleNameからnullが返された場合 217 * @throws IllegalArgumentException formatが不明である場合、または指定されたパラメータに対して見つかったリソースに不正なデータが含まれている場合。 218 * @throws ClassCastException ロードされたクラスをResourceBundleにキャストできない場合 219 * @throws IllegalAccessException クラスまたはその引数なしのコンストラクタにアクセスできない場合。 220 * @throws InstantiationException クラスのインスタンス化が何かほかの理由で失敗する場合。 221 * @throws ExceptionInInitializerError このメソッドによる初期化に失敗した場合。 222 * @throws SecurityException セキュリティ・マネージャが存在し、新しいインスタンスの作成が拒否された場合。詳細は、Class.newInstance()を参照してください。 223 * @throws IOException 何らかの入出力操作を使ってリソースを読み取る際にエラーが発生した場合 224 */ 225 @Override 226 public ResourceBundle newBundle( final String baseName, 227 final Locale locale, 228 final String format, 229 final ClassLoader loader, 230 final boolean reload ) throws IllegalAccessException, InstantiationException, IOException { 231 // The below is a copy of the default implementation. 232 final String bundleName = toBundleName( baseName , locale ); 233 final String resourceName = toResourceName( bundleName, "properties" ); 234 InputStream stream = null; 235 if( reload ) { 236 final URL url = loader.getResource( resourceName ); 237 if( url != null ) { 238 final URLConnection urlConn = url.openConnection(); 239 if( urlConn != null ) { 240 urlConn.setUseCaches( false ); 241 stream = urlConn.getInputStream(); 242 } 243 } 244 } else { 245 stream = loader.getResourceAsStream( resourceName ); 246 } 247 248 ResourceBundle bundle = null; 249 if( stream != null ) { 250 try { 251 // Only this line is changed to make it to read properties files as UTF-8. 252 bundle = new PropertyResourceBundle( new BufferedReader( new InputStreamReader( stream,UTF_8 ) ) ); 253 } finally { 254 stream.close(); 255 } 256 } 257 return bundle; 258 } 259 }; 260 261 /** 262 * リソース一覧を表示する main メソッドです。 263 * 264 * @param args コマンド引数配列 265 */ 266 public static void main( final String[] args ) { 267 // ResourceBundle.getBundle は、キャッシュされる・・・はず。 268 final ResourceBundle resource = ResourceBundle.getBundle( F_BS_NM , Locale.getDefault() , UTF8_CONTROL ); // リソースバンドルのすべてがキャッシュに格納されます。 269 270 for( final String key : new java.util.TreeSet<String>( resource.keySet() ) ) { 271 System.out.println( key + ":\t" + resource.getString( key ) ); 272 } 273 } 274}