Skip to content
Open
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
11 changes: 3 additions & 8 deletions src/Mono.Android/Android.Runtime/JavaCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,6 @@ internal Java.Lang.Object[] ToArray ()
//
public void CopyTo (Array array, int array_index)
{
[UnconditionalSuppressMessage ("Trimming", "IL2073", Justification = "JavaCollection<T> constructors are preserved by the MarkJavaObjects trimmer step.")]
[return: DynamicallyAccessedMembers (Constructors)]
static Type GetElementType (Array array) =>
array.GetType ().GetElementType ();

if (array == null)
throw new ArgumentNullException ("array");
if (array_index < 0)
Expand All @@ -163,13 +158,13 @@ static Type GetElementType (Array array) =>
if (id_toArray == IntPtr.Zero)
id_toArray = JNIEnv.GetMethodID (collection_class, "toArray", "()[Ljava/lang/Object;");

var converter = new JavaConvert.ArrayElementConverter (array);
IntPtr lrefArray = JNIEnv.CallObjectMethod (Handle, id_toArray);
for (int i = 0; i < Count; i++)
array.SetValue (
JavaConvert.FromJniHandle (
converter.FromJniHandle (
JNIEnv.GetObjectArrayElement (lrefArray, i),
JniHandleOwnership.TransferLocalRef,
GetElementType (array)),
JniHandleOwnership.TransferLocalRef),
array_index + i);
JNIEnv.DeleteLocalRef (lrefArray);
}
Expand Down
48 changes: 19 additions & 29 deletions src/Mono.Android/Android.Runtime/JavaList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,29 @@ public partial class JavaList : Java.Lang.Object, System.Collections.IList {
//
// https://developer.android.com/reference/java/util/List.html?hl=en#get(int)
//
internal unsafe object? InternalGet (
internal object? InternalGet (
int location,
[DynamicallyAccessedMembers (Constructors)]
Type? targetType = null)
{
var obj = InternalGetReference (location);
return JavaConvert.FromJniHandle (
obj.Handle,
JniHandleOwnership.TransferLocalRef,
targetType);
}

internal unsafe JniObjectReference InternalGetReference (int location)
{
const string id = "get.(I)Ljava/lang/Object;";
JniObjectReference obj;
try {
JniArgumentValue* parameters = stackalloc JniArgumentValue [1] {
new JniArgumentValue (location),
};
obj = list_members.InstanceMethods.InvokeAbstractObjectMethod (id, this, parameters);
return list_members.InstanceMethods.InvokeAbstractObjectMethod (id, this, parameters);
} catch (Java.Lang.IndexOutOfBoundsException ex) when (JNIEnv.ShouldWrapJavaException (ex)) {
throw new ArgumentOutOfRangeException (ex.Message, ex);
}

return JavaConvert.FromJniHandle (
obj.Handle,
JniHandleOwnership.TransferLocalRef,
targetType);
}

//
Expand Down Expand Up @@ -267,24 +270,21 @@ public unsafe bool Contains (object? item)
}
}

public void CopyTo (Array array, int array_index)
public unsafe void CopyTo (Array array, int array_index)
Comment thread
simonrozsival marked this conversation as resolved.
{
[UnconditionalSuppressMessage ("Trimming", "IL2073", Justification = "JavaList<T> constructors are preserved by the MarkJavaObjects trimmer step.")]
[return: DynamicallyAccessedMembers (Constructors)]
static Type GetElementType (Array array) =>
array.GetType ().GetElementType ();

if (array == null)
throw new ArgumentNullException ("array");
if (array_index < 0)
throw new ArgumentOutOfRangeException ("array_index");
if (array.Length < array_index + Count)
throw new ArgumentException ("array");

var targetType = GetElementType (array);
var converter = new JavaConvert.ArrayElementConverter (array);
int c = Count;
for (int i = 0; i < c; i++)
array.SetValue (InternalGet (i, targetType), array_index + i);
for (int i = 0; i < c; i++) {
var obj = InternalGetReference (i);
array.SetValue (converter.FromJniHandle (obj.Handle, JniHandleOwnership.TransferLocalRef), array_index + i);
}
}

public IEnumerator GetEnumerator ()
Expand Down Expand Up @@ -737,19 +737,9 @@ public JavaList (IEnumerable<T> items) : this ()
//
// https://developer.android.com/reference/java/util/List.html?hl=en#get(int)
//
internal unsafe T? InternalGet (int location)
internal T? InternalGet (int location)
{
const string id = "get.(I)Ljava/lang/Object;";
JniObjectReference obj;
try {
JniArgumentValue* parameters = stackalloc JniArgumentValue [1] {
new JniArgumentValue (location),
};
obj = list_members.InstanceMethods.InvokeAbstractObjectMethod (id, this, parameters);
} catch (Java.Lang.IndexOutOfBoundsException ex) when (JNIEnv.ShouldWrapJavaException (ex)) {
throw new ArgumentOutOfRangeException (ex.Message, ex);
}

var obj = InternalGetReference (location);
return JavaConvert.FromJniHandle<T> (
obj.Handle,
JniHandleOwnership.TransferLocalRef);
Expand Down
87 changes: 86 additions & 1 deletion src/Mono.Android/Java.Interop/JavaConvert.cs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,92 @@ static Func<IntPtr, JniHandleOwnership, object> GetJniHandleConverterForType ([D
typeof (Func<IntPtr, JniHandleOwnership, object>), m);
}

internal readonly struct ArrayElementConverter
{
readonly Type? elementType;
readonly Func<IntPtr, JniHandleOwnership, object>? converter;
readonly bool useRuntimeTypeMapping;

public ArrayElementConverter (Array array)
{
elementType = array.GetType ().GetElementType ();
converter = elementType != null ? GetJniHandleConverter (elementType) : null;
useRuntimeTypeMapping = elementType is null || elementType == typeof (object);
}

public object? FromJniHandle (IntPtr handle, JniHandleOwnership transfer)
{
if (handle == IntPtr.Zero)
return null;

if (useRuntimeTypeMapping)
return FromJniHandleWithRuntimeTypeMapping (handle, transfer);

if (elementType != null) {
var peeked = Java.Lang.Object.PeekObject (handle, elementType);
if (peeked != null) {
JNIEnv.DeleteRef (handle, transfer);
return peeked;
}
}

if (converter != null)
return converter (handle, transfer);

if (elementType != null && elementType.IsArray)
return JNIEnv.GetArray (handle, transfer, elementType.GetElementType ());

if (elementType != null && typeof (IJavaPeerable).IsAssignableFrom (elementType)) {
if (RuntimeFeature.TrimmableTypeMap)
return FromJniHandleWithTrimmableTypeMapping (handle, transfer, elementType);
return Java.Lang.Object.GetObject (handle, transfer, elementType);
}

var value = FromJniHandleWithRuntimeTypeMapping (handle, transfer);
if (value == null || elementType == null || elementType.IsAssignableFrom (value.GetType ()))
return value;
return Convert.ChangeType (value, elementType, CultureInfo.InvariantCulture);
}
}

static object? FromJniHandleWithRuntimeTypeMapping (IntPtr handle, JniHandleOwnership transfer)
{
var converter = GetJniHandleConverter (GetTypeMapping (handle));
if (converter != null)
return converter (handle, transfer);
return FromJniHandle (handle, transfer);
}

static object? FromJniHandleWithTrimmableTypeMapping (IntPtr handle, JniHandleOwnership transfer, Type elementType)
{
bool consumed = false;
try {
if (elementType.IsGenericType) {
throw new NotSupportedException (
FormattableString.Invariant ($"Cannot convert Java collection elements to closed generic array element type '{elementType}'."));
}

// This path intentionally avoids the reflection fallback used by TrimmableTypeMap.CreateInstance ()
// because passing array element types there would require DAM annotations. Closed generic element
// types cannot be supported without that fallback: creating a non-generic base peer would not be
// assignable to the requested closed generic array element type. If the requested element type is
// already a non-generic base type, the typemap lookup can still select that base mapping.
var peer = TrimmableTypeMap.Instance.CreateInstanceWithoutReflectionFallback (handle, elementType);
if (peer != null) {
consumed = true;
JNIEnv.DeleteRef (handle, transfer);
return peer;
}

throw new NotSupportedException (
Comment thread
simonrozsival marked this conversation as resolved.
FormattableString.Invariant ($"Cannot convert Java collection element to array element type '{elementType}' using the trimmable type map."));
} finally {
if (!consumed) {
JNIEnv.DeleteRef (handle, transfer);
}
Comment thread
simonrozsival marked this conversation as resolved.
}
}

public static T? FromJniHandle<
[DynamicallyAccessedMembers (Constructors)]
T
Expand Down Expand Up @@ -498,4 +584,3 @@ public static TReturn WithLocalJniHandle<TReturn>(object? value, Func<IntPtr, TR
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -493,21 +493,8 @@ void ProcessContext (HandleContext* context)
var resolvedTargetType = ResolvePeerType (targetType);

var typeMap = TrimmableTypeMap.Instance;
var proxy = typeMap.GetProxyForJavaObject (reference.Handle, resolvedTargetType);

// Open-generic proxies cannot instantiate closed targets.
IJavaPeerable? peer;
if (ShouldActivateClosedGenericTarget (proxy, resolvedTargetType)) {
peer = ActivateUsingReflection (resolvedTargetType, reference.Handle, JniHandleOwnership.DoNotTransfer);
} else {
peer = proxy?.CreateInstance (reference.Handle, JniHandleOwnership.DoNotTransfer);
}
var peer = typeMap.CreateInstance (reference.Handle, resolvedTargetType);
if (peer is not null) {
var peerState = peer.JniManagedPeerState | JniManagedPeerStates.Replaceable;
if (global::Java.Interop.Runtime.IsGCUserPeer (peer.PeerReference.Handle)) {
peerState |= JniManagedPeerStates.Activatable;
}
peer.SetJniManagedPeerState (peerState);
return peer;
}

Expand Down Expand Up @@ -556,31 +543,6 @@ void ProcessContext (HandleContext* context)
return type;
}

static bool ShouldActivateClosedGenericTarget (
[NotNullWhen (true)] JavaPeerProxy? proxy,
[NotNullWhen (true)] Type? resolvedTargetType)
{
return proxy is not null &&
proxy.TargetType.IsGenericTypeDefinition &&
resolvedTargetType is not null &&
resolvedTargetType.IsGenericType &&
!resolvedTargetType.IsGenericTypeDefinition;
}

static IJavaPeerable? ActivateUsingReflection (
[DynamicallyAccessedMembers (Constructors)]
Type closedType,
IntPtr handle,
JniHandleOwnership transfer)
{
var ctor = closedType.GetConstructor (ActivationConstructorBindingFlags, null, XAConstructorSignature, null);
if (ctor is null) {
return null;
}

return (IJavaPeerable) ctor.Invoke ([handle, transfer]);
}

/// <summary>
/// Returns true when <paramref name="targetType"/>'s Java class is not assignable from
/// <paramref name="reference"/>. Throws when <paramref name="targetType"/> has no usable mapping.
Expand Down
66 changes: 66 additions & 0 deletions src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -383,8 +383,74 @@ static JniMethodInfo GetClassGetInterfacesMethod ()
}
}

