はじめに
そもそもResourceBundle#getBundle
に対して複数のリソースを読み込ませたいという要求自体があまりないのかもしれません。
もしそれをしたいのであれば、ListなりにResourceBundle
を保持しておいて検索すれば事足りると思います。
ですが、BeanValidationでメッセージ出力をしているときに遭遇したのはResourceBundle
をインターフェースとしたクラスを引数として渡さないといけないというケースでした。
色々なやり方があるとは思いますが、私はnewBundle
を拡張するやり方でやってみました。
具体的には
で自作したライブラリの拡張です。
実装
方式としては、参照資産を全て1つのstream
に結合して読み込ませる、というだけです。
今回の実験で得た知識はSequenceInputStream
です。
なんと、JDK1.0
からのクラスです。知りませんでした。
それをリソースファイル分、再帰処理で結合していきました(#concateInputStrem
)。
ちょっとした工夫としては、リソースファイルが改行で終わっていないケースを考慮して 結合時に改行情報を差し込むようにしたところでしょうか。
protected ResourceBundle newBundleProperties(String baseName, Locale locale, ClassLoader loader, boolean reload) throws IllegalAccessException, InstantiationException, IOException { InputStream stream = readBaseNames(baseName, locale, loader, reload); ResourceBundle bundle = null; 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; } InputStream readBaseNames(String baseName, Locale locale, ClassLoader loader, boolean reload) throws IOException { String[] _baseNames = baseName.split(","); List<InputStream> propertiesInputStreams = new ArrayList<>(); for (String _baseName : _baseNames) { InputStream propertiesInputStream = readProperties(_baseName.trim(), locale, loader, reload); /* propertiesの結合時に使用する改行文字 */ InputStream lineSeparator = new ByteArrayInputStream(System.lineSeparator().getBytes(StandardCharsets.UTF_8)); if (propertiesInputStream != null) { InputStream inputStreamWithLineSeparator = new SequenceInputStream(propertiesInputStream, lineSeparator); propertiesInputStreams.add(inputStreamWithLineSeparator); } } if (propertiesInputStreams.isEmpty()) { return null; } Iterator<InputStream> inputStreamsIterator = propertiesInputStreams.iterator(); InputStream inputStream = inputStreamsIterator.next(); if (propertiesInputStreams.size() == 1) { return inputStream; } return concateInputStrem(inputStreamsIterator, inputStream); } InputStream readProperties(String baseName, Locale locale, ClassLoader loader, boolean reload) throws IOException { String bundleName = toBundleName(baseName, locale); final String resourceName = (bundleName.contains("://")) ? null : toResourceName(bundleName, "properties"); if (resourceName == null) { return null; } 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) { // Disable caches to get fresh data for reloading. connection.setUseCaches(false); is = connection.getInputStream(); } } } else { is = classLoader.getResourceAsStream(resourceName); } return is; }); } catch (PrivilegedActionException e) { throw (IOException) e.getException(); } return stream; } /** * InputStreamを結合して返却します. * * @param inputStreamsIterator 参照資産のイテレーター * @param concatedInputStream 結合先となるInputStream * @return 結合したInputStream */ InputStream concateInputStrem(Iterator<InputStream> inputStreamsIterator, InputStream concatedInputStream) throws IOException { if (inputStreamsIterator.hasNext() == false) { return concatedInputStream; } InputStream inputStream = inputStreamsIterator.next(); SequenceInputStream sequenceInputStream = new SequenceInputStream(concatedInputStream, inputStream); return concateInputStrem(inputStreamsIterator, sequenceInputStream); }
Code
さいごに
ResoueceBundle
そのものを拡張する、というやり方も試したりしたのですが、思ったようにできませんでした。
このやり方であれば特殊なことはしていないので(単純に読み込むstream
を結合しただけ)、コードのボリュームの割には単純なやり方なように思います。
ちょっと迷ったのは、Control
に追加したいPropertyFileを指定する、というやり方です。
こちらの案だったら、引数の型を配列なり、リストなりにして「複数指定していますよ」ということをメソッドで表現できます。
最終的に本方式にしたのは「ファイルの指定箇所が分散すると3日後の自分が忘れそうだ」というのが大きく、もう1つはControl
をResourceBundleの生成都度置き換えるという実装思想が、しっくり来なかったからです。