    001package net.minecraftforge.common;
    002
    003import java.lang.reflect.*;
    004import java.util.*;
    005
    006import cpw.mods.fml.common.FMLCommonHandler;
    007import cpw.mods.fml.common.FMLLog;
    008
    009import net.minecraft.block.EnumMobType;
    010import net.minecraft.block.material.Material;
    011import net.minecraft.enchantment.EnumEnchantmentType;
    012import net.minecraft.entity.EnumCreatureAttribute;
    013import net.minecraft.entity.EnumCreatureType;
    014import net.minecraft.entity.EnumEntitySize;
    015import net.minecraft.entity.player.EnumStatus;
    016import net.minecraft.item.EnumAction;
    017import net.minecraft.item.EnumArmorMaterial;
    018import net.minecraft.item.EnumToolMaterial;
    019import net.minecraft.util.EnumArt;
    020import net.minecraft.util.EnumMovingObjectType;
    021import net.minecraft.world.EnumSkyBlock;
    022import net.minecraft.world.gen.structure.EnumDoor;
    023import net.minecraftforge.classloading.FMLForgePlugin;
    024
    025public class EnumHelper
    026{
    027    private static Object reflectionFactory      = null;
    028    private static Method newConstructorAccessor = null;
    029    private static Method newInstance            = null;
    030    private static Method newFieldAccessor       = null;
    031    private static Method fieldAccessorSet       = null;
    032    private static boolean isSetup               = false;
    033
    034    //Some enums are decompiled with extra arguments, so lets check for that
    035    private static Class[][] commonTypes =
    036    {
    037        {EnumAction.class},
    038        {EnumArmorMaterial.class, int.class, int[].class, int.class},
    039        {EnumArt.class, String.class, int.class, int.class, int.class, int.class},
    040        {EnumCreatureAttribute.class},
    041        {EnumCreatureType.class, Class.class, int.class, Material.class, boolean.class},
    042        {EnumDoor.class},
    043        {EnumEnchantmentType.class},
    044        {EnumEntitySize.class},
    045        {EnumMobType.class},
    046        {EnumMovingObjectType.class},
    047        {EnumSkyBlock.class, int.class},
    048        {EnumStatus.class},
    049        {EnumToolMaterial.class, int.class, int.class, float.class, int.class, int.class}
    050    };
    051
    052    public static EnumAction addAction(String name)
    053    {
    054        return addEnum(EnumAction.class, name);
    055    }
    056    public static EnumArmorMaterial addArmorMaterial(String name, int durability, int[] reductionAmounts, int enchantability)
    057    {
    058        return addEnum(EnumArmorMaterial.class, name, durability, reductionAmounts, enchantability);
    059    }
    060    public static EnumArt addArt(String name, String tile, int sizeX, int sizeY, int offsetX, int offsetY)
    061    {
    062        return addEnum(EnumArt.class, name, tile, sizeX, sizeY, offsetX, offsetY);
    063    }
    064    public static EnumCreatureAttribute addCreatureAttribute(String name)
    065    {
    066        return addEnum(EnumCreatureAttribute.class, name);
    067    }
    068    public static EnumCreatureType addCreatureType(String name, Class typeClass, int maxNumber, Material material, boolean peaceful)
    069    {
    070        return addEnum(EnumCreatureType.class, name, typeClass, maxNumber, material, peaceful);
    071    }
    072    public static EnumDoor addDoor(String name)
    073    {
    074        return addEnum(EnumDoor.class, name);
    075    }
    076    public static EnumEnchantmentType addEnchantmentType(String name)
    077    {
    078        return addEnum(EnumEnchantmentType.class, name);
    079    }
    080    public static EnumEntitySize addEntitySize(String name)
    081    {
    082        return addEnum(EnumEntitySize.class, name);
    083    }
    084    public static EnumMobType addMobType(String name)
    085    {
    086        return addEnum(EnumMobType.class, name);
    087    }
    088    public static EnumMovingObjectType addMovingObjectType(String name)
    089    {
    090        if (!isSetup)
    091        {
    092            setup();
    093        }
    094
    095        return addEnum(EnumMovingObjectType.class, name);
    096    }
    097    public static EnumSkyBlock addSkyBlock(String name, int lightValue)
    098    {
    099        return addEnum(EnumSkyBlock.class, name, lightValue);
    100    }
    101    public static EnumStatus addStatus(String name)
    102    {
    103        return addEnum(EnumStatus.class, name);
    104    }
    105    public static EnumToolMaterial addToolMaterial(String name, int harvestLevel, int maxUses, float efficiency, int damage, int enchantability)
    106    {
    107        return addEnum(EnumToolMaterial.class, name, harvestLevel, maxUses, efficiency, damage, enchantability);
    108    }
    109
    110    private static void setup()
    111    {
    112        if (isSetup)
    113        {
    114            return;
    115        }
    116
    117        try
    118        {
    119            Method getReflectionFactory = Class.forName("sun.reflect.ReflectionFactory").getDeclaredMethod("getReflectionFactory");
    120            reflectionFactory      = getReflectionFactory.invoke(null);
    121            newConstructorAccessor = Class.forName("sun.reflect.ReflectionFactory").getDeclaredMethod("newConstructorAccessor", Constructor.class);
    122            newInstance            = Class.forName("sun.reflect.ConstructorAccessor").getDeclaredMethod("newInstance", Object[].class);
    123            newFieldAccessor       = Class.forName("sun.reflect.ReflectionFactory").getDeclaredMethod("newFieldAccessor", Field.class, boolean.class);
    124            fieldAccessorSet       = Class.forName("sun.reflect.FieldAccessor").getDeclaredMethod("set", Object.class, Object.class);
    125        }
    126        catch (Exception e)
    127        {
    128            e.printStackTrace();
    129        }
    130
    131        isSetup = true;
    132    }
    133
    134    /*
    135     * Everything below this is found at the site below, and updated to be able to compile in Eclipse/Java 1.6+
    136     * Also modified for use in decompiled code.
    137     * Found at: http://niceideas.ch/roller2/badtrash/entry/java_create_enum_instances_dynamically
    138     */
    139    private static Object getConstructorAccessor(Class<?> enumClass, Class<?>[] additionalParameterTypes) throws Exception
    140    {
    141        Class<?>[] parameterTypes = new Class[additionalParameterTypes.length + 2];
    142        parameterTypes[0] = String.class;
    143        parameterTypes[1] = int.class;
    144        System.arraycopy(additionalParameterTypes, 0, parameterTypes, 2, additionalParameterTypes.length);
    145        return newConstructorAccessor.invoke(reflectionFactory, enumClass.getDeclaredConstructor(parameterTypes));
    146    }
    147
    148    private static < T extends Enum<? >> T makeEnum(Class<T> enumClass, String value, int ordinal, Class<?>[] additionalTypes, Object[] additionalValues) throws Exception
    149    {
    150        Object[] parms = new Object[additionalValues.length + 2];
    151        parms[0] = value;
    152        parms[1] = Integer.valueOf(ordinal);
    153        System.arraycopy(additionalValues, 0, parms, 2, additionalValues.length);
    154        return enumClass.cast(newInstance.invoke(getConstructorAccessor(enumClass, additionalTypes), new Object[] {parms}));
    155    }
    156
    157    public static void setFailsafeFieldValue(Field field, Object target, Object value) throws Exception
    158    {
    159        field.setAccessible(true);
    160        Field modifiersField = Field.class.getDeclaredField("modifiers");
    161        modifiersField.setAccessible(true);
    162        modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
    163        Object fieldAccessor = newFieldAccessor.invoke(reflectionFactory, field, false);
    164        fieldAccessorSet.invoke(fieldAccessor, target, value);
    165    }
    166
    167    private static void blankField(Class<?> enumClass, String fieldName) throws Exception
    168    {
    169        for (Field field : Class.class.getDeclaredFields())
    170        {
    171            if (field.getName().contains(fieldName))
    172            {
    173                field.setAccessible(true);
    174                setFailsafeFieldValue(field, enumClass, null);
    175                break;
    176            }
    177        }
    178    }
    179
    180    private static void cleanEnumCache(Class<?> enumClass) throws Exception
    181    {
    182        blankField(enumClass, "enumConstantDirectory");
    183        blankField(enumClass, "enumConstants");
    184    }
    185
    186    public static <T extends Enum<? >> T addEnum(Class<T> enumType, String enumName, Object... paramValues)
    187    {
    188        return addEnum(commonTypes, enumType, enumName, paramValues);
    189    }
    190
    191    public static <T extends Enum<? >> T addEnum(Class[][] map, Class<T> enumType, String enumName, Object... paramValues)
    192    {
    193        for (Class[] lookup : map)
    194        {
    195            if (lookup[0] == enumType)
    196            {
    197                Class<?>[] paramTypes = new Class<?>[lookup.length - 1];
    198                if (paramTypes.length > 0)
    199                {
    200                    System.arraycopy(lookup, 1, paramTypes, 0, paramTypes.length);
    201                }
    202                return addEnum(enumType, enumName, paramTypes, paramValues);
    203            }
    204        }
    205        return null;
    206    }
    207
    208    @SuppressWarnings("unchecked")
    209    public static <T extends Enum<? >> T addEnum(Class<T> enumType, String enumName, Class<?>[] paramTypes, Object[] paramValues)
    210    {
    211        if (!isSetup)
    212        {
    213            setup();
    214        }
    215
    216        Field valuesField = null;
    217        Field[] fields = enumType.getDeclaredFields();
    218
    219        for (Field field : fields)
    220        {
    221            String name = field.getName();
    222            if (name.equals("$VALUES") || name.equals("ENUM$VALUES")) //Added 'ENUM$VALUES' because Eclipse's internal compiler doesn't follow standards
    223            {
    224                valuesField = field;
    225                break;
    226            }
    227        }
    228
    229        int flags = (FMLForgePlugin.RUNTIME_DEOBF ? Modifier.PUBLIC : Modifier.PRIVATE) | Modifier.STATIC | Modifier.FINAL | 0x1000 /*SYNTHETIC*/;
    230        if (valuesField == null)
    231        {
    232            String valueType = String.format("[L%s;", enumType.getName().replace('.', '/'));
    233
    234            for (Field field : fields)
    235            {
    236                if ((field.getModifiers() & flags) == flags &&
    237                     field.getType().getName().replace('.', '/').equals(valueType)) //Apparently some JVMs return .'s and some don't..
    238                {
    239                    valuesField = field;
    240                    break;
    241                }
    242            }
    243        }
    244
    245        if (valuesField == null)
    246        {
    247            FMLLog.severe("Could not find $VALUES field for enum: %s", enumType.getName());
    248            FMLLog.severe("Runtime Deobf: %s", FMLForgePlugin.RUNTIME_DEOBF);
    249            FMLLog.severe("Flags: %s", String.format("%16s", Integer.toBinaryString(flags)).replace(' ', '0'));
    250            FMLLog.severe("Fields:");
    251            for (Field field : fields)
    252            {
    253                String mods = String.format("%16s", Integer.toBinaryString(field.getModifiers())).replace(' ', '0');
    254                FMLLog.severe("       %s %s: %s", mods, field.getName(), field.getType().getName());
    255            }
    256            return null;
    257        }
    258
    259        valuesField.setAccessible(true);
    260
    261        try
    262        {
    263            T[] previousValues = (T[])valuesField.get(enumType);
    264            List<T> values = new ArrayList<T>(Arrays.asList(previousValues));
    265            T newValue = (T)makeEnum(enumType, enumName, values.size(), paramTypes, paramValues);
    266            values.add(newValue);
    267            setFailsafeFieldValue(valuesField, null, values.toArray((T[]) Array.newInstance(enumType, 0)));
    268            cleanEnumCache(enumType);
    269
    270            return newValue;
    271        }
    272        catch (Exception e)
    273        {
    274            e.printStackTrace();
    275            throw new RuntimeException(e.getMessage(), e);
    276        }
    277    }
    278
    279    static
    280    {
    281        if (!isSetup)
    282        {
    283            setup();
    284        }
    285    }
    286}