Update: Per James' comment below, the getEnumConstants()
method on Class
avoids all the messy reflection.
And as I should have remembered, Enum.valueOf(Class, String)
already does exactly what this parseXML()
method does, using the name()
method rather than toString()
. (My preferred approach would still be to define a special method and special "name" just for this purpose, though, rather than to use the enum's inherent name.)
If you pass in an instance of the enum class you think you're parsing, you can use reflection to access the values
method generically, if you really want to -- though I wouldn't recommend it:
@SuppressWarnings("unchecked")
public static <T extends Enum<T>> T parseXML(Class<T> enumClass, String value) {
try {
Method valuesMethod = enumClass.getDeclaredMethod("values");
T[] values = (T[]) valuesMethod.invoke(null);
for (T t : values) {
if (t.toString().equals(value)) {
return t;
}
}
throw new IllegalArgumentException("Bad value: " + value);
} catch (NoSuchMethodException e) {
// Should never happen: all enums have values()
throw new IllegalStateException(e);
} catch (InvocationTargetException e) {
// Should never happen: values() can't be overridden,
// so there should be no way to blow it up
throw new IllegalStateException(e);
} catch (IllegalAccessException e) {
// Should never happen: values() should always be public
throw new IllegalStateException(e);
}
}
One alternative would be to have all "parseable" enums register themselves with a "registry" class, e.g.
public class EnumRegistry {
private static final Map<Class<?>, Map<String, Enum<?>>> registry
= new HashMap<Class<?>, Map<String, Enum<?>>>();
/**
* @param enumClass The class of the enum to register -- note that calling
* getClass() on the value isn't safe, b/c if an enum declares abstract
* methods, each instance will be a different anonymous inner class
*/
public static <T extends Enum<T>> void register(Class<T> enumClass, T t) {
Map<String, Enum<?>> vals = registry.get(enumClass);
if (vals == null) {
vals = new HashMap<Class<?>, Map<String, Enum<?>>>();
registry.put(enumClass, vals);
}
String strVal = t.toString();
if (vals.containsKey(strVal)) {
throw new IllegalArgumentException("Duplicate value: " + strVal);
}
vals.put(strVal, t);
}
@SuppressWarnings("unchecked")
public static <T extends Enum<T>> T get(Class<T> enumClass, String s) {
Map<String, Enum<?>> vals = registry.get(enumClass);
if (vals == null) {
return null;
}
return (T) vals.get(s);
}
}
And to register:
public enum Foo {
BAR, BAZ;
private Foo() {
EnumRegistry.register(Foo.class, this);
}
}
Note: In any case, I'd suggest not overriding toString()
but instead, as Phill Sacre suggests, introducing an interface that defines a different method specifically for this purpose, e.g. getName()
. That way, among other things, you can be sure that only enums that are meant to be used with this XML mechanism of yours are involved, and you don't have to worry about the default implementation of toString()
causing a screwup when somebody renames an enum instance. Plus, you can then use the same mechanism with other classes that are not actually Enum
subclasses.