package openmods.reflection; import com.google.common.base.Preconditions; import com.google.common.collect.Maps; import cpw.mods.fml.common.discovery.ASMDataTable; import cpw.mods.fml.common.discovery.ASMDataTable.ASMData; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.lang.reflect.TypeVariable; import java.util.Map; import openmods.Log; import org.objectweb.asm.Type; public class TypeVariableHolderHandler { public void fillAllHolders(ASMDataTable data) { final Map<Field, Class<?>> classTargetToSource = Maps.newHashMap(); final Map<Field, Class<?>> fieldTargetToSource = Maps.newHashMap(); for (ASMData target : data.getAll(TypeVariableHolder.class.getName())) findTargets(target, classTargetToSource, fieldTargetToSource); fillFields(classTargetToSource, fieldTargetToSource); } public static void initializeClass(Class<?> targetClass) { new TypeVariableHolderHandler().fillHolders(targetClass); } public void fillHolders(Class<?> targetClass) { final Map<Field, Class<?>> targetToSource = Maps.newHashMap(); fillHolders(targetClass, targetToSource); for (Class<?> inner : targetClass.getDeclaredClasses()) fillHolders(inner, targetToSource); fillFields(targetToSource); } private static void fillHolders(Class<?> targetClass, final Map<Field, Class<?>> targetToSource) { final Field[] fields = targetClass.getDeclaredFields(); final TypeVariableHolder clsAnnotation = targetClass.getAnnotation(TypeVariableHolder.class); if (clsAnnotation != null) { final Class<?> sourceClass = findSourceClass(clsAnnotation, targetClass); for (Field f : fields) { if (isValidField(f)) targetToSource.put(f, sourceClass); } } for (Field f : fields) { final TypeVariableHolder fieldAnnotation = f.getAnnotation(TypeVariableHolder.class); if (fieldAnnotation != null) { Preconditions.checkArgument(isValidField(f), "Field %s marked with TypeVariableHolder annotation must be static, non-final and have TypeVariable type", f); final Class<?> sourceClass = findSourceClass(fieldAnnotation, targetClass); targetToSource.put(f, sourceClass); } } } private static Class<?> findSourceClass(TypeVariableHolder annotation, Class<?> targetClass) { if (annotation.value() == TypeVariableHolder.UseDeclaringType.class) return targetClass; else return annotation.value(); } private static final Type USE_DECLARING_TYPE_MARKER = Type.getType(TypeVariableHolder.UseDeclaringType.class); private static void findTargets(ASMData target, Map<Field, Class<?>> classTargetToSource, Map<Field, Class<?>> fieldTargetToSource) { final String targetClassName = target.getClassName(); final String targetObject = target.getObjectName(); final Type sourceClassName = (Type)target.getAnnotationInfo().get("value"); try { final Class<?> targetClass = Class.forName(targetClassName); final Class<?> sourceClass; if (sourceClassName == null || sourceClassName.equals(USE_DECLARING_TYPE_MARKER)) sourceClass = targetClass; else sourceClass = Class.forName(sourceClassName.getClassName()); if (targetClassName.equals(targetObject)) addClassFields(classTargetToSource, targetClass, sourceClass); else addField(fieldTargetToSource, targetClass, targetObject, sourceClass); } catch (Exception e) { Log.warn(e, "Failed to fill type variable holder at %s:%s", targetClassName, targetObject); } } private static boolean isValidField(Field field) { final int modifiers = field.getModifiers(); return field.getType() == TypeVariable.class && Modifier.isStatic(modifiers) && !Modifier.isFinal(modifiers); } private static void addField(Map<Field, Class<?>> fieldTargetToSource, Class<?> targetClass, String fieldName, Class<?> sourceClass) throws Exception { final Field f = targetClass.getDeclaredField(fieldName); Preconditions.checkArgument(isValidField(f), "Field %s marked with TypeVariableHolder annotation must be static, non-final and have TypeVariable type", f); fieldTargetToSource.put(f, sourceClass); } private static void addClassFields(Map<Field, Class<?>> classTargetToSource, Class<?> targetClass, Class<?> sourceClass) { for (Field f : targetClass.getDeclaredFields()) { if (isValidField(f)) classTargetToSource.put(f, sourceClass); } } private void fillFields(Map<Field, Class<?>> classTargetToSource, Map<Field, Class<?>> fieldTargetToSource) { // field parameters are more specific, so they override class-level entries final Map<Field, Class<?>> targetToSource = Maps.newHashMap(classTargetToSource); targetToSource.putAll(fieldTargetToSource); fillFields(targetToSource); } private void fillFields(Map<Field, Class<?>> fieldTargetToSource) { for (Map.Entry<Field, Class<?>> e : fieldTargetToSource.entrySet()) fillField(e.getKey(), e.getValue()); } private final Map<Class<?>, Map<String, TypeVariable<?>>> sourceCache = Maps.newHashMap(); private void fillField(Field targetField, Class<?> sourceClass) { try { final Map<String, TypeVariable<?>> sourceVariables = getSourceTypeVariables(sourceClass); final String variableName = targetField.getName(); final TypeVariable<?> sourceTypeVariable = sourceVariables.get(variableName); Preconditions.checkState(sourceTypeVariable != null, "Can't find type variable '%s' in class '%s", variableName, sourceClass); targetField.setAccessible(true); targetField.set(null, sourceTypeVariable); } catch (Exception e) { Log.warn(e, "Failed to set field %s", targetField); } } private Map<String, TypeVariable<?>> getSourceTypeVariables(Class<?> sourceClass) { Map<String, TypeVariable<?>> result = sourceCache.get(sourceClass); if (result == null) { result = Maps.newHashMap(); for (TypeVariable<?> t : sourceClass.getTypeParameters()) result.put(t.getName(), t); sourceCache.put(sourceClass, result); } return result; } }