diff --git a/fml/common/cpw/mods/fml/common/ILanguageAdapter.java b/fml/common/cpw/mods/fml/common/ILanguageAdapter.java index 24b00e40f..8e0f9f015 100644 --- a/fml/common/cpw/mods/fml/common/ILanguageAdapter.java +++ b/fml/common/cpw/mods/fml/common/ILanguageAdapter.java @@ -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 and _$eq. To those // familiar with scala.reflect.BeanProperty: these will always // be there, set and get 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. + } } } \ No newline at end of file diff --git a/fml/common/cpw/mods/fml/common/ProxyInjector.java b/fml/common/cpw/mods/fml/common/ProxyInjector.java index 977bd2707..a0e47d15b 100644 --- a/fml/common/cpw/mods/fml/common/ProxyInjector.java +++ b/fml/common/cpw/mods/fml/common/ProxyInjector.java @@ -67,5 +67,8 @@ public class ProxyInjector throw new LoaderException(e); } } + + // Allow language specific proxy injection. + languageAdapter.setInternalProxies(mod, side, mcl); } }