diff --git a/common/cpw/mods/fml/common/ILanguageAdapter.java b/common/cpw/mods/fml/common/ILanguageAdapter.java index 6aae70b66a..74e261f7a2 100644 --- a/common/cpw/mods/fml/common/ILanguageAdapter.java +++ b/common/cpw/mods/fml/common/ILanguageAdapter.java @@ -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 and _$eq. To those + // familiar with scala.reflect.BeanProperty: these will always + // be there, set and get 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. + } } } \ No newline at end of file diff --git a/common/cpw/mods/fml/common/ProxyInjector.java b/common/cpw/mods/fml/common/ProxyInjector.java index 977bd27079..a0e47d15b7 100644 --- a/common/cpw/mods/fml/common/ProxyInjector.java +++ b/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); } }