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
32 changes: 32 additions & 0 deletions src/Mono.Android/Android.Runtime/AndroidRuntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,38 @@ class AndroidValueManager : JniRuntime.ReflectionJniValueManager {

Dictionary<IntPtr, IdentityHashTargets> instances = new Dictionary<IntPtr, IdentityHashTargets> ();

protected override object? GetValueCore (
ref JniObjectReference reference,
JniObjectReferenceOptions options,
[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
Type? targetType = null)
{
if (JavaConvert.IsGenericDictionary (targetType))
return JavaConvert.FromObjectReference (ref reference, options, targetType);

return base.GetValueCore (ref reference, options, targetType);
}

[return: MaybeNull]
protected override T GetValueCore<[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] T> (
ref JniObjectReference reference,
JniObjectReferenceOptions options,
[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
Type? targetType = null)
{
if (targetType != null && !typeof (T).IsAssignableFrom (targetType))
return base.GetValueCore<T> (ref reference, options, targetType);

var requestedType = targetType ?? typeof (T);
if (JavaConvert.IsGenericDictionary (requestedType)) {
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
return (T) JavaConvert.FromObjectReference (ref reference, options, requestedType);
#pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type.
}

return base.GetValueCore<T> (ref reference, options, targetType);
}

public override void WaitForGCBridgeProcessing ()
{
if (!AndroidRuntimeInternal.BridgeProcessing)
Expand Down
13 changes: 12 additions & 1 deletion src/Mono.Android/Java.Interop/JavaConvert.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ static class JavaConvert {
[RequiresDynamicCode ("This API uses reflection to create generic types at runtime, which is not supported in AOT scenarios.")]
static Func<IntPtr, JniHandleOwnership, object?>? TryMakeGenericCollectionTypeFactory (Type target)
{
if (target.GetGenericTypeDefinition() == typeof (IDictionary<,>)) {
var genericTypeDefinition = target.GetGenericTypeDefinition ();
if (genericTypeDefinition == typeof (IDictionary<,>) || genericTypeDefinition == typeof (JavaDictionary<,>)) {
Type t = typeof (JavaDictionary<,>).MakeGenericType (target.GetGenericArguments ());
return GetJniHandleConverterForType (t);
}
Expand All @@ -141,6 +142,16 @@ static class JavaConvert {
}
}

internal static bool IsGenericDictionary (Type? type)
{
if (type == null || !type.IsGenericType || type.IsGenericTypeDefinition)
return false;

var genericTypeDefinition = type.GetGenericTypeDefinition ();
return genericTypeDefinition == typeof (IDictionary<,>) ||
genericTypeDefinition == typeof (JavaDictionary<,>);
}

/// <summary>
/// AOT-safe converter using <see cref="JavaPeerContainerFactory"/> from the generated proxy.
/// Avoids <c>MakeGenericType()</c> by using the pre-typed factory from the proxy attribute.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,38 @@ public JavaMarshalValueManager ()
JavaMarshalRegisteredPeers.InitializeIfNeeded ();
}

protected override object? GetValueCore (
ref JniObjectReference reference,
JniObjectReferenceOptions options,
[DynamicallyAccessedMembers (Constructors)]
Type? targetType = null)
{
if (JavaConvert.IsGenericDictionary (targetType))
return JavaConvert.FromObjectReference (ref reference, options, targetType);

return base.GetValueCore (ref reference, options, targetType);
}

[return: MaybeNull]
protected override T GetValueCore<[DynamicallyAccessedMembers (Constructors)] T> (
ref JniObjectReference reference,
JniObjectReferenceOptions options,
[DynamicallyAccessedMembers (Constructors)]
Type? targetType = null)
{
if (targetType != null && !typeof (T).IsAssignableFrom (targetType))
return base.GetValueCore<T> (ref reference, options, targetType);

var requestedType = targetType ?? typeof (T);
if (JavaConvert.IsGenericDictionary (requestedType)) {
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
return (T) JavaConvert.FromObjectReference (ref reference, options, requestedType);
#pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type.
}

return base.GetValueCore<T> (ref reference, options, targetType);
}

public override void WaitForGCBridgeProcessing ()
{
// Intentionally empty. The Mono runtime's own implementation acknowledges this
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

using Android.App;
using Android.Content;
using Android.Runtime;
using Java.Interop;

using NUnit.Framework;

Expand Down Expand Up @@ -113,6 +115,81 @@ public void NullStringMarshalsAsIntPtrZero ()
Assert.AreEqual (null, list [0]);
}

[Test]
public void ValueManagerGenericDictionaryConversions ()
{
AssertValueManagerDictionaryByType<int, string> (42, "forty-two", typeof (IDictionary<int, string>));
AssertValueManagerDictionaryByType<int?, double?> (7, 3.5, typeof (JavaDictionary<int?, double?>));

using (var value = new MyIntent ())
AssertValueManagerDictionary<string, MyIntent> ("intent", value);

using (var key = new MyIntent ())
AssertValueManagerJavaDictionary<MyIntent, long?> (key, 64);
}

[Test]
public void ValueManagerNonGenericDictionaryConversionPreservesCachedPeer ()
{
using (var source = new JavaDictionary ()) {
source.Add ("answer", 42);
var reference = new JniObjectReference (source.Handle);
var actual = JniEnvironment.Runtime.ValueManager.GetValue (
ref reference, JniObjectReferenceOptions.Copy, typeof (IDictionary));

Assert.AreSame (source, actual);
Assert.AreEqual (42, ((IDictionary) actual) ["answer"]);
}
}

[Test]
public void ValueManagerGenericDictionaryConversionPreservesAssignableCachedPeer ()
{
using (var source = new JavaDictionary<int, string> { { 42, "forty-two" } }) {
var actual = JniEnvironment.Runtime.ValueManager.GetValue<JavaDictionary<int, string>> (source.Handle);

Assert.AreSame (source, actual);
}
}

static void AssertValueManagerDictionaryByType<TKey, TValue> (TKey key, TValue value, Type targetType)
{
using (var source = new JavaDictionary ()) {
source.Add (key, value);
var reference = new JniObjectReference (source.Handle);
var actual = JniEnvironment.Runtime.ValueManager.GetValue (
ref reference, JniObjectReferenceOptions.Copy, targetType);

Assert.AreEqual (typeof (JavaDictionary<TKey, TValue>), actual.GetType ());
using (var dictionary = (JavaDictionary<TKey, TValue>) actual)
Assert.AreEqual (value, dictionary [key]);
}
}

static void AssertValueManagerDictionary<TKey, TValue> (TKey key, TValue value)
{
using (var source = new JavaDictionary ()) {
source.Add (key, value);
var actual = JniEnvironment.Runtime.ValueManager.GetValue<IDictionary<TKey, TValue>> (source.Handle);

Assert.AreEqual (typeof (JavaDictionary<TKey, TValue>), actual.GetType ());
using (var dictionary = (JavaDictionary<TKey, TValue>) actual)
Assert.AreEqual (value, dictionary [key]);
}
}

static void AssertValueManagerJavaDictionary<TKey, TValue> (TKey key, TValue value)
{
using (var source = new JavaDictionary ()) {
source.Add (key, value);
var actual = JniEnvironment.Runtime.ValueManager.GetValue<JavaDictionary<TKey, TValue>> (source.Handle);

Assert.AreEqual (typeof (JavaDictionary<TKey, TValue>), actual.GetType ());
using (actual)
Assert.AreEqual (value, actual [key]);
}
}

[Test]
public void MarshalInt23Array ()
{
Expand Down Expand Up @@ -140,4 +217,3 @@ static Java.Util.ArrayList CreateList (params int[][] items)
}
}
}

Loading