Some more Scala @SidedProxy adjustments. Now properly supporting pure singletons, i.e.

object C {
  @SidedProxy(...)
  var proxy: P = null
}
Removed fallback, as all such singletons are properly handled by the new code now, and class implementations fall back to the code also used for plain Java mods.
This commit is contained in:
Florian Nücke 2013-05-08 19:04:35 +02:00
parent 283dfb6e7d
commit a14c627cd7
2 changed files with 113 additions and 17 deletions

View file

@ -3,11 +3,16 @@ 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
@ -26,22 +31,47 @@ public interface ILanguageAdapter {
@Override
public void setProxy(Field target, Class<?> proxyTarget, Object proxy) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException
{
Object targetInstance;
try
{
// Check if the target class is a singleton, if so its
// implementation's class name is the actual classname
// postfixed with a $.
final Class<?> targetSingleton = Class.forName(proxyTarget.getName() + "$", true, proxyTarget.getClassLoader());
// 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());
}
// Get the instance via the MODULE$ field which is
// automatically generated by the scala compiler for
// automatically generated by the Scala compiler for
// singletons.
final Object targetInstance = targetSingleton.getField("MODULE$").get(null);
targetInstance = proxyTarget.getField("MODULE$").get(null);
}
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);
}
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
// 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
@ -55,27 +85,84 @@ public interface ILanguageAdapter {
setterParameters.length == 1 &&
setterParameters[0].isAssignableFrom(proxy.getClass()))
{
// Here goes nothing... if this invocation fails we'll
// just fall back to the original functionality.
// Here goes nothing...
setter.invoke(targetInstance, proxy);
return;
}
}
}
catch (ClassNotFoundException e)
{
FMLLog.fine("This scala class is not a singleton (scala object), falling back to old proxy setter.");
}
catch (InvocationTargetException e)
{
FMLLog.fine("There's a problem with this proxy's setter, falling back to old proxy setter.");
FMLLog.log(Level.SEVERE, e, "An error occured trying to load a proxy into %s.%s", proxyTarget.getSimpleName(), target.getName());
throw new LoaderException(e);
}
Field field = proxyTarget.getField("INSTANCE");
Object scalaObject = field.get(null);
target.set(scalaObject, proxy);
// 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
@ -95,5 +182,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);
}
}