ResourceBundle.Control を使う

  • 6
    Like
  • 0
    Comment
More than 1 year has passed since last update.

ResourceBundle を扱うコードなんて数年に一度ぐらいしか書かないと思うので知らなかったのですが, 最近の Java には ResourceBundle.Control というクラスが新設されているのをご存知でしょうか。これを使えばバンドルの読み込み処理にフックすることが出来るので, バンドルを XML や YAML にすることも出来ますし, properties ファイルであっても native2ascii せずとも良いようにすることも出来ます(他にもフォールバック処理の制御を行ったりすることも出来ます)。

例えば指定の Locale に対応するバンドルが存在しない場合にフォールバックせず, CHARSET_NAME(UTF-8)で記述された properties ファイルを読み込む Control を実装したい場合はこんな感じになります。

/**
 * 対応する {@link Locale} のバンドルが存在しない場合にフォールバックせず,
 * {@link CHARSET_NAME} に従いリソースバンドルを読み込む {@link Control} の実装です。
 * 
 * @author monzou
 */
private static class NoFallbackControl extends Control {

    /** {@inheritDoc} */
    @Override
    public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload)
            throws IllegalAccessException, InstantiationException, IOException {

        if ("java.properties".equals(format)) {
            String bundleName = toBundleName(baseName, locale);
            String resourceName = toResourceName(bundleName, "properties");
            Closer closer = Closer.create();
            try {
                InputStream stream = closer.register(openStream(loader, resourceName, reload));
                if (stream != null) {
                    Reader reader = closer.register(new BufferedReader(new InputStreamReader(stream, CHARSET_NAME)));
                    return new PropertyResourceBundle(reader);
                }
            } catch (Throwable t) {
                closer.rethrow(t);
            } finally {
                closer.close();
            }
        }

        return super.newBundle(baseName, locale, format, loader, reload);

    }

    private InputStream openStream(final ClassLoader classLoader, final String resourceName, final boolean reload) throws IOException {
        try {
            return AccessController.doPrivileged(new PrivilegedExceptionAction<InputStream>() {
                @Override
                public InputStream run() throws IOException {
                    InputStream in = null;
                    if (reload) {
                        URL url = classLoader.getResource(resourceName);
                        if (url != null) {
                            URLConnection connection = url.openConnection();
                            if (connection != null) {
                                connection.setUseCaches(false);
                                in = connection.getInputStream();
                            }
                        }
                    } else {
                        in = classLoader.getResourceAsStream(resourceName);
                    }
                    return in;
                }
            });
        } catch (PrivilegedActionException e) {
            throw (IOException) e.getException();
        }
    }

    /** {@inheritDoc} */
    @Override
    public List<String> getFormats(String baseName) {
        return ResourceBundle.Control.FORMAT_DEFAULT;
    }

    /** {@inheritDoc} */
    @Override
    public Locale getFallbackLocale(String baseName, Locale locale) {
        return null;
    }

}

このような Control を使った ResourceBundle のラッパークラスを作っておくと便利ですね(例によってコードの全量は gist に置いてみました)。個人的には最近 Java で設定ファイルを扱うときは大体 YAML にしてますが……。

知ってそうであんまり知らない人が多いんじゃないかと思われる小ネタでした。