Java tutorial
/* * Minecraft Forge * Copyright (c) 2016-2018. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation version 2.1 * of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ 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.*; import java.util.Map.Entry; import java.util.regex.Pattern; import com.google.common.collect.Lists; import net.minecraftforge.common.config.Config.RangeDouble; import net.minecraftforge.common.config.Config.RangeInt; import org.apache.commons.lang3.StringUtils; 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; private final String baseName; ITypeAdapter adapter; @SuppressWarnings("unchecked") private MapWrapper(String category, Field field, Object instance) { super(category, field, instance); this.baseName = (this.category == null) ? "" : (this.category + ".") + this.name.toLowerCase(Locale.ENGLISH) + "."; 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 (IllegalAccessException e) { throw new RuntimeException(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] = this.baseName + it.next(); } return keyArray; } @Override public Object getValue(String key) { return theMap.get(getSuffix(key)); } @Override public void setValue(String key, Object value) { theMap.put(getSuffix(key), value); } @Override public boolean hasKey(String key) { return theMap.containsKey(getSuffix(key)); } @Override public boolean handlesKey(String key) { if (key == null) return false; return key.startsWith(this.baseName); } @Override public void setupConfiguration(Configuration cfg, String desc, String langKey, boolean reqMCRestart, boolean reqWorldRestart) { ConfigCategory confCat = cfg.getCategory(getCategory()); confCat.setComment(desc); confCat.setLanguageKey(langKey); confCat.setRequiresMcRestart(reqMCRestart); confCat.setRequiresWorldRestart(reqWorldRestart); } @Override public String getCategory() { return (this.category == null) ? "" : (this.category + ".") + this.name.toLowerCase(Locale.ENGLISH); } /** * Removes the {@code this.baseName} prefix from the key * @param key the key to be edited * @return the keys suffix */ private String getSuffix(String key) { return StringUtils.replaceOnce(key, this.baseName, ""); } } 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 (IllegalAccessException e) { throw new RuntimeException(e); } } @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 (IllegalAccessException e) { throw new RuntimeException(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 (IllegalAccessException e) { throw new RuntimeException(e); } } @Override public void setValue(String key, Object value) { if (!hasKey(key)) throw new IllegalArgumentException("Unknown key: " + key); try { field.set(instance, value); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } @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); 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 key) { return (this.category + "." + this.name).equals(key); } @Override public boolean handlesKey(String key) { return hasKey(key); } @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 this.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."); } } }