package net.minecraftforge.common.config;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Pattern;
import java.util.Set;
import com.google.common.base.Throwables;
import com.google.common.collect.Lists;
import net.minecraftforge.common.config.Config.RangeDouble;
import net.minecraftforge.common.config.Config.RangeInt;
import static net.minecraftforge.common.config.ConfigManager.*;
public abstract class FieldWrapper implements IFieldWrapper
{
protected String category, name;
protected Field field;
protected Object instance;
public FieldWrapper(String category, Field field, Object instance)
{
this.instance = instance;
this.field = field;
this.category = category;
this.name = field.getName();
if (field.isAnnotationPresent(Config.Name.class))
this.name = field.getAnnotation(Config.Name.class).value();
this.field.setAccessible(true); // Just in case
}
public static IFieldWrapper get(Object instance, Field field, String category)
{
if (ADAPTERS.get(field.getType()) != null)
return new PrimitiveWrapper(category, field, instance);
else if (Enum.class.isAssignableFrom(field.getType()))
return new EnumWrapper(category, field, instance);
else if (Map.class.isAssignableFrom(field.getType()))
return new MapWrapper(category, field, instance);
else if (field.getType().getSuperclass().equals(Object.class))
throw new RuntimeException("Objects should not be handled by field wrappers");
else
throw new IllegalArgumentException(String.format("Fields of type '%s' are not supported!", field.getType().getCanonicalName()));
}
public static boolean hasWrapperFor(Field field)
{
if (ADAPTERS.get(field.getType()) != null)
return true;
else if (Enum.class.isAssignableFrom(field.getType()))
return true;
else if (Map.class.isAssignableFrom(field.getType()))
return true;
return false;
}
private static class MapWrapper extends FieldWrapper
{
private Map<String, Object> theMap = null;
private Type mType;
ITypeAdapter adapter;
@SuppressWarnings("unchecked")
private MapWrapper(String category, Field field, Object instance)
{
super(category, field, instance);
try
{
theMap = (Map<String, Object>) field.get(instance);
}
catch (ClassCastException cce)
{
throw new IllegalArgumentException(String.format("The map '%s' of class '%s' must have the key type String!", field.getName(),
field.getDeclaringClass().getCanonicalName()), cce);
}
catch (Exception e)
{
Throwables.propagate(e);
}
ParameterizedType type = (ParameterizedType) field.getGenericType();
mType = type.getActualTypeArguments()[1];
this.adapter = ADAPTERS.get(mType);
if (this.adapter == null && mType instanceof GenericArrayType)
{
this.adapter = ADAPTERS.get(ARRAY_REMAP.get(((GenericArrayType)mType).getGenericComponentType())); //J6 seems to have issues, Need to find a better way to translate this. We don't have access to array depth.
}
if (mType instanceof Class && Enum.class.isAssignableFrom((Class<?>)mType))
{
this.adapter = TypeAdapters.Str;
}
if (this.adapter == null)
throw new IllegalArgumentException(String.format("The map '%s' of class '%s' has target values which are neither primitive nor an enum!",
field.getName(), field.getDeclaringClass().getCanonicalName()));
}
@Override
public ITypeAdapter getTypeAdapter()
{
return adapter;
}
@Override
public String[] getKeys()
{
Set<String> keys = theMap.keySet();
String[] keyArray = new String[keys.size()];
Iterator<String> it = keys.iterator();
for (int i = 0; i < keyArray.length; i++)
{
keyArray[i] = category + "." + name + "." + it.next();
}
return keyArray;
}
@Override
public Object getValue(String key)
{
return theMap.get(key.replaceFirst(category + "." + name + ".", ""));
}
@Override
public void setValue(String key, Object value)
{
String suffix = key.replaceFirst(category + "." + name + ".", "");
theMap.put(suffix, value);
}
@Override
public boolean hasKey(String name)
{
return theMap.containsKey(name);
}
@Override
public boolean handlesKey(String name)
{
if (name == null)
return false;
return name.startsWith(category + "." + name + ".");
}
@Override
public void setupConfiguration(Configuration cfg, String desc, String langKey, boolean reqMCRestart, boolean reqWorldRestart)
{
ConfigCategory confCat = cfg.getCategory(category);
confCat.setComment(desc);
confCat.setLanguageKey(langKey);
confCat.setRequiresMcRestart(reqMCRestart);
confCat.setRequiresWorldRestart(reqWorldRestart);
}
@Override
public String getCategory()
{
return category + "." + name;
}
}
private static class EnumWrapper extends SingleValueFieldWrapper
{
private EnumWrapper(String category, Field field, Object instance)
{
super(category, field, instance);
}
@Override
public ITypeAdapter getTypeAdapter()
{
return TypeAdapters.Str;
}
@Override
public Object getValue(String key)
{
if (!hasKey(key))
throw new IllegalArgumentException("Unsupported Key!");
try
{
@SuppressWarnings("rawtypes")
Enum enu = (Enum) field.get(instance);
return enu.name();
}
catch (Exception e)
{
Throwables.propagate(e);
}
return null;
}
@Override
public void setValue(String key, Object value)
{
if (!hasKey(key))
throw new IllegalArgumentException("Unsupported Key!");
@SuppressWarnings({ "unchecked", "rawtypes" })
Enum enu = Enum.valueOf((Class<? extends Enum>) field.getType(), (String) value);
try
{
field.set(instance, enu);
}
catch (Exception e)
{
Throwables.propagate(e);
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public void setupConfiguration(Configuration cfg, String desc, String langKey, boolean reqMCRestart, boolean reqWorldRestart)
{
super.setupConfiguration(cfg, desc, langKey, reqMCRestart, reqWorldRestart);
Property prop = cfg.getCategory(this.category).get(this.name); // Will be setup in general by ConfigManager
List<String> lst = Lists.newArrayList();
for (Enum e : ((Class<? extends Enum>) field.getType()).getEnumConstants())
lst.add(e.name());
prop.setValidationPattern(Pattern.compile(PIPE.join(lst)));
prop.setValidValues(lst.toArray(new String[0]));
String validValues = NEW_LINE.join(lst);
if (desc != null)
prop.setComment(NEW_LINE.join(new String[] { desc, "Valid values:" }) + "\n" + validValues);
else
prop.setComment("Valid values:" + "\n" + validValues);
}
}
private static class PrimitiveWrapper extends SingleValueFieldWrapper
{
private PrimitiveWrapper(String category, Field field, Object instance)
{
super(category, field, instance);
}
@Override
public ITypeAdapter getTypeAdapter()
{
return ADAPTERS.get(field.getType());
}
@Override
public Object getValue(String key)
{
if (!hasKey(key))
throw new IllegalArgumentException("Unknown key!");
try
{
return field.get(instance);
}
catch (Exception e)
{
Throwables.propagate(e);
}
return null;
}
@Override
public void setValue(String key, Object value)
{
if (!hasKey(key))
throw new IllegalArgumentException("Unknown key: " + key);
try
{
field.set(instance, value);
}
catch (Exception e)
{
Throwables.propagate(e);
}
}
public void setupConfiguration(Configuration cfg, String desc, String langKey, boolean reqMCRestart, boolean reqWorldRestart)
{
super.setupConfiguration(cfg, desc, langKey, reqMCRestart, reqWorldRestart);
Property prop = cfg.getCategory(this.category).get(this.name);
RangeInt ia = field.getAnnotation(RangeInt.class);
if (ia != null)
{
prop.setMinValue(ia.min());
prop.setMaxValue(ia.max());
if (desc != null)
prop.setComment(NEW_LINE.join(new String[] { desc, "Min: " + ia.min(), "Max: " + ia.max() }));
else
prop.setComment(NEW_LINE.join(new String[] { "Min: " + ia.min(), "Max: " + ia.max() }));
}
RangeDouble da = field.getAnnotation(RangeDouble.class);
if (da != null)
{
prop.setMinValue(da.min());
prop.setMaxValue(da.max());
if (desc != null)
prop.setComment(NEW_LINE.join(new String[] { desc, "Min: " + da.min(), "Max: " + da.max() }));
else
prop.setComment(NEW_LINE.join(new String[] { "Min: " + da.min(), "Max: " + da.max() }));
}
}
}
private static abstract class SingleValueFieldWrapper extends FieldWrapper
{
private SingleValueFieldWrapper(String category, Field field, Object instance)
{
super(category, field, instance);
}
@Override
public String[] getKeys()
{
return asArray(this.category + "." + this.name);
}
@Override
public boolean hasKey(String name)
{
return (this.category + "." + this.name).equals(name);
}
@Override
public boolean handlesKey(String name)
{
return hasKey(name);
}
@Override
public void setupConfiguration(Configuration cfg, String desc, String langKey, boolean reqMCRestart, boolean reqWorldRestart)
{
Property prop = cfg.getCategory(this.category).get(this.name); // Will be setup in general by ConfigManager
prop.setComment(desc);
prop.setLanguageKey(langKey);
prop.setRequiresMcRestart(reqMCRestart);
prop.setRequiresWorldRestart(reqWorldRestart);
}
@Override
public String getCategory()
{
return category;
}
}
private static <T> T[] asArray(T... in)
{
return in;
}
public static class BeanEntry<K, V> implements Entry<K, V>
{
private K key;
private V value;
public BeanEntry(K key, V value)
{
this.key = key;
this.value = value;
}
@Override
public K getKey()
{
return key;
}
@Override
public V getValue()
{
return value;
}
@Override
public V setValue(V value)
{
throw new UnsupportedOperationException("This is a static bean.");
}
}
}