恥ずかしながら、これまで小さくローカル環境のシステムなので国際化も文字コードも考慮することがありませんでした。
これまで参画したプロジェクトではフレームワークなりで準備されたものを使っていたので、あまり意識していませんでした。
調べて見ると色々なことが分かりました。そして調べていくと良くあることなのですが「単純にやるならこれで十分。だけど本来ならこういうことも考えておきたい」という衝動です。
それだけでなく「全部入りで汎用なクラスがあったら後々便利かも!」という更なる衝動です。
YAGNI*1ということも考えるのですが、必要になったときに再び調べ直すのも面倒と思ったりしつつ、悶々としながら落としどころを勉強するくらいなら、いっそ自分なりの「これなら大体の事は大丈夫だろうクラス」を作ることにしました。
ということで、自分なりの全部入りResourseBundleというのを整理してみました。多分、これで当分はResourseBundleのことを自分としては忘れて良くなると思います。というか、そうなりたいです。
ちなみに、文字コードですが、自動で判別するような仕組みも考えようと思ったのですが、こちらについては良い案がありませんでした。個人的に色々と実験をしましたが、万能薬とまではいかないことと、自分が準備するファイルなのに毎回判定をするのは妥当なのかな?と考えた結果、文字コードは判別ではなく指定するやり方にしました。ということで、こちらはYAGNIと判断しました。
前置きが長くなりました。それ以上に長いコードと実行結果の貼り付けが以下に続きます。
実装
ResourceBundleのControlの拡張
package com.mycompany.samples.resourse;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.FileSystemNotFoundException;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;
import java.util.ResourceBundle.Control;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.Singular;
通常のCotrolに加えて、文字コードを指定してResourseを取得する拡張をしたResourceBundle.Controlクラスを生成するBuilder<br>
冗長的ではあるが拡張メソッドを把握するため、およびその用途メモを記すために、拡張対象メソッドを全てOverrideする.<br>
@author
@Builder
public class CustomControl extends Control {
formatに使用するXML用の定数
public static final List<String> FORMAT_XML = Collections.unmodifiableList(Arrays.asList("xml"));
private final String charCode;
private final Long timeToLive;
@Singular
private final List<String> formats;
@Singular
private final List<TargetCandidateLocalePair> targetCandidateLocalePairs;
@Getter(AccessLevel.PRIVATE)
private boolean isFallBackInfiniteLoop = false;
新しいResourceBundleを生成する.<br>
<br>
@see java.util.ResourceBundle.Control#newBundle(java.lang.String, java.util.Locale,
* java.lang.String,java.lang.ClassLoader, boolean)
@return
@throws java.lang.IllegalAccessException
@throws java.lang.InstantiationException
@throws java.io.IOException
@Override
public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload)
throws IllegalAccessException, InstantiationException, IOException {
this.isFallBackInfiniteLoop = false;
if (CustomControl.FORMAT_CLASS.contains(format)) {
return super.newBundle(baseName, locale, format, loader, reload);
}
if (CustomControl.FORMAT_PROPERTIES.contains(format)) {
return this.newBundleProperties(baseName, locale, format, loader, reload);
}
if (CustomControl.FORMAT_XML.contains(format)) {
return this.newBundleXML(baseName, locale, format, loader, reload);
}
throw new IllegalArgumentException("unknown format: " + format);
}
propertiesファイルの読み込みResourceBundleを生成する.
@param baseName
@param locale
@param format
@param loader
@param reload
@return
@throws IllegalAccessException
@throws InstantiationException
@throws IOException
private ResourceBundle newBundleProperties(String baseName, Locale locale, String format, ClassLoader loader, boolean reload)
throws IllegalAccessException, InstantiationException, IOException {
String bundleName = toBundleName(baseName, locale);
ResourceBundle bundle = null;
final String resourceName = (bundleName.contains("://"))
? null
: toResourceName(bundleName, "properties");
if (resourceName == null) {
return bundle;
}
final ClassLoader classLoader = loader;
final boolean reloadFlag = reload;
InputStream stream = null;
try {
stream = AccessController.doPrivileged((PrivilegedExceptionAction<InputStream>) () -> {
InputStream is = null;
if (reloadFlag) {
URL url = classLoader.getResource(resourceName);
if (url != null) {
URLConnection connection = url.openConnection();
if (connection != null) {
connection.setUseCaches(false);
is = connection.getInputStream();
}
}
} else {
is = classLoader.getResourceAsStream(resourceName);
}
return is;
});
} catch (PrivilegedActionException e) {
throw (IOException) e.getException();
}
if (stream != null) {
try {
if (this.charCode != null) {
bundle = new PropertyResourceBundle(new InputStreamReader(stream, this.charCode));
} else {
bundle = new PropertyResourceBundle(stream);
}
} finally {
stream.close();
}
}
return bundle;
}
XMLファイルの読み込みResourceBundleを生成する.
@param baseName
@param locale
@param format
@param loader
@param reload
@return
@throws IllegalAccessException
@throws InstantiationException
@throws IOException
private ResourceBundle newBundleXML(String baseName, Locale locale, String format, ClassLoader loader, boolean reload)
throws IllegalAccessException, InstantiationException, IOException {
ResourceBundle bundle = null;
String bundleName = toBundleName(baseName, locale);
final String resourceName = (bundleName.contains("://"))
? null
: toResourceName(bundleName, "xml");
InputStream stream = null;
if (reload) {
URL url = loader.getResource(resourceName);
if (url != null) {
URLConnection connection = url.openConnection();
if (connection != null) {
connection.setUseCaches(false);
stream = connection.getInputStream();
}
}
} else {
stream = loader.getResourceAsStream(resourceName);
}
if (stream != null) {
try (BufferedInputStream bis = new BufferedInputStream(stream)) {
bundle = new XMLResourceBundle(bis);
}
}
return bundle;
}
リソースを取得する優先度を指定する.<br>
<br>
<pre>
{@code
* if (locale.equals(Locale.JAPAN)) {
* return Arrays.asList(Locale.ENGLISH,
* locale,
* Locale.JAPANESE,
* Locale.ROOT);
* }
</pre>
@see java.util.ResourceBundle.Control#getCandidateLocales(java.lang.String, java.util.Locale)
@return
@Override
public List<Locale> getCandidateLocales(String baseName, Locale locale) {
Optional<List<Locale>> candidateLocales = targetCandidateLocalePairs.stream()
.filter(pair -> pair.getTargetLocale().equals(locale))
.map(TargetCandidateLocalePair::getCandidateLocales)
.findAny();
if (candidateLocales.isPresent() == false) {
return super.getCandidateLocales(baseName, locale);
}
List<Locale> localeSetedCandidateLocales = new ArrayList<>();
candidateLocales.get().stream()
.forEachOrdered(candidateLocale -> {
localeSetedCandidateLocales.add(candidateLocale == null ? locale : candidateLocale);
});
return localeSetedCandidateLocales;
}
デフォルトリソースを取得する.<br>
<br>
<br>
@see java.util.ResourceBundle.Control#getFallbackLocale(java.lang.String, java.util.Locale)
@return
@Override
public Locale getFallbackLocale(String baseName, Locale locale) {
if (this.isFallBackInfiniteLoop) {
throw new MissingResourceException("you set baseName is [" + baseName + "]. fallback locale, but does not exist baseName resource file. check ResourceBundle.getBundle param 'baseName' and resource file name.", baseName, "");
}
this.isFallBackInfiniteLoop = true;
return Locale.ROOT;
}
ResourceBundleとして読み込み対象とする分類を返却する.<br>
>><br>
<br>
<br>
@see java.util.ResourceBundle.Control#getFormats(java.lang.String)
@return
@Override
public List<String> getFormats(String baseName) {
return this.formats.isEmpty()
? Collections.unmodifiableList(Arrays.asList("java.class",
"java.properties",
FORMAT_XML.get(0)))
: Collections.unmodifiableList(this.formats);
}
キャッシュ内のロード済みバンドルの有効期限を取得する.<br>
<br>
@see java.util.ResourceBundle.Control#getTimeToLive(java.lang.String, java.util.Locale)
@return
@Override
public long getTimeToLive(String baseName, Locale locale) {
return this.timeToLive == null
? super.getTimeToLive(baseName, locale)
: this.timeToLive;
}
キャッシュ再ロード判定.<br>
<br>
<br>
@see java.util.ResourceBundle.Control#needsReload(java.lang.String, java.util.Locale, java.lang.String,
* java.lang.ClassLoader, java.util.ResourceBundle, long)
@return
@Override
public boolean needsReload(String baseName, Locale locale, String format, ClassLoader loader, ResourceBundle bundle, long loadTime) {
return super.needsReload(baseName, locale, format, loader, bundle, loadTime);
}
BaseNameとlocaleの組み合わせで取得対象となるプロパティファイル名を編集する.<br>
<br>
@see java.util.ResourceBundle.Control#toBundleName(java.lang.String, java.util.Locale)
@return
@Override
public String toBundleName(String baseName, Locale locale) {
return super.toBundleName(baseName, locale);
}
}
package com.mycompany.samples.resourse;
import java.util.List;
import java.util.Locale;
import lombok.Builder;
import lombok.Getter;
import lombok.Singular;
CustomControlで使用するCandidateLocaleのペア.<br>
{@link com.mycompany.samples.resourse.CustomControl#getCandidateLocales(java.lang.String, java.util.Locale)
* }
@author
@Builder @Getter
public class TargetCandidateLocalePair {
private final Locale targetLocale;
@Singular
private final List<Locale> candidateLocales;
}
package com.mycompany.samples.resourse;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.Properties;
import java.util.ResourceBundle;
XML形式のResourceBundleクラス.<br>
<pre>
{@code
* <!--
* Copyright 2006 Sun Microsystems, Inc. All rights reserved.
* -->
*
* <!-- DTD for properties -->
*
* <!ELEMENT properties ( comment?, entry* ) >
*
* <!ATTLIST properties version CDATA #FIXED "1.0">
*
* <!ELEMENT comment (#PCDATA) >
*
* <!ELEMENT entry (#PCDATA) >
*
* <!ATTLIST entry key CDATA #REQUIRED>
*
* }
</pre>
@author
public class XMLResourceBundle extends ResourceBundle {
private final Properties properties;
XML形式のResourceBundleのコンストラクタ<br>
Propertiesクラスを使用してXMLファイルを読み込む
@param stream
@throws IOException
public XMLResourceBundle(InputStream stream) throws IOException {
properties = new Properties();
properties.loadFromXML(stream);
}
@Override
public Object handleGetObject(String key) {
if (key == null) {
throw new NullPointerException();
}
return properties.get(key);
}
@Override
public Enumeration<String> getKeys() {
return (Enumeration<String>) properties.propertyNames();
}
}
ポイントになりそうなところは、コメントに極力書いたつもりです。
大きいところは、fallback制御のところと、文字コード指定のところでしょうか。参考にさせていただいた資料を元についでにXMLも処理できるようにしました。
実行クラス(テストケースもどき)
package com.mycompany.samples.resourse.runner;
import com.mycompany.samples.resourse.CustomControl;
import com.mycompany.samples.resourse.TargetCandidateLocalePair;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.Locale;
import java.util.ResourceBundle;
@author
public class ResourseTest {
public void execute() {
this.useAsciiProperties();
this.useUTF8PropertiesNoSetLocaleIsDefaultLocale();
this.useUTF8PropertiesNoExsistLocaleIsDefaultResource();
this.useSJISXML();
this.useSJISSameNamePropertiesXMLnotSetFormat();
this.useSJISSameNamePropertiesXMLsetSimpleFormat();
this.useSJISSameNamePropertiesXMLsetMultiFormat();
this.useUTF8candidateLocalesMatchTargetLocale();
this.useUTF8candidateLocalesNotMatchTargetLocale();
this.useUTF8candidateLocalesNotExistTargetLocale();
this.useUTF8candidateLocalesExistsNullTargetLocale();
this.useUTF8candidateLocalesExistsTargetLocaleButNotSetLocale();
this.useNoCache();
this.useUTF8Cache1();
this.useUTF8Cache2();
this.useUTF8SleepTimeLtInterval();
this.useUTF8IntervalLtSleepTime();
}
private void printLog(ResourceBundle bundle) {
Enumeration<String> enums = bundle.getKeys();
while (enums.hasMoreElements()) {
String key = enums.nextElement();
System.out.println("key= " + key + ":value= " + bundle.getString(key));
System.out.println();
}
}
標準のASCIIコードのpropertiesを参照する
private void useAsciiProperties() {
CustomControl control = CustomControl.builder().build();
ResourceBundle bundle = ResourceBundle.getBundle("message", control);
System.out.println("useAsciiProperties select message.properties");
this.printLog(bundle);
}
文字コードにUTF8を指定。ロケールの指定をしない場合は、デフォルトロケール(ロケールがja_JP)のpropertiesを参照する
private void useUTF8PropertiesNoSetLocaleIsDefaultLocale() {
CustomControl control = CustomControl.builder().charCode(StandardCharsets.UTF_8.toString()).build();
ResourceBundle bundle = ResourceBundle.getBundle("utf8", control);
System.out.println("useUTF8PropertiesNoSetLocaleIsDefaultLocale is default locale. select utf8_ja_JP.properties");
this.printLog(bundle);
}
文字コードにUTF8を指定。指定ロケールのpropertiesが存在しない場合は、デフォルトリソース(ロケール無)のpropertiesを参照する
private void useUTF8PropertiesNoExsistLocaleIsDefaultResource() {
CustomControl control = CustomControl.builder()
.charCode(StandardCharsets.UTF_8.toString()).build();
ResourceBundle bundle = ResourceBundle.getBundle("utf8", Locale.CHINESE, control);
System.out.println("useUTF8PropertiesNoExsistLocaleIsDefaultResource is default recourse. select nolabel utf8.properties");
this.printLog(bundle);
}
xmlのリソースを参照する.XMLの場合は文字コードの指定は不要
private void useSJISXML() {
CustomControl control = CustomControl.builder().build();
ResourceBundle bundle = ResourceBundle.getBundle("SJIS", control);
System.out.println("useSJISXML is default recourse. select nolabel SJIS.xml");
this.printLog(bundle);
}
xmlとpropertiesに同名のbaseNameの資産が存在する場合は、getFormats()の優先度(Class-Properties-XML)に従ってリソースを参照する.<br>
private void useSJISSameNamePropertiesXMLnotSetFormat() {
CustomControl control = CustomControl.builder().build();
ResourceBundle bundle = ResourceBundle.getBundle("SJIS-SAME", control);
System.out.println("useSJISSameNamePropertiesXMLnotSetFormat prioritize properties. select SJIS-SAME.properties garbled characters (T^T)");
this.printLog(bundle);
}
xmlとpropertiesに同名のbaseNameの資産が存在するが、formatsにXMLを指定しているためXMLを参照し、文字化けも発生しない
private void useSJISSameNamePropertiesXMLsetSimpleFormat() {
CustomControl control = CustomControl.builder().formats(CustomControl.FORMAT_XML).build();
ResourceBundle bundle = ResourceBundle.getBundle("SJIS-SAME2", control);
System.out.println("useSJISSameNamePropertiesXMLsetSingleFormat select xml. select SJIS-SAME2.xml no garbled characters");
this.printLog(bundle);
}
xmlとpropertiesに同名のbaseNameの資産が存在する.<br>
private void useSJISSameNamePropertiesXMLsetMultiFormat() {
CustomControl control = CustomControl.builder()
.charCode("SJIS")
.formats(CustomControl.FORMAT_DEFAULT)
.formats(CustomControl.FORMAT_XML)
.build();
ResourceBundle bundle = ResourceBundle.getBundle("SJIS-SAME3", control);
System.out.println("useSJISSameNamePropertiesXMLsetMultiFormat select properties. select SJIS-SAME3.properties no garbled characters");
this.printLog(bundle);
}
ロケールがJAPANESEの時の候補Localeの第一優先がUS(日本語じゃなくて、英語のメッセージが出力される
private void useUTF8candidateLocalesMatchTargetLocale() {
CustomControl control = CustomControl.builder()
.charCode("UTF-8")
.targetCandidateLocalePair(
TargetCandidateLocalePair.builder()
.targetLocale(Locale.JAPANESE)
.candidateLocale(Locale.US)
.candidateLocale(Locale.JAPAN)
.candidateLocale(Locale.ROOT)
.build())
.build();
ResourceBundle bundle = ResourceBundle.getBundle("utf8", Locale.JAPANESE, control);
System.out.println("useUTF8candidateLocalesMatchTargetLocale select properties. Locale US");
this.printLog(bundle);
}
優先度指定をしたロケール以外の場合は、対象ロケール本来の優先度に従って参照する.<br>
private void useUTF8candidateLocalesNotMatchTargetLocale() {
CustomControl control = CustomControl.builder()
.charCode("UTF-8")
.targetCandidateLocalePair(
TargetCandidateLocalePair.builder()
.targetLocale(Locale.JAPANESE)
.candidateLocale(Locale.JAPAN)
.candidateLocale(Locale.ENGLISH)
.candidateLocale(Locale.US)
.candidateLocale(Locale.ROOT)
.build())
.build();
ResourceBundle bundle = ResourceBundle.getBundle("utf8", Locale.US, control);
System.out.println("useUTF8candidateLocalesNotMatchTargetLocale select properties. Locale US");
this.printLog(bundle);
}
優先度指定をしたロケール以外の場合は、対象ロケール本来の優先度に従って参照する.<br>
private void useUTF8candidateLocalesNotExistTargetLocale() {
CustomControl control = CustomControl.builder()
.charCode("UTF-8")
.targetCandidateLocalePair(
TargetCandidateLocalePair.builder()
.targetLocale(Locale.JAPANESE)
.candidateLocale(Locale.JAPAN)
.candidateLocale(Locale.ROOT)
.build())
.build();
ResourceBundle bundle = ResourceBundle.getBundle("utf8", Locale.CHINA, control);
System.out.println("useUTF8candidateLocalesNotExistTargetLocale select properties. Locale default");
this.printLog(bundle);
}
優先度指定をしたロケールにNullを指定した場合は、パラメータで指定したロケールに置き換えて参照する.<br>
private void useUTF8candidateLocalesExistsNullTargetLocale() {
CustomControl control = CustomControl.builder()
.charCode("UTF-8")
.targetCandidateLocalePair(
TargetCandidateLocalePair.builder()
.targetLocale(Locale.JAPAN)
.candidateLocale(Locale.FRANCE)
.candidateLocale(null)
.build())
.build();
ResourceBundle bundle = ResourceBundle.getBundle("utf8", Locale.JAPAN, control);
System.out.println("useUTF8candidateLocalesExistsNullTargetLocale select properties. Locale null = JAPAN");
this.printLog(bundle);
}
ロケールの指定をしない場合は、デフォルトロケール(ロケールがja_JP)のpropertiesを参照して、優先度指定は無視する.<br>
private void useUTF8candidateLocalesExistsTargetLocaleButNotSetLocale() {
CustomControl control = CustomControl.builder()
.charCode("UTF-8")
.targetCandidateLocalePair(
TargetCandidateLocalePair.builder()
.targetLocale(Locale.JAPANESE)
.candidateLocale(Locale.ROOT)
.build())
.targetCandidateLocalePair(
TargetCandidateLocalePair.builder()
.targetLocale(Locale.JAPAN)
.candidateLocale(Locale.ROOT)
.build())
.build();
ResourceBundle bundle = ResourceBundle.getBundle("utf8", control);
System.out.println("useUTF8candidateLocalesExistsTargetLocaleButNotSetLocale select properties. Locale ja_JP");
this.printLog(bundle);
}
データをキャッシュしない(CustomControl.TTL_DONT_CACHE).<br>
private void useNoCache() {
CustomControl control = CustomControl.builder().timeToLive(CustomControl.TTL_DONT_CACHE).build();
ResourceBundle bundle = ResourceBundle.getBundle("utf8NoCache", control);
System.out.println("useNoCache before properties garbled characters. because setting charCode is default(ASCII)");
this.printLog(bundle);
control = CustomControl.builder().charCode("UTF-8").timeToLive(CustomControl.TTL_DONT_CACHE).build();
bundle = ResourceBundle.getBundle("utf8NoCache", control);
System.out.println("useNoCache after properties. no garbled characters. because setting charCode is UTF-8");
this.printLog(bundle);
}
データをキャッシュする(CustomControl.TTL_NO_EXPIRATION_CONTROL).<br>
private void useUTF8Cache1() {
CustomControl control = CustomControl.builder().charCode("UTF-8").timeToLive(CustomControl.TTL_NO_EXPIRATION_CONTROL).build();
ResourceBundle bundle = ResourceBundle.getBundle("utf8Cache1", control);
System.out.println("useNoCache before properties no garbled characters. because setting charCode is UTF-8");
this.printLog(bundle);
control = CustomControl.builder().charCode("SJIS").timeToLive(CustomControl.TTL_NO_EXPIRATION_CONTROL).build();
bundle = ResourceBundle.getBundle("utf8Cache1", control);
System.out.println("useNoCache after properties. no garbled characters. because use cash. setting charCode is UTF-8");
this.printLog(bundle);
}
データをキャッシュする(CustomControl.TTL_NO_EXPIRATION_CONTROL).<br>
private void useUTF8Cache2() {
CustomControl control = CustomControl.builder().timeToLive(CustomControl.TTL_NO_EXPIRATION_CONTROL).build();
ResourceBundle bundle = ResourceBundle.getBundle("utf8Cache2", control);
System.out.println("useCache2 before properties garbled characters. because setting charCode is default(ASCII)");
this.printLog(bundle);
control = CustomControl.builder().charCode("UTF-8").timeToLive(CustomControl.TTL_NO_EXPIRATION_CONTROL).build();
bundle = ResourceBundle.getBundle("utf8Cache2", control);
System.out.println("useCache2 after properties. garbled characters. because use cash. setting charCode is default(ASCII)");
this.printLog(bundle);
}
timeToLiveが停止時間よりも大きいのでキャッシュされた"utf8IntervalCache1"を参照する
private void useUTF8SleepTimeLtInterval() {
CustomControl control = CustomControl.builder().charCode("UTF-8").timeToLive(1000L).build();
ResourceBundle bundle = ResourceBundle.getBundle("utf8IntervalCache1", control);
System.out.println("useUTF8SleepTimeLtInterval before properties UTF-8");
this.printLog(bundle);
try {
System.err.println("sleeptime < timeToLive");
Thread.sleep(1 * 1);
} catch (InterruptedException e) {
System.err.println(e);
}
control = CustomControl.builder().charCode("SJIS").build();
bundle = ResourceBundle.getBundle("utf8IntervalCache1", control);
System.out.println("useUTF8SleepTimeLtInterval after properties UTF-8. no garbled characters. because use cashe");
this.printLog(bundle);
}
停止時間がtimeToLiveよりも大きいので改めて"utf8IntervalCache1"を参照したときに、ファイルが更新されていたらキャッシュを参照せず、再取得する
private void useUTF8IntervalLtSleepTime() {
CustomControl control = CustomControl.builder().charCode("UTF-8").timeToLive(1L).build();
ResourceBundle bundle = ResourceBundle.getBundle("utf8IntervalCache2", control);
System.out.println("useUTF8IntervalLtSleepTime before properties UTF-8");
this.printLog(bundle);
try {
System.err.println("timeToLive < sleeptime");
System.err.println("edit utf8IntervalCache2.properties while sleeping !!");
Thread.sleep(1 * 10000L);
} catch (InterruptedException e) {
System.err.println(e);
}
control = CustomControl.builder().charCode("SJIS").build();
bundle = ResourceBundle.getBundle("utf8IntervalCache2", control);
System.out.println("useUTF8IntervalLtSleepTime after properties UTF-8. garbled characters. because no use cashe");
this.printLog(bundle);
}
}
英語は実行結果で、ざっと分かれば良いレベルの適当なやっつけです。
最後のインターバルのテストは手作業が入っています。
おおよその使い方は、この実行クラスの例を見てもらえれば分かると思います。
参照するproperties
記事上は全て読めるようにしていますがmessage.properties
がASCIIコードであることを除き、その他のファイルはプリフィックスに記した文字コードで作成しています。
ファイル名と内容
message.properties
test=ascii(default)
SJIS.xml
<?xml version="1.0" encoding="Shift_JIS" standalone="no"?>
<properties>
<comment />
<entry key="sjis.xml">XML読み込み(SJIS)</entry>
</properties>
SJIS-SAME.properties
test=properties:同名XMLあり1(SJIS)
SJIS-SAME.xml
<?xml version="1.0" encoding="Shift_JIS" standalone="no"?>
<properties>
<comment />
<entry key="sjis1.xml">xml:同名propertiesあり1(SJIS)</entry>
</properties>
SJIS-SAME2.properties
test=properties:同名XMLあり2(SJIS)
SJIS-SAME2.xml
<?xml version="1.0" encoding="Shift_JIS" standalone="no"?>
<properties>
<comment />
<entry key="sjis2.xml">xml:同名propertiesあり2(SJIS)</entry>
</properties>
SJIS-SAME3.properties
test=properties:同名XMLあり3(SJIS)
SJIS-SAME3.xml
<?xml version="1.0" encoding="Shift_JIS" standalone="no"?>
<properties>
<comment />
<entry key="sjis3.xml">xml:同名propertiesあり3(SJIS)</entry>
</properties>
utf8.properties
test=UTF8のテスト(default)
utf8_en_US.properties
test=UTF8english
utf8_ja_JP.properties
test=UTF8のテスト(JP)
utf8Cache1.properties
test=UTF8のテスト(cache1)
utf8Cache2.properties
test=UTF8のテスト(cache2)
utf8IntervalCache1.properties
test=UTF8のテスト(IntervalCache1)
utf8IntervalCache2.properties
test=UTF8のテスト(IntervalCache2)
utf8NoCache.properties
test=UTF8のテスト(NoCache)
実行結果
--- exec-maven-plugin:1.2.1:exec (default-cli) @ samples ---
useAsciiProperties select message.properties
key= test:value= ascii(default)
useUTF8PropertiesNoSetLocaleIsDefaultLocale is default locale. select utf8_ja_JP.properties
key= test:value= UTF8のテスト(JP)
useUTF8PropertiesNoExsistLocaleIsDefaultResource is default recourse. select nolabel utf8.properties
key= test:value= UTF8のテスト(default)
useSJISXML is default recourse. select nolabel SJIS.xml
key= sjis.xml:value= XML読み込み(SJIS)
useSJISSameNamePropertiesXMLnotSetFormat prioritize properties. select SJIS-SAME.properties garbled characters (T^T)
key= test:value= properties:? ̄??XML????1(SJIS)
useSJISSameNamePropertiesXMLsetSingleFormat select xml. select SJIS-SAME2.xml no garbled characters
key= sjis2.xml:value= xml:同名propertiesあり2(SJIS)
useSJISSameNamePropertiesXMLsetMultiFormat select properties. select SJIS-SAME3.properties no garbled characters
key= test:value= properties:同名XMLあり3(SJIS)
useUTF8candidateLocalesMatchTargetLocale select properties. Locale US
key= test:value= UTF8english
useUTF8candidateLocalesNotMatchTargetLocale select properties. Locale US
key= test:value= UTF8english
useUTF8candidateLocalesNotExistTargetLocale select properties. Locale default
key= test:value= UTF8のテスト(default)
useUTF8candidateLocalesExistsNullTargetLocale select properties. Locale null = JAPAN
key= test:value= UTF8のテスト(JP)
useUTF8candidateLocalesExistsTargetLocaleButNotSetLocale select properties. Locale ja_JP
key= test:value= UTF8のテスト(JP)
useNoCache before properties garbled characters. because setting charCode is default(ASCII)
key= test:value= UTF8????????????(NoCache)
useNoCache after properties. no garbled characters. because setting charCode is UTF-8
key= test:value= UTF8のテスト(NoCache)
useNoCache before properties no garbled characters. because setting charCode is UTF-8
key= test:value= UTF8のテスト(cache1)
useNoCache after properties. no garbled characters. because use cash. setting charCode is UTF-8
key= test:value= UTF8のテスト(cache1)
useCache2 before properties garbled characters. because setting charCode is default(ASCII)
key= test:value= UTF8????????????(cache2)
useCache2 after properties. garbled characters. because use cash. setting charCode is default(ASCII)
key= test:value= UTF8????????????(cache2)
useUTF8SleepTimeLtInterval before properties UTF-8
key= test:value= UTF8のテスト(IntervalCache1)
sleeptime < timeToLive
useUTF8SleepTimeLtInterval after properties UTF-8. no garbled characters. because use cashe
key= test:value= UTF8のテスト(IntervalCache1)
useUTF8IntervalLtSleepTime before properties UTF-8
key= test:value= UTF8のテスト(IntervalCache2)
timeToLive < sleeptime
edit utf8IntervalCache2.properties while sleeping !!
useUTF8IntervalLtSleepTime after properties UTF-8. garbled characters. because no use cashe
key= test:value= UTF8縺ョ繝?繧ケ繝?(IntervalCache2edit)
最後のテストケースはスリープ中に手作業で直接ファイルを更新
更新前
更新後
参考リンク
ResourseBundle
国際化対応や、ResourseBundleそのものの詳細は「Java SE 6完全攻略」から目を通すのが一番良いと思います。
考慮はしませんでしたが調査足跡という意味でリンクだけ残しておこうと思います。ひょっとしたらファイルアップロード関連のチェックで改めて検討をする可能性もあるので。
本題とは関係ないですが、外部ライブラリを使用するにあたって調べたライセンス関連のリンク。
さいごに
やってみたものの、正直な感想は、こういう拡張は作法として良いのだろうか?という思いです。*2
いずれにせよ 全部入りなResourceBundleを とりあえず作れて個人的には満足です。
今後、今回のようなボリュームのあるものを記事にするときのためにGitHubについても勉強をした方が良いのかもしれないと新たな課題も見つかりました。
追記:2017/1/9
GitHubでプロジェクトを作成しました。
GitHub - vermeer-1977-blog/resource-bundle: 全部入りのResourceBundle