tags:

views:

5203

answers:

8

Hi there,

Given the followin enum :

public enum Car
{
        NANO ("Very Cheap", "India"),
        MERCEDES ("Expensive", "Germany"),
        FERRARI ("Very Expensive", "Italy");

        public final String cost;
        public final String madeIn;

        Car(String cost, String madeIn)
        {
                this.cost= cost;
                this.madeIn= madeIn;
        }

}

Does someone know a way to set up enum values via Spring IoC at construction time ?

Thank you for your time !

[Edit] Sorry, my question wasn't cristal clear. What I would like to do is injecting, at class load time, values that are harcoded in the code snippet above.

Let's say that the application must be deployed in Germany, where Nano's are better said "Nearly free", and in India where Ferrari's are "Unaffordable". In both country, there are only three cars, no more no less (hence an enum), but there "inner" values may differs. That is, a contextual initialization of immutables, which Spring is generally good at. Hope this is more clear (sorry guys, English is not my native langage, which is sometimes a real pain ;))

+1  A: 

What do you need to set up? The values are created when the class loads, and as it's an enum, no other values can be created (unless you add them to the source and recompile).

That's the point of an enum, to be able to give limit a type to an explicit range of constant, immutable values. Now, anywhere in your code, you can refer to a type Car, or its values, Car.NANO, Car.MERCEDES, etc.

If, on the other hand, you have a set of values that isn't an explicit range, and you want to be able to create arbitrary objects of this type, you'd use the same ctor as in your post, but as a regular, not enum class. Then Spring provides various helper clases to read values from some source (XML file, config file, whatever) and create Lists of that type.

tpdi
+4  A: 

Do you mean setting up the enum itself?

I don't think that's possible. You cannot instantiate enums because they have a static nature. So I think that Spring IoC can't create enums as well.

On the other hand, if you need to set initialize something with a enum please check out the Spring IoC chapter. (search for enum) There's a simple example that you can use.

bruno conde
Oups ! I was sure I had accept your answer month ago ! Thanks :)
Olivier
+2  A: 

Why not provide a setter (or constructor argument) that takes a String, and simply call Enum.valueOf(String s) to convert from a String to an enum. Note an exception will get thrown if this fails, and your Spring initialisation will bail out.

Brian Agnew
This is how I've done it in the past.
Greg Noe
I guess I see where you to go ... but doesn't see how. Could you give more explanations ? Thx
Olivier
I created a new answer to illustrate how to do this.
Greg Noe
Hi Olivier - I believe from your revised question that the above isn't what you require :-(
Brian Agnew
No problem, Brian :) As said, prior to the revision, my question was a little bit confusing
Olivier
+1  A: 
<bean id="car" class="Foo">
    <property name="carString" value="NANO" />
</bean>

And then in your class Foo, you would have this setter:

public void setCar(String carString) {
    this.carString = Car.valueOf(carString);
}
Greg Noe
It's the enum itself I would like to initialize via injection, not using it to initialize a bean's property ;)
Olivier
Oh haha, sorry, got carried away. I'll think about this.
Greg Noe
A: 

All right, this is a bit complex but you may find a way to integrate it. Enums are not meant to change at runtime, so this is a reflection hack. Sorry I don't have the Spring implementation part, but you could just build a bean to take in the enum class or object, and another field that would be the new value or values.

Constructor con = MyEnum.class.getDeclaredConstructors()[0];
Method[] methods = con.getClass().getDeclaredMethods();
for (Method m : methods) {
  if (m.getName().equals("acquireConstructorAccessor")) {
    m.setAccessible(true);
    m.invoke(con, new Object[0]);
  }
}
Field[] fields = con.getClass().getDeclaredFields();
Object ca = null;
for (Field f : fields) {
  if (f.getName().equals("constructorAccessor")) {
    f.setAccessible(true);
    ca = f.get(con);
  }
}
Method m = ca.getClass().getMethod(
  "newInstance", new Class[] { Object[].class });
m.setAccessible(true);
MyEnum v = (MyEnum) m.invoke(ca, new Object[] { 
  new Object[] { "MY_NEW_ENUM_VALUE", Integer.MAX_VALUE } });
  System.out.println(v.getClass() + ":" + v.name() + ":" + v.ordinal());

This is taken from this site.

Greg Noe
I don't think this is what he asked. The link you provided deals with adding a new enum value to an existing type at run-time, and the question is about setting the fields of enum to localized values.
javashlook
Correct, javashlook. More over, my question is currently motivated by investigation on a way to refactor ... another hack (ab)using of reflection :)
Olivier
+2  A: 

I don't think it can be done from Spring's ApplicationContext configuration. But, do you really need it done by Spring, or can you settle for simple externalization using ResourceBundle; like this:

public enum Car
{
    NANO,
    MERCEDES,
    FERRARI;

    public final String cost;
    public final String madeIn;

    Car()
    {
            this.cost = BUNDLE.getString("Car." + name() + ".cost");
            this.madeIn = BUNDLE.getString("Car." + name() + ".madeIn");
    }

    private static final ResourceBundle BUNDLE = ResourceBundle.getBundle(...);

}

In the properties file, one for each specific locale, enter the keys describing the possible internal enum values:

Car.NANO.cost=Very cheap
Car.NANO.madeIn=India
Car.MERCEDES.cost=Expensive
...

