niceideas.ch
Technological Thoughts by Jerome Kehrli

Java - Create enum instances dynamically

by Jerome Kehrli


Posted on Saturday Nov 13, 2010 at 09:08PM in Java


I remember the introduction of the brand new enum type in Java 5 (1.5) was a very exciting announce. However, when I finally switched from 1.4 to 1.5 and actually tried Java's flavoured enum types, I was a bit disappointed.

Before that, I was using Josh Bloch's "Typesafe enum" pattern (effective java) for quite a long time and I didn't really see what was so much better with the new Java native enum construction. Ok, fine, there was the ability to use enum instances in switch - case statements which seemed fine, but what else ?

Besides, what I used to find great with the "typesafe enum" pattern is that it could be tricked and changed the way I wanted, for instance to be able to dynamically (at runtime) add enum instances to a specific typesafe enum class. I found it very disappointing not to be able to do the very same thing easily with the native Java enum construction.

And now you might wonder "Why the hell could one ever need to dynamically add enum values ?!?". You do, right ? Well, let's imagine this scenario:

You have a specific column in a DB table which contains various codes as values. There are more than hundred different codes actually in use in this column. Related to this, you have a business logic which performs different operations on the rows coming from this table, the actual kind of operation applied on the row depends on the value of this code. So there are chance you end up with a lot of if - elseif statements checking the actual value of the code.
I myself am allergic to using string comparison in conditions so I want to be able to map the values from this column to an enum type in Java. This way I can compare enum values instead of strings in my conditions and reduce my dependency on the format of the string value.

Now when there are more than a hundred different possible codes in the DB I really don't have any intent to define them all manually in my enum type. I want to define only the few I am actually using the Java code and let the system add the other ones dynamically, at runtime, when it (the ORM system or whatever I am using for reading the DB rows) encounters a new value from the DB.

Hence my need for dynamically added enum values.

So recently I faced this need once again and took a few hours to build a little solution which enables one to dynamically add values to a Java enum type. The solution is the following :

(Complete code here : DynamicEnumTest.java)

void addEnum(Class<T> enumType, String enumName)

This method adds an enum instance to the enum type given as argument. The whole method code is presented below. We will first details its behaviour and then present every other method this one is using.

    /**
     * Add an enum instance to the enum class given as argument
     *
     * @param <T> the type of the enum (implicit)
     * @param enumType the class of the enum to be modified
     * @param enumName the name of the new enum instance to be added to the class.
     */

    @SuppressWarnings("unchecked")
    public static <T extends Enum<?>> void addEnum(Class<T> enumType, String enumName) {

        // 0. Sanity checks
        if (!Enum.class.isAssignableFrom(enumType)) {
            throw new RuntimeException("class " + enumType + " is not an instance of Enum");
        }
        // 1. Lookup "$VALUES" holder in enum class and get previous enum instances
        Field valuesField = null;
        Field[] fields = enumType.getDeclaredFields();
        for (Field field : fields) {
            if (field.getName().contains("$VALUES")) {
                valuesField = field;
                break;
            }
        }
        AccessibleObject.setAccessible(new Field[] { valuesField }, true);

        try {

            // 2. Copy it
            T[] previousValues = (T[]) valuesField.get(enumType);
            List<T> values = new ArrayList<T>(Arrays.asList(previousValues));

            // 3. build new enum
            T newValue = (T) makeEnum(enumType, // The target enum class
                    enumName, // THE NEW ENUM INSTANCE TO BE DYNAMICALLY ADDED
                    values.size(),
                    new Class<?>[] {}, // can be used to pass values to the enum constuctor
                    new Object[] {}); // can be used to pass values to the enum constuctor

            // 4. add new value
            values.add(newValue);

            // 5. Set new values field
            setFailsafeFieldValue(valuesField, null,
                    values.toArray((T[]) Array.newInstance(enumType, 0)));

            // 6. Clean enum cache
            cleanEnumCache(enumType);

        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e.getMessage(), e);
        }
    }

  1. The enum instances are stored in a static variable of the enum class named $VALUES. Here we search for this static variable, make it accessible and keep the reference for further usage.
  2. The enum instances contained in the $VALUES static variable are copied (stored) in another (new) list.
  3. Then the new enum instance is built using another method named makeEnum which will be discussed later.
  4. The new enum instance is added to the new enum list.
  5. The new enum instance list (containin the new enum instance) is set to the $VALUES field, overwriting the previous content.
  6. The cleaning of the cache answers to something special and tricky. The problem is that the $VALUES is purely generated by the compiler. So the static java code located in java.lang.Class cannot be statically linked to it. Hence it needs to access $VALUES variable using runtime reflection which is a little costy (compared to static method call linking). Hence the code in java.lang.Class which needs an access to the enum values makes a copy of them upon first usage in a private instance variable, the enum cache
    Thus, adding the new enum instance in the $VALUES list is not sufficient, one needs to make sure the java.lang.Class instance caches are cleared as well.

Object makeEnum(Class<?> enumClass, String value, int ordinal, Class<?>[] additionalTypes, Object[] additionalValues)

This method creates the new enum instance. It takes as argument the class for which to create a new enum instance and the name of the enum instance to create. The ordinal argument is the ordinal value which will be associated to the enum instance. The two last arrays are helpful if on want to add an enum instance to an enum type using constructor arguments to store values bound to the enum instances. The details of the method is as follows :

    private static Object makeEnum(Class<?> enumClass, String value, int ordinal,
            Class<?>[] additionalTypes, Object[] additionalValues) throws Exception {
        Object[] parms = new Object[additionalValues.length + 2];
        parms[0] = value;
        parms[1] = Integer.valueOf(ordinal);
        System.arraycopy(additionalValues, 0, parms, 2, additionalValues.length);
        return enumClass.cast(getConstructorAccessor(enumClass, additionalTypes).newInstance(parms));
    }

The makeEnum method uses another method named getConstructorAccessor to obtain a reference on the constructor accessor allowing to actually create the new enum instance :

    private static ConstructorAccessor getConstructorAccessor(Class<?> enumClass,
            Class<?>[] additionalParameterTypes) throws NoSuchMethodException {
        Class<?>[] parameterTypes = new Class[additionalParameterTypes.length + 2];
        parameterTypes[0] = String.class;
        parameterTypes[1] = int.class;
        System.arraycopy(additionalParameterTypes, 0,
                parameterTypes, 2, additionalParameterTypes.length);
        return reflectionFactory.newConstructorAccessor(
                enumClass.getDeclaredConstructor(parameterTypes));
    }

setFailsafeFieldValue(Field field, Object target, Object value) throws NoSuchFieldException, IllegalAccessException

This method is a convenient method whose responsibility is to set the value given as argument to the field given as argument on the target given as argument. This sounds easy yet doing in failsafely is a little bit more tricky than one could guess :

    private static void setFailsafeFieldValue(Field field, Object target, Object value)
             throws NoSuchFieldException, IllegalAccessException {

        // let's make the field accessible
        field.setAccessible(true);

        // next we change the modifier in the Field instance to
        // not be final anymore, thus tricking reflection into
        // letting us modify the static final field

        Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        int modifiers = modifiersField.getInt(field);

        // blank out the final bit in the modifiers int
        modifiers &= ~Modifier.FINAL;
        modifiersField.setInt(field, modifiers);

        FieldAccessor fa = reflectionFactory.newFieldAccessor(field, false);
        fa.set(target, value);
    }

cleanEnumCache(Class<?> enumClass) throws NoSuchFieldException, IllegalAccessException

This method clears the enum cache variable on two different JVM implementations.

    private static void cleanEnumCache(Class<?> enumClass)
            throws NoSuchFieldException, IllegalAccessException {
        blankField(enumClass, "enumConstantDirectory"); // Sun (Oracle?!?) JDK 1.5/6
        blankField(enumClass, "enumConstants"); // IBM JDK
    }

It used the blankField method to actually blank the target field :

    private static void blankField(Class<?> enumClass, String fieldName)
            throws NoSuchFieldException, IllegalAccessException {
        for (Field field : Class.class.getDeclaredFields()) {
            if (field.getName().contains(fieldName)) {
                AccessibleObject.setAccessible(new Field[] { field }, true);
                setFailsafeFieldValue(field, enumClass, null);
                break;
            }
        }
    }

On can test this dynamic enum instance adding system using the following little code snippet:

    private static enum TestEnum {
        a,
        b,
        c;
    };

    public static void main(String[] args) {

        // Dynamically add 3 new enum instances d, e, f to TestEnum
        addEnum(TestEnum.class , "d");
        addEnum(TestEnum.class , "e");
        addEnum(TestEnum.class , "f");

        // Run a few tests just to show it works OK.
        System.out.println(Arrays.deepToString(TestEnum.values()));
        // Shows : [a, b, c, d, e, f]
    }

The complete code presented in this article is available here : DynamicEnumTest.java

Interestingly, this is a situation where runtime reflection, as limited as it is, is yet sufficient for the few required manipulations involved in this dynamic enum creation feature. It would be interesting to see how a much more powerful mechanism such as javassist (bytecode manipulation) could make things easier. Maybe I'll dig into it someday ...

HTH



Comments:

good

Posted by 122.181.131.76 on May 26, 2011 at 05:27 AM CEST #


I'm thinking of doing something like this too. But how do you use your newly generated enums? You can't have codes like doSomething(TestEnum.f) if new TestEnum instances are created at runtime. annotation allows you to generate codes at compile time, wonder if that would work.

Posted by cindy on June 04, 2011 at 04:53 AM CEST #


Nice One Thanks

Posted by Aman on November 29, 2011 at 06:08 AM CET #


Thanks! Just what I needed :-)

Posted by Pål A Olsen on December 05, 2013 at 10:55 AM CET #


Great, thank you! Just what I was searching for!

Found one little bug:

Field[] fields = TestEnum.class.getDeclaredFields();
Should be:
Field[] fields = enumType.getDeclaredFields();

Posted by lauper on June 17, 2014 at 12:41 PM CEST #


Here is what i did, a sort of hack. public static enum Setter { DYNAMIC_ENUM_EXAMPLE { @Override public String setGetValue(String yourValue) { return "prefix " + yourValue + " postfix"; } }; public abstract String setGetValue(String value); } You can get the value like this : Setter.FEE_AGENT_DOESNT_EXIST.setGetValue("namaste") Output : prefix namaste postfix

Posted by Gagandeep Singh on September 26, 2014 at 11:31 AM CEST #


good!

Posted by 李成 on May 17, 2015 at 10:51 AM CEST #


wrote article for same thing using javassist: https://bojanv55.wordpress.com/2015/05/25/java-dynamic-enums/

Posted by bojan on May 25, 2015 at 07:13 PM CEST #


i have a enum with String array, i would like to modify that but facing some issue. e.g. i have enum private static enum TestEnum { APPLE("RED", GREEN), BANANA("YELLOW"); private String[] values; TestEnum (String .. values) this.values = values; }; so here i would like to add new constant dynamically e.g. ORANGE("ORANGE","YELLOW") can anyone help ?

Posted by anil on June 10, 2015 at 06:50 AM CEST #


Good job

Posted by ceb on June 18, 2015 at 11:39 AM CEST #


Can we dynamically generate Enums with methods in it. For example: private static enum TestEnum { a{public String doStuff(){}}, b{public String doStuff(){}}, c{public String doStuff(){}}; public abstract String doStuff(); }

Posted by Prakhar Asthana on April 04, 2016 at 11:39 AM CEST #


thanks a lot for this code. This really saved a lot of time for me

Posted by Anil on April 20, 2017 at 06:19 PM CEST #


thanks. How to dynamically delete an enum ?

Posted by kang on November 02, 2017 at 11:27 AM CET #


I don't understand how the above code helps since even if the values array of enum is modified by reflection, you still can't access the new d, e, f since construct like TestEnum.d or TestEnum.e are compile time constructs. Can you please explain how to get compiled code that contains TestEnum.d or TestEnum.e or "switch (TestEnum) { case d: ;//do something}"

Posted by AviG on January 11, 2018 at 04:33 PM CET #


@AviG You can't :-) The whole purpose of dynamically added enum values would be to access them by reflection, such as what an ORM would typically do. The introduction of this article gives some clues in this regards.

Posted by Jerome on January 12, 2018 at 02:21 PM CET #


Can you extend this to construct an Enum with parameters like TestEnum("a", 1), TestEnum("b", 2). I tried passing them but runs into the MethodNotFound exception when creating a constructor. Can you provide the code snippet that constructs the Enum with input params?

Posted by dasvin on June 26, 2019 at 02:21 PM CEST #


@dasvin, You just need to add two parameters to `addEnum(Class<?> enumClass, String value, int ordinal, Class<?>[] additionalTypes, Object[] additionalValues) throws Exception` these two arrays are then passed to `makeEnum`. @Jerome Kehrli - definitely a "nice idea", thank you for the implementation

Posted by caspar on August 27, 2019 at 09:49 PM CEST #


@caspar, how is the code after doing that you've said "You just need to add two parameters..."?

Posted by Jonatas Laet on February 20, 2020 at 10:28 PM CET #


thanks

Posted by dzc on June 08, 2020 at 04:27 AM CEST #


Leave a Comment

HTML Syntax: Allowed