Skip to content
Draft
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
Expand Up @@ -279,7 +279,7 @@ protected virtual bool TryConstructPeer (
return JavaPeerableValueMarshaler.Instance.CreateGenericValue (ref reference, options, targetType);
}
var marshaler = GetValueMarshaler (targetType);
return marshaler.CreateValue (ref reference, options, targetType);
return CreateMarshaledValue (ref reference, options, targetType, marshaler);
}

[return: MaybeNull]
Expand Down Expand Up @@ -319,8 +319,14 @@ protected virtual bool TryConstructPeer (
#pragma warning restore CS8600,CS8601 // Possible null reference assignment.
}

var marshaler = GetValueMarshaler<T> ();
return marshaler.CreateGenericValue (ref reference, options, targetType);
var marshaler = GetValueMarshaler (targetType);
var value = CreateMarshaledValue (ref reference, options, targetType, marshaler);
if (value == null) {
#pragma warning disable 8653
return default (T);
#pragma warning restore 8653
}
return (T) value;
}

object? PeekBoxedObject (JniObjectReference reference)
Expand Down Expand Up @@ -361,7 +367,7 @@ protected virtual bool TryConstructPeer (
return JavaPeerableValueMarshaler.Instance.CreateGenericValue (ref reference, options, targetType);
}
var marshaler = GetValueMarshaler (targetType);
return marshaler.CreateValue (ref reference, options, targetType);
return CreateMarshaledValue (ref reference, options, targetType, marshaler);
}


Expand Down Expand Up @@ -401,8 +407,74 @@ protected virtual bool TryConstructPeer (
#pragma warning restore CS8600,CS8601 // Possible null reference assignment.
}

var marshaler = GetValueMarshaler<T> ();
return marshaler.CreateGenericValue (ref reference, options, targetType);
var marshaler = GetValueMarshaler (targetType);
var value = CreateMarshaledValue (ref reference, options, targetType, marshaler);
if (value == null) {
#pragma warning disable 8653
return default (T);
#pragma warning restore 8653
}
return (T) value;
}

object? CreateMarshaledValue (
ref JniObjectReference reference,
JniObjectReferenceOptions options,
Type targetType,
JniValueMarshaler marshaler)
{
if (!IsPrimitiveArrayMarshaler (marshaler))
return marshaler.CreateValue (ref reference, options, targetType);

var jniType = JniEnvironment.Types.GetJniTypeNameFromInstance (reference);
if (IsMatchingPrimitiveArray (marshaler, jniType))
return marshaler.CreateValue (ref reference, options, targetType);

if (GetListType (targetType) != null && jniType != null && !jniType.StartsWith ("[", StringComparison.Ordinal)) {
if (JavaListType.IsInstanceOfType (reference))
return CreateNonArrayListValue (ref reference, options, targetType);
JniObjectReference.Dispose (ref reference, options);
throw new InvalidCastException ($"JNI reference of type '{jniType}' does not implement java.util.List required by managed type '{targetType}'.");
}

JniObjectReference.Dispose (ref reference, options);
throw new InvalidCastException ($"JNI reference of type '{jniType}' is not the primitive array required by managed type '{targetType}'.");
}

static JniType? javaListType;
static JniType JavaListType {
get {return JniType.GetCachedJniType (ref javaListType, "java/util/List");}
}

bool IsMatchingPrimitiveArray (JniValueMarshaler marshaler, string? jniType)
{
if (jniType == null || !JniTypeSignature.TryParse (jniType, out var actualType))
return false;

foreach (var candidate in JniPrimitiveArrayMarshalers.Value) {
if (!ReferenceEquals (candidate.Value, marshaler))
continue;
return Runtime.TypeManager.GetTypeSignature (candidate.Key) == actualType;
}
return false;
}

static bool IsPrimitiveArrayMarshaler (JniValueMarshaler marshaler)
{
foreach (var candidate in JniPrimitiveArrayMarshalers.Value) {
if (ReferenceEquals (candidate.Value, marshaler))
return true;
}
return false;
}

protected virtual object? CreateNonArrayListValue (
ref JniObjectReference reference,
JniObjectReferenceOptions options,
Type targetType)
{
JniObjectReference.Dispose (ref reference, options);
throw new InvalidCastException ($"JNI reference cannot be converted to managed list type '{targetType}'.");
}

Dictionary<Type, JniValueMarshaler> Marshalers = new Dictionary<Type, JniValueMarshaler> ();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,4 @@ override Java.Interop.JniRuntime.ReflectionJniTypeManager.GetTypesForSimpleRefer
override Java.Interop.JniRuntime.ReflectionJniTypeManager.RegisterNativeMembers(Java.Interop.JniType! nativeClass, System.Type! type, string? methods) -> void
override Java.Interop.JniRuntime.ReflectionJniTypeManager.RegisterNativeMembers(Java.Interop.JniType! nativeClass, System.Type! type, System.ReadOnlySpan<char> methods) -> void
virtual Java.Interop.JniRuntime.ReflectionJniValueManager.TryConstructPeer(Java.Interop.IJavaPeerable! self, ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions options, System.Type! type) -> bool
virtual Java.Interop.JniRuntime.ReflectionJniValueManager.CreateNonArrayListValue(ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions options, System.Type! targetType) -> object?
8 changes: 8 additions & 0 deletions src/Mono.Android/Android.Runtime/AndroidRuntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,14 @@ public override void WaitForGCBridgeProcessing ()
return peer;
}

