diff --git a/src/Java.Interop/Documentation/Java.Interop/JavaPeerableExtensions.xml b/src/Java.Interop/Documentation/Java.Interop/JavaPeerableExtensions.xml
new file mode 100644
index 000000000..49d0c854c
--- /dev/null
+++ b/src/Java.Interop/Documentation/Java.Interop/JavaPeerableExtensions.xml
@@ -0,0 +1,121 @@
+
+
+
+
+ Extension methods on .
+
+
+
+
+ Gets the JNI name of the type of the instance .
+
+ The instance
+ to get the JNI type name of.
+
+
+
+ The JNI type name is the name of the Java type, as it would be
+ used in Java Native Interface (JNI) API calls. For example,
+ instead of the Java name java.lang.Object, the JNI name
+ is java/lang/Object.
+
+
+
+
+
+ The type to coerce to.
+
+
+ A instance
+ to coerce to type .
+
+
+ When this method returns, contains a value of type
+ if can be
+ coerced to the Java type corresponding to ,
+ or null if the coercion is not valid.
+
+
+ Try to coerce to type ,
+ checking that the coercion is valid on the Java side.
+
+
+ if was converted successfully;
+ otherwise, .
+
+
+
+ Implementations of consist
+ of two halves: a Java peer and a managed peer.
+ The property
+ associates the managed peer to the Java peer.
+
+
+ The or
+ custom attributes are
+ used to associated a managed type to a Java type.
+
+
+
+
+ The Java peer type for could not be found.
+
+
+
+
+ The type or a Invoker type for
+ does not provide an
+ activation constructor, a constructor with a singature of
+ (ref JniObjectReference, JniObjectReferenceOptions) or
+ (IntPtr, JniHandleOwnership).
+
+
+
+
+
+
+ The type to coerce to.
+
+
+ A instance
+ to coerce to type .
+
+
+ Try to coerce to type ,
+ checking that the coercion is valid on the Java side.
+
+
+ A value of type if the Java peer to
+ can be coerced to the Java type corresponding
+ to ; otherwise, null.
+
+
+
+ Implementations of consist
+ of two halves: a Java peer and a managed peer.
+ The property
+ associates the managed peer to the Java peer.
+
+
+ The or
+ custom attributes are
+ used to associated a managed type to a Java type.
+
+
+
+
+ The Java peer type for could not be found.
+
+
+
+
+ The type or a Invoker type for
+ does not provide an
+ activation constructor, a constructor with a singature of
+ (ref JniObjectReference, JniObjectReferenceOptions) or
+ (IntPtr, JniHandleOwnership).
+
+
+
+
+
diff --git a/src/Java.Interop/Java.Interop/JavaObject.cs b/src/Java.Interop/Java.Interop/JavaObject.cs
index df666821d..111ec735a 100644
--- a/src/Java.Interop/Java.Interop/JavaObject.cs
+++ b/src/Java.Interop/Java.Interop/JavaObject.cs
@@ -8,7 +8,8 @@ namespace Java.Interop
[JniTypeSignature ("java/lang/Object", GenerateJavaPeer=false)]
unsafe public class JavaObject : IJavaPeerable
{
- internal const DynamicallyAccessedMemberTypes ConstructorsAndInterfaces = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.Interfaces;
+ internal const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors;
+ internal const DynamicallyAccessedMemberTypes ConstructorsAndInterfaces = Constructors | DynamicallyAccessedMemberTypes.Interfaces;
readonly static JniPeerMembers _members = new JniPeerMembers ("java/lang/Object", typeof (JavaObject));
diff --git a/src/Java.Interop/Java.Interop/JavaPeerableExtensions.cs b/src/Java.Interop/Java.Interop/JavaPeerableExtensions.cs
index e6e7e1691..8bb9dbb55 100644
--- a/src/Java.Interop/Java.Interop/JavaPeerableExtensions.cs
+++ b/src/Java.Interop/Java.Interop/JavaPeerableExtensions.cs
@@ -1,15 +1,51 @@
#nullable enable
using System;
+using System.Diagnostics.CodeAnalysis;
namespace Java.Interop {
+ ///
public static class JavaPeerableExtensions {
+ ///
public static string? GetJniTypeName (this IJavaPeerable self)
{
JniPeerMembers.AssertSelf (self);
return JniEnvironment.Types.GetJniTypeNameFromInstance (self.PeerReference);
}
+
+ ///
+ public static bool TryJavaCast<
+ [DynamicallyAccessedMembers (JavaObject.Constructors)]
+ TResult
+ > (this IJavaPeerable? self, [NotNullWhen (true)] out TResult? result)
+ where TResult : class, IJavaPeerable
+ {
+ result = JavaAs (self);
+ return result != null;
+ }
+
+ ///
+ public static TResult? JavaAs<
+ [DynamicallyAccessedMembers (JavaObject.Constructors)]
+ TResult
+ > (this IJavaPeerable? self)
+ where TResult : class, IJavaPeerable
+ {
+ if (self == null || !self.PeerReference.IsValid) {
+ return null;
+ }
+
+ if (self is TResult result) {
+ return result;
+ }
+
+ var r = self.PeerReference;
+ return JniEnvironment.Runtime.ValueManager.CreatePeer (
+ ref r, JniObjectReferenceOptions.Copy,
+ targetType: typeof (TResult))
+ as TResult;
+ }
}
}
diff --git a/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs b/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs
index 263bbdf08..88f973ec0 100644
--- a/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs
+++ b/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs
@@ -276,16 +276,45 @@ static Type GetPeerType ([DynamicallyAccessedMembers (Constructors)] Type type)
if (disposed)
throw new ObjectDisposedException (GetType ().Name);
+ if (!reference.IsValid) {
+ return null;
+ }
+
targetType = targetType ?? typeof (JavaObject);
targetType = GetPeerType (targetType);
if (!typeof (IJavaPeerable).IsAssignableFrom (targetType))
throw new ArgumentException ($"targetType `{targetType.AssemblyQualifiedName}` must implement IJavaPeerable!", nameof (targetType));
- var ctor = GetPeerConstructor (reference, targetType);
- if (ctor == null)
+ var targetSig = Runtime.TypeManager.GetTypeSignature (targetType);
+ if (!targetSig.IsValid || targetSig.SimpleReference == null) {
+ throw new ArgumentException ($"Could not determine Java type corresponding to `{targetType.AssemblyQualifiedName}`.", nameof (targetType));
+ }
+
+ var refClass = JniEnvironment.Types.GetObjectClass (reference);
+ JniObjectReference targetClass;
+ try {
+ targetClass = JniEnvironment.Types.FindClass (targetSig.SimpleReference);
+ } catch (Exception e) {
+ JniObjectReference.Dispose (ref refClass);
+ throw new ArgumentException ($"Could not find Java class `{targetSig.SimpleReference}`.",
+ nameof (targetType),
+ e);
+ }
+
+ if (!JniEnvironment.Types.IsAssignableFrom (refClass, targetClass)) {
+ JniObjectReference.Dispose (ref refClass);
+ JniObjectReference.Dispose (ref targetClass);
+ return null;
+ }
+
+ JniObjectReference.Dispose (ref targetClass);
+
+ var ctor = GetPeerConstructor (ref refClass, targetType);
+ if (ctor == 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,
@@ -303,11 +332,10 @@ static Type GetPeerType ([DynamicallyAccessedMembers (Constructors)] Type type)
static readonly Type ByRefJniObjectReference = typeof (JniObjectReference).MakeByRefType ();
ConstructorInfo? GetPeerConstructor (
- JniObjectReference instance,
+ ref JniObjectReference klass,
[DynamicallyAccessedMembers (Constructors)]
Type fallbackType)
{
- var klass = JniEnvironment.Types.GetObjectClass (instance);
var jniTypeName = JniEnvironment.Types.GetJniTypeNameFromClass (klass);
Type? type = null;
diff --git a/src/Java.Interop/Java.Interop/ManagedPeer.cs b/src/Java.Interop/Java.Interop/ManagedPeer.cs
index 6a9834954..11997ba2e 100644
--- a/src/Java.Interop/Java.Interop/ManagedPeer.cs
+++ b/src/Java.Interop/Java.Interop/ManagedPeer.cs
@@ -18,7 +18,6 @@ namespace Java.Interop {
/* static */ sealed class ManagedPeer : JavaObject {
internal const string JniTypeName = "net/dot/jni/ManagedPeer";
- internal const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors;
internal const DynamicallyAccessedMemberTypes ConstructorsMethodsNestedTypes = Constructors | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods | DynamicallyAccessedMemberTypes.NonPublicNestedTypes;
diff --git a/src/Java.Interop/PublicAPI.Unshipped.txt b/src/Java.Interop/PublicAPI.Unshipped.txt
index 7dc5c5811..3d4565551 100644
--- a/src/Java.Interop/PublicAPI.Unshipped.txt
+++ b/src/Java.Interop/PublicAPI.Unshipped.txt
@@ -1 +1,3 @@
#nullable enable
+static Java.Interop.JavaPeerableExtensions.TryJavaCast(this Java.Interop.IJavaPeerable? self, out TResult? result) -> bool
+static Java.Interop.JavaPeerableExtensions.JavaAs(this Java.Interop.IJavaPeerable? self) -> TResult?
diff --git a/tests/Java.Interop-Tests/Java.Interop-Tests.csproj b/tests/Java.Interop-Tests/Java.Interop-Tests.csproj
index 51fa6e6df..0677be887 100644
--- a/tests/Java.Interop-Tests/Java.Interop-Tests.csproj
+++ b/tests/Java.Interop-Tests/Java.Interop-Tests.csproj
@@ -41,6 +41,8 @@
+
+
diff --git a/tests/Java.Interop-Tests/Java.Interop/JavaPeerableExtensionsTests.cs b/tests/Java.Interop-Tests/Java.Interop/JavaPeerableExtensionsTests.cs
new file mode 100644
index 000000000..33df401c6
--- /dev/null
+++ b/tests/Java.Interop-Tests/Java.Interop/JavaPeerableExtensionsTests.cs
@@ -0,0 +1,114 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using Java.Interop;
+
+using NUnit.Framework;
+
+namespace Java.InteropTests;
+
+[TestFixture]
+public class JavaPeerableExtensionsTests {
+
+ [Test]
+ public void JavaAs_Exceptions ()
+ {
+ using var v = new MyJavaInterfaceImpl ();
+
+ // The Java type corresponding to JavaObjectWithMissingJavaPeer doesn't exist
+ Assert.Throws(() => v.JavaAs());
+
+ var r = v.PeerReference;
+ using var o = new JavaObject (ref r, JniObjectReferenceOptions.Copy);
+ // MyJavaInterfaceImpl doesn't provide an activation constructor
+ Assert.Throws(() => o.JavaAs());
+#if !__ANDROID__
+ // JavaObjectWithNoJavaPeer has no Java peer
+ Assert.Throws(() => v.JavaAs());
+#endif // !__ANDROID__
+ }
+
+ [Test]
+ public void JavaAs_NullSelfReturnsNull ()
+ {
+ Assert.AreEqual (null, JavaPeerableExtensions.JavaAs (null));
+ }
+
+ public void JavaAs_InvalidPeerRefReturnsNull ()
+ {
+ var v = new MyJavaInterfaceImpl ();
+ v.Dispose ();
+ Assert.AreEqual (null, JavaPeerableExtensions.JavaAs (v));
+ }
+
+ [Test]
+ public void JavaAs_InstanceThatDoesNotImplementInterfaceReturnsNull ()
+ {
+ using var v = new MyJavaInterfaceImpl ();
+ Assert.AreEqual (null, JavaPeerableExtensions.JavaAs (v));
+ }
+
+ [Test]
+ public void JavaAs ()
+ {
+ using var impl = new MyJavaInterfaceImpl ();
+ using var iface = impl.JavaAs ();
+ Assert.IsNotNull (iface);
+ Assert.AreEqual ("Hello from Java!", iface.Value);
+ }
+}
+
+// Note: Java side implements JavaInterface, while managed binding DOES NOT.
+[JniTypeSignature (JniTypeName, GenerateJavaPeer=false)]
+public class MyJavaInterfaceImpl : JavaObject {
+ internal const string JniTypeName = "net/dot/jni/test/MyJavaInterfaceImpl";
+
+ internal static readonly JniPeerMembers _members = new JniPeerMembers (JniTypeName, typeof (MyJavaInterfaceImpl));
+
+ public override JniPeerMembers JniPeerMembers {
+ get {return _members;}
+ }
+
+ public unsafe MyJavaInterfaceImpl ()
+ : base (ref *InvalidJniObjectReference, JniObjectReferenceOptions.None)
+ {
+ const string id = "()V";
+ var peer = _members.InstanceMethods.StartCreateInstance (id, GetType (), null);
+ Construct (ref peer, JniObjectReferenceOptions.CopyAndDispose);
+ _members.InstanceMethods.FinishCreateInstance (id, this, null);
+ }
+}
+
+[JniTypeSignature (JniTypeName, GenerateJavaPeer=false)]
+interface IJavaInterface : IJavaPeerable {
+ internal const string JniTypeName = "net/dot/jni/test/JavaInterface";
+
+ public string Value {
+ [JniMethodSignatureAttribute("getValue", "()Ljava/lang/String;")]
+ get;
+ }
+}
+
+[JniTypeSignature (IJavaInterface.JniTypeName, GenerateJavaPeer=false)]
+internal class IJavaInterfaceInvoker : JavaObject, IJavaInterface {
+
+ internal static readonly JniPeerMembers _members = new JniPeerMembers (IJavaInterface.JniTypeName, typeof (IJavaInterfaceInvoker));
+
+ public override JniPeerMembers JniPeerMembers {
+ get {return _members;}
+ }
+
+ public IJavaInterfaceInvoker (ref JniObjectReference reference, JniObjectReferenceOptions options)
+ : base (ref reference, options)
+ {
+ }
+
+ public unsafe string Value {
+ get {
+ const string id = "getValue.()Ljava/lang/String;";
+ var r = JniPeerMembers.InstanceMethods.InvokeVirtualObjectMethod (id, this, null);
+ return JniEnvironment.Strings.ToString (ref r, JniObjectReferenceOptions.CopyAndDispose);
+ }
+ }
+}
diff --git a/tests/Java.Interop-Tests/Java.Interop/JavaVMFixture.cs b/tests/Java.Interop-Tests/Java.Interop/JavaVMFixture.cs
index bbb8c0c5e..ee3413795 100644
--- a/tests/Java.Interop-Tests/Java.Interop/JavaVMFixture.cs
+++ b/tests/Java.Interop-Tests/Java.Interop/JavaVMFixture.cs
@@ -41,9 +41,12 @@ class JavaVMFixtureTypeManager : JniRuntime.JniTypeManager {
[CallVirtualFromConstructorDerived.JniTypeName] = typeof (CallVirtualFromConstructorDerived),
[CrossReferenceBridge.JniTypeName] = typeof (CrossReferenceBridge),
[GetThis.JniTypeName] = typeof (GetThis),
+ [IAndroidInterface.JniTypeName] = typeof (IAndroidInterface),
+ [IJavaInterface.JniTypeName] = typeof (IJavaInterface),
[JavaDisposedObject.JniTypeName] = typeof (JavaDisposedObject),
[JavaObjectWithMissingJavaPeer.JniTypeName] = typeof (JavaObjectWithMissingJavaPeer),
[MyDisposableObject.JniTypeName] = typeof (JavaDisposedObject),
+ [MyJavaInterfaceImpl.JniTypeName] = typeof (MyJavaInterfaceImpl),
};
public JavaVMFixtureTypeManager ()
diff --git a/tests/Java.Interop-Tests/Java.Interop/JniPeerMembersTests.cs b/tests/Java.Interop-Tests/Java.Interop/JniPeerMembersTests.cs
index f225f86bc..ab40dc794 100644
--- a/tests/Java.Interop-Tests/Java.Interop/JniPeerMembersTests.cs
+++ b/tests/Java.Interop-Tests/Java.Interop/JniPeerMembersTests.cs
@@ -264,7 +264,7 @@ public override unsafe int hashCode ()
interface IAndroidInterface : IJavaPeerable {
internal const string JniTypeName = "net/dot/jni/test/AndroidInterface";
- private static JniPeerMembers _members = new JniPeerMembers (JniTypeName, typeof (IAndroidInterface), isInterface: true);
+ internal static JniPeerMembers _members = new JniPeerMembers (JniTypeName, typeof (IAndroidInterface), isInterface: true);
public static unsafe string getClassName ()
{
@@ -272,5 +272,16 @@ public static unsafe string getClassName ()
return JniEnvironment.Strings.ToString (ref s, JniObjectReferenceOptions.CopyAndDispose);
}
}
+
+ [JniTypeSignature (IAndroidInterface.JniTypeName, GenerateJavaPeer=false)]
+ internal class IAndroidInterfaceInvoker : JavaObject, IAndroidInterface {
+
+ public override JniPeerMembers JniPeerMembers => IAndroidInterface._members;
+
+ public IAndroidInterfaceInvoker (ref JniObjectReference reference, JniObjectReferenceOptions options)
+ : base (ref reference, options)
+ {
+ }
+ }
#endif // NET
}
diff --git a/tests/Java.Interop-Tests/java/net/dot/jni/test/JavaInterface.java b/tests/Java.Interop-Tests/java/net/dot/jni/test/JavaInterface.java
new file mode 100644
index 000000000..c1e55895a
--- /dev/null
+++ b/tests/Java.Interop-Tests/java/net/dot/jni/test/JavaInterface.java
@@ -0,0 +1,6 @@
+package net.dot.jni.test;
+
+public interface JavaInterface {
+
+ String getValue();
+}
diff --git a/tests/Java.Interop-Tests/java/net/dot/jni/test/MyJavaInterfaceImpl.java b/tests/Java.Interop-Tests/java/net/dot/jni/test/MyJavaInterfaceImpl.java
new file mode 100644
index 000000000..3667fa0b7
--- /dev/null
+++ b/tests/Java.Interop-Tests/java/net/dot/jni/test/MyJavaInterfaceImpl.java
@@ -0,0 +1,13 @@
+package net.dot.jni.test;
+
+public class MyJavaInterfaceImpl
+ implements JavaInterface, Cloneable
+{
+ public String getValue() {
+ return "Hello from Java!";
+ }
+
+ public Object clone() {
+ return this;
+ }
+}