Last active
September 15, 2021 09:20
-
-
Save seraphy/b421ac03d64d541667b2 to your computer and use it in GitHub Desktop.
XMLプロパティファイルをリソースバンドルとして扱うためのリソースバンドルコントローラの例.ロケールのほかに、osにより_osx, _windowsのサフィックスでの検索も行う.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package jp.seraphyware.utils; | |
import static java.lang.annotation.ElementType.*; | |
import static java.lang.annotation.RetentionPolicy.*; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.Target; | |
import jakarta.enterprise.util.Nonbinding; | |
import jakarta.inject.Qualifier; | |
/** | |
* CDIによりResourceBundleをInjectするとき、message-resoufce.xmlのリソースファイルを引き当てるためのQualifierマーカー | |
*/ | |
@Qualifier | |
@Retention(RUNTIME) | |
@Target({METHOD, FIELD, PARAMETER, TYPE}) | |
public @interface MessageResource { | |
/** | |
* リソース名、デフォルトは「message-resource.xml」 | |
* @return | |
*/ | |
@Nonbinding | |
String value() default "message-resource.xml"; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package jp.seraphyware.utils; | |
import java.io.IOException; | |
import java.util.ArrayList; | |
import java.util.List; | |
import java.util.Locale; | |
import java.util.MissingResourceException; | |
import java.util.ResourceBundle; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
import jakarta.enterprise.context.ApplicationScoped; | |
import jakarta.enterprise.context.Dependent; | |
import jakarta.enterprise.inject.Produces; | |
import jakarta.enterprise.inject.spi.InjectionPoint; | |
/** | |
* メッセージリソースをロードするためのファクトリクラス。 | |
*/ | |
@ApplicationScoped | |
public class MessageResourceFactory { | |
private static final Logger logger = LoggerFactory.getLogger(MessageResourceFactory.class); | |
@Produces | |
@MessageResource | |
@Dependent | |
public ResourceBundle getMessages(InjectionPoint ip) { | |
MessageResource prefixAnnt = ip.getAnnotated().getAnnotation(MessageResource.class); | |
String resName = prefixAnnt.value(); | |
if (resName.endsWith(".xml")) { | |
// xml形式でのロード | |
// java9以降の名前付きモジュールでは、Control派生クラスのリソースバンドルでの利用がサポートされていないため、 | |
// 明示的にxmlのコントロールを利用することで代替とする。 | |
XMLResourceBundleControl xmlReader = new XMLResourceBundleControl(); | |
ClassLoader clsldr = getClass().getClassLoader(); | |
try { | |
String simpleResName = resName.substring(0, resName.length() - 4); | |
Locale defLocale = Locale.getDefault(); | |
// 検索するロケールサフィックスを準備する | |
List<Locale> candidateLocales = new ArrayList<>(xmlReader.getCandidateLocales(simpleResName, defLocale)); | |
candidateLocales.add(Locale.ROOT); // サフィックスなしを最後に検索する | |
for (Locale locale : candidateLocales) { | |
ResourceBundle bundle = xmlReader.newBundle(simpleResName, locale, "xml", clsldr, true); | |
logger.info("find-resource: locale={}, result={}", locale, bundle); | |
if (bundle != null) { | |
return bundle; | |
} | |
} | |
throw new MissingResourceException("Can't find bundle for base name " | |
+ simpleResName + ", locale " + defLocale, | |
simpleResName + "_" + defLocale, // className | |
""); | |
} catch (IllegalAccessException | InstantiationException | IOException ex) { | |
logger.error("failed to read resource-bundle {}", resName, ex); | |
throw new RuntimeException(ex); | |
} | |
} | |
// プロパティファイル形式(*.properties)のロード | |
return ResourceBundle.getBundle(resName); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package jp.seraphyware.utils; | |
import java.io.IOException; | |
import java.net.URL; | |
import java.util.ArrayList; | |
import java.util.Arrays; | |
import java.util.Enumeration; | |
import java.util.List; | |
import java.util.Locale; | |
import java.util.Properties; | |
import java.util.ResourceBundle; | |
/** | |
* XMLプロパティ形式のリソースバンドルを読み込み可能にするコントローラ.<br> | |
* リソース名はロケールおよびOSの種類によって識別されます.<br> | |
* ロケールによる、標準のリソースバンドルの形式のサフィックスに加えて、 | |
* Macの場合は「_osx」、Windowsの場合は「_windows」のサフィックスが追加で検索され、 | |
* 該当のプロパティファイルがある場合は、サフィックスなしのものとマージされた結果として返されます.<br> | |
* @author seraphy | |
*/ | |
public class XMLResourceBundleControl extends ResourceBundle.Control { | |
/** | |
* 拡張子 | |
*/ | |
private static final String XML = "xml"; | |
/** | |
* OS名 | |
*/ | |
private static final String OS_NAME = System.getProperty("os.name") | |
.toLowerCase(Locale.ENGLISH); | |
/** | |
* このコントローラが対象する拡張子の一覧を返す. | |
* "xml"だけが有効である. | |
* @param baseName ベース名 | |
* @return 対象となる拡張子一覧 | |
*/ | |
@Override | |
public List<String> getFormats(String baseName) { | |
return Arrays.asList(XML); | |
} | |
/** | |
* 新しいリソースバンドルを生成して返す. | |
* xml形式のプロパティファイルから現在のロケールに従って取得したリソースバンドルを返す. | |
* ただし、リソースはOSがmacまたはWindowsの場合は「_osx」または「_windows」のサフィックスをつけた | |
* XMLプロパティファイルが存在すれば、それを重ねて読み込んだものが返されます.<br> | |
* @return リソースバンドル | |
*/ | |
@Override | |
public ResourceBundle newBundle(String baseName, Locale locale, | |
String format, ClassLoader loader, boolean reload) | |
throws IllegalAccessException, InstantiationException, IOException { | |
if (!XML.equals(format)) { | |
return null; | |
} | |
// ロケールと結合したリソース名を求める | |
String plainBundleName = toBundleName(baseName, locale); | |
// osの種類(win/macごとの違いのリソースも試行するように) | |
ArrayList<String> bundleNames = new ArrayList<>(); | |
bundleNames.add(plainBundleName); | |
if (OS_NAME.indexOf("windows") > -1) { | |
bundleNames.add(plainBundleName + "_windows"); | |
} else if (OS_NAME.indexOf("os x") > -1) { | |
bundleNames.add(plainBundleName + "_osx"); | |
} | |
// XMLプロパティを重ねてロードする. | |
Properties props = new Properties(); | |
int numOfLoadedProps = 0; | |
for (String bundleName : bundleNames) { | |
// 対応するフォーマットと結合したリソース名を求める | |
String resourceName = toResourceName(bundleName, format); | |
URL url = loader.getResource(resourceName); | |
// XMLプロパティを上書きロードする. | |
if (url != null) { | |
props.loadFromXML(url.openStream()); | |
numOfLoadedProps++; | |
} | |
} | |
if (numOfLoadedProps > 0) { | |
// 少なくとも、どちらかのXMLプロパティが読み込めた場合、 | |
// XMLプロパティをリソースバンドルに接続する. | |
return new ResourceBundle() { | |
@Override | |
protected Object handleGetObject(String key) { | |
return props.getProperty(key); | |
} | |
@SuppressWarnings("unchecked") | |
@Override | |
public Enumeration<String> getKeys() { | |
return (Enumeration<String>) props.propertyNames(); | |
} | |
}; | |
} | |
// ロードできず. | |
return null; | |
} | |
@Override | |
public Locale getFallbackLocale(String baseName, Locale locale) { | |
// 基底バンドル以外が見つからなかった場合は、そこで探索は終了する. | |
// (デフォルトロケールでの探索はしなくて良い) | |
return null; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment