Skip to content

Commit bd7dddd

Browse files
committed
Merge remote-tracking branch 'origin/main' into dev/jonp/jonp-zero-alloc-member-lookup
2 parents cdf4902 + 7a058c0 commit bd7dddd

File tree

12 files changed

+343
-7
lines changed

12 files changed

+343
-7
lines changed
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
<?xml version="1.0"?>
2+
<docs>
3+
<member name="T:JavaPeerableExtensions">
4+
<summary>
5+
Extension methods on <see cref="T:Java.Interop.IJavaPeerable" />.
6+
</summary>
7+
<remarks />
8+
</member>
9+
<member name="M:GetJniTypeName">
10+
<summary>Gets the JNI name of the type of the instance <paramref name="self" />.</summary>
11+
<param name="self">
12+
The <see cref="T:Java.Interop.IJavaPeerable" /> instance
13+
to get the JNI type name of.
14+
</param>
15+
<remarks>
16+
<para>
17+
The JNI type name is the name of the Java type, as it would be
18+
used in Java Native Interface (JNI) API calls. For example,
19+
instead of the Java name <c>java.lang.Object</c>, the JNI name
20+
is <c>java/lang/Object</c>.
21+
</para>
22+
</remarks>
23+
</member>
24+
<member name="M:TryJavaCast">
25+
<typeparam name="TResult">
26+
The type to coerce <paramref name="self" /> to.
27+
</typeparam>
28+
<param name="self">
29+
A <see cref="T:Java.Interop.IJavaPeerable" /> instance
30+
to coerce to type <typeparamref name="TResult" />.
31+
</param>
32+
<param name="result">
33+
When this method returns, contains a value of type
34+
<typeparamref name="TResult" /> if <paramref name="self" /> can be
35+
coerced to the Java type corresponding to <typeparamref name="TResult" />,
36+
or <c>null</c> if the coercion is not valid.
37+
</param>
38+
<summary>
39+
Try to coerce <paramref name="self" /> to type <typeparamref name="TResult" />,
40+
checking that the coercion is valid on the Java side.
41+
</summary>
42+
<returns>
43+
<see langword="true" /> if <pramref name="self" /> was converted successfully;
44+
otherwise, <see langword="false" />.
45+
</returns>
46+
<remarks>
47+
<block subset="none" type="note">
48+
Implementations of <see cref="T:Java.Interop.IJavaPeerable" /> consist
49+
of two halves: a <i>Java peer</i> and a <i>managed peer</i>.
50+
The <see cref="P:Java.Interop.IJavaPeerable.PeerReference" /> property
51+
associates the managed peer to the Java peer.
52+
</block>
53+
<block subset="none" type="note">
54+
The <see cref="T:Java.Interop.JniTypeSignatureAttribute" /> or
55+
<see cref="T:Android.Runtime.RegisterAttribute" /> custom attributes are
56+
used to associated a managed type to a Java type.
57+
</block>
58+
</remarks>
59+
<exception cref="T:System.ArgumentException">
60+
<para>
61+
The Java peer type for <typeparamref name="TResult" /> could not be found.
62+
</para>
63+
</exception>
64+
<exception cref="T:System.NotSupportedException">
65+
<para>
66+
The type <typeparamref name="TResult" /> or a <i>Invoker type</i> for
67+
<typeparamref name="TResult" /> does not provide an
68+
<i>activation constructor</i>, a constructor with a singature of
69+
<c>(ref JniObjectReference, JniObjectReferenceOptions)</c> or
70+
<c>(IntPtr, JniHandleOwnership)</c>.
71+
</para>
72+
</exception>
73+
<seealso cref="M:Java.Interop.JavaPeerableExtensions.JavaAs``1(Java.Interop.IJavaPeerable)" />
74+
</member>
75+
<member name="M:JavaAs">
76+
<typeparam name="TResult">
77+
The type to coerce <paramref name="self" /> to.
78+
</typeparam>
79+
<param name="self">
80+
A <see cref="T:Java.Interop.IJavaPeerable" /> instance
81+
to coerce to type <typeparamref name="TResult" />.
82+
</param>
83+
<summary>
84+
Try to coerce <paramref name="self" /> to type <typeparamref name="TResult" />,
85+
checking that the coercion is valid on the Java side.
86+
</summary>
87+
<returns>
88+
A value of type <typeparamref name="TResult" /> if the Java peer to
89+
<paramref name="self" /> can be coerced to the Java type corresponding
90+
to <typeparamref name="TResult" />; otherwise, <c>null</c>.
91+
</returns>
92+
<remarks>
93+
<block subset="none" type="note">
94+
Implementations of <see cref="T:Java.Interop.IJavaPeerable" /> consist
95+
of two halves: a <i>Java peer</i> and a <i>managed peer</i>.
96+
The <see cref="P:Java.Interop.IJavaPeerable.PeerReference" /> property
97+
associates the managed peer to the Java peer.
98+
</block>
99+
<block subset="none" type="note">
100+
The <see cref="T:Java.Interop.JniTypeSignatureAttribute" /> or
101+
<see cref="T:Android.Runtime.RegisterAttribute" /> custom attributes are
102+
used to associated a managed type to a Java type.
103+
</block>
104+
</remarks>
105+
<exception cref="T:System.ArgumentException">
106+
<para>
107+
The Java peer type for <typeparamref name="TResult" /> could not be found.
108+
</para>
109+
</exception>
110+
<exception cref="T:System.NotSupportedException">
111+
<para>
112+
The type <typeparamref name="TResult" /> or a <i>Invoker type</i> for
113+
<typeparamref name="TResult" /> does not provide an
114+
<i>activation constructor</i>, a constructor with a singature of
115+
<c>(ref JniObjectReference, JniObjectReferenceOptions)</c> or
116+
<c>(IntPtr, JniHandleOwnership)</c>.
117+
</para>
118+
</exception>
119+
<seealso cref="P:Java.Interop.JavaPeerableExtensions.TryJavaCast``1(Java.Interop.IJavaPeerable)" />
120+
</member>
121+
</docs>

