Merge pull request #230 from fnuecke/master
Alternative setter logic for @SidedProxy in Scala classes/mods.
This commit is contained in:
commit
8f779cf618
2 changed files with 144 additions and 3 deletions
|
@ -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.
|
||||
}
|
||||
}
|
||||
}
|
|
@ -67,5 +67,8 @@ public class ProxyInjector
|
|||
throw new LoaderException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Allow language specific proxy injection.
|
||||
languageAdapter.setInternalProxies(mod, side, mcl);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue