diff --git a/src/main/java/org/gcube/dataanalysis/ecoengine/utils/DynamicEnum.java b/src/main/java/org/gcube/dataanalysis/ecoengine/utils/DynamicEnum.java new file mode 100644 index 0000000..5274d94 --- /dev/null +++ b/src/main/java/org/gcube/dataanalysis/ecoengine/utils/DynamicEnum.java @@ -0,0 +1,151 @@ +package org.gcube.dataanalysis.ecoengine.utils; + +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.gcube.dataanalysis.ecoengine.utils.LMEEnum.LMEEnumType; + +import sun.reflect.ConstructorAccessor; +import sun.reflect.FieldAccessor; +import sun.reflect.ReflectionFactory; + +public class DynamicEnum { + + private ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory(); + + private 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); + } + + private 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; + } + } + } + + private void cleanEnumCache(Class enumClass) throws NoSuchFieldException, IllegalAccessException { + blankField(enumClass, "enumConstantDirectory"); // Sun (Oracle?!?) JDK 1.5/6 + blankField(enumClass, "enumConstants"); // IBM JDK + } + + private 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)); + } + + private 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)); + } + + /** + * Add an enum instance to the enum class given as argument + * + * @param 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 > void addEnum(Class 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 = getFields(); + 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 values = new ArrayList(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[] {}, // could be used to pass values to the enum constuctor if needed + new Object[] {}); // could be used to pass values to the enum constuctor if needed + + // 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); + } + } + + public static enum DynamicEnumE { + + }; + + public Field[] getFields(){ + Field[] fields =DynamicEnumE.class.getDeclaredFields(); + return fields; + } + + public static void main(String[] args) { + DynamicEnum en = new DynamicEnum(); + + // Dynamically add 3 new enum instances d, e, f to TestEnum + en.addEnum(DynamicEnumE.class, "d"); + en.addEnum(DynamicEnumE.class, "e"); + en.addEnum(DynamicEnumE.class, "f"); + + // Run a few tests just to show it works OK. + System.out.println(Arrays.deepToString(DynamicEnumE.values())); + // Shows : [a, b, c, d, e, f] + } +} diff --git a/src/main/java/org/gcube/dataanalysis/ecoengine/utils/LMEEnum.java b/src/main/java/org/gcube/dataanalysis/ecoengine/utils/LMEEnum.java new file mode 100644 index 0000000..0c0202c --- /dev/null +++ b/src/main/java/org/gcube/dataanalysis/ecoengine/utils/LMEEnum.java @@ -0,0 +1,37 @@ +package org.gcube.dataanalysis.ecoengine.utils; + +import java.lang.reflect.Field; +import java.util.Arrays; + +public class LMEEnum extends DynamicEnum { + + public enum LMEEnumType { + + }; + + public Field[] getFields() { + Field[] fields = LMEEnumType.class.getDeclaredFields(); + return fields; + } + + public static void main(String[] args) { + LMEEnum en = new LMEEnum(); + + en.addEnum(LMEEnumType.class, "CIAO"); + en.addEnum(LMEEnumType.class, "TEST"); + en.addEnum(LMEEnumType.class, "MIAO*_$"); + + System.out.println(Arrays.deepToString(LMEEnumType.values())); +/* + DynamicEnum den = new DynamicEnum(); + + den.addEnum(DynamicEnum.DynamicEnumE.class, "y"); + den.addEnum(DynamicEnum.DynamicEnumE.class, "r"); + den.addEnum(DynamicEnum.DynamicEnumE.class, "t"); + + System.out.println(Arrays.deepToString(DynamicEnum.DynamicEnumE.values())); +*/ + + } + +}