package openmods.access;
import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import cpw.mods.fml.common.Loader;
import cpw.mods.fml.common.discovery.ASMDataTable;
import cpw.mods.fml.common.discovery.ASMDataTable.ASMData;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;
import java.util.Set;
import net.minecraftforge.common.util.EnumHelper;
import openmods.Log;
import openmods.utils.CachedFactory;
import org.objectweb.asm.Type;
public class ApiFactory {
public static final ApiFactory instance = new ApiFactory();
private interface Setter {
public Class<?> getType();
public void set(Object value) throws Exception;
}
private static class ClassInfo {
private Map<String, Setter> setters = Maps.newHashMap();
public ClassInfo(Class<?> cls) {
for (final Field f : cls.getDeclaredFields()) {
final int modifiers = f.getModifiers();
if (Modifier.isStatic(modifiers)) {
final Class<?> type = f.getType();
f.setAccessible(true);
if (Modifier.isFinal(modifiers)) {
setters.put(f.getName(), new Setter() {
@Override
public void set(Object value) throws Exception {
EnumHelper.setFailsafeFieldValue(f, null, value);
}
@Override
public Class<?> getType() {
return type;
}
});
} else {
setters.put(f.getName(), new Setter() {
@Override
public void set(Object value) throws Exception {
f.set(null, value);
}
@Override
public Class<?> getType() {
return type;
}
});
}
}
}
for (final Method m : cls.getDeclaredMethods()) {
final int modifiers = m.getModifiers();
if (Modifier.isStatic(modifiers) && m.getReturnType() == void.class) {
final Class<?>[] params = m.getParameterTypes();
if (params.length == 1) {
final Class<?> param = params[0];
m.setAccessible(true);
final String id = m.getName() + Type.getMethodDescriptor(m);
setters.put(id, new Setter() {
@Override
public void set(Object value) throws Exception {
m.invoke(null, value);
}
@Override
public Class<?> getType() {
return param;
}
});
}
}
}
}
public Setter get(String name) {
return setters.get(name);
}
}
private static class ClassInfoCache extends CachedFactory<String, ClassInfo> {
@Override
protected ClassInfo create(String className) {
try {
final Class<?> cls = Class.forName(className, true, getClass().getClassLoader());
return new ClassInfo(cls);
} catch (Exception ex) {
throw Throwables.propagate(ex);
}
}
}
private final Set<Class<? extends Annotation>> apis = Sets.newHashSet();
private static <A> void fillTargetField(ClassInfoCache clsCache, ApiProviderRegistry<A> registry, ASMData data, Class<A> interfaceMarker) {
final String targetClassName = data.getClassName();
final String targetObjectName = data.getObjectName();
final ClassInfo targetCls = clsCache.getOrCreate(targetClassName);
final Setter setter = targetCls.get(targetObjectName);
Preconditions.checkArgument(setter != null, "Entry '%s' in class '%s' is not valid target for API annotation", targetObjectName, targetClassName);
final Class<?> acceptedType = setter.getType();
Preconditions.checkState(interfaceMarker.isAssignableFrom(acceptedType), "Failed to set API object on %s:%s - invalid type, expected %s",
targetClassName, targetObjectName, interfaceMarker);
final Class<? extends A> castAcceptedType = acceptedType.asSubclass(interfaceMarker);
final A api = registry.getApi(castAcceptedType);
if (api != null) {
try {
setter.set(api);
} catch (Throwable t) {
throw new RuntimeException(String.format("Failed to set entry '%s' in class '%s'", targetObjectName, targetClassName), t);
}
Log.trace("Injecting instance of %s from mod %s to field %s:%s from file %s",
castAcceptedType,
Loader.instance().activeModContainer().getModId(),
targetClassName,
targetObjectName,
data.getCandidate().getModContainer());
} else {
Log.info("Can't set API field %s:%s - no API for type %s",
targetClassName, targetObjectName, castAcceptedType);
}
}
private static <A> void fillTargetFields(final ApiProviderRegistry<A> registry, ASMDataTable table, Class<? extends Annotation> fieldMarker, Class<A> interfaceMarker) {
final ClassInfoCache clsCache = new ClassInfoCache();
final Set<ASMData> targets = table.getAll(fieldMarker.getName());
for (ASMData data : targets)
fillTargetField(clsCache, registry, data, interfaceMarker);
}
public interface ApiProviderSetup<A> {
public void setup(ApiProviderRegistry<A> registry);
}
public <A> ApiProviderRegistry<A> createApi(Class<? extends Annotation> fieldMarker, Class<A> interfaceMarker, ASMDataTable table, ApiProviderSetup<A> registrySetup) {
Preconditions.checkState(apis.add(fieldMarker), "Duplicate API registration on %s", fieldMarker);
final ApiProviderRegistry<A> registry = new ApiProviderRegistry<A>(interfaceMarker);
registrySetup.setup(registry);
registry.freeze();
fillTargetFields(registry, table, fieldMarker, interfaceMarker);
return registry;
}
public <A> void createApi(Class<? extends Annotation> fieldMarker, Class<A> interfaceMarker, ASMDataTable table, ApiProviderRegistry<A> registry) {
Preconditions.checkState(apis.add(fieldMarker), "Duplicate API registration on %s", fieldMarker);
Preconditions.checkState(registry.isFrozen(), "Registry must be frozen");
fillTargetFields(registry, table, fieldMarker, interfaceMarker);
}
}