StringLoader

太一がEclipseプラグインとかでよくやっていたpropertiesファイルのメッセージを
public staticな変数にぶち込むやつをぱくってみた。名前もそのまんま。StringLoader。

使い方は、propertiesファイルの名前のpublic staticなフィールドにして並べておいて、
そのClassをStringLoaderにロードさせる。すると同名のpropertiesファイルを読んで、
キーと一致する変数名があれば設定してくれる。



テストはこんな感じ。

public class StringLoaderTest extends TestCase {

	public static String aaa;

	public static String bbb;

	public static String ccc;

	public void test1() throws Exception {
		StringLoader.load(StringLoaderTest.class);
		assertEquals("AAA", aaa);
		assertEquals("BBB", bbb);
		assertEquals("CCC", ccc);

		//propertiesの名前が違う場合
		StringLoader.load(StringLoaderTest.class,
				"commons.util.StringLoaderTest2");
		assertEquals("1", aaa);
		assertEquals("2", bbb);
		assertEquals("3", ccc);
	}
}


StringLoaderTest.propertiesはこんな感じで。

aaa=AAA
bbb=BBB
ccc=CCC

実装。Java6前提でResourceBundleはキャッシュさせないようにした。

package commons.util;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.ResourceBundle.Control;

import common.Logger;

/**
 * StringLoader is an utility class to get messages from properties file. This
 * class is intended to use loading properties statically, and also is not
 * intend to be used multiple locale environments which message by locale is
 * dynamically switched by each user request.
 * 
 * @author shot
 */
public class StringLoader {

	private static final Logger logger = Logger.getLogger(StringLoader.class);

	public static void load(Class<?> holder) {
		load(holder, holder.getName());
	}

	public static void load(Class<?> holder, String name) {
		ResourceBundle rb = getBundle(name, holder.getClassLoader());
		if (rb == null) {
			return;
		}
		Field[] fields = holder.getDeclaredFields();
		for (int i = 0; i < fields.length; i++) {
			Field field = fields[i];
			if (validateMask(field)) {
				continue;
			}
			String key = field.getName();
			if (rb.containsKey(key) == false) {
				logger.debug(key + " not found in " + name);
			}
			String msg = rb.getString(key);
			try {
				if (isAssignableFrom(String.class, field)) {
					field.set(null, msg);
				}
			} catch (Exception e) {
				logger.debug(e.getMessage() + e);
			}
		}
	}

	private static boolean validateMask(Field f) {
		final int MOD_EXPECTED = Modifier.PUBLIC | Modifier.STATIC;
		final int MOD_MASK = MOD_EXPECTED | Modifier.FINAL;
		return (f.getModifiers() & MOD_MASK) != MOD_EXPECTED;
	}

	private static ResourceBundle getBundle(String name, ClassLoader loader) {
		try {
			return ResourceBundle.getBundle(name, Locale.getDefault(), loader,
					new ResourceBundle.Control() {

						@Override
						public long getTimeToLive(String baseName, Locale locale) {
							return Control.TTL_DONT_CACHE;
						}

					});
		} catch (MissingResourceException e) {
			return null;
		}
	}

	private static boolean isAssignableFrom(final Class<?> clazz,
			final Field target) {
		return clazz.isAssignableFrom(target.getType());
	}
}


課題は、JavaDocにも少し書いておいたけど、ロケールがころころ変わるような
環境ではこの方法じゃ駄目。なので、前提としてServerサイドのロケールだけを見て
一発でばーんと決まるようなメッセージ以外は使えないっすね。

これについては対処するコードも書いてみたんだけど、どうも気持ち悪いコードになるので却下。
なんとなくロケールがころころ変わるような場合においては、StringLoaderのアプローチは現実的じゃないので。