src/Java.Interop/Java.Interop/JavaObject.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ namespace Java.Interop
88
[JniTypeSignature ("java/lang/Object", GenerateJavaPeer=false)]
99
unsafe public class JavaObject : IJavaPeerable
1010
{
11-
internal const DynamicallyAccessedMemberTypes ConstructorsAndInterfaces = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.Interfaces;
11+
internal const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors;
12+
internal const DynamicallyAccessedMemberTypes ConstructorsAndInterfaces = Constructors | DynamicallyAccessedMemberTypes.Interfaces;
1213

1314
readonly static JniPeerMembers _members = new JniPeerMembers ("java/lang/Object", typeof (JavaObject));
1415

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,51 @@
11
#nullable enable
22

33
using System;
4+
using System.Diagnostics.CodeAnalysis;
45

56
namespace Java.Interop {
67

8+
/// <include file="../Documentation/Java.Interop/JavaPeerableExtensions.xml" path="/docs/member[@name='T:JavaPeerableExtensions']/*" />
79
public static class JavaPeerableExtensions {
810

11+
/// <include file="../Documentation/Java.Interop/JavaPeerableExtensions.xml" path="/docs/member[@name='M:GetJniTypeName']/*" />
912
public static string? GetJniTypeName (this IJavaPeerable self)
1013
{
1114
JniPeerMembers.AssertSelf (self);
1215
return JniEnvironment.Types.GetJniTypeNameFromInstance (self.PeerReference);
1316
}
17+
18+
/// <include file="../Documentation/Java.Interop/JavaPeerableExtensions.xml" path="/docs/member[@name='M:TryJavaCast']/*" />
19+
public static bool TryJavaCast<
20+
[DynamicallyAccessedMembers (JavaObject.Constructors)]
21+
TResult
22+
> (this IJavaPeerable? self, [NotNullWhen (true)] out TResult? result)
23+
where TResult : class, IJavaPeerable
24+
{
25+
result = JavaAs<TResult> (self);
26+
return result != null;
27+
}
28+
29+
/// <include file="../Documentation/Java.Interop/JavaPeerableExtensions.xml" path="/docs/member[@name='M:JavaAs']/*" />
30+
public static TResult? JavaAs<
31+
[DynamicallyAccessedMembers (JavaObject.Constructors)]
32+
TResult
33+
> (this IJavaPeerable? self)
34+
where TResult : class, IJavaPeerable
35+
{
36+
if (self == null || !self.PeerReference.IsValid) {
37+
return null;
38+
}
39+
40+
if (self is TResult result) {
41+
return result;
42+
}
43+
44+
var r = self.PeerReference;
45+
return JniEnvironment.Runtime.ValueManager.CreatePeer (
46+
ref r, JniObjectReferenceOptions.Copy,
47+
targetType: typeof (TResult))
48+
as TResult;
49+
}
1450
}
1551
}

src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -276,16 +276,45 @@ static Type GetPeerType ([DynamicallyAccessedMembers (Constructors)] Type type)
276276
if (disposed)
277277
throw new ObjectDisposedException (GetType ().Name);
278278

279+
if (!reference.IsValid) {
280+
return null;
281+
}
282+
279283
targetType = targetType ?? typeof (JavaObject);
280284
targetType = GetPeerType (targetType);
281285

282286
if (!typeof (IJavaPeerable).IsAssignableFrom (targetType))
283287
throw new ArgumentException ($"targetType `{targetType.AssemblyQualifiedName}` must implement IJavaPeerable!", nameof (targetType));
284288

285-
var ctor = GetPeerConstructor (reference, targetType);
286-
if (ctor == null)
289+
var targetSig = Runtime.TypeManager.GetTypeSignature (targetType);
290+
if (!targetSig.IsValid || targetSig.SimpleReference == null) {
291+
throw new ArgumentException ($"Could not determine Java type corresponding to `{targetType.AssemblyQualifiedName}`.", nameof (targetType));
292+
}
293+
294+
var refClass = JniEnvironment.Types.GetObjectClass (reference);
295+
JniObjectReference targetClass;
296+
try {
297+
targetClass = JniEnvironment.Types.FindClass (targetSig.SimpleReference);
298+
} catch (Exception e) {
299+
JniObjectReference.Dispose (ref refClass);
300+
throw new ArgumentException ($"Could not find Java class `{targetSig.SimpleReference}`.",
301+
nameof (targetType),
302+
e);
303+
}
304+
305+
if (!JniEnvironment.Types.IsAssignableFrom (refClass, targetClass)) {
306+
JniObjectReference.Dispose (ref refClass);
307+
JniObjectReference.Dispose (ref targetClass);
308+
return null;
309+
}
310+
311+
JniObjectReference.Dispose (ref targetClass);
312+
313+
var ctor = GetPeerConstructor (ref refClass, targetType);
314+
if (ctor == null) {
287315
throw new NotSupportedException (string.Format ("Could not find an appropriate constructable wrapper type for Java type '{0}', targetType='{1}'.",
288316
JniEnvironment.Types.GetJniTypeNameFromInstance (reference), targetType));
317+
}
289318

290319
var acts = new object[] {
291320
reference,
@@ -303,11 +332,10 @@ static Type GetPeerType ([DynamicallyAccessedMembers (Constructors)] Type type)
303332
static readonly Type ByRefJniObjectReference = typeof (JniObjectReference).MakeByRefType ();
304333

305334
ConstructorInfo? GetPeerConstructor (
306-
JniObjectReference instance,
335+
ref JniObjectReference klass,
307336
[DynamicallyAccessedMembers (Constructors)]
308337
Type fallbackType)
309338
{
310-
var klass = JniEnvironment.Types.GetObjectClass (instance);
311339
var jniTypeName = JniEnvironment.Types.GetJniTypeNameFromClass (klass);
312340

313341
Type? type = null;

src/Java.Interop/Java.Interop/ManagedPeer.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ namespace Java.Interop {
1818
/* static */ sealed class ManagedPeer : JavaObject {
1919

2020
internal const string JniTypeName = "net/dot/jni/ManagedPeer";
21-
internal const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors;
2221
internal const DynamicallyAccessedMemberTypes ConstructorsMethodsNestedTypes = Constructors | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods | DynamicallyAccessedMemberTypes.NonPublicNestedTypes;
2322

2423

src/Java.Interop/PublicAPI.Unshipped.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,5 @@ Java.Interop.JniPeerMembers.JniStaticMethods.InvokeSByteMethod(Java.Interop.JniM
9999
Java.Interop.JniPeerMembers.JniStaticMethods.InvokeSingleMethod(Java.Interop.JniMemberInfoLookup member, System.ReadOnlySpan<Java.Interop.JniArgumentValue> parameters) -> float
100100
Java.Interop.JniPeerMembers.JniStaticMethods.InvokeVoidMethod(Java.Interop.JniMemberInfoLookup member, System.ReadOnlySpan<Java.Interop.JniArgumentValue> parameters) -> void
101101
virtual Java.Interop.JniRuntime.JniTypeManager.GetReplacementMethodInfoCore(string! jniSimpleReference, System.ReadOnlySpan<byte> jniMethodName, System.ReadOnlySpan<byte> jniMethodSignature) -> Java.Interop.JniRuntime.ReplacementMethodInfo?
102+
static Java.Interop.JavaPeerableExtensions.TryJavaCast<TResult>(this Java.Interop.IJavaPeerable? self, out TResult? result) -> bool
103+
static Java.Interop.JavaPeerableExtensions.JavaAs<TResult>(this Java.Interop.IJavaPeerable? self) -> TResult?

tests/Java.Interop-Tests/Java.Interop-Tests.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
<JavaInteropTestJar Include="$(MSBuildThisFileDirectory)java\net\dot\jni\test\CallVirtualFromConstructorBase.java" />
4242
<JavaInteropTestJar Include="$(MSBuildThisFileDirectory)java\net\dot\jni\test\CallVirtualFromConstructorDerived.java" />
4343
<JavaInteropTestJar Include="$(MSBuildThisFileDirectory)java\net\dot\jni\test\GetThis.java" />
44+
<JavaInteropTestJar Include="$(MSBuildThisFileDirectory)java\net\dot\jni\test\JavaInterface.java" />
45+
<JavaInteropTestJar Include="$(MSBuildThisFileDirectory)java\net\dot\jni\test\MyJavaInterfaceImpl.java" />
4446
<JavaInteropTestJar Include="$(MSBuildThisFileDirectory)java\net\dot\jni\test\ObjectHelper.java" />
4547
<JavaInteropTestJar Include="$(MSBuildThisFileDirectory)java\net\dot\jni\test\RenameClassBase1.java" />
4648
<JavaInteropTestJar Include="$(MSBuildThisFileDirectory)java\net\dot\jni\test\RenameClassBase2.java" />
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
5+
using Java.Interop;
6+
7+
using NUnit.Framework;
8+
9+
namespace Java.InteropTests;
10+
11+
[TestFixture]
12+
public class JavaPeerableExtensionsTests {
13+
14+
[Test]
15+
public void JavaAs_Exceptions ()
16+
{
17+
using var v = new MyJavaInterfaceImpl ();
18+
19+
// The Java type corresponding to JavaObjectWithMissingJavaPeer doesn't exist
20+
Assert.Throws<ArgumentException>(() => v.JavaAs<JavaObjectWithMissingJavaPeer>());
21+
22+
var r = v.PeerReference;
23+
using var o = new JavaObject (ref r, JniObjectReferenceOptions.Copy);
24+
// MyJavaInterfaceImpl doesn't provide an activation constructor
25+
Assert.Throws<NotSupportedException>(() => o.JavaAs<MyJavaInterfaceImpl>());
26+
#if !__ANDROID__
27+
// JavaObjectWithNoJavaPeer has no Java peer
28+
Assert.Throws<ArgumentException>(() => v.JavaAs<JavaObjectWithNoJavaPeer>());
29+
#endif // !__ANDROID__
30+
}
31+
32+
[Test]
33+
public void JavaAs_NullSelfReturnsNull ()
34+
{
35+
Assert.AreEqual (null, JavaPeerableExtensions.JavaAs<IAndroidInterface> (null));
36+
}
37+
38+
public void JavaAs_InvalidPeerRefReturnsNull ()
39+
{
40+
var v = new MyJavaInterfaceImpl ();
41+
v.Dispose ();
42+
Assert.AreEqual (null, JavaPeerableExtensions.JavaAs<IJavaInterface> (v));
43+
}
44+
45+
[Test]
46+
public void JavaAs_InstanceThatDoesNotImplementInterfaceReturnsNull ()
47+
{
48+
using var v = new MyJavaInterfaceImpl ();
49+
Assert.AreEqual (null, JavaPeerableExtensions.JavaAs<IAndroidInterface> (v));
50+
}
51+
52+
[Test]
53+
public void JavaAs ()
54+
{
55+
using var impl = new MyJavaInterfaceImpl ();
56+
using var iface = impl.JavaAs<IJavaInterface> ();
57+
Assert.IsNotNull (iface);
58+
Assert.AreEqual ("Hello from Java!", iface.Value);
59+
}
60+
}
61+
62+
// Note: Java side implements JavaInterface, while managed binding DOES NOT.
63+
[JniTypeSignature (JniTypeName, GenerateJavaPeer=false)]
64+
public class MyJavaInterfaceImpl : JavaObject {
65+
internal const string JniTypeName = "net/dot/jni/test/MyJavaInterfaceImpl";
66+
67+
internal static readonly JniPeerMembers _members = new JniPeerMembers (JniTypeName, typeof (MyJavaInterfaceImpl));
68+
69+
public override JniPeerMembers JniPeerMembers {
70+
get {return _members;}
71+
}
72+
73+
public unsafe MyJavaInterfaceImpl ()
74+
: base (ref *InvalidJniObjectReference, JniObjectReferenceOptions.None)
75+
{
76+
const string id = "()V";
77+
var peer = _members.InstanceMethods.StartCreateInstance (id, GetType (), null);
78+
Construct (ref peer, JniObjectReferenceOptions.CopyAndDispose);
79+
_members.InstanceMethods.FinishCreateInstance (id, this, null);
80+
}
81+
}
82+
83+
[JniTypeSignature (JniTypeName, GenerateJavaPeer=false)]
84+
interface IJavaInterface : IJavaPeerable {
85+
internal const string JniTypeName = "net/dot/jni/test/JavaInterface";
86+
87+
public string Value {
88+
[JniMethodSignatureAttribute("getValue", "()Ljava/lang/String;")]
89+
get;
90+
}
91+
}
92+
93+
[JniTypeSignature (IJavaInterface.JniTypeName, GenerateJavaPeer=false)]
94+
internal class IJavaInterfaceInvoker : JavaObject, IJavaInterface {
95+
96+
internal static readonly JniPeerMembers _members = new JniPeerMembers (IJavaInterface.JniTypeName, typeof (IJavaInterfaceInvoker));
97+
98+
public override JniPeerMembers JniPeerMembers {
99+
get {return _members;}
100+
}
101+
102+
public IJavaInterfaceInvoker (ref JniObjectReference reference, JniObjectReferenceOptions options)
103+
: base (ref reference, options)
104+
{
105+
}
106+
107+
public unsafe string Value {
108+
get {
109+
const string id = "getValue.()Ljava/lang/String;";
110+
var r = JniPeerMembers.InstanceMethods.InvokeVirtualObjectMethod (id, this, null);
111+
return JniEnvironment.Strings.ToString (ref r, JniObjectReferenceOptions.CopyAndDispose);
112+
}
113+
}
114+
}

tests/Java.Interop-Tests/Java.Interop/JavaVMFixture.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,12 @@ class JavaVMFixtureTypeManager : JniRuntime.JniTypeManager {
4141
[CallVirtualFromConstructorDerived.JniTypeName] = typeof (CallVirtualFromConstructorDerived),
4242
[CrossReferenceBridge.JniTypeName] = typeof (CrossReferenceBridge),
4343
[GetThis.JniTypeName] = typeof (GetThis),
44+
[IAndroidInterface.JniTypeName] = typeof (IAndroidInterface),
45+
[IJavaInterface.JniTypeName] = typeof (IJavaInterface),
4446
[JavaDisposedObject.JniTypeName] = typeof (JavaDisposedObject),
4547
[JavaObjectWithMissingJavaPeer.JniTypeName] = typeof (JavaObjectWithMissingJavaPeer),
4648
[MyDisposableObject.JniTypeName] = typeof (JavaDisposedObject),
49+
[MyJavaInterfaceImpl.JniTypeName] = typeof (MyJavaInterfaceImpl),
4750
};
4851

4952
public JavaVMFixtureTypeManager ()

0 commit comments

Comments
 (0)