diff --git a/src/Java.Interop/Documentation/Java.Interop/JniRuntime.JniValueManager.xml b/src/Java.Interop/Documentation/Java.Interop/JniRuntime.JniValueManager.xml
new file mode 100644
index 000000000..76e953659
--- /dev/null
+++ b/src/Java.Interop/Documentation/Java.Interop/JniRuntime.JniValueManager.xml
@@ -0,0 +1,193 @@
+
+
+
+
+ Manages the mapping between Java instances and registered managed instances.
+
+
+
+ JniRuntime.JniValueManager manages a mapping between Java instances,
+ and instances. A IJavaPeerable is
+ registered if it has been added to JniRuntime.JniValueManager.
+ Certain methods only deal with registered instances. Managed-to-Java instance
+ method invocation does not require the use of registered instances.
+ Java-to-Managed instance method invocation requires the use of registred
+ instances; if possible, a managed peer will be implicitly created.
+ See for details.
+
+
+ -
+ Marshaling infrastructure:
+ .
+
+ -
+ Lifetime management of all registered peers:
+ ,
+ ,
+ .
+
+ -
+ Registration management for individual peers:
+ ,
+ ,
+ ,
+ ,
+ .
+
+ -
+ Java-to-managed marshaling support:
+ ,
+ ,
+ .
+
+ -
+ Managed-to-Java marshaling support:
+ .
+
+
+
+ Managed-to-Java marshaling support is handled via explicit usage,
+ or "generically" via and
+ .
+
+
+ All subclasses must be thread safe.
+
+
+ This type is thread safe.
+
+
+
+
+ Infrastructure. Called during Java-to-managed transitions.
+
+
+
+
+
+ Whether or not is supported.
+
+
+
+ When CanCollectPeers returns false, calls to
+ will throw
+ .
+
+
+
+
+
+ Garbage collects all peer instances.
+
+
+
+ When CanCollectPeers returns false, calls to
+ will throw
+ .
+
+
+
+ Garbage collection of peers is not supported.
+
+
+ has previously been invoked.
+
+
+
+
+ Garbage collects all peer instances.
+
+
+
+ The CollectPeersCore method will not be invoked
+ after Dispose() has been invoked.
+
+
+
+ Garbage collection of peers is not supported.
+
+
+
+
+ Dispose of all registered peer instances.
+
+
+
+ Calls on all peer instances.
+
+
+
+ Contains all exceptions thrown by registered instances when calling
+ .
+
+
+ has previously been invoked.
+
+
+
+
+ Dispose of all registered peer instances.
+
+
+
+
+ The DisposePeersCore method will not be invoked
+ after has been invoked.
+ Inheritors should invoke
+ on all peer instances. Should any peer throw from the Dispose
+ invocation, then DisposePeersCore should capture all thrown
+ exceptions and re-raise them within a .
+
+
+
+
+ Contains all exceptions thrown by .
+
+
+
+
+ Release all registered peer instances.
+
+
+
+ The JniValueManager unregisters all peers.
+ Methods such as will not find return any peers.
+
+
+ Peer values may still be used, even if not referenced by a JniValueManager.
+
+
+
+ has previously been invoked.
+
+
+
+
+ Release all registered peer instances.
+
+
+
+ The ReleasePeersCore method will not be invoked
+ after Dispose() has been invoked.
+
+
+
+
+
+
+ Register a managed peer.
+
+
+
+ The ReleasePeersCore method will not be invoked
+ after Dispose() has been invoked.
+
+
+
+
diff --git a/src/Java.Interop/GlobalSuppressions.cs b/src/Java.Interop/GlobalSuppressions.cs
index e5cc3b533..a479454c8 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.JavaScope.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/JavaScope.cs b/src/Java.Interop/Java.Interop/JavaScope.cs
new file mode 100644
index 000000000..67bc881ff
--- /dev/null
+++ b/src/Java.Interop/Java.Interop/JavaScope.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace Java.Interop {
+
+ public enum JavaScopeCleanup {
+ RegisterWithManager,
+ Dispose,
+ Release,
+ }
+
+ public ref struct JavaScope {
+
+ JavaScopeCleanup? cleanup;
+ PeerableCollection? scope;
+
+ public JavaScope (JavaScopeCleanup cleanup)
+ {
+ this.cleanup = cleanup;
+ scope = JniEnvironment.CurrentInfo.BeginScope (cleanup);
+ }
+
+ public void Dispose ()
+ {
+ if (cleanup == null || scope == null) {
+ return;
+ }
+ List? exceptions = null;
+ switch (cleanup) {
+ case JavaScopeCleanup.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/JniEnvironment.cs b/src/Java.Interop/Java.Interop/JniEnvironment.cs
index a577ecfdd..420a962fe 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 (JavaScopeCleanup 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 JavaScopeCleanup 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/JniRuntime.JniValueManager.cs b/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs
index 03ac65994..95b1c4327 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 ();
}
@@ -47,10 +49,14 @@ partial void SetValueManager (CreationOptions options)
valueManager = SetRuntime (manager);
}
+ ///
public abstract partial class JniValueManager : ISetRuntime, IDisposable {
+ readonly ConditionalWeakTable