From 23b070c7d02a8da44bf04c2f9ba2b485a44ad967 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Wed, 8 May 2013 05:36:28 +0200 Subject: [PATCH 1/3] Alternative @SidedProxy setter for Scala. --- .../cpw/mods/fml/common/ILanguageAdapter.java | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/common/cpw/mods/fml/common/ILanguageAdapter.java b/common/cpw/mods/fml/common/ILanguageAdapter.java index 6aae70b66a..24b00e40f1 100644 --- a/common/cpw/mods/fml/common/ILanguageAdapter.java +++ b/common/cpw/mods/fml/common/ILanguageAdapter.java @@ -1,6 +1,8 @@ package cpw.mods.fml.common; import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; public interface ILanguageAdapter { public Object getNewInstance(FMLModContainer container, Class objectClass, ClassLoader classLoader) throws Exception; @@ -24,6 +26,51 @@ public interface ILanguageAdapter { @Override public void setProxy(Field target, Class proxyTarget, Object proxy) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException { + 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 instance via the MODULE$ field which is + // automatically generated by the scala compiler for + // singletons. + final Object targetInstance = targetSingleton.getField("MODULE$").get(null); + + // 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... if this invocation fails we'll + // just fall back to the original functionality. + 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."); + } + Field field = proxyTarget.getField("INSTANCE"); Object scalaObject = field.get(null); target.set(scalaObject, proxy); From 2cca7aa759b4b6c3a128ce43bbd924e4762c43c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Wed, 8 May 2013 19:04:35 +0200 Subject: [PATCH 2/3] 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. --- .../cpw/mods/fml/common/ILanguageAdapter.java | 127 +++++++++++++++--- common/cpw/mods/fml/common/ProxyInjector.java | 3 + 2 files changed, 113 insertions(+), 17 deletions(-) diff --git a/common/cpw/mods/fml/common/ILanguageAdapter.java b/common/cpw/mods/fml/common/ILanguageAdapter.java index 24b00e40f1..8e0f9f015d 100644 --- a/common/cpw/mods/fml/common/ILanguageAdapter.java +++ b/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/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); } } From 8517a824e5251c409e05999d42fc6d70497609f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Wed, 8 May 2013 20:16:15 +0200 Subject: [PATCH 3/3] Merge declaration and initialization of a variable. --- common/cpw/mods/fml/common/ILanguageAdapter.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/common/cpw/mods/fml/common/ILanguageAdapter.java b/common/cpw/mods/fml/common/ILanguageAdapter.java index 8e0f9f015d..74e261f7a2 100644 --- a/common/cpw/mods/fml/common/ILanguageAdapter.java +++ b/common/cpw/mods/fml/common/ILanguageAdapter.java @@ -31,8 +31,6 @@ public interface ILanguageAdapter { @Override public void setProxy(Field target, Class proxyTarget, Object proxy) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException { - Object targetInstance; - try { // Get the actual singleton class. The two variants are from @@ -53,11 +51,6 @@ public interface ILanguageAdapter { // 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 - // singletons. - targetInstance = proxyTarget.getField("MODULE$").get(null); } catch (ClassNotFoundException e) { @@ -66,6 +59,11 @@ public interface ILanguageAdapter { 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