Merge pull request #230 from fnuecke/master

Alternative setter logic for @SidedProxy in Scala classes/mods.
This commit is contained in:
cpw 2013-05-11 15:34:37 -07:00
commit 8f779cf618
2 changed files with 144 additions and 3 deletions

View file

@ -1,11 +1,18 @@
package cpw.mods.fml.common;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.logging.Level;
import cpw.mods.fml.common.Mod.Instance;
import cpw.mods.fml.relauncher.Side;
public interface ILanguageAdapter {
public Object getNewInstance(FMLModContainer container, Class<?> objectClass, ClassLoader classLoader) throws Exception;
public boolean supportsStatics();
public void setProxy(Field target, Class<?> proxyTarget, Object proxy) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException;
public void setInternalProxies(ModContainer mod, Side side, ClassLoader loader);
public static class ScalaAdapter implements ILanguageAdapter {
@Override
@ -24,11 +31,136 @@ public interface ILanguageAdapter {
@Override
public void setProxy(Field target, Class<?> proxyTarget, Object proxy) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException
{
Field field = proxyTarget.getField("INSTANCE");
Object scalaObject = field.get(null);
target.set(scalaObject, proxy);
try
{
// Get the actual singleton class. The two variants are from
// whether the @SidedProxy is declared in a the class block
// of the object directly, or in the object block, i.e.
// whether it's:
// class ModName {
// @SidedProxy ...
// }
// object ModName extends ModName {}
// which leads to us getting the outer class, or
// object ModName {
// @SidedProxy ...
// }
// which leads to us getting the inner class.
if (!proxyTarget.getName().endsWith("$"))
{
// Get internal class generated by Scala.
proxyTarget = Class.forName(proxyTarget.getName() + "$", true, proxyTarget.getClassLoader());
}
}
catch (ClassNotFoundException e)
{
// Not a singleton, look for @Instance field as a fallback.
FMLLog.log(Level.SEVERE, e, "An error occured trying to load a proxy into %s.%s. Did you declare your mod as 'class' instead of 'object'?", proxyTarget.getSimpleName(), target.getName());
throw new LoaderException(e);
}
// Get the instance via the MODULE$ field which is
// automatically generated by the Scala compiler for
// singletons.
Object targetInstance = proxyTarget.getField("MODULE$").get(null);
try
{
// Find setter function. We do it this way because we can't
// necessarily use proxyTarget.getMethod(proxy.getClass()), as
// it might be a subclass and not the exact parameter type.
// All fields are private in Scala, wrapped by a getter and
// setter named <fieldname> and <fieldname>_$eq. To those
// familiar with scala.reflect.BeanProperty: these will always
// be there, set<Fieldname> and get<Fieldname> will always
// only be generated *additionally*.
final String setterName = target.getName() + "_$eq";
for (Method setter : proxyTarget.getMethods())
{
Class<?>[] setterParameters = setter.getParameterTypes();
if (setterName.equals(setter.getName()) &&
// Some more validation.
setterParameters.length == 1 &&
setterParameters[0].isAssignableFrom(proxy.getClass()))
{
// Here goes nothing...
setter.invoke(targetInstance, proxy);
return;
}
}
}
catch (InvocationTargetException e)
{
FMLLog.log(Level.SEVERE, e, "An error occured trying to load a proxy into %s.%s", proxyTarget.getSimpleName(), target.getName());
throw new LoaderException(e);
}
// If we come here we could not find a setter for this proxy.
FMLLog.severe("Failed loading proxy into %s.%s, could not find setter function. Did you declare the field with 'val' instead of 'var'?", proxyTarget.getSimpleName(), target.getName());
throw new LoaderException();
}
@Override
public void setInternalProxies(ModContainer mod, Side side, ClassLoader loader)
{
// For Scala mods, we want to enable authors to write them like so:
// object ModName {
// @SidedProxy(...)
// var proxy: ModProxy = null
// }
// For this to work, we have to search inside the inner class Scala
// generates for singletons, which is in called ModName$. These are
// not automatically handled, because the mod discovery code ignores
// internal classes.
// Note that it is alternatively possible to write this like so:
// class ModName {
// @SidedProxy(...)
// var proxy: ModProxy = null
// }
// object ModName extends ModName { ... }
// which will fall back to the normal injection code which calls
// setProxy in turn.
// Get the actual mod implementation, which will be the inner class
// if we have a singleton.
Class<?> proxyTarget = mod.getMod().getClass();
if (proxyTarget.getName().endsWith("$"))
{
// So we have a singleton class, check if there are targets.
for (Field target : proxyTarget.getDeclaredFields())
{
// This will not turn up anything if the alternative
// approach was taken (manually declaring the class).
// So we don't initialize the field twice.
if (target.getAnnotation(SidedProxy.class) != null)
{
String targetType = side.isClient() ? target.getAnnotation(SidedProxy.class).clientSide() : target.getAnnotation(SidedProxy.class).serverSide();
try
{
Object proxy = Class.forName(targetType, true, loader).newInstance();
if (!target.getType().isAssignableFrom(proxy.getClass()))
{
FMLLog.severe("Attempted to load a proxy type %s into %s.%s, but the types don't match", targetType, proxyTarget.getSimpleName(), target.getName());
throw new LoaderException();
}
setProxy(target, proxyTarget, proxy);
}
catch (Exception e) {
FMLLog.log(Level.SEVERE, e, "An error occured trying to load a proxy into %s.%s", proxyTarget.getSimpleName(), target.getName());
throw new LoaderException(e);
}
}
}
}
else
{
FMLLog.finer("Mod does not appear to be a singleton.");
}
}
}
public static class JavaAdapter implements ILanguageAdapter {
@Override
public Object getNewInstance(FMLModContainer container, Class<?> objectClass, ClassLoader classLoader) throws Exception
@ -48,5 +180,11 @@ public interface ILanguageAdapter {
{
target.set(null, proxy);
}
@Override
public void setInternalProxies(ModContainer mod, Side side, ClassLoader loader)
{
// Nothing to do here.
}
}
}

View file

@ -67,5 +67,8 @@ public class ProxyInjector
throw new LoaderException(e);
}
}
// Allow language specific proxy injection.
languageAdapter.setInternalProxies(mod, side, mcl);
}
}