diff --git a/src/Java.Interop/Documentation/Java.Interop/JniRuntime.JniTypeManager.xml b/src/Java.Interop/Documentation/Java.Interop/JniRuntime.JniTypeManager.xml new file mode 100644 index 000000000..5adca3ece --- /dev/null +++ b/src/Java.Interop/Documentation/Java.Interop/JniRuntime.JniTypeManager.xml @@ -0,0 +1,28 @@ + + + + + Manages bound Java types. + + + + + + Gets the Invoker type for + + + An Invoker type is a concrete type which can be constructed, + which is used to invoke instances of abstract type that cannot be constructed. + For example, the interface type Java.Lang.IRunnable cannot be constructed, + but if a java.lang.Runnable instance enters managed code, + a Invoker must be constructed around the instance so that it may be used. + + + + If is an interface or abstract class, returns the + type which should be constructed around instances of . + If no such type exists, or if is a concrete type, + then is returned. + + + diff --git a/src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs b/src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs index 5f93dbe2b..0399a70e4 100644 --- a/src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs +++ b/src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs @@ -79,8 +79,10 @@ public override string ToString () } #endif // NET + /// public partial class JniTypeManager : IDisposable, ISetRuntime { + internal const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors; internal const DynamicallyAccessedMemberTypes Methods = DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods; internal const DynamicallyAccessedMemberTypes MethodsAndPrivateNested = Methods | DynamicallyAccessedMemberTypes.NonPublicNestedTypes; internal const DynamicallyAccessedMemberTypes MethodsConstructors = MethodsAndPrivateNested | DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors; @@ -385,6 +387,49 @@ IEnumerable CreateGetTypesForSimpleReferenceEnumerator (string jniSimpleRe yield break; } + /// + [return: DynamicallyAccessedMembers (Constructors)] + public Type? GetInvokerType ( + [DynamicallyAccessedMembers (Constructors)] + Type type) + { + if (type.IsAbstract || type.IsInterface) { + return GetInvokerTypeCore (type); + } + return null; + } + + [return: DynamicallyAccessedMembers (Constructors)] + protected virtual Type? GetInvokerTypeCore ( + [DynamicallyAccessedMembers (Constructors)] + Type type) + { + // https://github.com/xamarin/xamarin-android/blob/5472eec991cc075e4b0c09cd98a2331fb93aa0f3/src/Microsoft.Android.Sdk.ILLink/MarkJavaObjects.cs#L176-L186 + const string makeGenericTypeMessage = "Generic 'Invoker' types are preserved by the MarkJavaObjects trimmer step."; + + [UnconditionalSuppressMessage ("Trimming", "IL2055", Justification = makeGenericTypeMessage)] + [return: DynamicallyAccessedMembers (Constructors)] + static Type MakeGenericType ( + [DynamicallyAccessedMembers (Constructors)] + Type type, + Type [] arguments) => + // FIXME: https://github.com/dotnet/java-interop/issues/1192 + #pragma warning disable IL3050 + type.MakeGenericType (arguments); + #pragma warning restore IL3050 + + var signature = type.GetCustomAttribute (); + if (signature == null || signature.InvokerType == null) { + return null; + } + + Type[] arguments = type.GetGenericArguments (); + if (arguments.Length == 0) + return signature.InvokerType; + + return MakeGenericType (signature.InvokerType, arguments); + } + #if NET public IReadOnlyList? GetStaticMethodFallbackTypes (string jniSimpleReference) diff --git a/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs b/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs index ad8d04c35..eb9e23790 100644 --- a/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs +++ b/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs @@ -336,31 +336,21 @@ static Type GetPeerType ([DynamicallyAccessedMembers (Constructors)] Type type) JniObjectReference.Dispose (ref targetClass); - var ctor = GetPeerConstructor (ref refClass, targetType); - if (ctor == null) { + var peer = CreatePeerInstance (ref refClass, targetType, ref reference, transfer); + if (peer == null) { throw new NotSupportedException (string.Format ("Could not find an appropriate constructable wrapper type for Java type '{0}', targetType='{1}'.", JniEnvironment.Types.GetJniTypeNameFromInstance (reference), targetType)); } - - var acts = new object[] { - reference, - transfer, - }; - try { - var peer = (IJavaPeerable) ctor.Invoke (acts); - peer.SetJniManagedPeerState (peer.JniManagedPeerState | JniManagedPeerStates.Replaceable); - return peer; - } finally { - reference = (JniObjectReference) acts [0]; - } + peer.SetJniManagedPeerState (peer.JniManagedPeerState | JniManagedPeerStates.Replaceable); + return peer; } - static readonly Type ByRefJniObjectReference = typeof (JniObjectReference).MakeByRefType (); - - ConstructorInfo? GetPeerConstructor ( + IJavaPeerable? CreatePeerInstance ( ref JniObjectReference klass, [DynamicallyAccessedMembers (Constructors)] - Type fallbackType) + Type fallbackType, + ref JniObjectReference reference, + JniObjectReferenceOptions transfer) { var jniTypeName = JniEnvironment.Types.GetJniTypeNameFromClass (klass); @@ -373,11 +363,11 @@ static Type GetPeerType ([DynamicallyAccessedMembers (Constructors)] Type type) type = Runtime.TypeManager.GetType (sig); if (type != null) { - var ctor = GetActivationConstructor (type); + var peer = TryCreatePeerInstance (ref reference, transfer, type); - if (ctor != null) { + if (peer != null) { JniObjectReference.Dispose (ref klass); - return ctor; + return peer; } } @@ -391,51 +381,41 @@ static Type GetPeerType ([DynamicallyAccessedMembers (Constructors)] Type type) } JniObjectReference.Dispose (ref klass, JniObjectReferenceOptions.CopyAndDispose); - return GetActivationConstructor (fallbackType); + return TryCreatePeerInstance (ref reference, transfer, fallbackType); } - static ConstructorInfo? GetActivationConstructor ( + IJavaPeerable? TryCreatePeerInstance ( + ref JniObjectReference reference, + JniObjectReferenceOptions options, [DynamicallyAccessedMembers (Constructors)] Type type) { - if (type.IsAbstract || type.IsInterface) { - type = GetInvokerType (type) ?? type; - } - foreach (var c in type.GetConstructors (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) { - var p = c.GetParameters (); - if (p.Length == 2 && p [0].ParameterType == ByRefJniObjectReference && p [1].ParameterType == typeof (JniObjectReferenceOptions)) - return c; - } - return null; + type = Runtime.TypeManager.GetInvokerType (type) ?? type; + return TryCreatePeer (ref reference, options, type); } - [return: DynamicallyAccessedMembers (Constructors)] - static Type? GetInvokerType (Type type) - { - // https://github.com/xamarin/xamarin-android/blob/5472eec991cc075e4b0c09cd98a2331fb93aa0f3/src/Microsoft.Android.Sdk.ILLink/MarkJavaObjects.cs#L176-L186 - const string makeGenericTypeMessage = "Generic 'Invoker' types are preserved by the MarkJavaObjects trimmer step."; - - [UnconditionalSuppressMessage ("Trimming", "IL2055", Justification = makeGenericTypeMessage)] - [return: DynamicallyAccessedMembers (Constructors)] - static Type MakeGenericType ( - [DynamicallyAccessedMembers (Constructors)] - Type type, - Type [] arguments) => - // FIXME: https://github.com/dotnet/java-interop/issues/1192 - #pragma warning disable IL3050 - type.MakeGenericType (arguments); - #pragma warning restore IL3050 - - var signature = type.GetCustomAttribute (); - if (signature == null || signature.InvokerType == null) { - return null; - } + const BindingFlags ActivationConstructorBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; + static readonly Type ByRefJniObjectReference = typeof (JniObjectReference).MakeByRefType (); + static readonly Type[] JIConstructorSignature = new Type [] { ByRefJniObjectReference, typeof (JniObjectReferenceOptions) }; - Type[] arguments = type.GetGenericArguments (); - if (arguments.Length == 0) - return signature.InvokerType; - return MakeGenericType (signature.InvokerType, arguments); + protected virtual IJavaPeerable? TryCreatePeer ( + ref JniObjectReference reference, + JniObjectReferenceOptions options, + [DynamicallyAccessedMembers (Constructors)] + Type type) + { + var c = type.GetConstructor (ActivationConstructorBindingFlags, null, JIConstructorSignature, null); + if (c != null) { + var args = new object[] { + reference, + options, + }; + var p = (IJavaPeerable) c.Invoke (args); + reference = (JniObjectReference) args [0]; + return p; + } + return null; } public object? CreateValue ( diff --git a/src/Java.Interop/PublicAPI.Unshipped.txt b/src/Java.Interop/PublicAPI.Unshipped.txt index 5a788dfa6..f25b9f6bb 100644 --- a/src/Java.Interop/PublicAPI.Unshipped.txt +++ b/src/Java.Interop/PublicAPI.Unshipped.txt @@ -3,8 +3,11 @@ static Java.Interop.JniEnvironment.BeginMarshalMethod(nint jnienv, out Java.Inte static Java.Interop.JniEnvironment.EndMarshalMethod(ref Java.Interop.JniTransition transition) -> void virtual Java.Interop.JniRuntime.OnEnterMarshalMethod() -> void virtual Java.Interop.JniRuntime.OnUserUnhandledException(ref Java.Interop.JniTransition transition, System.Exception! e) -> void +virtual Java.Interop.JniRuntime.JniTypeManager.GetInvokerTypeCore(System.Type! type) -> System.Type? +virtual Java.Interop.JniRuntime.JniValueManager.TryCreatePeer(ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions options, System.Type! type) -> Java.Interop.IJavaPeerable? Java.Interop.JavaException.JavaException(ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions transfer, Java.Interop.JniObjectReference throwableOverride) -> void Java.Interop.JavaException.SetJavaStackTrace(Java.Interop.JniObjectReference peerReferenceOverride = default(Java.Interop.JniObjectReference)) -> void +Java.Interop.JniRuntime.JniTypeManager.GetInvokerType(System.Type! type) -> System.Type? Java.Interop.JniRuntime.JniValueManager.GetPeer(Java.Interop.JniObjectReference reference, System.Type? targetType = null) -> Java.Interop.IJavaPeerable? Java.Interop.JniTypeSignatureAttribute.InvokerType.get -> System.Type? Java.Interop.JniTypeSignatureAttribute.InvokerType.set -> void diff --git a/tests/Java.Interop-Tests/Java.Interop/JniRuntime.JniTypeManagerTests.cs b/tests/Java.Interop-Tests/Java.Interop/JniRuntime.JniTypeManagerTests.cs new file mode 100644 index 000000000..2fe3c2cf7 --- /dev/null +++ b/tests/Java.Interop-Tests/Java.Interop/JniRuntime.JniTypeManagerTests.cs @@ -0,0 +1,33 @@ +using System; +using System.Reflection; +using System.Collections.Generic; + +using Java.Interop; + +using NUnit.Framework; + +namespace Java.InteropTests { + + [TestFixture] + public class JniRuntimeJniTypeManagerTests : JavaVMFixture { + + [Test] + public void GetInvokerType () + { + using (var vm = new MyTypeManager ()) { + // Concrete type; no invoker + Assert.IsNull (vm.GetInvokerType (typeof (JavaObject))); + + // Not a bound abstract Java type; no invoker + Assert.IsNull (vm.GetInvokerType (typeof (System.ICloneable))); + + // Bound abstract Java type; has an invoker + Assert.AreSame (typeof (IJavaInterfaceInvoker), vm.GetInvokerType (typeof (IJavaInterface))); + } + } + + class MyTypeManager : JniRuntime.JniTypeManager { + } + } +} +