internal IJavaPeerable? CreateInstance (
IntPtr handle,
[DynamicallyAccessedMembers (Constructors)]
Type? targetType = null)
{
var proxy = GetProxyForJavaObject (handle, targetType);

IJavaPeerable? peer;
if (ShouldActivateClosedGenericTarget (proxy, targetType)) {
peer = ActivateUsingReflection (targetType, handle, JniHandleOwnership.DoNotTransfer);
} else {
peer = proxy?.CreateInstance (handle, JniHandleOwnership.DoNotTransfer);
}
if (peer is not null) {
MarkCreatedPeer (peer);
}
return peer;
}

internal IJavaPeerable? CreateInstanceWithoutReflectionFallback (IntPtr handle, Type? targetType = null)
{
var peer = GetProxyForJavaObject (handle, targetType)?.CreateInstance (handle, JniHandleOwnership.DoNotTransfer);
if (peer is not null) {
MarkCreatedPeer (peer);
}
return peer;
}

const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors;

const BindingFlags ActivationConstructorBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;

static readonly Type[] XAConstructorSignature = new Type [] { typeof (IntPtr), typeof (JniHandleOwnership) };

static bool ShouldActivateClosedGenericTarget (
[NotNullWhen (true)] JavaPeerProxy? proxy,
[NotNullWhen (true)] Type? targetType)
{
return proxy is not null &&
proxy.TargetType.IsGenericTypeDefinition &&
targetType is not null &&
targetType.IsGenericType &&
!targetType.IsGenericTypeDefinition;
}

static IJavaPeerable? ActivateUsingReflection (
[DynamicallyAccessedMembers (Constructors)]
Type closedType,
IntPtr handle,
JniHandleOwnership transfer)
{
var ctor = closedType.GetConstructor (ActivationConstructorBindingFlags, null, XAConstructorSignature, null);
if (ctor is null) {
return null;
}

return (IJavaPeerable) ctor.Invoke ([handle, transfer]);
}

static void MarkCreatedPeer (IJavaPeerable peer)
{
var peerState = peer.JniManagedPeerState | JniManagedPeerStates.Replaceable;
if (global::Java.Interop.Runtime.IsGCUserPeer (peer.PeerReference.Handle)) {
peerState |= JniManagedPeerStates.Activatable;
}
peer.SetJniManagedPeerState (peerState);
}

/// <summary>
/// Match the proxy's stored target type against a hint from the caller.
/// The proxy's target type is the open generic definition for generic peers
Expand Down
2 changes: 1 addition & 1 deletion src/Mono.Android/metadata
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@
<attr path="/api/package[@name='android.widget']/class[@name='TextView']/method[@name='getShadowColor']" name="return" api-since="16">Android.Graphics.Color</attr>

<attr api-since="36.1" path="/api/package[@name='android.graphics.pdf.component']/*/method[
starts-with(@jni-signature='(I)') and (
starts-with(@jni-signature, '(I)') and (
@name='setTextColor'
or @name='setBackgroundColor'
or @name='setColor'
Expand Down
Loading