protected override object? CreateNonArrayListValue (
ref JniObjectReference reference,
JniObjectReferenceOptions options,
Type targetType)
{
return JavaConvert.FromObjectReference (ref reference, options, targetType);
}

public override void AddPeer (IJavaPeerable value)
{
if (value == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ public override List<JniSurfacedPeerInfo> GetSurfacedPeers ()
return JavaMarshalRegisteredPeers.GetSurfacedPeers ();
}

protected override object? CreateNonArrayListValue (
ref JniObjectReference reference,
JniObjectReferenceOptions options,
Type targetType)
{
return JavaConvert.FromObjectReference (ref reference, options, targetType);
}

protected override bool TryConstructPeer (
IJavaPeerable self,
ref JniObjectReference reference,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,16 @@ public override bool TryCreateWrapper (
return false;
}

var jniType = JniEnvironment.Types.GetJniTypeNameFromInstance (reference);
if (jniType != "[" + jniSimpleReference) {
if (IsCompatibleListType (targetType) && JavaListType.IsInstanceOfType (reference)) {
value = null;
return false;
}
JniObjectReference.Dispose (ref reference, options);
throw new InvalidCastException ($"JNI reference of type '{jniType}' is not the primitive array required by managed type '{targetType}'.");
}

var array = createFromReference (ref reference, options);
if (targetType == typeof (T[]) || IsCompatibleListType (targetType)) {
try {
Expand Down Expand Up @@ -170,6 +180,9 @@ static bool IsCompatibleListType (Type targetType)
list => new JavaDoubleArray (list)),
];

static JniType? javaListType;
static JniType JavaListType => JniType.GetCachedJniType (ref javaListType, "java/util/List");

public static bool TryGetArrayTypes (Type elementType, [NotNullWhen (true)] out Type[]? arrayTypes)
{
foreach (var handler in Handlers) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,60 @@ public void MarshalInt23Array ()
}
}

[Test]
public void ValueManagerConvertsJavaListToPrimitiveIList ()
{
using (var values = new Java.Util.ArrayList ())
using (var first = new Java.Lang.Boolean (true))
using (var second = new Java.Lang.Boolean (false)) {
values.Add (first);
values.Add (second);

var reference = values.PeerReference;
var converted = JniEnvironment.Runtime.ValueManager.GetValue<IList<bool>> (ref reference, JniObjectReferenceOptions.Copy);

CollectionAssert.AreEqual (new [] { true, false }, converted);
}
}

[Test]
public void ValueManagerConvertsPrimitiveArrayToIList ()
{
var reference = new JniObjectReference (JNIEnv.NewArray (new [] { true, false }), JniObjectReferenceType.Local);
var converted = JniEnvironment.Runtime.ValueManager.GetValue<IList<bool>> (ref reference, JniObjectReferenceOptions.CopyAndDispose);

CollectionAssert.AreEqual (new [] { true, false }, converted);
}

[TestCase (JniObjectReferenceOptions.Copy, true)]
[TestCase (JniObjectReferenceOptions.CopyAndDispose, false)]
public void ValueManagerRejectsNonListForPrimitiveIListAndHonorsOwnership (
JniObjectReferenceOptions options,
bool remainsValid)
{
var reference = new JniObjectReference (JNIEnv.NewString ("not a list"), JniObjectReferenceType.Local);
try {
Assert.Throws<InvalidCastException> (() =>
JniEnvironment.Runtime.ValueManager.GetValue<IList<bool>> (ref reference, options));
Assert.AreEqual (remainsValid, reference.IsValid);
} finally {
JniObjectReference.Dispose (ref reference);
}
}

[Test]
public void ValueManagerRejectsMismatchedPrimitiveArrayAndDisposesReference ()
{
var reference = new JniObjectReference (JNIEnv.NewArray (new [] { 1, 2 }), JniObjectReferenceType.Local);
try {
Assert.Throws<InvalidCastException> (() =>
JniEnvironment.Runtime.ValueManager.GetValue<IList<bool>> (ref reference, JniObjectReferenceOptions.CopyAndDispose));
Assert.IsFalse (reference.IsValid);
} finally {
JniObjectReference.Dispose (ref reference);
}
}

static Java.Util.ArrayList CreateList (params int[][] items)
{
var list = new Java.Util.ArrayList ();
Expand All @@ -139,5 +193,5 @@ static Java.Util.ArrayList CreateList (params int[][] items)
return list;
}
}
}

}
Loading