diff --git a/src/Java.Interop/GlobalSuppressions.cs b/src/Java.Interop/GlobalSuppressions.cs index e5cc3b533..14fb85d20 100644 --- a/src/Java.Interop/GlobalSuppressions.cs +++ b/src/Java.Interop/GlobalSuppressions.cs @@ -14,6 +14,8 @@ [assembly: SuppressMessage ("Design", "CA1024:Use properties where appropriate", Justification = "", Scope = "member", Target = "~M:Java.Interop.JniRuntime.GetRegisteredRuntimes()")] +[assembly: SuppressMessage ("Design", "CA1031:Do not catch general exception types", Justification = "Excceptions are bundled into an AggregateException and rethrown", Scope = "type", Target = "~M:Java.Interop.JniPeerRegistrationScope.Dispose")] + [assembly: SuppressMessage ("Design", "CA1032:Implement standard exception constructors", Justification = "System.Runtime.Serialization.SerializationInfo doesn't exist in our targeted PCL profile, so we can't provide the (SerializationInfo, StreamingContext) constructor.", Scope = "type", Target = "~T:Java.Interop.JavaProxyThrowable")] [assembly: SuppressMessage ("Design", "CA1032:Implement standard exception constructors", Justification = "System.Runtime.Serialization.SerializationInfo doesn't exist in our targeted PCL profile, so we can't provide the (SerializationInfo, StreamingContext) constructor.", Scope = "type", Target = "~T:Java.Interop.JniLocationException")] diff --git a/src/Java.Interop/Java.Interop/JavaProxyObject.cs b/src/Java.Interop/Java.Interop/JavaProxyObject.cs index 5454185b0..d247dbb83 100644 --- a/src/Java.Interop/Java.Interop/JavaProxyObject.cs +++ b/src/Java.Interop/Java.Interop/JavaProxyObject.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; @@ -28,10 +29,8 @@ public override JniPeerMembers JniPeerMembers { } } - JavaProxyObject (object value) + internal JavaProxyObject (object value) { - if (value == null) - throw new ArgumentNullException (nameof (value)); Value = value; } @@ -56,18 +55,11 @@ public override bool Equals (object? obj) return Value.ToString (); } - [return: NotNullIfNotNull ("object")] - public static JavaProxyObject? GetProxy (object value) + protected override void Dispose (bool disposing) { - if (value == null) - return null; - - lock (CachedValues) { - if (CachedValues.TryGetValue (value, out var proxy)) - return proxy; - proxy = new JavaProxyObject (value); - CachedValues.Add (value, proxy); - return proxy; + base.Dispose (disposing); + if (disposing) { + CachedValues.Remove (Value); } } diff --git a/src/Java.Interop/Java.Interop/JniEnvironment.cs b/src/Java.Interop/Java.Interop/JniEnvironment.cs index a577ecfdd..0221e55a9 100644 --- a/src/Java.Interop/Java.Interop/JniEnvironment.cs +++ b/src/Java.Interop/Java.Interop/JniEnvironment.cs @@ -2,10 +2,12 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Text; using System.Threading; namespace Java.Interop { @@ -189,6 +191,8 @@ sealed class JniEnvironmentInfo : IDisposable { bool disposed; JniRuntime? runtime; + List? scopes; + public int LocalReferenceCount {get; internal set;} public bool WithinNewObjectScope {get; set;} public JniRuntime Runtime { @@ -229,6 +233,12 @@ public bool IsValid { get {return Runtime != null && environmentPointer != IntPtr.Zero;} } + public List? + Scopes => scopes; + + public PeerableCollection? CurrentScope => + scopes == null ? null : scopes [scopes.Count-1]; + public JniEnvironmentInfo () { Runtime = JniRuntime.CurrentRuntime; @@ -279,6 +289,29 @@ public void Dispose () disposed = true; } + public PeerableCollection BeginScope (JniPeerRegistrationScopeCleanup cleanup) + { + scopes = scopes ?? new List (); + var scope = new PeerableCollection () { + Cleanup = cleanup, + }; + scopes.Add (scope); + return scope; + } + + public void EndScope (PeerableCollection scope) + { + Debug.Assert (scopes != null); + if (scopes == null) { + return; + } + Debug.Assert (scopes.Count > 0); + scopes.Remove (scope); + if (scopes.Count == 0) { + scopes = null; + } + } + #if FEATURE_JNIENVIRONMENT_SAFEHANDLES internal List> LocalReferences = new List> () { new List (), @@ -295,5 +328,43 @@ static unsafe JniEnvironmentInvoker CreateInvoker (IntPtr handle) } #endif // !FEATURE_JNIENVIRONMENT_JI_PINVOKES } + + class PeerableCollection : KeyedCollection { + + public JniPeerRegistrationScopeCleanup Cleanup { get; set; } + + protected override int GetKeyForItem (IJavaPeerable item) => item.JniIdentityHashCode; + + public IJavaPeerable? GetPeerableForObjectReference (JniObjectReference reference) + { +#if NETCOREAPP + if (TryGetValue (JniSystem.IdentityHashCode (reference), out var p) && + JniEnvironment.Types.IsSameObject (reference, p.PeerReference)) { + return p; + } +#else // !NETCOREAPP + Collection c = this; + for (int x = 0; x < c.Count; ++x) { + if (JniEnvironment.Types.IsSameObject (reference, c [x].PeerReference)) { + return c [x]; + } + } +#endif // !NETCOREAPP + return null; + } + + public override string ToString () + { + var c = (Collection) this; + var s = new StringBuilder (); + s.Append ("PeerableCollection[").Append (Count).Append ("]"); + for (int i = 0; i < Count; ++i ) { + s.AppendLine (); + var e = c [i]; + s.Append ($" [{i}] hash={e.JniIdentityHashCode} ref={e.PeerReference} type={e.GetType ().ToString ()} value=`{e.ToString ()}`"); + } + return s.ToString (); + } + } } diff --git a/src/Java.Interop/Java.Interop/JniPeerRegistrationScope.cs b/src/Java.Interop/Java.Interop/JniPeerRegistrationScope.cs new file mode 100644 index 000000000..0f9bce2c3 --- /dev/null +++ b/src/Java.Interop/Java.Interop/JniPeerRegistrationScope.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Java.Interop { + + public enum JniPeerRegistrationScopeCleanup { + RegisterWithManager, + Dispose, + Release, + } + + public ref struct JniPeerRegistrationScope { + + JniPeerRegistrationScopeCleanup? cleanup; + PeerableCollection? scope; + + public JniPeerRegistrationScope (JniPeerRegistrationScopeCleanup cleanup) + { + this.cleanup = cleanup; + scope = JniEnvironment.CurrentInfo.BeginScope (cleanup); + } + + public void Dispose () + { + if (cleanup == null || scope == null) { + return; + } + List? exceptions = null; + switch (cleanup) { + case JniPeerRegistrationScopeCleanup.Dispose: + // Need to iterate over a copy of `scope`, as `p.Dispose()` will modify `scope` + var copy = new IJavaPeerable [scope.Count]; + scope.CopyTo (copy, 0); + foreach (var p in copy) { + try { + p.Dispose (); + } + catch (Exception e) { + exceptions = exceptions ?? new List(); + exceptions.Add (e); + Trace.WriteLine (e); + } + } + break; + } + JniEnvironment.CurrentInfo.EndScope (scope); + scope.Clear (); + scope = null; + if (exceptions != null) { + throw new AggregateException (exceptions); + } + } + } +} diff --git a/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs b/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs index 03ac65994..1c5ef7f90 100644 --- a/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs +++ b/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs @@ -2,12 +2,14 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Text; using System.Threading; using Java.Interop.Expressions; @@ -32,7 +34,7 @@ partial class CreationOptions { public JniValueManager? ValueManager {get; set;} } - JniValueManager? valueManager; + internal JniValueManager? valueManager; public JniValueManager ValueManager { get => valueManager ?? throw new NotSupportedException (); } @@ -49,8 +51,11 @@ partial void SetValueManager (CreationOptions options) public abstract partial class JniValueManager : ISetRuntime, IDisposable { + readonly ConditionalWeakTable cachedValues = new ConditionalWeakTable (); + JniRuntime? runtime; bool disposed; + public JniRuntime Runtime { get => runtime ?? throw new NotSupportedException (); } @@ -76,20 +81,178 @@ protected virtual void Dispose (bool disposing) public abstract void WaitForGCBridgeProcessing (); - public abstract void CollectPeers (); + public void CollectPeers () + { + if (disposed) { + throw new ObjectDisposedException (this.GetType ().ToString ()); + } + CollectPeersCore (); + } + + protected abstract void CollectPeersCore (); + + public void AddPeer (IJavaPeerable value) + { + if (disposed) { + throw new ObjectDisposedException (this.GetType ().ToString ()); + } + if (value == null) { + throw new ArgumentNullException (nameof (value)); + } + + var r = value.PeerReference; + if (!r.IsValid) { + throw new ObjectDisposedException (value.GetType ().FullName); + } + + var o = PeekPeer (value.PeerReference); + if (o != null) + return; + + if (r.Type != JniObjectReferenceType.Global) { + value.SetPeerReference (r.NewGlobalRef ()); + JniObjectReference.Dispose (ref r, JniObjectReferenceOptions.CopyAndDispose); + } + + var scope = JniEnvironment.CurrentInfo.CurrentScope; + + if (scope != null) { + scope.Add (value); + } else { + AddPeerToManager (value); + } + } + + protected abstract void AddPeerToManager (IJavaPeerable value); + + public void RemovePeer (IJavaPeerable value) + { + if (disposed) { + throw new ObjectDisposedException (this.GetType ().ToString ()); + } + if (value == null) { + throw new ArgumentNullException (nameof (value)); + } + + var scope = JniEnvironment.CurrentInfo.CurrentScope; + if (scope != null && scope.Cleanup != JniPeerRegistrationScopeCleanup.RegisterWithManager) { + scope.Remove (value); + } else { + RemovePeerFromManager (value); + } + } + + protected abstract void RemovePeerFromManager (IJavaPeerable value); - public abstract void AddPeer (IJavaPeerable value); + public void FinalizePeer (IJavaPeerable value) + { + if (disposed) { + throw new ObjectDisposedException (this.GetType ().ToString ()); + } + if (value == null) { + throw new ArgumentNullException (nameof (value)); + } - public abstract void RemovePeer (IJavaPeerable value); + if (!ShouldFinalizePeer (value)) { + GC.ReRegisterForFinalize (value); + return; + } + var h = value.PeerReference; + var o = Runtime.ObjectReferenceManager; + if (o.LogGlobalReferenceMessages) { + o.WriteGlobalReferenceLine ("Finalizing PeerReference={0} IdentityHashCode=0x{1} Instance=0x{2} Instance.Type={3}", + h.ToString (), + value.JniIdentityHashCode.ToString ("x"), + RuntimeHelpers.GetHashCode (value).ToString ("x"), + value.GetType ().ToString ()); + } + RemovePeer (value); + value.SetPeerReference (new JniObjectReference ()); + value.Finalized (); + + // MUST NOT use SafeHandle.ReferenceType: local refs are tied to a JniEnvironment + // and the JniEnvironment's corresponding thread; it's a thread-local value. + // Accessing SafeHandle.ReferenceType won't kill anything (so far...), but + // instead it always returns JniReferenceType.Invalid. + if (h.IsValid || h.Type != JniObjectReferenceType.Local) { + JniObjectReference.Dispose (ref h); + } + } - public abstract void FinalizePeer (IJavaPeerable value); + protected abstract bool ShouldFinalizePeer (IJavaPeerable value); + // AndroidRuntime: ShouldFinalizePeer(IJavaPeerable value) => !value.PeerReference.IsValid; - public abstract List GetSurfacedPeers (); + [Flags] + public enum PeerLocations { + ValueManager = 1 << 0, + CurrentThread = 1 << 1, + AllThreads = 1 << 2, + Everywhere = ValueManager | CurrentThread | AllThreads, + } + public List GetSurfacedPeers (PeerLocations locations = PeerLocations.Everywhere) + { + if (disposed) { + throw new ObjectDisposedException (this.GetType ().ToString ()); + } - public abstract void ActivatePeer (IJavaPeerable? self, JniObjectReference reference, ConstructorInfo cinfo, object? []? argumentValues); + var peers = new List (); + + if (locations.HasFlag (PeerLocations.ValueManager)) + AddSurfacedPeers (peers); + if (locations.HasFlag (PeerLocations.AllThreads)) { + foreach (var info in JniEnvironment.Info.Values) { + AddScopes (info.Scopes); + } + } + if (locations.HasFlag (PeerLocations.CurrentThread) && !locations.HasFlag (PeerLocations.AllThreads)) { + AddScopes (JniEnvironment.CurrentInfo.Scopes); + } + + return peers; + + void AddScopes (List? scopes) { + if (scopes == null) { + return; + } + foreach (var scope in scopes) { + if (scope == null) { + continue; + } + foreach (var peer in scope) { + peers.Add (new JniSurfacedPeerInfo (peer.JniIdentityHashCode, CreateRef (peer))); + } + } + } + + WeakReference CreateRef (IJavaPeerable value) { + return new WeakReference (value, trackResurrection: false); + } + } + + protected abstract void AddSurfacedPeers (ICollection collection); + + public void ActivatePeer (JniObjectReference reference, ConstructorInfo constructor, object? []? argumentValues) + { + if (disposed) { + throw new ObjectDisposedException (this.GetType ().ToString ()); + } + if (!reference.IsValid) { + throw new ArgumentException ("reference is not valid", nameof (reference)); + } + if (constructor == null) { + throw new ArgumentNullException (nameof (constructor)); + } + ActivatePeerCore (reference, constructor, argumentValues); + } + + protected abstract void ActivatePeerCore (JniObjectReference reference, ConstructorInfo constructor, object?[]? argumentValues); public void ConstructPeer (IJavaPeerable peer, ref JniObjectReference reference, JniObjectReferenceOptions options) { + if (disposed) { + throw new ObjectDisposedException (this.GetType ().ToString ()); + } + if (peer == null) throw new ArgumentNullException (nameof (peer)); @@ -137,7 +300,7 @@ public int GetJniIdentityHashCode (JniObjectReference reference) return JniSystem.IdentityHashCode (reference); } - public virtual void DisposePeer (IJavaPeerable value) + public void DisposePeer (IJavaPeerable value) { if (disposed) throw new ObjectDisposedException (GetType ().Name); @@ -160,9 +323,6 @@ public virtual void DisposePeer (IJavaPeerable value) void DisposePeer (JniObjectReference h, IJavaPeerable value) { - if (disposed) - throw new ObjectDisposedException (GetType ().Name); - var o = Runtime.ObjectReferenceManager; if (o.LogGlobalReferenceMessages) { o.WriteGlobalReferenceLine ("Disposing PeerReference={0} IdentityHashCode=0x{1} Instance=0x{2} Instance.Type={3} Java.Type={4}", @@ -186,7 +346,7 @@ void DisposePeer (JniObjectReference h, IJavaPeerable value) GC.SuppressFinalize (value); } - public virtual void DisposePeerUnlessReferenced (IJavaPeerable value) + public void DisposePeerUnlessReferenced (IJavaPeerable value) { if (disposed) throw new ObjectDisposedException (GetType ().Name); @@ -205,7 +365,26 @@ public virtual void DisposePeerUnlessReferenced (IJavaPeerable value) DisposePeer (h, value); } - public abstract IJavaPeerable? PeekPeer (JniObjectReference reference); + public IJavaPeerable? PeekPeer (JniObjectReference reference) + { + if (disposed) { + throw new ObjectDisposedException (this.GetType ().ToString ()); + } + + if (!reference.IsValid) + return null; + + var scope = JniEnvironment.CurrentInfo.CurrentScope; + if (scope != null && scope.Cleanup != JniPeerRegistrationScopeCleanup.RegisterWithManager) { + var peer = scope.GetPeerableForObjectReference (reference); + if (peer != null) { + return peer; + } + } + return PeekPeerFromManager (reference); + } + + protected abstract IJavaPeerable? PeekPeerFromManager (JniObjectReference reference); public object? PeekValue (JniObjectReference reference) { @@ -267,7 +446,7 @@ static Type GetPeerType (Type type) return type; } - public virtual IJavaPeerable? CreatePeer (ref JniObjectReference reference, JniObjectReferenceOptions transfer, Type? targetType) + public IJavaPeerable? CreatePeer (ref JniObjectReference reference, JniObjectReferenceOptions transfer, Type? targetType) { if (disposed) throw new ObjectDisposedException (GetType ().Name); @@ -278,10 +457,56 @@ static Type GetPeerType (Type type) if (!typeof (IJavaPeerable).IsAssignableFrom (targetType)) throw new ArgumentException ($"targetType `{targetType.AssemblyQualifiedName}` must implement IJavaPeerable!", nameof (targetType)); + if (transfer == JniObjectReferenceOptions.None || !reference.IsValid) { + return null; + } + + var peer = CreatePeerCore (ref reference, transfer, targetType, out var ctorSigs); + if (peer != null) { + return peer; + } + // Could be that: + // 1. we're within a JniPeerRegistrationScope stack, and + // 2. current scope entry didn't register this type, and + // 3. `reference` refers to an *already created* instance which is in a *parent* or "global" scope + // PeekPeer() won't find it because of (1) + (2); look in *all* scopes. + peer = TryGetPeerFromScopes (reference); + if (peer != null) { + if (targetType.IsAssignableFrom (peer.GetType ())) { + return peer; + } + throw new NotSupportedException ($"Found peer `{peer.GetType()}` for reference=`{reference}`, which is not convertible to type `{targetType}`."); + } + var error = new StringBuilder (); + error.Append ("Could not find an appropriate constructor wrapper for Java type ") + .Append (JniEnvironment.Types.GetJniTypeNameFromInstance (reference)) + .Append ("`. Looked for constructor signatures: "); + foreach (var ctorSig in (ctorSigs ?? new Type[][]{})) { + error.Append (targetType.FullName).Append ("("); + bool first = true; + foreach (var t in ctorSig) { + if (!first) { + error.Append (", "); + } + first = false; + error.Append (t); + } + error.Append (")"); + } + throw new NotSupportedException (error.ToString ()); + } + + protected virtual IJavaPeerable? CreatePeerCore (ref JniObjectReference reference, JniObjectReferenceOptions transfer, Type targetType, out IEnumerable[]? attemptedConstructorSignatures) + { + attemptedConstructorSignatures = null; + var ctor = GetPeerConstructor (reference, 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)); + if (ctor == null) { + attemptedConstructorSignatures = new[]{ + new[]{ByRefJniObjectReference, typeof (JniObjectReferenceOptions)}, + }; + return null; + } var acts = new object[] { reference, @@ -296,6 +521,27 @@ static Type GetPeerType (Type type) } } + IJavaPeerable? TryGetPeerFromScopes (JniObjectReference reference) + { + var scopes = JniEnvironment.CurrentInfo.Scopes; + if (scopes == null) { + return null; + } + + int count = (scopes.Count - 1); + for (int i = count; i >= 0; --i) { + var scope = scopes [i]; + if (scope.Cleanup == JniPeerRegistrationScopeCleanup.RegisterWithManager) { + continue; + } + var peer = scope.GetPeerableForObjectReference (reference); + if (peer != null) { + return peer; + } + } + return PeekPeerFromManager (reference); + } + static readonly Type ByRefJniObjectReference = typeof (JniObjectReference).MakeByRefType (); ConstructorInfo? GetPeerConstructor (JniObjectReference instance, Type fallbackType) @@ -589,6 +835,21 @@ protected virtual JniValueMarshaler GetValueMarshalerCore (Type type) { return ProxyValueMarshaler.Instance; } + + [return: NotNullIfNotNull ("object")] + internal JavaProxyObject? GetProxy (object value) + { + if (value == null) + return null; + + lock (cachedValues) { + if (cachedValues.TryGetValue (value, out var proxy)) + return proxy; + proxy = new JavaProxyObject (value); + cachedValues.Add (value, proxy); + return proxy; + } + } } } @@ -770,7 +1031,7 @@ public override JniValueMarshalerState CreateGenericObjectReferenceArgumentState return new JniValueMarshalerState (s, vm); } - var p = JavaProxyObject.GetProxy (value); + var p = jvm.ValueManager.GetProxy (value); return new JniValueMarshalerState (p!.PeerReference.NewLocalRef ()); } diff --git a/src/Java.Interop/Java.Interop/ManagedPeer.cs b/src/Java.Interop/Java.Interop/ManagedPeer.cs index 5c0e033c3..6326b4838 100644 --- a/src/Java.Interop/Java.Interop/ManagedPeer.cs +++ b/src/Java.Interop/Java.Interop/ManagedPeer.cs @@ -109,7 +109,7 @@ static void Construct ( return; } - JniEnvironment.Runtime.ValueManager.ActivatePeer (self, new JniObjectReference (n_self), cinfo, pvalues); + JniEnvironment.Runtime.ValueManager.ActivatePeer (new JniObjectReference (n_self), cinfo, pvalues); } catch (Exception e) when (JniEnvironment.Runtime.ExceptionShouldTransitionToJni (e)) { envp.SetPendingException (e); diff --git a/src/Java.Runtime.Environment/Java.Interop/ManagedValueManager.cs b/src/Java.Runtime.Environment/Java.Interop/ManagedValueManager.cs index 1dcb12220..603b39f85 100644 --- a/src/Java.Runtime.Environment/Java.Interop/ManagedValueManager.cs +++ b/src/Java.Runtime.Environment/Java.Interop/ManagedValueManager.cs @@ -19,14 +19,11 @@ public override void WaitForGCBridgeProcessing () { } - public override void CollectPeers () + protected override void CollectPeersCore () { - if (RegisteredInstances == null) - throw new ObjectDisposedException (nameof (MonoRuntimeValueManager)); - var peers = new List (); - lock (RegisteredInstances) { + lock (RegisteredInstances!) { foreach (var ps in RegisteredInstances.Values) { foreach (var p in ps) { peers.Add (p); @@ -48,22 +45,11 @@ public override void CollectPeers () throw new AggregateException ("Exceptions while collecting peers.", exceptions); } - public override void AddPeer (IJavaPeerable value) + protected override void AddPeerToManager (IJavaPeerable value) { if (RegisteredInstances == null) throw new ObjectDisposedException (nameof (MonoRuntimeValueManager)); - var r = value.PeerReference; - if (!r.IsValid) - throw new ObjectDisposedException (value.GetType ().FullName); - var o = PeekPeer (value.PeerReference); - if (o != null) - return; - - if (r.Type != JniObjectReferenceType.Global) { - value.SetPeerReference (r.NewGlobalRef ()); - JniObjectReference.Dispose (ref r, JniObjectReferenceOptions.CopyAndDispose); - } int key = value.JniIdentityHashCode; lock (RegisteredInstances) { List peers; @@ -113,7 +99,7 @@ void WarnNotReplacing (int key, IJavaPeerable ignoreValue, IJavaPeerable keepVal JniEnvironment.Types.GetJniTypeNameFromInstance (keepValue.PeerReference)); } - public override IJavaPeerable? PeekPeer (JniObjectReference reference) + protected override IJavaPeerable? PeekPeerFromManager (JniObjectReference reference) { if (RegisteredInstances == null) throw new ObjectDisposedException (nameof (MonoRuntimeValueManager)); @@ -139,14 +125,11 @@ void WarnNotReplacing (int key, IJavaPeerable ignoreValue, IJavaPeerable keepVal return null; } - public override void RemovePeer (IJavaPeerable value) + protected override void RemovePeerFromManager (IJavaPeerable value) { if (RegisteredInstances == null) throw new ObjectDisposedException (nameof (MonoRuntimeValueManager)); - if (value == null) - throw new ArgumentNullException (nameof (value)); - int key = value.JniIdentityHashCode; lock (RegisteredInstances) { List peers; @@ -164,73 +147,35 @@ public override void RemovePeer (IJavaPeerable value) } } - public override void FinalizePeer (IJavaPeerable value) - { - var h = value.PeerReference; - var o = Runtime.ObjectReferenceManager; - // MUST NOT use SafeHandle.ReferenceType: local refs are tied to a JniEnvironment - // and the JniEnvironment's corresponding thread; it's a thread-local value. - // Accessing SafeHandle.ReferenceType won't kill anything (so far...), but - // instead it always returns JniReferenceType.Invalid. - if (!h.IsValid || h.Type == JniObjectReferenceType.Local) { - if (o.LogGlobalReferenceMessages) { - o.WriteGlobalReferenceLine ("Finalizing PeerReference={0} IdentityHashCode=0x{1} Instance=0x{2} Instance.Type={3}", - h.ToString (), - value.JniIdentityHashCode.ToString ("x"), - RuntimeHelpers.GetHashCode (value).ToString ("x"), - value.GetType ().ToString ()); - } - RemovePeer (value); - value.SetPeerReference (new JniObjectReference ()); - value.Finalized (); - return; - } - - RemovePeer (value); - if (o.LogGlobalReferenceMessages) { - o.WriteGlobalReferenceLine ("Finalizing PeerReference={0} IdentityHashCode=0x{1} Instance=0x{2} Instance.Type={3}", - h.ToString (), - value.JniIdentityHashCode.ToString ("x"), - RuntimeHelpers.GetHashCode (value).ToString ("x"), - value.GetType ().ToString ()); - } - value.SetPeerReference (new JniObjectReference ()); - JniObjectReference.Dispose (ref h); - value.Finalized (); - } + protected override bool ShouldFinalizePeer (IJavaPeerable value) => true; - public override void ActivatePeer (IJavaPeerable? self, JniObjectReference reference, ConstructorInfo cinfo, object?[]? argumentValues) + protected override void ActivatePeerCore (JniObjectReference reference, ConstructorInfo constructor, object?[]? argumentValues) { var runtime = JniEnvironment.Runtime; try { - var f = runtime.MarshalMemberBuilder.CreateConstructActivationPeerFunc (cinfo); - f (cinfo, reference, argumentValues); + var f = runtime.MarshalMemberBuilder.CreateConstructActivationPeerFunc (constructor); + f (constructor, reference, argumentValues); } catch (Exception e) { var m = string.Format ("Could not activate {{ PeerReference={0} IdentityHashCode=0x{1} Java.Type={2} }} for managed type '{3}'.", reference, runtime.ValueManager.GetJniIdentityHashCode (reference).ToString ("x"), JniEnvironment.Types.GetJniTypeNameFromInstance (reference), - cinfo.DeclaringType.FullName); + constructor.DeclaringType.FullName); Debug.WriteLine (m); throw new NotSupportedException (m, e); } } - public override List GetSurfacedPeers () + protected override void AddSurfacedPeers (ICollection peers) { - if (RegisteredInstances == null) - throw new ObjectDisposedException (nameof (MonoRuntimeValueManager)); - - lock (RegisteredInstances) { - var peers = new List (RegisteredInstances.Count); + lock (RegisteredInstances!) { foreach (var e in RegisteredInstances) { foreach (var p in e.Value) { peers.Add (new JniSurfacedPeerInfo (e.Key, new WeakReference (p))); } } - return peers; } } } diff --git a/src/Java.Runtime.Environment/Java.Interop/MonoRuntimeValueManager.cs b/src/Java.Runtime.Environment/Java.Interop/MonoRuntimeValueManager.cs index 1f7cea114..65affd3c9 100644 --- a/src/Java.Runtime.Environment/Java.Interop/MonoRuntimeValueManager.cs +++ b/src/Java.Runtime.Environment/Java.Interop/MonoRuntimeValueManager.cs @@ -63,7 +63,7 @@ public override void WaitForGCBridgeProcessing () NativeMethods.java_interop_gc_bridge_wait_for_bridge_processing (bridge); } - public override void CollectPeers () + protected override void CollectPeersCore () { GC.Collect (); } @@ -78,19 +78,24 @@ protected override void Dispose (bool disposing) if (RegisteredInstances == null) return; + List> values; + lock (RegisteredInstances) { + values = new List> (RegisteredInstances.Count); foreach (var o in RegisteredInstances.Values) { - foreach (var r in o) { - IJavaPeerable t; - if (!r.TryGetTarget (out t)) - continue; - t.Dispose (); - } + values.AddRange (o); } RegisteredInstances.Clear (); RegisteredInstances = null; } + foreach (var r in values) { + IJavaPeerable t; + if (!r.TryGetTarget (out t)) + continue; + t.Dispose (); + } + if (bridge != IntPtr.Zero) { NativeMethods.java_interop_gc_bridge_remove_current_app_domain (bridge); bridge = IntPtr.Zero; @@ -100,40 +105,24 @@ protected override void Dispose (bool disposing) Dictionary>>? RegisteredInstances = new Dictionary>>(); - public override List GetSurfacedPeers () + protected override void AddSurfacedPeers (ICollection peers) { if (RegisteredInstances == null) throw new ObjectDisposedException (nameof (MonoRuntimeValueManager)); lock (RegisteredInstances) { - var peers = new List (RegisteredInstances.Count); foreach (var e in RegisteredInstances) { foreach (var p in e.Value) { peers.Add (new JniSurfacedPeerInfo (e.Key, p)); } } - return peers; } } - public override void AddPeer (IJavaPeerable value) + protected override void AddPeerToManager (IJavaPeerable value) { - if (RegisteredInstances == null) - throw new ObjectDisposedException (nameof (MonoRuntimeValueManager)); - - var r = value.PeerReference; - if (!r.IsValid) - throw new ObjectDisposedException (value.GetType ().FullName); - var o = PeekPeer (value.PeerReference); - if (o != null) - return; - - if (r.Type != JniObjectReferenceType.Global) { - value.SetPeerReference (r.NewGlobalRef ()); - JniObjectReference.Dispose (ref r, JniObjectReferenceOptions.CopyAndDispose); - } int key = value.JniIdentityHashCode; - lock (RegisteredInstances) { + lock (RegisteredInstances!) { List> peers; if (!RegisteredInstances.TryGetValue (key, out peers)) { peers = new List> () { @@ -187,16 +176,10 @@ static bool Replaceable (IJavaPeerable peer) return (peer.JniManagedPeerState & JniManagedPeerStates.Replaceable) == JniManagedPeerStates.Replaceable; } - public override void RemovePeer (IJavaPeerable value) + protected override void RemovePeerFromManager (IJavaPeerable value) { - if (RegisteredInstances == null) - throw new ObjectDisposedException (nameof (MonoRuntimeValueManager)); - - if (value == null) - throw new ArgumentNullException (nameof (value)); - int key = value.JniIdentityHashCode; - lock (RegisteredInstances) { + lock (RegisteredInstances!) { List> peers; if (!RegisteredInstances.TryGetValue (key, out peers)) return; @@ -218,17 +201,11 @@ public override void RemovePeer (IJavaPeerable value) } } - public override IJavaPeerable? PeekPeer (JniObjectReference reference) + protected override IJavaPeerable? PeekPeerFromManager (JniObjectReference reference) { - if (RegisteredInstances == null) - throw new ObjectDisposedException (nameof (MonoRuntimeValueManager)); - - if (!reference.IsValid) - return null; - int key = GetJniIdentityHashCode (reference); - lock (RegisteredInstances) { + lock (RegisteredInstances!) { List> peers; if (!RegisteredInstances.TryGetValue (key, out peers)) return null; @@ -257,95 +234,26 @@ static Exception CreateJniLocationException () } } - public override void ActivatePeer (IJavaPeerable? self, JniObjectReference reference, ConstructorInfo cinfo, object?[]? argumentValues) + protected override void ActivatePeerCore (JniObjectReference reference, ConstructorInfo constructor, object?[]? argumentValues) { var runtime = JniEnvironment.Runtime; try { - var f = runtime.MarshalMemberBuilder.CreateConstructActivationPeerFunc (cinfo); - f (cinfo, reference, argumentValues); + var f = runtime.MarshalMemberBuilder.CreateConstructActivationPeerFunc (constructor); + f (constructor, reference, argumentValues); } catch (Exception e) { var m = string.Format ("Could not activate {{ PeerReference={0} IdentityHashCode=0x{1} Java.Type={2} }} for managed type '{3}'.", reference, runtime.ValueManager.GetJniIdentityHashCode (reference).ToString ("x"), JniEnvironment.Types.GetJniTypeNameFromInstance (reference), - cinfo.DeclaringType.FullName); + constructor.DeclaringType.FullName); Debug.WriteLine (m); throw new NotSupportedException (m, e); } } - public override void FinalizePeer (IJavaPeerable value) - { - var h = value.PeerReference; - var o = Runtime.ObjectReferenceManager; - // MUST NOT use SafeHandle.ReferenceType: local refs are tied to a JniEnvironment - // and the JniEnvironment's corresponding thread; it's a thread-local value. - // Accessing SafeHandle.ReferenceType won't kill anything (so far...), but - // instead it always returns JniReferenceType.Invalid. - if (!h.IsValid || h.Type == JniObjectReferenceType.Local) { - if (o.LogGlobalReferenceMessages) { - o.WriteGlobalReferenceLine ("Finalizing PeerReference={0} IdentityHashCode=0x{1} Instance=0x{2} Instance.Type={3}", - h.ToString (), - value.JniIdentityHashCode.ToString ("x"), - RuntimeHelpers.GetHashCode (value).ToString ("x"), - value.GetType ().ToString ()); - } - RemovePeer (value); - value.SetPeerReference (new JniObjectReference ()); - value.Finalized (); - return; - } - - try { - bool collected = TryGC (value, ref h); - if (collected) { - RemovePeer (value); - value.SetPeerReference (new JniObjectReference ()); - if (o.LogGlobalReferenceMessages) { - o.WriteGlobalReferenceLine ("Finalizing PeerReference={0} IdentityHashCode=0x{1} Instance=0x{2} Instance.Type={3}", - h.ToString (), - value.JniIdentityHashCode.ToString ("x"), - RuntimeHelpers.GetHashCode (value).ToString ("x"), - value.GetType ().ToString ()); - } - value.Finalized (); - } else { - value.SetPeerReference (h); - GC.ReRegisterForFinalize (value); - } - } catch (Exception e) { - Runtime.FailFast ("Unable to perform a GC! " + e); - } - } - - /// - /// Try to garbage collect . - /// - /// - /// true, if was collected and - /// is invalid; otherwise false. - /// - /// - /// The instance to collect. - /// - /// - /// The of . - /// This value may be updated, and - /// will be updated with this value. - /// - internal protected virtual bool TryGC (IJavaPeerable value, ref JniObjectReference handle) - { - if (!handle.IsValid) - return true; - var wgref = handle.NewWeakGlobalRef (); - JniObjectReference.Dispose (ref handle); - JniGC.Collect (); - handle = wgref.NewGlobalRef (); - JniObjectReference.Dispose (ref wgref); - return !handle.IsValid; - } + protected override bool ShouldFinalizePeer (IJavaPeerable value) => !value.PeerReference.IsValid; } static class JavaLangRuntime { diff --git a/tests/Java.Interop-Tests/Java.Interop-Tests.csproj b/tests/Java.Interop-Tests/Java.Interop-Tests.csproj index a39a8e8f1..46516d767 100644 --- a/tests/Java.Interop-Tests/Java.Interop-Tests.csproj +++ b/tests/Java.Interop-Tests/Java.Interop-Tests.csproj @@ -4,6 +4,9 @@ net472;netcoreapp3.1 false true + 8.0 + true + ..\..\product.snk @@ -50,6 +53,7 @@ + diff --git a/tests/Java.Interop-Tests/Java.Interop/JavaObjectTest.cs b/tests/Java.Interop-Tests/Java.Interop/JavaObjectTest.cs index 94b635b49..8270e534a 100644 --- a/tests/Java.Interop-Tests/Java.Interop/JavaObjectTest.cs +++ b/tests/Java.Interop-Tests/Java.Interop/JavaObjectTest.cs @@ -240,7 +240,7 @@ protected override void Dispose (bool disposing) [JniTypeSignature ("java/lang/Object")] class MyDisposableObject : JavaObject { - bool _isDisposed; + internal bool _isDisposed; public MyDisposableObject () { diff --git a/tests/Java.Interop-Tests/Java.Interop/JavaSingleton.cs b/tests/Java.Interop-Tests/Java.Interop/JavaSingleton.cs new file mode 100644 index 000000000..7f86c115b --- /dev/null +++ b/tests/Java.Interop-Tests/Java.Interop/JavaSingleton.cs @@ -0,0 +1,39 @@ +using System; + +using Java.Interop; + +namespace Java.InteropTests +{ + [JniTypeSignature (JavaSingleton.JniTypeName)] + public sealed class JavaSingleton : JavaObject + { + internal const string JniTypeName = "com/xamarin/interop/Singleton"; + + readonly static JniPeerMembers _members = new JniPeerMembers (JniTypeName, typeof (JavaSingleton)); + + public override JniPeerMembers JniPeerMembers { + get {return _members;} + } + + public bool disposed; + + internal JavaSingleton (ref JniObjectReference reference, JniObjectReferenceOptions options) + : base (ref reference, options) + { + } + + protected override void Dispose (bool disposing) + { + disposed = disposed || disposing; + base.Dispose (disposing); + } + + public static unsafe JavaSingleton Singleton { + get { + var o = _members.StaticMethods.InvokeObjectMethod ("getSingleton.()Lcom/xamarin/interop/Singleton;", null); + return JniEnvironment.Runtime.ValueManager.GetValue (ref o, JniObjectReferenceOptions.CopyAndDispose); + } + } + } +} + diff --git a/tests/Java.Interop-Tests/Java.Interop/JniRuntime.JniValueManagerContract.cs b/tests/Java.Interop-Tests/Java.Interop/JniRuntime.JniValueManagerContract.cs new file mode 100644 index 000000000..203b7a64e --- /dev/null +++ b/tests/Java.Interop-Tests/Java.Interop/JniRuntime.JniValueManagerContract.cs @@ -0,0 +1,392 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +using Java.Interop; + +using NUnit.Framework; + +namespace Java.InteropTests { + + // Modifies JniRuntime.valueManager instance field; can't be done in parallel + [NonParallelizable] + public abstract class JniRuntimeJniValueManagerContract : JavaVMFixture { + + protected abstract Type ValueManagerType { + get; + } + + protected virtual JniRuntime.JniValueManager CreateValueManager () + { + var manager = Activator.CreateInstance (ValueManagerType) as JniRuntime.JniValueManager; + return manager ?? throw new InvalidOperationException ($"Could not create instance of `{ValueManagerType}`!"); + } + +#pragma warning disable CS8618 + JniRuntime.JniValueManager systemManager; + JniRuntime.JniValueManager valueManager; +#pragma warning restore CS8618 + + [SetUp] + public void CreateVM () + { + systemManager = JniRuntime.CurrentRuntime.valueManager!; + valueManager = CreateValueManager (); + valueManager.OnSetRuntime (JniRuntime.CurrentRuntime); + JniRuntime.CurrentRuntime.valueManager = valueManager; + } + + [TearDown] + public void DestroyVM () + { + JniRuntime.CurrentRuntime.valueManager = systemManager; + systemManager = null!; + valueManager?.Dispose (); + valueManager = null!; + } + + [Test] + public void AddPeer () + { + Assert.Throws(() => valueManager.AddPeer (null!)); + } + + [Test] + public void AddPeer_NoDuplicates () + { + int startPeerCount = valueManager.GetSurfacedPeers ().Count; + using (var v = new MyDisposableObject ()) { + // MyDisposableObject ctor implicitly calls AddPeer(); + Assert.AreEqual (startPeerCount + 1, valueManager.GetSurfacedPeers ().Count); + valueManager.AddPeer (v); + Assert.AreEqual (startPeerCount + 1, valueManager.GetSurfacedPeers ().Count); + } + } + + [Test] + public void ConstructPeer_ImplicitViaBindingConstructor_PeerIsInSurfacedPeers () + { + int startPeerCount = valueManager.GetSurfacedPeers ().Count; + + var g = new GetThis (); + var surfaced = valueManager.GetSurfacedPeers (); + Assert.AreEqual (startPeerCount + 1, surfaced.Count); + + var found = false; + foreach (var pr in surfaced) { + if (!pr.SurfacedPeer.TryGetTarget (out var p)) + continue; + if (object.ReferenceEquals (g, p)) { + found = true; + } + } + Assert.IsTrue (found); + + var localRef = g.PeerReference.NewLocalRef (); + g.Dispose (); + Assert.AreEqual (startPeerCount, valueManager.GetSurfacedPeers ().Count); + Assert.IsNull (valueManager.PeekPeer (localRef)); + JniObjectReference.Dispose (ref localRef); + } + + [Test] + public void ConstructPeer_ImplicitViaBindingMethod_PeerIsInSurfacedPeers () + { + int startPeerCount = valueManager.GetSurfacedPeers ().Count; + + var g = new GetThis (); + var surfaced = valueManager.GetSurfacedPeers (); + Assert.AreEqual (startPeerCount + 1, surfaced.Count); + + var found = false; + foreach (var pr in surfaced) { + if (!pr.SurfacedPeer.TryGetTarget (out var p)) + continue; + if (object.ReferenceEquals (g, p)) { + found = true; + } + } + Assert.IsTrue (found); + + var localRef = g.PeerReference.NewLocalRef (); + g.Dispose (); + Assert.AreEqual (startPeerCount, valueManager.GetSurfacedPeers ().Count); + Assert.IsNull (valueManager.PeekPeer (localRef)); + JniObjectReference.Dispose (ref localRef); + } + + + [Test] + public void CollectPeers () + { + // TODO + } + + [Test] + public void CreateValue () + { + using (var o = new JavaObject ()) { + var r = o.PeerReference; + var x = (IJavaPeerable) valueManager.CreateValue (ref r, JniObjectReferenceOptions.Copy)!; + Assert.AreNotSame (o, x); + x.Dispose (); + + x = valueManager.CreateValue (ref r, JniObjectReferenceOptions.Copy); + Assert.AreNotSame (o, x); + x.Dispose (); + } + } + + [Test] + public void GetValue_ReturnsAlias () + { + var local = new JavaObject (); + local.UnregisterFromRuntime (); + Assert.IsNull (valueManager.PeekValue (local.PeerReference)); + // GetObject must always return a value (unless handle is null, etc.). + // However, since we called local.UnregisterFromRuntime(), + // JniRuntime.PeekObject() is null (asserted above), but GetObject() must + // **still** return _something_. + // In this case, it returns an _alias_. + // TODO: "most derived type" alias generation. (Not relevant here, but...) + var p = local.PeerReference; + var alias = JniRuntime.CurrentRuntime.ValueManager.GetValue (ref p, JniObjectReferenceOptions.Copy); + Assert.AreNotSame (local, alias); + alias.Dispose (); + local.Dispose (); + } + + [Test] + public void GetValue_ReturnsNullWithNullHandle () + { + var r = new JniObjectReference (); + var o = valueManager.GetValue (ref r, JniObjectReferenceOptions.Copy); + Assert.IsNull (o); + } + + [Test] + public void GetValue_ReturnsNullWithInvalidSafeHandle () + { + var invalid = new JniObjectReference (); + Assert.IsNull (valueManager.GetValue (ref invalid, JniObjectReferenceOptions.CopyAndDispose)); + } + + [Test] + public unsafe void GetValue_FindBestMatchType () + { +#if !NO_MARSHAL_MEMBER_BUILDER_SUPPORT + using (var t = new JniType (TestType.JniTypeName)) { + var c = t.GetConstructor ("()V"); + var o = t.NewObject (c, null); + using (var w = valueManager.GetValue (ref o, JniObjectReferenceOptions.CopyAndDispose)) { + Assert.AreEqual (typeof (TestType), w.GetType ()); + Assert.IsTrue (((TestType) w).ExecutedActivationConstructor); + } + } +#endif // !NO_MARSHAL_MEMBER_BUILDER_SUPPORT + } + + [Test] + public void PeekPeer () + { + Assert.IsNull (valueManager.PeekPeer (new JniObjectReference ())); + + using (var v = new MyDisposableObject ()) { + Assert.IsNotNull (valueManager.PeekPeer (v.PeerReference)); + Assert.AreSame (v, valueManager.PeekPeer (v.PeerReference)); + } + } + + [Test] + public void PeekValue () + { + JniObjectReference lref; + using (var o = new JavaObject ()) { + lref = o.PeerReference.NewLocalRef (); + Assert.AreSame (o, valueManager.PeekValue (lref)); + } + // At this point, the Java-side object is kept alive by `lref`, + // but the wrapper instance has been disposed, and thus should + // be unregistered, and thus unfindable. + Assert.IsNull (valueManager.PeekValue (lref)); + JniObjectReference.Dispose (ref lref); + } + + [Test] + public void PeekValue_BoxedObjects () + { + var marshaler = valueManager.GetValueMarshaler (); + var ad = AppDomain.CurrentDomain; + + var proxy = marshaler.CreateGenericArgumentState (ad); + Assert.AreSame (ad, valueManager.PeekValue (proxy.ReferenceValue)); + marshaler.DestroyGenericArgumentState (ad, ref proxy); + + var ex = new InvalidOperationException ("boo!"); + proxy = marshaler.CreateGenericArgumentState (ex); + Assert.AreSame (ex, valueManager.PeekValue (proxy.ReferenceValue)); + marshaler.DestroyGenericArgumentState (ex, ref proxy); + } + + void AllNestedRegistrationScopeTests () + { + AddPeer (); + AddPeer_NoDuplicates (); + ConstructPeer_ImplicitViaBindingConstructor_PeerIsInSurfacedPeers (); + CreateValue (); + GetValue_FindBestMatchType (); + GetValue_ReturnsAlias (); + GetValue_ReturnsNullWithInvalidSafeHandle (); + GetValue_ReturnsNullWithNullHandle (); + PeekPeer (); + PeekValue (); + PeekValue_BoxedObjects (); + } + + [Test] + public void WithRegistrationScope_Dispose () + { + Assert.AreEqual (0, valueManager.GetSurfacedPeers ().Count); + MyDisposableObject v; + using (new JniPeerRegistrationScope (JniPeerRegistrationScopeCleanup.Dispose)) { + v = new MyDisposableObject (); + Assert.IsFalse (v._isDisposed); + Assert.AreEqual (1, valueManager.GetSurfacedPeers ().Count); + } + Assert.AreEqual (0, valueManager.GetSurfacedPeers ().Count); + Assert.IsTrue (v._isDisposed); + } + + [Test] + public void WithRegistrationScope_Dispose_All () + { + using (new JniPeerRegistrationScope (JniPeerRegistrationScopeCleanup.Dispose)) { + AllNestedRegistrationScopeTests (); + } + } + + [Test] + public void WithRegistrationScope_Dispose_Nested () + { + Assert.AreEqual (0, valueManager.GetSurfacedPeers ().Count); + var u1 = (MyDisposableObject?) null; + using (var c1 = new JniPeerRegistrationScope (JniPeerRegistrationScopeCleanup.Dispose)) { + var b1 = JavaSingleton.Singleton; + var b2 = (JavaSingleton?) null; + var array = new JavaObjectArray (1); + u1 = new MyDisposableObject (); + array [0] = u1; + Assert.AreEqual (3, valueManager.GetSurfacedPeers ().Count); + using (var c2 = new JniPeerRegistrationScope (JniPeerRegistrationScopeCleanup.Dispose)) { + // Creating a new scope means that previously created "bound" instances won't be + // returned by JniValueManager.GetValue(). + b2 = JavaSingleton.Singleton; + Assert.AreNotSame (b1, b2); + Assert.AreEqual (4, valueManager.GetSurfacedPeers ().Count); + + // Creating a new scope means that previously created "unbound" instances MUST be + // returned by JniValueManager.GetValue(), as they don't have an activation ctor + var u2 = array [0]; + Assert.AreSame (u1, u2); + Assert.AreEqual (4, valueManager.GetSurfacedPeers ().Count, DumpPeers (valueManager.GetSurfacedPeers ())); + } + Assert.IsFalse (b1.disposed); + Assert.IsTrue (b2.disposed); + Assert.AreEqual (3, valueManager.GetSurfacedPeers ().Count, DumpPeers (valueManager.GetSurfacedPeers ())); + } + Assert.AreEqual (0, valueManager.GetSurfacedPeers ().Count); + Assert.IsTrue (u1._isDisposed); + } + + static string DumpPeers (IEnumerable peers) + { + return DumpPeers (peers.Select (p => { + if (p.SurfacedPeer.TryGetTarget (out var q)) + return q; + return null; + })); + } + + static string DumpPeers (IEnumerable? peers) + { + if (peers == null) + return ""; + return string.Join (", ", peers.Where (p => p != null) + .Select (p => $"{p!.GetType ().FullName}(\"{p!.ToString ()}\", PeerReference={p.PeerReference})")); + } + + [Test] + public void WithRegistrationScope_Release () + { + Assert.AreEqual (0, valueManager.GetSurfacedPeers ().Count); + MyDisposableObject v; + using (new JniPeerRegistrationScope (JniPeerRegistrationScopeCleanup.Release)) { + v = new MyDisposableObject (); + Assert.IsFalse (v._isDisposed); + Assert.AreEqual (1, valueManager.GetSurfacedPeers ().Count); + } + Assert.AreEqual (0, valueManager.GetSurfacedPeers ().Count); + Assert.IsFalse (v._isDisposed); + } + + [Test] + public void WithRegistrationScope_Release_All () + { + using (new JniPeerRegistrationScope (JniPeerRegistrationScopeCleanup.Release)) { + AllNestedRegistrationScopeTests (); + } + } + + [Test] + public void WithRegistrationScope_RegisterWithRuntime () + { + Assert.AreEqual (0, valueManager.GetSurfacedPeers ().Count); + MyDisposableObject v; + using (new JniPeerRegistrationScope (JniPeerRegistrationScopeCleanup.Release)) { + v = new MyDisposableObject (); + Assert.IsFalse (v._isDisposed); + Assert.AreEqual (1, valueManager.GetSurfacedPeers ().Count); + } + Assert.AreEqual (0, valueManager.GetSurfacedPeers ().Count); + Assert.IsFalse (v._isDisposed); + } + + [Test] + public void WithRegistrationScope_RegisterWithRuntime_All () + { + using (new JniPeerRegistrationScope (JniPeerRegistrationScopeCleanup.Release)) { + AllNestedRegistrationScopeTests (); + } + } + + // also test: + // Singleton scenario + // Types w/o "activation" constructors -- need to support checking parent scopes + // nesting of scopes + // Adding an instance already added in a previous scope? + } + + public abstract class JniRuntimeJniValueManagerContract : JniRuntimeJniValueManagerContract { + + protected override Type ValueManagerType => typeof (T); + } + +#if !NETCOREAPP + [TestFixture] + public class JniRuntimeJniValueManagerContract_Mono : JniRuntimeJniValueManagerContract { + static Type MonoRuntimeValueManagerType = Type.GetType ("Java.Interop.MonoRuntimeValueManager, Java.Runtime.Environment", throwOnError:true)!; + + protected override Type ValueManagerType => MonoRuntimeValueManagerType; + } +#endif // !NETCOREAPP + + [TestFixture] + public class JniRuntimeJniValueManagerContract_NoGCIntegration : JniRuntimeJniValueManagerContract { + static Type ManagedValueManagerType = Type.GetType ("Java.Interop.ManagedValueManager, Java.Runtime.Environment", throwOnError:true)!; + + protected override Type ValueManagerType => ManagedValueManagerType; + } +} diff --git a/tests/Java.Interop-Tests/Java.Interop/JniRuntime.JniValueManagerTests.cs b/tests/Java.Interop-Tests/Java.Interop/JniRuntime.JniValueManagerTests.cs index f8f8c42bb..830c0f7ec 100644 --- a/tests/Java.Interop-Tests/Java.Interop/JniRuntime.JniValueManagerTests.cs +++ b/tests/Java.Interop-Tests/Java.Interop/JniRuntime.JniValueManagerTests.cs @@ -35,35 +35,32 @@ public override void WaitForGCBridgeProcessing () { } - public override void CollectPeers () + protected override void AddPeerToManager (IJavaPeerable peer) { } - public override void AddPeer (IJavaPeerable reference) + protected override void CollectPeersCore () { } - public override void RemovePeer (IJavaPeerable reference) - { - } + protected override bool ShouldFinalizePeer (IJavaPeerable peer) => true; - public override void FinalizePeer (IJavaPeerable reference) + protected override void AddSurfacedPeers (ICollection peers) { } - public override List GetSurfacedPeers () + protected override IJavaPeerable PeekPeerFromManager (JniObjectReference reference) { return null; } - public override IJavaPeerable PeekPeer (JniObjectReference reference) + protected override void ActivatePeerCore (JniObjectReference reference, ConstructorInfo constructor, object [] argumentValues) { - return null; + throw new NotImplementedException (); } - public override void ActivatePeer (IJavaPeerable self, JniObjectReference reference, ConstructorInfo cinfo, object [] argumentValues) + protected override void RemovePeerFromManager (IJavaPeerable peer) { - throw new NotImplementedException (); } } diff --git a/tests/Java.Interop-Tests/Java.Interop/JniRuntimeTest.cs b/tests/Java.Interop-Tests/Java.Interop/JniRuntimeTest.cs index 5503dcbf6..c12c2c4aa 100644 --- a/tests/Java.Interop-Tests/Java.Interop/JniRuntimeTest.cs +++ b/tests/Java.Interop-Tests/Java.Interop/JniRuntimeTest.cs @@ -130,34 +130,31 @@ public override int WeakGlobalReferenceCount { class ProxyValueManager : JniValueManager { - public override void AddPeer (IJavaPeerable peer) + protected override void AddPeerToManager (IJavaPeerable peer) { } - public override void CollectPeers () + protected override void CollectPeersCore () { } - public override void FinalizePeer (IJavaPeerable peer) - { - } + protected override bool ShouldFinalizePeer (IJavaPeerable peer) => true; - public override List GetSurfacedPeers () + protected override void AddSurfacedPeers (ICollection peers) { - return null; } - public override IJavaPeerable PeekPeer (JniObjectReference reference) + protected override IJavaPeerable PeekPeerFromManager (JniObjectReference reference) { return null; } - public override void ActivatePeer (IJavaPeerable self, JniObjectReference reference, ConstructorInfo cinfo, object [] argumentValues) + protected override void ActivatePeerCore (JniObjectReference reference, ConstructorInfo constructor, object [] argumentValues) { throw new NotImplementedException (); } - public override void RemovePeer (IJavaPeerable peer) + protected override void RemovePeerFromManager (IJavaPeerable peer) { } diff --git a/tests/Java.Interop-Tests/java/com/xamarin/interop/Singleton.java b/tests/Java.Interop-Tests/java/com/xamarin/interop/Singleton.java new file mode 100644 index 000000000..d91e64500 --- /dev/null +++ b/tests/Java.Interop-Tests/java/com/xamarin/interop/Singleton.java @@ -0,0 +1,13 @@ +package com.xamarin.interop; + +public final class Singleton { + + static final Singleton singleton = new Singleton (); + + private Singleton() { + } + + public static Singleton getSingleton() { + return singleton; + } +} diff --git a/tools/java-source-utils/.project b/tools/java-source-utils/.project index 8337a1c56..8f21f01f7 100644 --- a/tools/java-source-utils/.project +++ b/tools/java-source-utils/.project @@ -20,4 +20,15 @@ org.eclipse.jdt.core.javanature org.eclipse.buildship.core.gradleprojectnature + + + 1619812743103 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + +