Pregunta

I have been tinkering with this idea for a few days, and I was wondering if anyone else has thought of doing this. I would like to try and create a ResourceBundle that I can access the values with by using an enum. The benefits of this approach would be that my keys would be well defined, and hopefully, my IDE can pick up on the types and auto-complete the variable names for me. In other words, I'm after a sort of refined ListResourceBundle.

Essentially, this is what I'm after...

I have an enum that consists of various bundles set up like so:

interface Bundle {
    String getBundleName();
    EnumResourceBundle<??????> getEnumResourceBundle();
}

enum Bundles implements Bundle {
    BUNDLE1("com.example.Bundle1", Keys.class);

    private final String bundleName;
    private final EnumResouceBundle<??????> bundle;

/**
 * I understand here I need to do some cast with ResourceBundle.getBundle(bundleName);
 * in order to have it back-track through parents properly. I'm fiddling with this
 * right now using either what I specified earlier (saving bundleName and then
 * retrieving the ResourceBundle as needed), and saving a reference to the 
 * ResourceBundle.
 */
    private <E extends Enum<E> & Key> Bundles(String bundleName, Class<E> clazz) {
        this.bundleName = bundleName;
        this.bundle = new EnumResourceBundle<??????>(clazz);
    }

    @Override
    public String getBundleName() {
        return bundleName;
    }

    @Override
    public EnumResourceBundle<??????> getEnumResourceBundle() {
        return bundle;
    }
}

interface Key {
    String getValue();
}

enum Keys implements Key {
    KEY1("This is a key"),
    KEY2("This is another key");

    private final String value;

    private Keys(String value) {
        this.value = value;
    }

    @Override
    public String getKey() {
        return value;
    }
}

class EnumResourceBundle<E extends Enum<E> & Key> extends ResourceBundle {
    // Can also store Object in case we need it
    private final EnumMap<E, Object> lookup;

    public EnumResourceBundle(Class<E> clazz) {
        lookup = new EnumMap<>(clazz);
    }

    public String getString(E key) {
        return (String)lookup.get(key);
    }
}

So my overall goal would be to have to code look something like this:

public static void main(String[] args) {
    Bundles.CLIENT.getEnumResourceBundle().getString(Keys.KEY1);
    Bundles.CLIENT.getEnumResourceBundle().getString(Keys.KEY2);

    // or Bundles.CLIENT.getString(Keys.KEY1);
}

I'd also like to provide support for formatting replacements (%s, %d, ...).

I realize that it isn't possible to back-track a type from a class, and that wouldn't help me because I've already instantiated Bundles#bundle, so I was wondering if I could somehow declare EnumResourceBundle, where the generic type is an enum which has implemented the Key interface. Any ideas, help, or thoughts would be appreciated. I would really like to see if I can get it working like this before I resort to named constants.

Update: I had a thought that maybe I could also try changing EnumResourceBundle#getString(E) to take a Key instead, but this would not guarantee that it's a valid Key specified in the enum, or any enum for that matter. Then again, I'm not sure how that method would work when using a parent enum Key within a child EnumResourceBundle, so maybe Key is a better option.

¿Fue útil?

Solución

I've done something like this before but I approached it the other way around and it was pretty simple.

I just created an enum translator class that accepts the enum, and then maps the enum name to the value from the property file.

I used a single resource bundle and then the translate just looked something like (from memory):

<T extends enum>String translate(T e) {
   return resources.getString(e.getClass().getName()+"."+e.getName());
}

<T extends enum>String format(T e, Object... params) {
   return MessageFormat.format(translate(e), params);
}

Now for any enum you can just add a string to the file:

com.example.MyEnum.FOO = This is a foo
com.example.MyEnum.BAR = Bar this!

If you want to ensure that the passed class is the correct enum for this you could either define a shared interface for those enums or you could make this into a class with the T defined on the class type and then generate instances of it for each enum you want to be able to translate. You could then do things like create a translator class for any enum just by doing new EnumFormatter(). Making format() protected would allow you to give a specific enforceable format for each enum type too by implementing that in the EnumFormatter.

Using the class idea even lets you go one step further and when you create the class you can specify both the enum that it is for and the properties file. It can then immediately scan the properties file and ensure that there is a mapping there for every value in the enum - throwing an exception if one is missing. This will help ensure early detection of any missing values in the properties file.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top