From 8776d1afa6dce054f3bc7cb421c86f3e2fe06ab3 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 10 Dec 2017 18:05:18 -0500 Subject: adjust reflection API to correctly reflect what it does (#410) --- src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs | 167 +++++++++++++-------- 1 file changed, 105 insertions(+), 62 deletions(-) (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs b/src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs index 8788b142..81453003 100644 --- a/src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs @@ -17,6 +17,9 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The mod name for error messages. private readonly string ModName; + /// Manages deprecation warnings. + private readonly DeprecationManager DeprecationManager; + /********* ** Public methods @@ -25,15 +28,88 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The unique ID of the relevant mod. /// The mod name for error messages. /// The underlying reflection helper. - public ReflectionHelper(string modID, string modName, Reflector reflector) + /// Manages deprecation warnings. + public ReflectionHelper(string modID, string modName, Reflector reflector, DeprecationManager deprecationManager) : base(modID) { this.ModName = modName; this.Reflector = reflector; + this.DeprecationManager = deprecationManager; + } + + /// Get an instance field. + /// The field type. + /// The object which has the field. + /// The field name. + /// Whether to throw an exception if the field is not found. + public IReflectedField GetField(object obj, string name, bool required = true) + { + return this.AssertAccessAllowed( + this.Reflector.GetField(obj, name, required) + ); + } + + /// Get a static field. + /// The field type. + /// The type which has the field. + /// The field name. + /// Whether to throw an exception if the field is not found. + public IReflectedField GetField(Type type, string name, bool required = true) + { + return this.AssertAccessAllowed( + this.Reflector.GetField(type, name, required) + ); + } + + /// Get an instance property. + /// The property type. + /// The object which has the property. + /// The property name. + /// Whether to throw an exception if the property is not found. + public IReflectedProperty GetProperty(object obj, string name, bool required = true) + { + return this.AssertAccessAllowed( + this.Reflector.GetProperty(obj, name, required) + ); + } + + /// Get a static property. + /// The property type. + /// The type which has the property. + /// The property name. + /// Whether to throw an exception if the property is not found. + public IReflectedProperty GetProperty(Type type, string name, bool required = true) + { + return this.AssertAccessAllowed( + this.Reflector.GetProperty(type, name, required) + ); + } + + /// Get an instance method. + /// The object which has the method. + /// The field name. + /// Whether to throw an exception if the field is not found. + public IReflectedMethod GetMethod(object obj, string name, bool required = true) + { + return this.AssertAccessAllowed( + this.Reflector.GetMethod(obj, name, required) + ); + } + + /// Get a static method. + /// The type which has the method. + /// The field name. + /// Whether to throw an exception if the field is not found. + public IReflectedMethod GetMethod(Type type, string name, bool required = true) + { + return this.AssertAccessAllowed( + this.Reflector.GetMethod(type, name, required) + ); } + /**** - ** Fields + ** Obsolete ****/ /// Get a private instance field. /// The field type. @@ -41,11 +117,11 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The field name. /// Whether to throw an exception if the private field is not found. /// Returns the field wrapper, or null if the field doesn't exist and is false. + [Obsolete] public IPrivateField GetPrivateField(object obj, string name, bool required = true) { - return this.AssertAccessAllowed( - this.Reflector.GetPrivateField(obj, name, required) - ); + this.DeprecationManager.Warn($"{nameof(IReflectionHelper)}.GetPrivate*", "2.3", DeprecationLevel.Notice); + return (IPrivateField)this.GetField(obj, name, required); } /// Get a private static field. @@ -53,26 +129,23 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The type which has the field. /// The field name. /// Whether to throw an exception if the private field is not found. + [Obsolete] public IPrivateField GetPrivateField(Type type, string name, bool required = true) { - return this.AssertAccessAllowed( - this.Reflector.GetPrivateField(type, name, required) - ); + this.DeprecationManager.Warn($"{nameof(IReflectionHelper)}.GetPrivate*", "2.3", DeprecationLevel.Notice); + return (IPrivateField)this.GetField(type, name, required); } - /**** - ** Properties - ****/ /// Get a private instance property. /// The property type. /// The object which has the property. /// The property name. /// Whether to throw an exception if the private property is not found. + [Obsolete] public IPrivateProperty GetPrivateProperty(object obj, string name, bool required = true) { - return this.AssertAccessAllowed( - this.Reflector.GetPrivateProperty(obj, name, required) - ); + this.DeprecationManager.Warn($"{nameof(IReflectionHelper)}.GetPrivate*", "2.3", DeprecationLevel.Notice); + return (IPrivateProperty)this.GetProperty(obj, name, required); } /// Get a private static property. @@ -80,17 +153,13 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The type which has the property. /// The property name. /// Whether to throw an exception if the private property is not found. + [Obsolete] public IPrivateProperty GetPrivateProperty(Type type, string name, bool required = true) { - return this.AssertAccessAllowed( - this.Reflector.GetPrivateProperty(type, name, required) - ); + this.DeprecationManager.Warn($"{nameof(IReflectionHelper)}.GetPrivate*", "2.3", DeprecationLevel.Notice); + return (IPrivateProperty)this.GetProperty(type, name, required); } - /**** - ** Field values - ** (shorthand since this is the most common case) - ****/ /// Get the value of a private instance field. /// The field type. /// The object which has the field. @@ -101,9 +170,11 @@ namespace StardewModdingAPI.Framework.ModHelpers /// This is a shortcut for followed by . /// When is false, this will return the default value if reflection fails. If you need to check whether the field exists, use instead. /// + [Obsolete] public TValue GetPrivateValue(object obj, string name, bool required = true) { - IPrivateField field = this.GetPrivateField(obj, name, required); + this.DeprecationManager.Warn($"{nameof(IReflectionHelper)}.GetPrivate*", "2.3", DeprecationLevel.Notice); + IPrivateField field = (IPrivateField)this.GetField(obj, name, required); return field != null ? field.GetValue() : default(TValue); @@ -119,64 +190,36 @@ namespace StardewModdingAPI.Framework.ModHelpers /// This is a shortcut for followed by . /// When is false, this will return the default value if reflection fails. If you need to check whether the field exists, use instead. /// + [Obsolete] public TValue GetPrivateValue(Type type, string name, bool required = true) { - IPrivateField field = this.GetPrivateField(type, name, required); + this.DeprecationManager.Warn($"{nameof(IReflectionHelper)}.GetPrivate*", "2.3", DeprecationLevel.Notice); + IPrivateField field = (IPrivateField)this.GetField(type, name, required); return field != null ? field.GetValue() : default(TValue); } - /**** - ** Methods - ****/ /// Get a private instance method. /// The object which has the method. /// The field name. /// Whether to throw an exception if the private field is not found. + [Obsolete] public IPrivateMethod GetPrivateMethod(object obj, string name, bool required = true) { - return this.AssertAccessAllowed( - this.Reflector.GetPrivateMethod(obj, name, required) - ); + this.DeprecationManager.Warn($"{nameof(IReflectionHelper)}.GetPrivate*", "2.3", DeprecationLevel.Notice); + return (IPrivateMethod)this.GetMethod(obj, name, required); } /// Get a private static method. /// The type which has the method. /// The field name. /// Whether to throw an exception if the private field is not found. + [Obsolete] public IPrivateMethod GetPrivateMethod(Type type, string name, bool required = true) { - return this.AssertAccessAllowed( - this.Reflector.GetPrivateMethod(type, name, required) - ); - } - - /**** - ** Methods by signature - ****/ - /// Get a private instance method. - /// The object which has the method. - /// The field name. - /// The argument types of the method signature to find. - /// Whether to throw an exception if the private field is not found. - public IPrivateMethod GetPrivateMethod(object obj, string name, Type[] argumentTypes, bool required = true) - { - return this.AssertAccessAllowed( - this.Reflector.GetPrivateMethod(obj, name, argumentTypes, required) - ); - } - - /// Get a private static method. - /// The type which has the method. - /// The field name. - /// The argument types of the method signature to find. - /// Whether to throw an exception if the private field is not found. - public IPrivateMethod GetPrivateMethod(Type type, string name, Type[] argumentTypes, bool required = true) - { - return this.AssertAccessAllowed( - this.Reflector.GetPrivateMethod(type, name, argumentTypes, required) - ); + this.DeprecationManager.Warn($"{nameof(IReflectionHelper)}.GetPrivate*", "2.3", DeprecationLevel.Notice); + return (IPrivateMethod)this.GetMethod(type, name, required); } @@ -187,7 +230,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The field value type. /// The field being accessed. /// Returns the same field instance for convenience. - private IPrivateField AssertAccessAllowed(IPrivateField field) + private IReflectedField AssertAccessAllowed(IReflectedField field) { this.AssertAccessAllowed(field?.FieldInfo); return field; @@ -197,7 +240,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The property value type. /// The property being accessed. /// Returns the same property instance for convenience. - private IPrivateProperty AssertAccessAllowed(IPrivateProperty property) + private IReflectedProperty AssertAccessAllowed(IReflectedProperty property) { this.AssertAccessAllowed(property?.PropertyInfo); return property; @@ -206,7 +249,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// Assert that mods can use the reflection helper to access the given member. /// The method being accessed. /// Returns the same method instance for convenience. - private IPrivateMethod AssertAccessAllowed(IPrivateMethod method) + private IReflectedMethod AssertAccessAllowed(IReflectedMethod method) { this.AssertAccessAllowed(method?.MethodInfo); return method; -- cgit From 971aff8368a8a2c196d942984926efc2f80cc216 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 11 Dec 2017 22:29:56 -0500 Subject: generalise internal mod registry (#409) --- src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs index 9e824694..4e3f56de 100644 --- a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System.Collections.Generic; +using System.Linq; namespace StardewModdingAPI.Framework.ModHelpers { @@ -27,7 +28,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// Get metadata for all loaded mods. public IEnumerable GetAll() { - return this.Registry.GetAll(); + return this.Registry.GetAll().Select(p => p.Manifest); } /// Get metadata for a loaded mod. @@ -35,14 +36,14 @@ namespace StardewModdingAPI.Framework.ModHelpers /// Returns the matching mod's metadata, or null if not found. public IManifest Get(string uniqueID) { - return this.Registry.Get(uniqueID); + return this.Registry.Get(uniqueID)?.Manifest; } /// Get whether a mod has been loaded. /// The mod's unique ID. public bool IsLoaded(string uniqueID) { - return this.Registry.IsLoaded(uniqueID); + return this.Registry.Get(uniqueID) != null; } } } -- cgit From 2c909f26fcf48fc1de7f3b23f5f83d28d4a5e253 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 11 Dec 2017 23:33:10 -0500 Subject: add prototype of mod-provided APIs (#409) --- src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs index 4e3f56de..340205f3 100644 --- a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs @@ -45,5 +45,11 @@ namespace StardewModdingAPI.Framework.ModHelpers { return this.Registry.Get(uniqueID) != null; } + + /// Get the API provided by a mod, or null if it has none. This signature requires using the API to access the API's properties and methods. + public IModProvidedApi GetApi(string uniqueID) + { + return this.Registry.Get(uniqueID)?.Api; + } } } -- cgit From 7d644aeabee63c0d51d4e89360d2fdab0e51b8be Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 12 Dec 2017 00:09:28 -0500 Subject: switch to simpler approach for mod-provided APIs (#409) --- src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs index 340205f3..949d986a 100644 --- a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs @@ -47,7 +47,7 @@ namespace StardewModdingAPI.Framework.ModHelpers } /// Get the API provided by a mod, or null if it has none. This signature requires using the API to access the API's properties and methods. - public IModProvidedApi GetApi(string uniqueID) + public object GetApi(string uniqueID) { return this.Registry.Get(uniqueID)?.Api; } -- cgit From d04cacbdd0729140e4d8e93323ba66ee90ff9d2a Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 12 Dec 2017 00:16:34 -0500 Subject: log mod-provided API access (#409) --- src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs index 949d986a..827c77d5 100644 --- a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs @@ -12,6 +12,12 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The underlying mod registry. private readonly ModRegistry Registry; + /// Encapsulates monitoring and logging for the mod. + private readonly IMonitor Monitor; + + /// The mod IDs for APIs accessed by this instanced. + private readonly HashSet AccessedModApis = new HashSet(); + /********* ** Public methods @@ -19,10 +25,12 @@ namespace StardewModdingAPI.Framework.ModHelpers /// Construct an instance. /// The unique ID of the relevant mod. /// The underlying mod registry. - public ModRegistryHelper(string modID, ModRegistry registry) + /// Encapsulates monitoring and logging for the mod. + public ModRegistryHelper(string modID, ModRegistry registry, IMonitor monitor) : base(modID) { this.Registry = registry; + this.Monitor = monitor; } /// Get metadata for all loaded mods. @@ -49,7 +57,10 @@ namespace StardewModdingAPI.Framework.ModHelpers /// Get the API provided by a mod, or null if it has none. This signature requires using the API to access the API's properties and methods. public object GetApi(string uniqueID) { - return this.Registry.Get(uniqueID)?.Api; + IModMetadata mod = this.Registry.Get(uniqueID); + if (mod?.Api != null && this.AccessedModApis.Add(mod.Manifest.UniqueID)) + this.Monitor.Log($"Accessed mod-provided API for {mod.DisplayName}.", LogLevel.Trace); + return mod?.Api; } } } -- cgit From 0e43041777d68b96f110fa38ad7424b855db761a Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 12 Dec 2017 01:00:32 -0500 Subject: add support for casting mod-provided API to an interface without a direct assembly reference (#409) --- .../Framework/ModHelpers/ModRegistryHelper.cs | 24 ++++++++++++++++++++++ 1 file changed, 24 insertions(+) (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs index 827c77d5..68201d9a 100644 --- a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using ImpromptuInterface; namespace StardewModdingAPI.Framework.ModHelpers { @@ -62,5 +63,28 @@ namespace StardewModdingAPI.Framework.ModHelpers this.Monitor.Log($"Accessed mod-provided API for {mod.DisplayName}.", LogLevel.Trace); return mod?.Api; } + + /// Get the API provided by a mod, mapped to a given interface which specifies the expected properties and methods. If the mod has no API or it's not compatible with the given interface, get null. + /// The interface which matches the properties and methods you intend to access. + /// The mod's unique ID. + public TInterface GetApi(string uniqueID) where TInterface : class + { + // validate + if (!typeof(TInterface).IsInterface) + { + this.Monitor.Log("Tried to map a mod-provided API into a class; must be an interface."); + return null; + } + + // get raw API + object api = this.GetApi(uniqueID); + if (api == null) + return null; + + // get API of type + if (api is TInterface castApi) + return castApi; + return api.ActLike(); + } } } -- cgit From 59a25a12ffdb90c5a9a3db90be933ca4e76eb64f Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 12 Dec 2017 01:09:43 -0500 Subject: validate interface is public (#409) --- src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs index 68201d9a..d39c885c 100644 --- a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs @@ -72,7 +72,12 @@ namespace StardewModdingAPI.Framework.ModHelpers // validate if (!typeof(TInterface).IsInterface) { - this.Monitor.Log("Tried to map a mod-provided API into a class; must be an interface."); + this.Monitor.Log("Tried to map a mod-provided API to a class; must be a public interface.", LogLevel.Error); + return null; + } + if (!typeof(TInterface).IsPublic) + { + this.Monitor.Log("Tried to map a mod-provided API to a non-public interface; must be a public interface.", LogLevel.Error); return null; } -- cgit From e00424068f3da7c4f91187872e96c90fa61e47db Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 12 Dec 2017 01:33:11 -0500 Subject: block access to mod-provided APIs until all mods are initialised (#409) --- src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs index d39c885c..9574a632 100644 --- a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs @@ -70,6 +70,11 @@ namespace StardewModdingAPI.Framework.ModHelpers public TInterface GetApi(string uniqueID) where TInterface : class { // validate + if (!this.Registry.AreAllModsInitialised) + { + this.Monitor.Log("Tried to access a mod-provided API before all mods were initialised.", LogLevel.Error); + return null; + } if (!typeof(TInterface).IsInterface) { this.Monitor.Log("Tried to map a mod-provided API to a class; must be a public interface.", LogLevel.Error); -- cgit From 21fd2d1e39a6a94758f6298c2da52cd46cffdfcd Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 15 Dec 2017 21:37:08 -0500 Subject: emit proxy classes directly to simplify crossplatform compatibility (#409) --- src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs index 9574a632..ea0dbb38 100644 --- a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; using System.Linq; -using ImpromptuInterface; +using StardewModdingAPI.Framework.Reflection; namespace StardewModdingAPI.Framework.ModHelpers { @@ -19,6 +19,9 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The mod IDs for APIs accessed by this instanced. private readonly HashSet AccessedModApis = new HashSet(); + /// Generates proxy classes to access mod APIs through an arbitrary interface. + private readonly InterfaceProxyBuilder ProxyBuilder; + /********* ** Public methods @@ -26,11 +29,13 @@ namespace StardewModdingAPI.Framework.ModHelpers /// Construct an instance. /// The unique ID of the relevant mod. /// The underlying mod registry. + /// Generates proxy classes to access mod APIs through an arbitrary interface. /// Encapsulates monitoring and logging for the mod. - public ModRegistryHelper(string modID, ModRegistry registry, IMonitor monitor) + public ModRegistryHelper(string modID, ModRegistry registry, InterfaceProxyBuilder proxyBuilder, IMonitor monitor) : base(modID) { this.Registry = registry; + this.ProxyBuilder = proxyBuilder; this.Monitor = monitor; } @@ -94,7 +99,7 @@ namespace StardewModdingAPI.Framework.ModHelpers // get API of type if (api is TInterface castApi) return castApi; - return api.ActLike(); + return this.ProxyBuilder.CreateProxy(api, this.ModID, uniqueID); } } } -- cgit