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            if (field.getName().equals("$VALUES"))
222            {
223                valuesField = field;
224                break;
225            }
226        }
227
228        if (valuesField == null)
229        {
230            int flags = (FMLForgePlugin.RUNTIME_DEOBF ? Modifier.PUBLIC : Modifier.PRIVATE) | Modifier.STATIC | Modifier.FINAL | 0x1000 /*SYNTHETIC*/;
231            String valueType = String.format("[L%s;", enumType.getName().replace('.', '/'));
232    
233            for (Field field : fields)
234            {
235                if ((field.getModifiers() & flags) == flags &&
236                        field.getType().getName().replace('.', '/').equals(valueType)) //Apparently some JVMs return .'s and some don't..
237                {
238                    valuesField = field;
239                    break;
240                }
241            }
242        }
243
244        if (valuesField == null)
245        {
246            FMLLog.severe("Could not find $VALUES field for enum: %s", enumType.getName());
247            FMLLog.severe("Runtime Deobf: %s", FMLForgePlugin.RUNTIME_DEOBF);
248            FMLLog.severe("Fields:");
249            for (Field field : fields)
250            {
251                FMLLog.severe("    %s: %s", field.getName(), field.getType().getName());
252            }
253            return null;
254        }
255
256        valuesField.setAccessible(true);
257
258        try
259        {
260            T[] previousValues = (T[])valuesField.get(enumType);
261            List<T> values = new ArrayList<T>(Arrays.asList(previousValues));
262            T newValue = (T)makeEnum(enumType, enumName, values.size(), paramTypes, paramValues);
263            values.add(newValue);
264            setFailsafeFieldValue(valuesField, null, values.toArray((T[]) Array.newInstance(enumType, 0)));
265            cleanEnumCache(enumType);
266
267            return newValue;
268        }
269        catch (Exception e)
270        {
271            e.printStackTrace();
272            throw new RuntimeException(e.getMessage(), e);
273        }
274    }
275
276    static
277    {
278        if (!isSetup)
279        {
280            setup();
281        }
282    }
283}