Skip to content

Instantly share code, notes, and snippets.

@seraphy
Last active September 15, 2021 09:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save seraphy/b421ac03d64d541667b2 to your computer and use it in GitHub Desktop.
Save seraphy/b421ac03d64d541667b2 to your computer and use it in GitHub Desktop.
XMLプロパティファイルをリソースバンドルとして扱うためのリソースバンドルコントローラの例.ロケールのほかに、osにより_osx, _windowsのサフィックスでの検索も行う.
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";
}
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);
}
}
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