Skip to content

[Java.Interop] Add JniRuntime.JniValueManager.TryCreatePeer() #1301

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Feb 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?xml version="1.0"?>
<docs>
<member name="T:JniTypeManager">
<summary>
Manages bound Java types.
</summary>
<remarks>
</remarks>
</member>
<member name="M:GetInvokerType">
<summary>Gets the <i>Invoker</i> type for <paramref name="type" /></summary>
<remarks>
<para>
An <i>Invoker type</i> 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 <c>Java.Lang.IRunnable</c> cannot be constructed,
but if a <c>java.lang.Runnable</c> instance enters managed code,
a Invoker must be constructed around the instance so that it may be used.
</para>
</remarks>
<returns>
If <paramref name="type" /> is an interface or abstract class, returns the
type which should be constructed around instances of <paramref name="type" />.
If no such type exists, or if <paramref name="type" /> is a concrete type,
then <see langword="null" /> is returned.
</returns>
</member>
</docs>
45 changes: 45 additions & 0 deletions src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,10 @@ public override string ToString ()
}
#endif // NET

/// <include file="../Documentation/Java.Interop/JniRuntime.JniTypeManager.xml" path="/docs/member[@name='T:JniTypeManager']/*" />
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;
Expand Down Expand Up @@ -385,6 +387,49 @@ IEnumerable<Type> CreateGetTypesForSimpleReferenceEnumerator (string jniSimpleRe
yield break;
}

/// <include file="../Documentation/Java.Interop/JniRuntime.JniTypeManager.xml" path="/docs/member[@name='M:GetInvokerType']/*" />
[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<JniTypeSignatureAttribute> ();
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<string>? GetStaticMethodFallbackTypes (string jniSimpleReference)
Expand Down
94 changes: 37 additions & 57 deletions src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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;
}
}

Expand All @@ -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<JniTypeSignatureAttribute> ();
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 (
Expand Down
3 changes: 3 additions & 0 deletions src/Java.Interop/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
@@ -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 {
}
}
}