The only drawback of this approach is having to repeat the name of enum fields (cost, madeIn) in Java code as strings. Edit: And on the plus side, you can stack all properties of all enums into one properties file per language/locale.

javashlook
Very nice workaround, really :), but i was using String for simplicity matter. However, your idea give me an another idea ... that works !!! :DI'll post my solution tomorrow (nearly 0.00AM here ;), but you proposal drove me halfway ! Thank you
Olivier
A: 

Here is the solution I came to (thanks to Javashlook whose answer put me on track). It works, but it's most probably not a production-grade way of doing it.

But better than a thousand words, here is the code, I'll let you judge by yourself.

Let's take a look at the revised Car enum :

public enum Car {
    NANO(CarEnumerationInitializer.getNANO()), MERCEDES(
      CarEnumerationInitializer.getMERCEDES()), FERRARI(
      CarEnumerationInitializer.getFERRARI());

    public final String cost;
    public final String madeIn;

    Car(ICarProperties properties) {
     this.cost = properties.getCost();
     this.madeIn = properties.getMadeIn();
    }
}

And here are the "plumbling" classes :

//Car's properties placeholder interface ...
public interface ICarProperties {
    public String getMadeIn();
    public String getCost();
}
//... and its implementation
public class CarProperties implements ICarProperties {
    public final String cost;
    public final String madeIn;

    public CarProperties(String cost, String madeIn) {
     this.cost = cost;
     this.madeIn = madeIn;
    }
    @Override
    public String getCost() {
     return this.cost;
    }
    @Override
    public String getMadeIn() {
     return this.madeIn;
    }
}

//Singleton that will be provide Car's properties, that will be defined at applicationContext loading.
public final class CarEnumerationInitializer {
    private static CarEnumerationInitializer INSTANCE;
    private static ICarProperties NANO;
    private static ICarProperties MERCEDES;
    private static ICarProperties FERRARI;

    private CarEnumerationInitializer(ICarProperties nano,
      ICarProperties mercedes, ICarProperties ferrari) {
     CarEnumerationInitializer.NANO = nano;
     CarEnumerationInitializer.MERCEDES = mercedes;
     CarEnumerationInitializer.FERRARI = ferrari;
    }

    public static void forbidInvocationOnUnsetInitializer() {
     if (CarEnumerationInitializer.INSTANCE == null) {
      throw new IllegalStateException(CarEnumerationInitializer.class
        .getName()
        + " unset.");
     }
    }

    public static CarEnumerationInitializer build(CarProperties nano,
      CarProperties mercedes, CarProperties ferrari) {
     if (CarEnumerationInitializer.INSTANCE == null) {
      CarEnumerationInitializer.INSTANCE = new CarEnumerationInitializer(
        nano, mercedes, ferrari);
     }
     return CarEnumerationInitializer.INSTANCE;
    }

    public static ICarProperties getNANO() {
            forbidInvocationOnUnsetInitializer();
     return NANO;
    }

    public static ICarProperties getMERCEDES() {
            forbidInvocationOnUnsetInitializer();
     return MERCEDES;
    }

    public static ICarProperties getFERRARI() {
            forbidInvocationOnUnsetInitializer();
     return FERRARI;
    }
}

Finally, the applicationContext definition :

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <bean id="nano" class="be.vinkolat.poc.core.car.CarProperties">
     <constructor-arg type="java.lang.String" value="Cheap"></constructor-arg>
     <constructor-arg type="java.lang.String" value="India"></constructor-arg>
    </bean>
    <bean id="mercedes"
     class="be.vinkolat.poc.core.car.CarProperties">
     <constructor-arg type="java.lang.String" value="Expensive"></constructor-arg>
     <constructor-arg type="java.lang.String" value="Germany"></constructor-arg>
    </bean>
    <bean id="ferrari" class="be.vinkolat.poc.core.car.CarProperties">
     <constructor-arg type="java.lang.String"
      value="Very Expensive">
     </constructor-arg>
     <constructor-arg type="java.lang.String" value="Italy"></constructor-arg>
    </bean>
    <bean id="carInitializer"
     class="be.vinkolat.poc.core.car.CarEnumerationInitializer"
     factory-method="build" lazy-init="false">
     <constructor-arg type="be.vinkolat.poc.core.car.CarProperties"
      ref="nano" />
     <constructor-arg type="be.vinkolat.poc.core.car.CarProperties"
      ref="mercedes" />
     <constructor-arg type="be.vinkolat.poc.core.car.CarProperties"
      ref="ferrari" />
    </bean>
</beans>

It works, but there is one major weakness : CarEnumerationInitializer MUST be instantiated BEFORE any reference is made to Car enumeration, otherwise CarProperties are null, meaning that Car's properties can't be set when Car is loaded (hence the IllegalStateException thrown, to at least make it crashes in a predictable and documentated way). carInitializer bean's property lazy-init set to an explicit false, to put emphasis on the need to load it as soon as possible. I would say it may be useful in a simple application, one where you can easely guess where a first call to Car will be made. For a larger one, it will probably be such a clutter that I didn't encourage you to use it.

Hope this help, comments and vote (up and down) very welcome :) I'll wait for a few days to make this one the accepted answer, to let you react.

Olivier
A: 

Attempting to mutate an Enum is well silly and goes completely against their design objectives. An enum by definition represents a distinct value within a group. If you ever need more / less values you will need to update the source. While you can change an enums state by adding setters (after all they are just objects) your hacking the system.

mP