View Javadoc
1   package com.github.mygreen.cellformatter.lang;
2   
3   import java.io.IOException;
4   import java.io.InputStreamReader;
5   import java.util.ArrayList;
6   import java.util.Collections;
7   import java.util.Enumeration;
8   import java.util.List;
9   import java.util.Locale;
10  import java.util.Map;
11  import java.util.Properties;
12  import java.util.ResourceBundle;
13  import java.util.concurrent.ConcurrentHashMap;
14  
15  /**
16   * メッセージソースを管理するクラス。
17   * <p>ロケールを指定した場合、そのロケールで存在しないキーがあるときに、標準の値を返す。
18   *
19   * @version 0.10
20   * @since 0.5
21   * @author T.TSUCHIE
22   *
23   */
24  public class MessageResolver {
25  
26      /**
27       * 解決するリソース名。
28       * <p>リソースバンドル名の形式。
29       */
30      private final String resourceName;
31  
32      /**
33       * デフォルトのメッセージがない場合を許可するかどうか。
34       */
35      private final boolean allowedNoDefault;
36  
37      /**
38       * クラスパスのルートにあるユーザ定義のメッセージソースも読み込むかどうか指定します。
39       */
40      private final boolean appendUserResource;
41  
42      /**
43       * デフォルトのメッセージ情報
44       */
45      private MessageResource defaultResource;
46  
47  
48      /**
49       * ロケールごとのメッセージリソースの取得
50       */
51      private Map<Locale, MessageResource> resources;
52  
53      /**
54       * リソース名を指定してインスタンスを生成する。
55       * @param resourceName リソース名。形式は、{@link ResourceBundle}の名称。
56       */
57      public MessageResolver(final String resourceName) {
58          this(resourceName, false, false);
59  
60      }
61  
62      /**
63       * リソース名を指定してインスタンスを生成する。
64       * @param resourceName リソース名。形式は、{@link ResourceBundle}の名称。
65       * @param allowedNoDefault デフォルトのメッセージがない場合を許可するかどうか。
66       * @param appendUserResouce クラスパスのルートにあるユーザ定義のメッセージソースも読み込むかどうか指定します。
67       *      引数resourceNameの値が {@literal sample.SampleMessages}のとき、クラスパスのルート上にある「SampleMessages」を読み込みます。
68       */
69      public MessageResolver(final String resourceName, final boolean allowedNoDefault, final boolean appendUserResouce) {
70          this.resourceName = resourceName;
71          this.allowedNoDefault = allowedNoDefault;
72          this.appendUserResource = appendUserResouce;
73          this.defaultResource = loadDefaultResource(allowedNoDefault, appendUserResouce);
74          this.resources = new ConcurrentHashMap<>();
75      }
76  
77      private String getPropertyPath() {
78  
79          final String baseName = getResourceName().replaceAll("\\.", "/");
80          String path = new StringBuilder()
81                  .append("/")
82                  .append(baseName)
83                  .append(".properties")
84                  .toString();
85  
86          return path;
87      }
88  
89      private String[] getPropertyPath(final Locale locale) {
90  
91          final List<String> list = new ArrayList<>();
92  
93          final String baseName = getResourceName().replaceAll("\\.", "/");
94          if(Utils.isNotEmpty(locale.getLanguage())) {
95              String path = new StringBuilder()
96                      .append("/")
97                      .append(baseName)
98                      .append("_").append(locale.getLanguage())
99                      .append(".properties")
100                     .toString();
101             list.add(path);
102         }
103 
104         if(Utils.isNotEmpty(locale.getLanguage()) && Utils.isNotEmpty(locale.getCountry())) {
105             String path = new StringBuilder()
106                     .append("/")
107                     .append(baseName)
108                     .append("_").append(locale.getLanguage())
109                     .append("_").append(locale.getCountry())
110                     .append(".properties")
111                     .toString();
112             list.add(path);
113         }
114 
115         Collections.reverse(list);
116 
117         return list.toArray(new String[list.size()]);
118     }
119 
120     /**
121      * プロパティファイルから取得する。
122      * <p>プロパティ名を補完する。
123      * @param path プロパティファイルのパス名
124      * @param allowedNoDefault デフォルトのメッセージがない場合を許可するかどうか。
125      * @param appendUserResource クラスパスのルートにあるユーザ定義のメッセージソースも読み込むかどうか指定します。
126      * @return
127      */
128     private MessageResource loadDefaultResource(final boolean allowedNoDefault, final boolean appendUserResource) {
129 
130         final String path = getPropertyPath();
131         Properties props = new Properties();
132         try {
133             props.load(new InputStreamReader(MessageResolver.class.getResourceAsStream(path), "UTF-8"));
134         } catch (NullPointerException | IOException e) {
135             if(allowedNoDefault) {
136                 return MessageResource.NULL_OBJECT;
137             } else {
138                 throw new RuntimeException("fail default properties. :" + path, e);
139             }
140         }
141 
142         final MessageResourceg/MessageResource.html#MessageResource">MessageResource resource = new MessageResource();
143 
144         final Enumeration<?> keys = props.propertyNames();
145         while(keys.hasMoreElements()) {
146             String key = (String) keys.nextElement();
147             String value = props.getProperty(key);
148             resource.addMessage(key, value);
149         }
150 
151         // ユーザのリソースの読み込み
152         if(appendUserResource) {
153             resource.addMessage(loadUserResource(path));
154         }
155 
156         return resource;
157 
158     }
159 
160     /**
161      * 指定したロケールのリソースを取得する。
162      * <p>該当するロケールのリソースが存在しない場合は、デフォルトのリソースを返す。
163      * @param locale ロケールがnullの場合は、デフォルトのリソースを返す。
164      * @return
165      */
166     MessageResource loadResource(final Locale locale) {
167 
168         if(locale == null) {
169             return defaultResource;
170         }
171 
172         if(resources.containsKey(locale)) {
173             return resources.get(locale);
174         }
175 
176         MessageResource localeResource = null;
177         synchronized (resources) {
178             for(String path : getPropertyPath(locale)) {
179 
180                 try {
181                     final Properties props = new Properties();
182                     props.load(new InputStreamReader(MessageResolver.class.getResourceAsStream(path), "UTF-8"));
183 
184                     localeResource = new MessageResource();
185 
186                     final Enumeration<?> keys = props.propertyNames();
187                     while(keys.hasMoreElements()) {
188                         String key = (String) keys.nextElement();
189                         String value = props.getProperty(key);
190                         localeResource.addMessage(key, value);
191                     }
192 
193                     // ユーザのリソースの読み込み
194                     if(appendUserResource) {
195                         localeResource.addMessage(loadUserResource(path));
196                     }
197 
198                     break;
199 
200                 } catch(NullPointerException | IOException e) {
201                     continue;
202                 }
203 
204             }
205 
206             if(localeResource == null) {
207                 // 該当するリソースが見つからない場合は、デフォルトの値を返す。
208                 localeResource = defaultResource;
209             }
210 
211             resources.put(locale, localeResource);
212         }
213 
214         return localeResource;
215 
216     }
217 
218     /**
219      *
220      * @param basePath 基準となるプロパティファイルパス。「/」区切りで、拡張子、ロケール名が付いている。
221      * @return 読み込んだメッセージソース。存在しない場合は、{@link MessageResource#NULL_OBJECT}を返す。
222      */
223     private MessageResource loadUserResource(final String basePath) {
224 
225         // ファイル名の切り出し
226         final int index = basePath.lastIndexOf("/");
227         if(index <= 0) {
228             return MessageResource.NULL_OBJECT;
229         }
230 
231         final String userPropertyPath = "/" + basePath.substring(index+1);
232         try {
233             Properties props = new Properties();
234             props.load(new InputStreamReader(MessageResolver.class.getResourceAsStream(userPropertyPath), "UTF-8"));
235 
236             final MessageResourceg/MessageResource.html#MessageResource">MessageResource resource = new MessageResource();
237             final Enumeration<?> keys = props.propertyNames();
238             while(keys.hasMoreElements()) {
239                 String key = (String) keys.nextElement();
240                 String value = props.getProperty(key);
241                 resource.addMessage(key, value);
242             }
243 
244             return resource;
245 
246         } catch(NullPointerException | IOException e) {
247             return MessageResource.NULL_OBJECT;
248         }
249 
250     }
251 
252     /**
253      * リソース名の取得。
254      * @return
255      */
256     public String getResourceName() {
257         return resourceName;
258     }
259 
260     /**
261      * デフォルトのメッセージ情報がない場合を許可するかどうか。
262      * @return
263      */
264     public boolean isAllowedNoDefault() {
265         return allowedNoDefault;
266     }
267 
268     /**
269      * キーを指定してメッセージを取得する。
270      * @param key メッセージキー
271      * @return 存在しないキーの場合、nullを返す。
272      */
273     public String getMessage(final String key) {
274         return defaultResource.getMessage(key);
275     }
276 
277     /**
278      * ロケールとキーを指定してメッセージを取得する。
279      * <p>ロケールに該当する値を取得する。
280      * @param locale ロケール
281      * @param key メッセージキー
282      * @return 該当するロケールのメッセージが見つからない場合は、デフォルトのリソースから取得する。
283      */
284     public String getMessage(final MSLocale locale, final String key) {
285         if(locale == null) {
286             return loadResource(null).getMessage(key);
287 
288         } else {
289             return loadResource(locale.getLocale()).getMessage(key);
290         }
291     }
292 
293     /**
294      * ロケールとキーを指定してメッセージを取得する。
295      * @param locale ロケール
296      * @param key メッセージキー
297      * @param defaultValue
298      * @return 該当するロケールのメッセージが見つからない場合は、引数で指定した'defaultValue'の値を返す。
299      */
300     public String getMessage(final MSLocale locale, final String key, final String defaultValue) {
301         String message = getMessage(locale, key);
302         return message == null ? defaultValue : message;
303     }
304 
305     /**
306      * ロケールとキーを指定してメッセージを取得する。
307      * <p>ロケールに該当する値を取得する。
308      * @param locale ロケール
309      * @param key メッセージキー
310      * @return 該当するロケールのメッセージが見つからない場合は、デフォルトのリソースから取得する。
311      */
312     public String getMessage(final Locale locale, final String key) {
313         return loadResource(locale).getMessage(key);
314     }
315 
316     /**
317      * 値がnullの場合、defaultValueの値を返す。
318      * @param locale ロケール
319      * @param key メッセージキー
320      * @param defaultValue
321      * @return 該当するロケールのメッセージが見つからない場合は、引数で指定した'defaultValue'の値を返す。
322      */
323     public String getMessage(final Locale locale, final String key, final String defaultValue) {
324         String message = getMessage(locale, key);
325         return message == null ? defaultValue : message;
326     }
327 
328     /**
329      * 書式のロケールを優先して、キーに対するメッセージを取得する。
330      * formatLocaleがnullのとき、runtimeLocaleから値を取得する。
331      *
332      * @since 0.10
333      * @param formatLocale 書式に指定されているロケール。nullの場合がある。
334      * @param runtimeLocale 実行時のロケール。
335      * @param key メッセージキー
336      * @return 該当するロケールのメッセージが見つからない場合は、デフォルトのリソースから取得する。
337      */
338     public String getMessage(final MSLocale formatLocale, Locale runtimeLocale, final String key) {
339 
340         if(formatLocale != null) {
341             return getMessage(formatLocale, key);
342         } else {
343             return getMessage(runtimeLocale, key);
344         }
345 
346     }
347 
348     /**
349      * 書式のロケールを優先して、キーに対するメッセージを取得する。
350      * formatLocaleがnullのとき、runtimeLocaleから値を取得する。
351      *
352      * @since 0.10
353      * @param formatLocale 書式に指定されているロケール。nullの場合がある。
354      * @param runtimeLocale 実行時のロケール。
355      * @param key メッセージキー
356      * @param defaultValue
357      * @return 該当するロケールのメッセージが見つからない場合は、引数で指定した'defaultValue'の値を返す。
358      */
359     public String getMessage(final MSLocale formatLocale, Locale runtimeLocale, final String key, final String defaultValue) {
360 
361         if(formatLocale != null) {
362             return getMessage(formatLocale, key, defaultValue);
363         } else {
364             return getMessage(runtimeLocale, key, defaultValue);
365         }
366 
367     }
368 
369 
370 }