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