diff --git a/samples/Hello/Program.cs b/samples/Hello/Program.cs index 293b6305e..07e573414 100644 --- a/samples/Hello/Program.cs +++ b/samples/Hello/Program.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Threading; using Mono.Options; @@ -9,10 +10,13 @@ namespace Hello { class App { + const int N = 1000000; + public static void Main (string[] args) { string? jvmPath = global::Java.InteropTests.TestJVM.GetJvmLibraryPath (); bool createMultipleVMs = false; + bool reportTiming = false; bool showHelp = false; var options = new OptionSet () { "Using the JVM from C#!", @@ -24,6 +28,9 @@ public static void Main (string[] args) { "m", "Create multiple Java VMs. This will likely creash.", v => createMultipleVMs = v != null }, + { "t", + $"Timing; invoke Object.hashCode() {N} times, print average.", + v => reportTiming = v != null }, { "h|help", "Show this message and exit.", v => showHelp = v != null }, @@ -33,23 +40,25 @@ public static void Main (string[] args) options.WriteOptionDescriptions (Console.Out); return; } - Console.WriteLine ("Hello World!"); var builder = new JreRuntimeOptions () { JniAddNativeMethodRegistrationAttributePresent = true, JvmLibraryPath = jvmPath, }; builder.AddOption ("-Xcheck:jni"); + var jvm = builder.CreateJreVM (); - Console.WriteLine ($"JniRuntime.CurrentRuntime == jvm? {ReferenceEquals (JniRuntime.CurrentRuntime, jvm)}"); - foreach (var h in JniRuntime.GetAvailableInvocationPointers ()) { - Console.WriteLine ("PRE: GetCreatedJavaVMHandles: {0}", h); - } - CreateJLO (); + if (reportTiming) { + ReportTiming (); + return; + } if (createMultipleVMs) { CreateAnotherJVM (); + return; } + + CreateJLO (); } static void CreateJLO () @@ -58,6 +67,17 @@ static void CreateJLO () Console.WriteLine ($"binding? {jlo.ToString ()}"); } + static void ReportTiming () + { + var jlo = new Java.Lang.Object (); + var t = Stopwatch.StartNew (); + for (int i = 0; i < N; ++i) { + jlo.GetHashCode (); + } + t.Stop (); + Console.WriteLine ($"Object.hashCode: {N} invocations. Total={t.Elapsed}; Average={t.Elapsed.TotalMilliseconds / (double) N}ms"); + } + static unsafe void CreateAnotherJVM () { Console.WriteLine ("Part 2!"); diff --git a/src/Java.Interop/Java.Interop.csproj b/src/Java.Interop/Java.Interop.csproj index 0d666353f..95d0802f0 100644 --- a/src/Java.Interop/Java.Interop.csproj +++ b/src/Java.Interop/Java.Interop.csproj @@ -26,7 +26,8 @@ $(ToolOutputFullPath) $(ToolOutputFullPath)Java.Interop.xml $(BuildToolOutputFullPath) - 8.0 + 9.0 + 8.0 enable true NU1702 diff --git a/src/Java.Interop/Java.Interop/JavaArray.cs b/src/Java.Interop/Java.Interop/JavaArray.cs index d2c69234b..5b05131b4 100644 --- a/src/Java.Interop/Java.Interop/JavaArray.cs +++ b/src/Java.Interop/Java.Interop/JavaArray.cs @@ -231,9 +231,9 @@ bool IList.IsFixedSize { object? IList.this [int index] { get {return this [index];} -#pragma warning disable 8601 +#pragma warning disable 8600,8601 set {this [index] = (T) value;} -#pragma warning restore 8601 +#pragma warning restore 8600,8601 } void ICollection.CopyTo (Array array, int index) diff --git a/src/Java.Interop/Java.Interop/JniEnvironment.Types.cs b/src/Java.Interop/Java.Interop/JniEnvironment.Types.cs index cfa49f8d9..af0ca4ff1 100644 --- a/src/Java.Interop/Java.Interop/JniEnvironment.Types.cs +++ b/src/Java.Interop/Java.Interop/JniEnvironment.Types.cs @@ -31,7 +31,12 @@ static Types () } } - public static unsafe JniObjectReference FindClass (string classname) + public static JniObjectReference FindClass (string classname) + { + return TryFindClass (classname, throwOnError: true); + } + + static unsafe JniObjectReference TryFindClass (string classname, bool throwOnError) { if (classname == null) throw new ArgumentNullException (nameof (classname)); @@ -85,6 +90,10 @@ public static unsafe JniObjectReference FindClass (string classname) } } + if (!throwOnError) { + (pendingException as IJavaPeerable)?.Dispose (); + return default; + } throw pendingException!; #endif // !FEATURE_JNIENVIRONMENT_JI_PINVOKES #if FEATURE_JNIOBJECTREFERENCE_SAFEHANDLES @@ -120,10 +129,27 @@ public static unsafe JniObjectReference FindClass (string classname) var loadClassThrown = new JniObjectReference (thrown, JniObjectReferenceType.Local); LogCreateLocalRef (loadClassThrown); pendingException = info.Runtime.GetExceptionForThrowable (ref loadClassThrown, JniObjectReferenceOptions.CopyAndDispose); + if (!throwOnError) { + (pendingException as IJavaPeerable)?.Dispose (); + return default; + } throw pendingException!; #endif // !FEATURE_JNIOBJECTREFERENCE_SAFEHANDLES } +#if NET + public static bool TryFindClass (string classname, out JniObjectReference instance) + { + if (classname == null) + throw new ArgumentNullException (nameof (classname)); + if (classname.Length == 0) + throw new ArgumentException ("'classname' cannot be a zero-length string.", nameof (classname)); + + instance = TryFindClass (classname, throwOnError: false); + return instance.IsValid; + } +#endif // NET + public static JniType? GetTypeFromInstance (JniObjectReference instance) { if (!instance.IsValid) diff --git a/src/Java.Interop/Java.Interop/JniMemberSignature.cs b/src/Java.Interop/Java.Interop/JniMemberSignature.cs new file mode 100644 index 000000000..946cf7bb5 --- /dev/null +++ b/src/Java.Interop/Java.Interop/JniMemberSignature.cs @@ -0,0 +1,134 @@ +#nullable enable + +#if NET + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace Java.Interop +{ + public struct JniMemberSignature : IEquatable + { + public static readonly JniMemberSignature Empty; + + string? memberName; + string? memberSignature; + + public string MemberName => memberName ?? throw new InvalidOperationException (); + public string MemberSignature => memberSignature ?? throw new InvalidOperationException (); + + public JniMemberSignature (string memberName, string memberSignature) + { + if (string.IsNullOrEmpty (memberName)) { + throw new ArgumentNullException (nameof (memberName)); + } + if (string.IsNullOrEmpty (memberSignature)) { + throw new ArgumentNullException (nameof (memberSignature)); + } + this.memberName = memberName; + this.memberSignature = memberSignature; + } + + public static int GetParameterCountFromMethodSignature (string jniMethodSignature) + { + if (jniMethodSignature.Length < "()V".Length || jniMethodSignature [0] != '(' ) { + throw new ArgumentException ( + $"Member signature `{jniMethodSignature}` is not a method signature. Method signatures must start with `(`.", + nameof (jniMethodSignature)); + } + int count = 0; + int index = 1; + while (index < jniMethodSignature.Length && + jniMethodSignature [index] != ')') { + ExtractType (jniMethodSignature, ref index); + count++; + } + return count; + } + + internal static (int StartIndex, int Length) ExtractType (string signature, ref int index) + { + AssertSignatureIndex (signature, index); + var i = index++; + switch (signature [i]) { + case '[': + if ((i+1) >= signature.Length) + throw new InvalidOperationException ($"Missing array type after '[' at index {i} in: `{signature}`"); + var rest = ExtractType (signature, ref index); + return (StartIndex: i, Length: index - i); + case 'B': + case 'C': + case 'D': + case 'F': + case 'I': + case 'J': + case 'S': + case 'V': + case 'Z': + return (StartIndex: i, Length: 1); + case 'L': + int depth = 0; + int e = index; + while (e < signature.Length) { + var c = signature [e++]; + if (depth == 0 && c == ';') + break; + } + if (e > signature.Length) + throw new InvalidOperationException ($"Missing reference type after `{signature [i]}` at index {i} in `{signature}`!"); + index = e; + return (StartIndex: i, Length: (e - i)); + default: + throw new InvalidOperationException ($"Unknown JNI Type `{signature [i]}` within: `{signature}`!"); + } + } + + internal static void AssertSignatureIndex (string signature, int index) + { + if (signature == null) + throw new ArgumentNullException (nameof (signature)); + if (signature.Length == 0) + throw new ArgumentException ("Descriptor cannot be empty string", nameof (signature)); + if (index >= signature.Length) + throw new ArgumentException ("index >= descriptor.Length", nameof (index)); + } + + public override int GetHashCode () + { + return (memberName?.GetHashCode () ?? 0) ^ + (memberSignature?.GetHashCode () ?? 0); + } + + public override bool Equals (object? obj) + { + var v = obj as JniMemberSignature?; + if (v.HasValue) + return Equals (v.Value); + return false; + } + + public bool Equals (JniMemberSignature other) + { + return memberName == other.memberName && + memberSignature == other.memberSignature; + } + + public override string ToString () + { + return $"{nameof (JniMemberSignature)} {{ " + + $"{nameof (MemberName)} = {(memberName == null ? "null" : "\"" + memberName + "\"")}" + + $", {nameof (MemberSignature)} = {(memberSignature == null ? "null" : "\"" + memberSignature + "\"")}" + + $"}}"; + } + + public static bool operator== (JniMemberSignature a, JniMemberSignature b) => a.Equals (b); + public static bool operator!= (JniMemberSignature a, JniMemberSignature b) => !a.Equals (b); + } +} + +#endif // NET diff --git a/src/Java.Interop/Java.Interop/JniMethodInfo.cs b/src/Java.Interop/Java.Interop/JniMethodInfo.cs index b2458fae7..d8f4bf3bc 100644 --- a/src/Java.Interop/Java.Interop/JniMethodInfo.cs +++ b/src/Java.Interop/Java.Interop/JniMethodInfo.cs @@ -10,6 +10,11 @@ public sealed class JniMethodInfo public bool IsStatic {get; private set;} +#if NET + internal JniType? StaticRedirect; + internal int? ParameterCount; +#endif //NET + internal bool IsValid { get {return ID != IntPtr.Zero;} } diff --git a/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods.cs b/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods.cs index 2a825486c..cee7af696 100644 --- a/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods.cs +++ b/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods.cs @@ -84,14 +84,46 @@ internal JniInstanceMethods GetConstructorsForType (Type declaringType) public JniMethodInfo GetMethodInfo (string encodedMember) { lock (InstanceMethods) { - if (!InstanceMethods.TryGetValue (encodedMember, out var m)) { - string method, signature; - JniPeerMembers.GetNameAndSignature (encodedMember, out method, out signature); - m = JniPeerType.GetInstanceMethod (method, signature); - InstanceMethods.Add (encodedMember, m); + if (InstanceMethods.TryGetValue (encodedMember, out var m)) { + return m; } - return m; } + string method, signature; + JniPeerMembers.GetNameAndSignature (encodedMember, out method, out signature); + var info = GetMethodInfo (method, signature); + lock (InstanceMethods) { + if (InstanceMethods.TryGetValue (encodedMember, out var m)) { + return m; + } + InstanceMethods.Add (encodedMember, info); + } + return info; + } + + JniMethodInfo GetMethodInfo (string method, string signature) + { +#if NET + var m = (JniMethodInfo?) null; + var newMethod = JniEnvironment.Runtime.TypeManager.GetReplacementMethodInfo (Members.JniPeerTypeName, method, signature); + if (newMethod.HasValue) { + var typeName = newMethod.Value.TargetJniType ?? Members.JniPeerTypeName; + var methodName = newMethod.Value.TargetJniMethodName ?? method; + var methodSig = newMethod.Value.TargetJniMethodSignature ?? signature; + + using var t = new JniType (typeName); + if (newMethod.Value.TargetJniMethodInstanceToStatic && + t.TryGetStaticMethod (methodName, methodSig, out m)) { + m.ParameterCount = newMethod.Value.TargetJniMethodParameterCount; + m.StaticRedirect = new JniType (typeName); + return m; + } + if (t.TryGetInstanceMethod (methodName, methodSig, out m)) { + return m; + } + Console.Error.WriteLine ($"warning: For declared method `{Members.JniPeerTypeName}.{method}.{signature}`, could not find requested method `{typeName}.{methodName}.{methodSig}`!"); + } +#endif // NET + return JniPeerType.GetInstanceMethod (method, signature); } public unsafe JniObjectReference StartCreateInstance (string constructorSignature, Type declaringType, JniArgumentValue* parameters) diff --git a/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods_Invoke.cs b/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods_Invoke.cs index df5325a22..0e96fa29c 100644 --- a/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods_Invoke.cs +++ b/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods_Invoke.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Diagnostics; namespace Java.Interop { @@ -8,33 +9,76 @@ partial class JniPeerMembers { partial class JniInstanceMethods { +#pragma warning disable CA1801 + static unsafe bool TryInvokeVoidStaticRedirect (JniMethodInfo method, IJavaPeerable self, JniArgumentValue* parameters) + { + +#if !NET + return false; +#else // NET + if (method.StaticRedirect == null || !method.ParameterCount.HasValue) { + return false; + } + + int c = method.ParameterCount.Value; + Debug.Assert (c > 0); + var p = stackalloc JniArgumentValue [c]; + p [0] = new JniArgumentValue (self); + for (int i = 0; i < c-1; ++i) { + p [i+1] = parameters [i]; + } + + JniEnvironment.StaticMethods.CallStaticVoidMethod (method.StaticRedirect.PeerReference, method, p); + return true; +#endif // NET + } +#pragma warning restore CA1801 + public unsafe void InvokeAbstractVoidMethod (string encodedMember, IJavaPeerable self, JniArgumentValue* parameters) { JniPeerMembers.AssertSelf (self); - var m = GetMethodInfo (encodedMember); + try { + var m = GetMethodInfo (encodedMember); + if (TryInvokeVoidStaticRedirect (m, self, parameters)) { + return; + } - JniEnvironment.InstanceMethods.CallVoidMethod (self.PeerReference, m, parameters); - GC.KeepAlive (self); - return; + JniEnvironment.InstanceMethods.CallVoidMethod (self.PeerReference, m, parameters); + return; + } + finally { + GC.KeepAlive (self); + } } public unsafe void InvokeVirtualVoidMethod (string encodedMember, IJavaPeerable self, JniArgumentValue* parameters) { JniPeerMembers.AssertSelf (self); - var declaringType = DeclaringType; - if (Members.UsesVirtualDispatch (self, declaringType)) { - var m = GetMethodInfo (encodedMember); - JniEnvironment.InstanceMethods.CallVoidMethod (self.PeerReference, m, parameters); + try { + var declaringType = DeclaringType; + if (Members.UsesVirtualDispatch (self, declaringType)) { + var m = GetMethodInfo (encodedMember); + if (TryInvokeVoidStaticRedirect (m, self, parameters)) { + return; + } + JniEnvironment.InstanceMethods.CallVoidMethod (self.PeerReference, m, parameters); + return; + } + var j = Members.GetPeerMembers (self); + var n = j.InstanceMethods.GetMethodInfo (encodedMember); + do { + if (TryInvokeVoidStaticRedirect (n, self, parameters)) { + return; + } + JniEnvironment.InstanceMethods.CallNonvirtualVoidMethod (self.PeerReference, j.JniPeerType.PeerReference, n, parameters); + return; + } while (false); + } + finally { GC.KeepAlive (self); - return; } - var j = Members.GetPeerMembers (self); - var n = j.InstanceMethods.GetMethodInfo (encodedMember); - JniEnvironment.InstanceMethods.CallNonvirtualVoidMethod (self.PeerReference, j.JniPeerType.PeerReference, n, parameters); - GC.KeepAlive (self); - return; } public unsafe void InvokeNonvirtualVoidMethod (string encodedMember, IJavaPeerable self, JniArgumentValue* parameters) @@ -42,39 +86,88 @@ public unsafe void InvokeNonvirtualVoidMethod (string encodedMember, IJavaPeerab JniPeerMembers.AssertSelf (self); var m = GetMethodInfo (encodedMember); + try { + if (TryInvokeVoidStaticRedirect (m, self, parameters)) { + return; + } + JniEnvironment.InstanceMethods.CallNonvirtualVoidMethod (self.PeerReference, JniPeerType.PeerReference, m, parameters); + return; + } + finally { + GC.KeepAlive (self); + } + } + +#pragma warning disable CA1801 + static unsafe bool TryInvokeBooleanStaticRedirect (JniMethodInfo method, IJavaPeerable self, JniArgumentValue* parameters, out bool r) + { + r = default; +#if !NET + return false; +#else // NET + if (method.StaticRedirect == null || !method.ParameterCount.HasValue) { + return false; + } - JniEnvironment.InstanceMethods.CallNonvirtualVoidMethod (self.PeerReference, JniPeerType.PeerReference, m, parameters); - GC.KeepAlive (self); - return; + int c = method.ParameterCount.Value; + Debug.Assert (c > 0); + var p = stackalloc JniArgumentValue [c]; + p [0] = new JniArgumentValue (self); + for (int i = 0; i < c-1; ++i) { + p [i+1] = parameters [i]; + } + + r = JniEnvironment.StaticMethods.CallStaticBooleanMethod (method.StaticRedirect.PeerReference, method, p); + return true; +#endif // NET } +#pragma warning restore CA1801 public unsafe bool InvokeAbstractBooleanMethod (string encodedMember, IJavaPeerable self, JniArgumentValue* parameters) { JniPeerMembers.AssertSelf (self); - var m = GetMethodInfo (encodedMember); + try { + var m = GetMethodInfo (encodedMember); + if (TryInvokeBooleanStaticRedirect (m, self, parameters, out bool r)) { + return r; + } - var r = JniEnvironment.InstanceMethods.CallBooleanMethod (self.PeerReference, m, parameters); - GC.KeepAlive (self); - return r; + r = JniEnvironment.InstanceMethods.CallBooleanMethod (self.PeerReference, m, parameters); + return r; + } + finally { + GC.KeepAlive (self); + } } public unsafe bool InvokeVirtualBooleanMethod (string encodedMember, IJavaPeerable self, JniArgumentValue* parameters) { JniPeerMembers.AssertSelf (self); - var declaringType = DeclaringType; - if (Members.UsesVirtualDispatch (self, declaringType)) { - var m = GetMethodInfo (encodedMember); - var _nr = JniEnvironment.InstanceMethods.CallBooleanMethod (self.PeerReference, m, parameters); + try { + var declaringType = DeclaringType; + if (Members.UsesVirtualDispatch (self, declaringType)) { + var m = GetMethodInfo (encodedMember); + if (TryInvokeBooleanStaticRedirect (m, self, parameters, out bool r)) { + return r; + } + r = JniEnvironment.InstanceMethods.CallBooleanMethod (self.PeerReference, m, parameters); + return r; + } + var j = Members.GetPeerMembers (self); + var n = j.InstanceMethods.GetMethodInfo (encodedMember); + do { + if (TryInvokeBooleanStaticRedirect (n, self, parameters, out bool r)) { + return r; + } + r = JniEnvironment.InstanceMethods.CallNonvirtualBooleanMethod (self.PeerReference, j.JniPeerType.PeerReference, n, parameters); + return r; + } while (false); + } + finally { GC.KeepAlive (self); - return _nr; } - var j = Members.GetPeerMembers (self); - var n = j.InstanceMethods.GetMethodInfo (encodedMember); - var r = JniEnvironment.InstanceMethods.CallNonvirtualBooleanMethod (self.PeerReference, j.JniPeerType.PeerReference, n, parameters); - GC.KeepAlive (self); - return r; } public unsafe bool InvokeNonvirtualBooleanMethod (string encodedMember, IJavaPeerable self, JniArgumentValue* parameters) @@ -82,39 +175,88 @@ public unsafe bool InvokeNonvirtualBooleanMethod (string encodedMember, IJavaPee JniPeerMembers.AssertSelf (self); var m = GetMethodInfo (encodedMember); + try { + if (TryInvokeBooleanStaticRedirect (m, self, parameters, out bool r)) { + return r; + } + r = JniEnvironment.InstanceMethods.CallNonvirtualBooleanMethod (self.PeerReference, JniPeerType.PeerReference, m, parameters); + return r; + } + finally { + GC.KeepAlive (self); + } + } + +#pragma warning disable CA1801 + static unsafe bool TryInvokeSByteStaticRedirect (JniMethodInfo method, IJavaPeerable self, JniArgumentValue* parameters, out sbyte r) + { + r = default; +#if !NET + return false; +#else // NET + if (method.StaticRedirect == null || !method.ParameterCount.HasValue) { + return false; + } - var r = JniEnvironment.InstanceMethods.CallNonvirtualBooleanMethod (self.PeerReference, JniPeerType.PeerReference, m, parameters); - GC.KeepAlive (self); - return r; + int c = method.ParameterCount.Value; + Debug.Assert (c > 0); + var p = stackalloc JniArgumentValue [c]; + p [0] = new JniArgumentValue (self); + for (int i = 0; i < c-1; ++i) { + p [i+1] = parameters [i]; + } + + r = JniEnvironment.StaticMethods.CallStaticByteMethod (method.StaticRedirect.PeerReference, method, p); + return true; +#endif // NET } +#pragma warning restore CA1801 public unsafe sbyte InvokeAbstractSByteMethod (string encodedMember, IJavaPeerable self, JniArgumentValue* parameters) { JniPeerMembers.AssertSelf (self); - var m = GetMethodInfo (encodedMember); + try { + var m = GetMethodInfo (encodedMember); + if (TryInvokeSByteStaticRedirect (m, self, parameters, out sbyte r)) { + return r; + } - var r = JniEnvironment.InstanceMethods.CallByteMethod (self.PeerReference, m, parameters); - GC.KeepAlive (self); - return r; + r = JniEnvironment.InstanceMethods.CallByteMethod (self.PeerReference, m, parameters); + return r; + } + finally { + GC.KeepAlive (self); + } } public unsafe sbyte InvokeVirtualSByteMethod (string encodedMember, IJavaPeerable self, JniArgumentValue* parameters) { JniPeerMembers.AssertSelf (self); - var declaringType = DeclaringType; - if (Members.UsesVirtualDispatch (self, declaringType)) { - var m = GetMethodInfo (encodedMember); - var _nr = JniEnvironment.InstanceMethods.CallByteMethod (self.PeerReference, m, parameters); + try { + var declaringType = DeclaringType; + if (Members.UsesVirtualDispatch (self, declaringType)) { + var m = GetMethodInfo (encodedMember); + if (TryInvokeSByteStaticRedirect (m, self, parameters, out sbyte r)) { + return r; + } + r = JniEnvironment.InstanceMethods.CallByteMethod (self.PeerReference, m, parameters); + return r; + } + var j = Members.GetPeerMembers (self); + var n = j.InstanceMethods.GetMethodInfo (encodedMember); + do { + if (TryInvokeSByteStaticRedirect (n, self, parameters, out sbyte r)) { + return r; + } + r = JniEnvironment.InstanceMethods.CallNonvirtualByteMethod (self.PeerReference, j.JniPeerType.PeerReference, n, parameters); + return r; + } while (false); + } + finally { GC.KeepAlive (self); - return _nr; } - var j = Members.GetPeerMembers (self); - var n = j.InstanceMethods.GetMethodInfo (encodedMember); - var r = JniEnvironment.InstanceMethods.CallNonvirtualByteMethod (self.PeerReference, j.JniPeerType.PeerReference, n, parameters); - GC.KeepAlive (self); - return r; } public unsafe sbyte InvokeNonvirtualSByteMethod (string encodedMember, IJavaPeerable self, JniArgumentValue* parameters) @@ -122,39 +264,88 @@ public unsafe sbyte InvokeNonvirtualSByteMethod (string encodedMember, IJavaPeer JniPeerMembers.AssertSelf (self); var m = GetMethodInfo (encodedMember); + try { + if (TryInvokeSByteStaticRedirect (m, self, parameters, out sbyte r)) { + return r; + } + r = JniEnvironment.InstanceMethods.CallNonvirtualByteMethod (self.PeerReference, JniPeerType.PeerReference, m, parameters); + return r; + } + finally { + GC.KeepAlive (self); + } + } + +#pragma warning disable CA1801 + static unsafe bool TryInvokeCharStaticRedirect (JniMethodInfo method, IJavaPeerable self, JniArgumentValue* parameters, out char r) + { + r = default; +#if !NET + return false; +#else // NET + if (method.StaticRedirect == null || !method.ParameterCount.HasValue) { + return false; + } - var r = JniEnvironment.InstanceMethods.CallNonvirtualByteMethod (self.PeerReference, JniPeerType.PeerReference, m, parameters); - GC.KeepAlive (self); - return r; + int c = method.ParameterCount.Value; + Debug.Assert (c > 0); + var p = stackalloc JniArgumentValue [c]; + p [0] = new JniArgumentValue (self); + for (int i = 0; i < c-1; ++i) { + p [i+1] = parameters [i]; + } + + r = JniEnvironment.StaticMethods.CallStaticCharMethod (method.StaticRedirect.PeerReference, method, p); + return true; +#endif // NET } +#pragma warning restore CA1801 public unsafe char InvokeAbstractCharMethod (string encodedMember, IJavaPeerable self, JniArgumentValue* parameters) { JniPeerMembers.AssertSelf (self); - var m = GetMethodInfo (encodedMember); + try { + var m = GetMethodInfo (encodedMember); + if (TryInvokeCharStaticRedirect (m, self, parameters, out char r)) { + return r; + } - var r = JniEnvironment.InstanceMethods.CallCharMethod (self.PeerReference, m, parameters); - GC.KeepAlive (self); - return r; + r = JniEnvironment.InstanceMethods.CallCharMethod (self.PeerReference, m, parameters); + return r; + } + finally { + GC.KeepAlive (self); + } } public unsafe char InvokeVirtualCharMethod (string encodedMember, IJavaPeerable self, JniArgumentValue* parameters) { JniPeerMembers.AssertSelf (self); - var declaringType = DeclaringType; - if (Members.UsesVirtualDispatch (self, declaringType)) { - var m = GetMethodInfo (encodedMember); - var _nr = JniEnvironment.InstanceMethods.CallCharMethod (self.PeerReference, m, parameters); + try { + var declaringType = DeclaringType; + if (Members.UsesVirtualDispatch (self, declaringType)) { + var m = GetMethodInfo (encodedMember); + if (TryInvokeCharStaticRedirect (m, self, parameters, out char r)) { + return r; + } + r = JniEnvironment.InstanceMethods.CallCharMethod (self.PeerReference, m, parameters); + return r; + } + var j = Members.GetPeerMembers (self); + var n = j.InstanceMethods.GetMethodInfo (encodedMember); + do { + if (TryInvokeCharStaticRedirect (n, self, parameters, out char r)) { + return r; + } + r = JniEnvironment.InstanceMethods.CallNonvirtualCharMethod (self.PeerReference, j.JniPeerType.PeerReference, n, parameters); + return r; + } while (false); + } + finally { GC.KeepAlive (self); - return _nr; } - var j = Members.GetPeerMembers (self); - var n = j.InstanceMethods.GetMethodInfo (encodedMember); - var r = JniEnvironment.InstanceMethods.CallNonvirtualCharMethod (self.PeerReference, j.JniPeerType.PeerReference, n, parameters); - GC.KeepAlive (self); - return r; } public unsafe char InvokeNonvirtualCharMethod (string encodedMember, IJavaPeerable self, JniArgumentValue* parameters) @@ -162,39 +353,88 @@ public unsafe char InvokeNonvirtualCharMethod (string encodedMember, IJavaPeerab JniPeerMembers.AssertSelf (self); var m = GetMethodInfo (encodedMember); + try { + if (TryInvokeCharStaticRedirect (m, self, parameters, out char r)) { + return r; + } + r = JniEnvironment.InstanceMethods.CallNonvirtualCharMethod (self.PeerReference, JniPeerType.PeerReference, m, parameters); + return r; + } + finally { + GC.KeepAlive (self); + } + } + +#pragma warning disable CA1801 + static unsafe bool TryInvokeInt16StaticRedirect (JniMethodInfo method, IJavaPeerable self, JniArgumentValue* parameters, out short r) + { + r = default; +#if !NET + return false; +#else // NET + if (method.StaticRedirect == null || !method.ParameterCount.HasValue) { + return false; + } - var r = JniEnvironment.InstanceMethods.CallNonvirtualCharMethod (self.PeerReference, JniPeerType.PeerReference, m, parameters); - GC.KeepAlive (self); - return r; + int c = method.ParameterCount.Value; + Debug.Assert (c > 0); + var p = stackalloc JniArgumentValue [c]; + p [0] = new JniArgumentValue (self); + for (int i = 0; i < c-1; ++i) { + p [i+1] = parameters [i]; + } + + r = JniEnvironment.StaticMethods.CallStaticShortMethod (method.StaticRedirect.PeerReference, method, p); + return true; +#endif // NET } +#pragma warning restore CA1801 public unsafe short InvokeAbstractInt16Method (string encodedMember, IJavaPeerable self, JniArgumentValue* parameters) { JniPeerMembers.AssertSelf (self); - var m = GetMethodInfo (encodedMember); + try { + var m = GetMethodInfo (encodedMember); + if (TryInvokeInt16StaticRedirect (m, self, parameters, out short r)) { + return r; + } - var r = JniEnvironment.InstanceMethods.CallShortMethod (self.PeerReference, m, parameters); - GC.KeepAlive (self); - return r; + r = JniEnvironment.InstanceMethods.CallShortMethod (self.PeerReference, m, parameters); + return r; + } + finally { + GC.KeepAlive (self); + } } public unsafe short InvokeVirtualInt16Method (string encodedMember, IJavaPeerable self, JniArgumentValue* parameters) { JniPeerMembers.AssertSelf (self); - var declaringType = DeclaringType; - if (Members.UsesVirtualDispatch (self, declaringType)) { - var m = GetMethodInfo (encodedMember); - var _nr = JniEnvironment.InstanceMethods.CallShortMethod (self.PeerReference, m, parameters); + try { + var declaringType = DeclaringType; + if (Members.UsesVirtualDispatch (self, declaringType)) { + var m = GetMethodInfo (encodedMember); + if (TryInvokeInt16StaticRedirect (m, self, parameters, out short r)) { + return r; + } + r = JniEnvironment.InstanceMethods.CallShortMethod (self.PeerReference, m, parameters); + return r; + } + var j = Members.GetPeerMembers (self); + var n = j.InstanceMethods.GetMethodInfo (encodedMember); + do { + if (TryInvokeInt16StaticRedirect (n, self, parameters, out short r)) { + return r; + } + r = JniEnvironment.InstanceMethods.CallNonvirtualShortMethod (self.PeerReference, j.JniPeerType.PeerReference, n, parameters); + return r; + } while (false); + } + finally { GC.KeepAlive (self); - return _nr; } - var j = Members.GetPeerMembers (self); - var n = j.InstanceMethods.GetMethodInfo (encodedMember); - var r = JniEnvironment.InstanceMethods.CallNonvirtualShortMethod (self.PeerReference, j.JniPeerType.PeerReference, n, parameters); - GC.KeepAlive (self); - return r; } public unsafe short InvokeNonvirtualInt16Method (string encodedMember, IJavaPeerable self, JniArgumentValue* parameters) @@ -202,39 +442,88 @@ public unsafe short InvokeNonvirtualInt16Method (string encodedMember, IJavaPeer JniPeerMembers.AssertSelf (self); var m = GetMethodInfo (encodedMember); + try { + if (TryInvokeInt16StaticRedirect (m, self, parameters, out short r)) { + return r; + } + r = JniEnvironment.InstanceMethods.CallNonvirtualShortMethod (self.PeerReference, JniPeerType.PeerReference, m, parameters); + return r; + } + finally { + GC.KeepAlive (self); + } + } + +#pragma warning disable CA1801 + static unsafe bool TryInvokeInt32StaticRedirect (JniMethodInfo method, IJavaPeerable self, JniArgumentValue* parameters, out int r) + { + r = default; +#if !NET + return false; +#else // NET + if (method.StaticRedirect == null || !method.ParameterCount.HasValue) { + return false; + } - var r = JniEnvironment.InstanceMethods.CallNonvirtualShortMethod (self.PeerReference, JniPeerType.PeerReference, m, parameters); - GC.KeepAlive (self); - return r; + int c = method.ParameterCount.Value; + Debug.Assert (c > 0); + var p = stackalloc JniArgumentValue [c]; + p [0] = new JniArgumentValue (self); + for (int i = 0; i < c-1; ++i) { + p [i+1] = parameters [i]; + } + + r = JniEnvironment.StaticMethods.CallStaticIntMethod (method.StaticRedirect.PeerReference, method, p); + return true; +#endif // NET } +#pragma warning restore CA1801 public unsafe int InvokeAbstractInt32Method (string encodedMember, IJavaPeerable self, JniArgumentValue* parameters) { JniPeerMembers.AssertSelf (self); - var m = GetMethodInfo (encodedMember); + try { + var m = GetMethodInfo (encodedMember); + if (TryInvokeInt32StaticRedirect (m, self, parameters, out int r)) { + return r; + } - var r = JniEnvironment.InstanceMethods.CallIntMethod (self.PeerReference, m, parameters); - GC.KeepAlive (self); - return r; + r = JniEnvironment.InstanceMethods.CallIntMethod (self.PeerReference, m, parameters); + return r; + } + finally { + GC.KeepAlive (self); + } } public unsafe int InvokeVirtualInt32Method (string encodedMember, IJavaPeerable self, JniArgumentValue* parameters) { JniPeerMembers.AssertSelf (self); - var declaringType = DeclaringType; - if (Members.UsesVirtualDispatch (self, declaringType)) { - var m = GetMethodInfo (encodedMember); - var _nr = JniEnvironment.InstanceMethods.CallIntMethod (self.PeerReference, m, parameters); + try { + var declaringType = DeclaringType; + if (Members.UsesVirtualDispatch (self, declaringType)) { + var m = GetMethodInfo (encodedMember); + if (TryInvokeInt32StaticRedirect (m, self, parameters, out int r)) { + return r; + } + r = JniEnvironment.InstanceMethods.CallIntMethod (self.PeerReference, m, parameters); + return r; + } + var j = Members.GetPeerMembers (self); + var n = j.InstanceMethods.GetMethodInfo (encodedMember); + do { + if (TryInvokeInt32StaticRedirect (n, self, parameters, out int r)) { + return r; + } + r = JniEnvironment.InstanceMethods.CallNonvirtualIntMethod (self.PeerReference, j.JniPeerType.PeerReference, n, parameters); + return r; + } while (false); + } + finally { GC.KeepAlive (self); - return _nr; } - var j = Members.GetPeerMembers (self); - var n = j.InstanceMethods.GetMethodInfo (encodedMember); - var r = JniEnvironment.InstanceMethods.CallNonvirtualIntMethod (self.PeerReference, j.JniPeerType.PeerReference, n, parameters); - GC.KeepAlive (self); - return r; } public unsafe int InvokeNonvirtualInt32Method (string encodedMember, IJavaPeerable self, JniArgumentValue* parameters) @@ -242,39 +531,88 @@ public unsafe int InvokeNonvirtualInt32Method (string encodedMember, IJavaPeerab JniPeerMembers.AssertSelf (self); var m = GetMethodInfo (encodedMember); + try { + if (TryInvokeInt32StaticRedirect (m, self, parameters, out int r)) { + return r; + } + r = JniEnvironment.InstanceMethods.CallNonvirtualIntMethod (self.PeerReference, JniPeerType.PeerReference, m, parameters); + return r; + } + finally { + GC.KeepAlive (self); + } + } + +#pragma warning disable CA1801 + static unsafe bool TryInvokeInt64StaticRedirect (JniMethodInfo method, IJavaPeerable self, JniArgumentValue* parameters, out long r) + { + r = default; +#if !NET + return false; +#else // NET + if (method.StaticRedirect == null || !method.ParameterCount.HasValue) { + return false; + } + + int c = method.ParameterCount.Value; + Debug.Assert (c > 0); + var p = stackalloc JniArgumentValue [c]; + p [0] = new JniArgumentValue (self); + for (int i = 0; i < c-1; ++i) { + p [i+1] = parameters [i]; + } - var r = JniEnvironment.InstanceMethods.CallNonvirtualIntMethod (self.PeerReference, JniPeerType.PeerReference, m, parameters); - GC.KeepAlive (self); - return r; + r = JniEnvironment.StaticMethods.CallStaticLongMethod (method.StaticRedirect.PeerReference, method, p); + return true; +#endif // NET } +#pragma warning restore CA1801 public unsafe long InvokeAbstractInt64Method (string encodedMember, IJavaPeerable self, JniArgumentValue* parameters) { JniPeerMembers.AssertSelf (self); - var m = GetMethodInfo (encodedMember); + try { + var m = GetMethodInfo (encodedMember); + if (TryInvokeInt64StaticRedirect (m, self, parameters, out long r)) { + return r; + } - var r = JniEnvironment.InstanceMethods.CallLongMethod (self.PeerReference, m, parameters); - GC.KeepAlive (self); - return r; + r = JniEnvironment.InstanceMethods.CallLongMethod (self.PeerReference, m, parameters); + return r; + } + finally { + GC.KeepAlive (self); + } } public unsafe long InvokeVirtualInt64Method (string encodedMember, IJavaPeerable self, JniArgumentValue* parameters) { JniPeerMembers.AssertSelf (self); - var declaringType = DeclaringType; - if (Members.UsesVirtualDispatch (self, declaringType)) { - var m = GetMethodInfo (encodedMember); - var _nr = JniEnvironment.InstanceMethods.CallLongMethod (self.PeerReference, m, parameters); + try { + var declaringType = DeclaringType; + if (Members.UsesVirtualDispatch (self, declaringType)) { + var m = GetMethodInfo (encodedMember); + if (TryInvokeInt64StaticRedirect (m, self, parameters, out long r)) { + return r; + } + r = JniEnvironment.InstanceMethods.CallLongMethod (self.PeerReference, m, parameters); + return r; + } + var j = Members.GetPeerMembers (self); + var n = j.InstanceMethods.GetMethodInfo (encodedMember); + do { + if (TryInvokeInt64StaticRedirect (n, self, parameters, out long r)) { + return r; + } + r = JniEnvironment.InstanceMethods.CallNonvirtualLongMethod (self.PeerReference, j.JniPeerType.PeerReference, n, parameters); + return r; + } while (false); + } + finally { GC.KeepAlive (self); - return _nr; } - var j = Members.GetPeerMembers (self); - var n = j.InstanceMethods.GetMethodInfo (encodedMember); - var r = JniEnvironment.InstanceMethods.CallNonvirtualLongMethod (self.PeerReference, j.JniPeerType.PeerReference, n, parameters); - GC.KeepAlive (self); - return r; } public unsafe long InvokeNonvirtualInt64Method (string encodedMember, IJavaPeerable self, JniArgumentValue* parameters) @@ -282,39 +620,88 @@ public unsafe long InvokeNonvirtualInt64Method (string encodedMember, IJavaPeera JniPeerMembers.AssertSelf (self); var m = GetMethodInfo (encodedMember); + try { + if (TryInvokeInt64StaticRedirect (m, self, parameters, out long r)) { + return r; + } + r = JniEnvironment.InstanceMethods.CallNonvirtualLongMethod (self.PeerReference, JniPeerType.PeerReference, m, parameters); + return r; + } + finally { + GC.KeepAlive (self); + } + } + +#pragma warning disable CA1801 + static unsafe bool TryInvokeSingleStaticRedirect (JniMethodInfo method, IJavaPeerable self, JniArgumentValue* parameters, out float r) + { + r = default; +#if !NET + return false; +#else // NET + if (method.StaticRedirect == null || !method.ParameterCount.HasValue) { + return false; + } + + int c = method.ParameterCount.Value; + Debug.Assert (c > 0); + var p = stackalloc JniArgumentValue [c]; + p [0] = new JniArgumentValue (self); + for (int i = 0; i < c-1; ++i) { + p [i+1] = parameters [i]; + } - var r = JniEnvironment.InstanceMethods.CallNonvirtualLongMethod (self.PeerReference, JniPeerType.PeerReference, m, parameters); - GC.KeepAlive (self); - return r; + r = JniEnvironment.StaticMethods.CallStaticFloatMethod (method.StaticRedirect.PeerReference, method, p); + return true; +#endif // NET } +#pragma warning restore CA1801 public unsafe float InvokeAbstractSingleMethod (string encodedMember, IJavaPeerable self, JniArgumentValue* parameters) { JniPeerMembers.AssertSelf (self); - var m = GetMethodInfo (encodedMember); + try { + var m = GetMethodInfo (encodedMember); + if (TryInvokeSingleStaticRedirect (m, self, parameters, out float r)) { + return r; + } - var r = JniEnvironment.InstanceMethods.CallFloatMethod (self.PeerReference, m, parameters); - GC.KeepAlive (self); - return r; + r = JniEnvironment.InstanceMethods.CallFloatMethod (self.PeerReference, m, parameters); + return r; + } + finally { + GC.KeepAlive (self); + } } public unsafe float InvokeVirtualSingleMethod (string encodedMember, IJavaPeerable self, JniArgumentValue* parameters) { JniPeerMembers.AssertSelf (self); - var declaringType = DeclaringType; - if (Members.UsesVirtualDispatch (self, declaringType)) { - var m = GetMethodInfo (encodedMember); - var _nr = JniEnvironment.InstanceMethods.CallFloatMethod (self.PeerReference, m, parameters); + try { + var declaringType = DeclaringType; + if (Members.UsesVirtualDispatch (self, declaringType)) { + var m = GetMethodInfo (encodedMember); + if (TryInvokeSingleStaticRedirect (m, self, parameters, out float r)) { + return r; + } + r = JniEnvironment.InstanceMethods.CallFloatMethod (self.PeerReference, m, parameters); + return r; + } + var j = Members.GetPeerMembers (self); + var n = j.InstanceMethods.GetMethodInfo (encodedMember); + do { + if (TryInvokeSingleStaticRedirect (n, self, parameters, out float r)) { + return r; + } + r = JniEnvironment.InstanceMethods.CallNonvirtualFloatMethod (self.PeerReference, j.JniPeerType.PeerReference, n, parameters); + return r; + } while (false); + } + finally { GC.KeepAlive (self); - return _nr; } - var j = Members.GetPeerMembers (self); - var n = j.InstanceMethods.GetMethodInfo (encodedMember); - var r = JniEnvironment.InstanceMethods.CallNonvirtualFloatMethod (self.PeerReference, j.JniPeerType.PeerReference, n, parameters); - GC.KeepAlive (self); - return r; } public unsafe float InvokeNonvirtualSingleMethod (string encodedMember, IJavaPeerable self, JniArgumentValue* parameters) @@ -322,39 +709,88 @@ public unsafe float InvokeNonvirtualSingleMethod (string encodedMember, IJavaPee JniPeerMembers.AssertSelf (self); var m = GetMethodInfo (encodedMember); + try { + if (TryInvokeSingleStaticRedirect (m, self, parameters, out float r)) { + return r; + } + r = JniEnvironment.InstanceMethods.CallNonvirtualFloatMethod (self.PeerReference, JniPeerType.PeerReference, m, parameters); + return r; + } + finally { + GC.KeepAlive (self); + } + } - var r = JniEnvironment.InstanceMethods.CallNonvirtualFloatMethod (self.PeerReference, JniPeerType.PeerReference, m, parameters); - GC.KeepAlive (self); - return r; +#pragma warning disable CA1801 + static unsafe bool TryInvokeDoubleStaticRedirect (JniMethodInfo method, IJavaPeerable self, JniArgumentValue* parameters, out double r) + { + r = default; +#if !NET + return false; +#else // NET + if (method.StaticRedirect == null || !method.ParameterCount.HasValue) { + return false; + } + + int c = method.ParameterCount.Value; + Debug.Assert (c > 0); + var p = stackalloc JniArgumentValue [c]; + p [0] = new JniArgumentValue (self); + for (int i = 0; i < c-1; ++i) { + p [i+1] = parameters [i]; + } + + r = JniEnvironment.StaticMethods.CallStaticDoubleMethod (method.StaticRedirect.PeerReference, method, p); + return true; +#endif // NET } +#pragma warning restore CA1801 public unsafe double InvokeAbstractDoubleMethod (string encodedMember, IJavaPeerable self, JniArgumentValue* parameters) { JniPeerMembers.AssertSelf (self); - var m = GetMethodInfo (encodedMember); + try { + var m = GetMethodInfo (encodedMember); + if (TryInvokeDoubleStaticRedirect (m, self, parameters, out double r)) { + return r; + } - var r = JniEnvironment.InstanceMethods.CallDoubleMethod (self.PeerReference, m, parameters); - GC.KeepAlive (self); - return r; + r = JniEnvironment.InstanceMethods.CallDoubleMethod (self.PeerReference, m, parameters); + return r; + } + finally { + GC.KeepAlive (self); + } } public unsafe double InvokeVirtualDoubleMethod (string encodedMember, IJavaPeerable self, JniArgumentValue* parameters) { JniPeerMembers.AssertSelf (self); - var declaringType = DeclaringType; - if (Members.UsesVirtualDispatch (self, declaringType)) { - var m = GetMethodInfo (encodedMember); - var _nr = JniEnvironment.InstanceMethods.CallDoubleMethod (self.PeerReference, m, parameters); + try { + var declaringType = DeclaringType; + if (Members.UsesVirtualDispatch (self, declaringType)) { + var m = GetMethodInfo (encodedMember); + if (TryInvokeDoubleStaticRedirect (m, self, parameters, out double r)) { + return r; + } + r = JniEnvironment.InstanceMethods.CallDoubleMethod (self.PeerReference, m, parameters); + return r; + } + var j = Members.GetPeerMembers (self); + var n = j.InstanceMethods.GetMethodInfo (encodedMember); + do { + if (TryInvokeDoubleStaticRedirect (n, self, parameters, out double r)) { + return r; + } + r = JniEnvironment.InstanceMethods.CallNonvirtualDoubleMethod (self.PeerReference, j.JniPeerType.PeerReference, n, parameters); + return r; + } while (false); + } + finally { GC.KeepAlive (self); - return _nr; } - var j = Members.GetPeerMembers (self); - var n = j.InstanceMethods.GetMethodInfo (encodedMember); - var r = JniEnvironment.InstanceMethods.CallNonvirtualDoubleMethod (self.PeerReference, j.JniPeerType.PeerReference, n, parameters); - GC.KeepAlive (self); - return r; } public unsafe double InvokeNonvirtualDoubleMethod (string encodedMember, IJavaPeerable self, JniArgumentValue* parameters) @@ -362,39 +798,88 @@ public unsafe double InvokeNonvirtualDoubleMethod (string encodedMember, IJavaPe JniPeerMembers.AssertSelf (self); var m = GetMethodInfo (encodedMember); + try { + if (TryInvokeDoubleStaticRedirect (m, self, parameters, out double r)) { + return r; + } + r = JniEnvironment.InstanceMethods.CallNonvirtualDoubleMethod (self.PeerReference, JniPeerType.PeerReference, m, parameters); + return r; + } + finally { + GC.KeepAlive (self); + } + } + +#pragma warning disable CA1801 + static unsafe bool TryInvokeObjectStaticRedirect (JniMethodInfo method, IJavaPeerable self, JniArgumentValue* parameters, out JniObjectReference r) + { + r = default; +#if !NET + return false; +#else // NET + if (method.StaticRedirect == null || !method.ParameterCount.HasValue) { + return false; + } - var r = JniEnvironment.InstanceMethods.CallNonvirtualDoubleMethod (self.PeerReference, JniPeerType.PeerReference, m, parameters); - GC.KeepAlive (self); - return r; + int c = method.ParameterCount.Value; + Debug.Assert (c > 0); + var p = stackalloc JniArgumentValue [c]; + p [0] = new JniArgumentValue (self); + for (int i = 0; i < c-1; ++i) { + p [i+1] = parameters [i]; + } + + r = JniEnvironment.StaticMethods.CallStaticObjectMethod (method.StaticRedirect.PeerReference, method, p); + return true; +#endif // NET } +#pragma warning restore CA1801 public unsafe JniObjectReference InvokeAbstractObjectMethod (string encodedMember, IJavaPeerable self, JniArgumentValue* parameters) { JniPeerMembers.AssertSelf (self); - var m = GetMethodInfo (encodedMember); + try { + var m = GetMethodInfo (encodedMember); + if (TryInvokeObjectStaticRedirect (m, self, parameters, out JniObjectReference r)) { + return r; + } - var r = JniEnvironment.InstanceMethods.CallObjectMethod (self.PeerReference, m, parameters); - GC.KeepAlive (self); - return r; + r = JniEnvironment.InstanceMethods.CallObjectMethod (self.PeerReference, m, parameters); + return r; + } + finally { + GC.KeepAlive (self); + } } public unsafe JniObjectReference InvokeVirtualObjectMethod (string encodedMember, IJavaPeerable self, JniArgumentValue* parameters) { JniPeerMembers.AssertSelf (self); - var declaringType = DeclaringType; - if (Members.UsesVirtualDispatch (self, declaringType)) { - var m = GetMethodInfo (encodedMember); - var _nr = JniEnvironment.InstanceMethods.CallObjectMethod (self.PeerReference, m, parameters); + try { + var declaringType = DeclaringType; + if (Members.UsesVirtualDispatch (self, declaringType)) { + var m = GetMethodInfo (encodedMember); + if (TryInvokeObjectStaticRedirect (m, self, parameters, out JniObjectReference r)) { + return r; + } + r = JniEnvironment.InstanceMethods.CallObjectMethod (self.PeerReference, m, parameters); + return r; + } + var j = Members.GetPeerMembers (self); + var n = j.InstanceMethods.GetMethodInfo (encodedMember); + do { + if (TryInvokeObjectStaticRedirect (n, self, parameters, out JniObjectReference r)) { + return r; + } + r = JniEnvironment.InstanceMethods.CallNonvirtualObjectMethod (self.PeerReference, j.JniPeerType.PeerReference, n, parameters); + return r; + } while (false); + } + finally { GC.KeepAlive (self); - return _nr; } - var j = Members.GetPeerMembers (self); - var n = j.InstanceMethods.GetMethodInfo (encodedMember); - var r = JniEnvironment.InstanceMethods.CallNonvirtualObjectMethod (self.PeerReference, j.JniPeerType.PeerReference, n, parameters); - GC.KeepAlive (self); - return r; } public unsafe JniObjectReference InvokeNonvirtualObjectMethod (string encodedMember, IJavaPeerable self, JniArgumentValue* parameters) @@ -402,10 +887,16 @@ public unsafe JniObjectReference InvokeNonvirtualObjectMethod (string encodedMem JniPeerMembers.AssertSelf (self); var m = GetMethodInfo (encodedMember); - - var r = JniEnvironment.InstanceMethods.CallNonvirtualObjectMethod (self.PeerReference, JniPeerType.PeerReference, m, parameters); - GC.KeepAlive (self); - return r; + try { + if (TryInvokeObjectStaticRedirect (m, self, parameters, out JniObjectReference r)) { + return r; + } + r = JniEnvironment.InstanceMethods.CallNonvirtualObjectMethod (self.PeerReference, JniPeerType.PeerReference, m, parameters); + return r; + } + finally { + GC.KeepAlive (self); + } } } } diff --git a/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods_Invoke.tt b/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods_Invoke.tt index 04f8178fd..a326b6b1f 100644 --- a/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods_Invoke.tt +++ b/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods_Invoke.tt @@ -21,6 +21,7 @@ #nullable enable using System; +using System.Diagnostics; namespace Java.Interop { @@ -29,35 +30,83 @@ namespace Java.Interop { partial class JniInstanceMethods { <# foreach (var returnType in jniReturnTypes) { + string byRefParamDecl = returnType.ReturnType == "void" ? "" : ", out " + returnType.ReturnType + " r"; + // string byRefParam = returnType.ReturnType == "void" ? "" : " r"; + string setByRefToDefault = returnType.ReturnType == "void" ? "" : "r = default;"; + string setByRefParam = returnType.ReturnType == "void" ? "" : "r = "; + string returnByRefParam = returnType.ReturnType == "void" ? "return" : "return r"; #> +#pragma warning disable CA1801 + static unsafe bool TryInvoke<#= returnType.ManagedType #>StaticRedirect (JniMethodInfo method, IJavaPeerable self, JniArgumentValue* parameters<#= byRefParamDecl #>) + { + <#= setByRefToDefault #> +#if !NET + return false; +#else // NET + if (method.StaticRedirect == null || !method.ParameterCount.HasValue) { + return false; + } + + int c = method.ParameterCount.Value; + Debug.Assert (c > 0); + var p = stackalloc JniArgumentValue [c]; + p [0] = new JniArgumentValue (self); + for (int i = 0; i < c-1; ++i) { + p [i+1] = parameters [i]; + } + + <#= setByRefParam #>JniEnvironment.StaticMethods.CallStatic<#= returnType.JniCallType #>Method (method.StaticRedirect.PeerReference, method, p); + return true; +#endif // NET + } +#pragma warning restore CA1801 + public unsafe <#= returnType.ReturnType #> InvokeAbstract<#= returnType.ManagedType #>Method (string encodedMember, IJavaPeerable self, JniArgumentValue* parameters) { JniPeerMembers.AssertSelf (self); - var m = GetMethodInfo (encodedMember); + try { + var m = GetMethodInfo (encodedMember); + if (TryInvoke<#= returnType.ManagedType #>StaticRedirect (m, self, parameters<#= byRefParamDecl #>)) { + <#= returnByRefParam #>; + } - <#= returnType.ReturnType != "void" ? "var r = " : "" #>JniEnvironment.InstanceMethods.Call<#= returnType.JniCallType #>Method (self.PeerReference, m, parameters); - GC.KeepAlive (self); - return<#= returnType.ReturnType == "void" ? "" : " r" #>; + <#= setByRefParam #>JniEnvironment.InstanceMethods.Call<#= returnType.JniCallType #>Method (self.PeerReference, m, parameters); + <#= returnByRefParam #>; + } + finally { + GC.KeepAlive (self); + } } public unsafe <#= returnType.ReturnType #> InvokeVirtual<#= returnType.ManagedType #>Method (string encodedMember, IJavaPeerable self, JniArgumentValue* parameters) { JniPeerMembers.AssertSelf (self); - var declaringType = DeclaringType; - if (Members.UsesVirtualDispatch (self, declaringType)) { - var m = GetMethodInfo (encodedMember); - <#= returnType.ReturnType != "void" ? "var _nr = " : "" #>JniEnvironment.InstanceMethods.Call<#= returnType.JniCallType #>Method (self.PeerReference, m, parameters); + try { + var declaringType = DeclaringType; + if (Members.UsesVirtualDispatch (self, declaringType)) { + var m = GetMethodInfo (encodedMember); + if (TryInvoke<#= returnType.ManagedType #>StaticRedirect (m, self, parameters<#= byRefParamDecl #>)) { + <#= returnByRefParam #>; + } + <#= setByRefParam #>JniEnvironment.InstanceMethods.Call<#= returnType.JniCallType #>Method (self.PeerReference, m, parameters); + <#= returnByRefParam #>; + } + var j = Members.GetPeerMembers (self); + var n = j.InstanceMethods.GetMethodInfo (encodedMember); + do { + if (TryInvoke<#= returnType.ManagedType #>StaticRedirect (n, self, parameters<#= byRefParamDecl #>)) { + <#= returnByRefParam #>; + } + <#= setByRefParam #>JniEnvironment.InstanceMethods.CallNonvirtual<#= returnType.JniCallType #>Method (self.PeerReference, j.JniPeerType.PeerReference, n, parameters); + <#= returnByRefParam #>; + } while (false); + } + finally { GC.KeepAlive (self); - return<#= returnType.ReturnType == "void" ? "" : " _nr" #>; } - var j = Members.GetPeerMembers (self); - var n = j.InstanceMethods.GetMethodInfo (encodedMember); - <#= returnType.ReturnType != "void" ? "var r = " : "" #>JniEnvironment.InstanceMethods.CallNonvirtual<#= returnType.JniCallType #>Method (self.PeerReference, j.JniPeerType.PeerReference, n, parameters); - GC.KeepAlive (self); - return<#= returnType.ReturnType == "void" ? "" : " r" #>; } public unsafe <#= returnType.ReturnType #> InvokeNonvirtual<#= returnType.ManagedType #>Method (string encodedMember, IJavaPeerable self, JniArgumentValue* parameters) @@ -65,10 +114,16 @@ namespace Java.Interop { JniPeerMembers.AssertSelf (self); var m = GetMethodInfo (encodedMember); - - <#= returnType.ReturnType != "void" ? "var r = " : "" #>JniEnvironment.InstanceMethods.CallNonvirtual<#= returnType.JniCallType #>Method (self.PeerReference, JniPeerType.PeerReference, m, parameters); - GC.KeepAlive (self); - return<#= returnType.ReturnType == "void" ? "" : " r" #>; + try { + if (TryInvoke<#= returnType.ManagedType #>StaticRedirect (m, self, parameters<#= byRefParamDecl #>)) { + <#= returnByRefParam #>; + } + <#= setByRefParam #>JniEnvironment.InstanceMethods.CallNonvirtual<#= returnType.JniCallType #>Method (self.PeerReference, JniPeerType.PeerReference, m, parameters); + <#= returnByRefParam #>; + } + finally { + GC.KeepAlive (self); + } } <# } diff --git a/src/Java.Interop/Java.Interop/JniPeerMembers.JniStaticMethods.cs b/src/Java.Interop/Java.Interop/JniPeerMembers.JniStaticMethods.cs index 7218668af..d27920a58 100644 --- a/src/Java.Interop/Java.Interop/JniPeerMembers.JniStaticMethods.cs +++ b/src/Java.Interop/Java.Interop/JniPeerMembers.JniStaticMethods.cs @@ -25,15 +25,71 @@ internal void Dispose () public JniMethodInfo GetMethodInfo (string encodedMember) { lock (StaticMethods) { - if (!StaticMethods.TryGetValue (encodedMember, out var m)) { - string method, signature; - JniPeerMembers.GetNameAndSignature (encodedMember, out method, out signature); - m = Members.JniPeerType.GetStaticMethod (method, signature); - StaticMethods.Add (encodedMember, m); + if (StaticMethods.TryGetValue (encodedMember, out var m)) { + return m; } + } + string method, signature; + JniPeerMembers.GetNameAndSignature (encodedMember, out method, out signature); + var info = GetMethodInfo (method, signature); + lock (StaticMethods) { + if (StaticMethods.TryGetValue (encodedMember, out var m)) { + return m; + } + StaticMethods.Add (encodedMember, info); + } + return info; + } + + JniMethodInfo GetMethodInfo (string method, string signature) + { +#if NET + var m = (JniMethodInfo?) null; + var newMethod = JniEnvironment.Runtime.TypeManager.GetReplacementMethodInfo (Members.JniPeerTypeName, method, signature); + if (newMethod.HasValue) { + using var t = new JniType (newMethod.Value.TargetJniType ?? Members.JniPeerTypeName); + if (t.TryGetStaticMethod ( + newMethod.Value.TargetJniMethodName ?? method, + newMethod.Value.TargetJniMethodSignature ?? signature, + out m)) { + return m; + } + } + if (Members.JniPeerType.TryGetStaticMethod (method, signature, out m)) { return m; } + m = FindInFallbackTypes (method, signature); + if (m != null) { + return m; + } +#endif // NET + return Members.JniPeerType.GetStaticMethod (method, signature); + } + +#if NET + JniMethodInfo? FindInFallbackTypes (string method, string signature) + { + var fallbackTypes = JniEnvironment.Runtime.TypeManager.GetStaticMethodFallbackTypes (Members.JniPeerTypeName); + if (fallbackTypes == null) { + return null; + } + foreach (var ft in fallbackTypes) { + JniType? t = null; + try { + if (!JniType.TryParse (ft, out t)) { + continue; + } + if (t.TryGetStaticMethod (method, signature, out var m)) { + return m; + } + } + finally { + t?.Dispose (); + } + } + return null; } +#endif // NET public unsafe void InvokeVoidMethod (string encodedMember, JniArgumentValue* parameters) { diff --git a/src/Java.Interop/Java.Interop/JniPeerMembers.cs b/src/Java.Interop/Java.Interop/JniPeerMembers.cs index 29be44adb..9e445be47 100644 --- a/src/Java.Interop/Java.Interop/JniPeerMembers.cs +++ b/src/Java.Interop/Java.Interop/JniPeerMembers.cs @@ -17,21 +17,18 @@ public JniPeerMembers (string jniPeerTypeName, Type managedPeerType, bool isInte } public JniPeerMembers (string jniPeerTypeName, Type managedPeerType) - : this (jniPeerTypeName, managedPeerType, checkManagedPeerType: true, isInterface: false) + : this (jniPeerTypeName = GetReplacementType (jniPeerTypeName), managedPeerType, checkManagedPeerType: true, isInterface: false) { - if (managedPeerType == null) - throw new ArgumentNullException (nameof (managedPeerType)); - if (!typeof (IJavaPeerable).IsAssignableFrom (managedPeerType)) - throw new ArgumentException ("'managedPeerType' must implement the IJavaPeerable interface.", nameof (managedPeerType)); - - Debug.Assert ( - JniEnvironment.Runtime.TypeManager.GetTypeSignature (managedPeerType).SimpleReference == jniPeerTypeName, - string.Format ("ManagedPeerType <=> JniTypeName Mismatch! javaVM.GetJniTypeInfoForType(typeof({0})).JniTypeName=\"{1}\" != \"{2}\"", - managedPeerType.FullName, - JniEnvironment.Runtime.TypeManager.GetTypeSignature (managedPeerType).SimpleReference, - jniPeerTypeName)); + } - ManagedPeerType = managedPeerType; + static string GetReplacementType (string jniPeerTypeName) + { +#if NET + var replacement = JniEnvironment.Runtime.TypeManager.GetReplacementType (jniPeerTypeName); + if (replacement != null) + return replacement; +#endif // NET + return jniPeerTypeName; } JniPeerMembers (string jniPeerTypeName, Type managedPeerType, bool checkManagedPeerType, bool isInterface = false) @@ -45,12 +42,16 @@ public JniPeerMembers (string jniPeerTypeName, Type managedPeerType) if (!typeof (IJavaPeerable).IsAssignableFrom (managedPeerType)) throw new ArgumentException ("'managedPeerType' must implement the IJavaPeerable interface.", nameof (managedPeerType)); - Debug.Assert ( - JniEnvironment.Runtime.TypeManager.GetTypeSignature (managedPeerType).SimpleReference == jniPeerTypeName, - string.Format ("ManagedPeerType <=> JniTypeName Mismatch! javaVM.GetJniTypeInfoForType(typeof({0})).JniTypeName=\"{1}\" != \"{2}\"", - managedPeerType.FullName, - JniEnvironment.Runtime.TypeManager.GetTypeSignature (managedPeerType).SimpleReference, - jniPeerTypeName)); +#if DEBUG + var signatureFromType = JniEnvironment.Runtime.TypeManager.GetTypeSignature (managedPeerType); + if (signatureFromType.SimpleReference != jniPeerTypeName) { + Debug.WriteLine ("WARNING-Java.Interop: ManagedPeerType <=> JniTypeName Mismatch! javaVM.GetJniTypeInfoForType(typeof({0})).JniTypeName=\"{1}\" != \"{2}\"", + managedPeerType.FullName, + signatureFromType.SimpleReference, + jniPeerTypeName); + Debug.WriteLine (new System.Diagnostics.StackTrace (true)); + } +#endif // DEBUG } JniPeerTypeName = jniPeerTypeName; diff --git a/src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs b/src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs index 66f7ccb6d..8e6738fa6 100644 --- a/src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs +++ b/src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs @@ -3,15 +3,82 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; +using System.Runtime.Versioning; using System.Threading; namespace Java.Interop { public partial class JniRuntime { +#if NET + [SuppressMessage ("Design", "CA1034:Nested types should not be visible", + Justification = "Deliberate choice to 'hide' these types from code completion for `Java.Interop.`; see 045b8af7.")] + public struct ReplacementMethodInfo : IEquatable + { + public string? SourceJniType {get; set;} + public string? SourceJniMethodName {get; set;} + public string? SourceJniMethodSignature {get; set;} + public string? TargetJniType {get; set;} + public string? TargetJniMethodName {get; set;} + public string? TargetJniMethodSignature {get; set;} + public int? TargetJniMethodParameterCount {get; set;} + public bool TargetJniMethodInstanceToStatic {get; set;} + + public override bool Equals (object? obj) + { + if (obj is ReplacementMethodInfo o) { + return Equals (o); + } + return false; + } + + public bool Equals (ReplacementMethodInfo other) + { + return string.Equals (SourceJniType, other.SourceJniType) && + string.Equals (SourceJniMethodName, other.SourceJniMethodName) && + string.Equals (SourceJniMethodSignature, other.SourceJniMethodSignature) && + string.Equals (TargetJniType, other.TargetJniType) && + string.Equals (TargetJniMethodName, other.TargetJniMethodName) && + string.Equals (TargetJniMethodSignature, other.TargetJniMethodSignature) && + TargetJniMethodParameterCount == other.TargetJniMethodParameterCount && + TargetJniMethodInstanceToStatic == other.TargetJniMethodInstanceToStatic; + } + + public override int GetHashCode () + { + return (SourceJniType?.GetHashCode () ?? 0) ^ + (SourceJniMethodName?.GetHashCode () ?? 0) ^ + (SourceJniMethodSignature?.GetHashCode () ?? 0) ^ + (TargetJniType?.GetHashCode () ?? 0) ^ + (TargetJniMethodName?.GetHashCode () ?? 0) ^ + (TargetJniMethodSignature?.GetHashCode () ?? 0) ^ + (TargetJniMethodParameterCount?.GetHashCode () ?? 0) ^ + TargetJniMethodInstanceToStatic.GetHashCode (); + } + + public override string ToString () + { + return $"{nameof (ReplacementMethodInfo)} {{ " + + $"{nameof (SourceJniType)} = \"{SourceJniType}\"" + + $", {nameof (SourceJniMethodName)} = \"{SourceJniMethodName}\"" + + $", {nameof (SourceJniMethodSignature)} = \"{SourceJniMethodSignature}\"" + + $", {nameof (TargetJniType)} = \"{TargetJniType}\"" + + $", {nameof (TargetJniMethodName)} = \"{TargetJniMethodName}\"" + + $", {nameof (TargetJniMethodSignature)} = \"{TargetJniMethodSignature}\"" + + $", {nameof (TargetJniMethodParameterCount)} = {TargetJniMethodParameterCount?.ToString () ?? "null"}" + + $", {nameof (TargetJniMethodInstanceToStatic)} = {TargetJniMethodInstanceToStatic}" + + $"}}"; + } + + public static bool operator==(ReplacementMethodInfo a, ReplacementMethodInfo b) => a.Equals (b); + public static bool operator!=(ReplacementMethodInfo a, ReplacementMethodInfo b) => !a.Equals (b); + } +#endif // NET + public class JniTypeManager : IDisposable, ISetRuntime { JniRuntime? runtime; @@ -46,6 +113,18 @@ void AssertValid () throw new ObjectDisposedException (nameof (JniTypeManager)); } + internal static void AssertSimpleReference (string jniSimpleReference, string argumentName = "jniSimpleReference") + { + if (jniSimpleReference == null) + throw new ArgumentNullException (argumentName); + if (jniSimpleReference != null && jniSimpleReference.IndexOf (".", StringComparison.Ordinal) >= 0) + throw new ArgumentException ("JNI type names do not contain '.', they use '/'. Are you sure you're using a JNI type name?", argumentName); + if (jniSimpleReference != null && jniSimpleReference.StartsWith ("[", StringComparison.Ordinal)) + throw new ArgumentException ("Arrays cannot be present in simplified type references.", argumentName); + if (jniSimpleReference != null && jniSimpleReference.StartsWith ("L", StringComparison.Ordinal) && jniSimpleReference.EndsWith (";", StringComparison.Ordinal)) + throw new ArgumentException ("JNI type references are not supported.", argumentName); + } + // NOTE: This method needs to be kept in sync with GetTypeSignatures() // This version of the method has removed IEnumerable for performance reasons. public JniTypeSignature GetTypeSignature (Type type) @@ -71,6 +150,12 @@ public JniTypeSignature GetTypeSignature (Type type) var name = type.GetCustomAttribute (inherit: false); if (name != null) { +#if NET + var altRef = GetReplacementType (name.SimpleReference); + if (altRef != null) { + return new JniTypeSignature (altRef, name.ArrayRank + rank, name.IsKeyword); + } +#endif // NET return new JniTypeSignature (name.SimpleReference, name.ArrayRank + rank, name.IsKeyword); } @@ -229,15 +314,7 @@ IEnumerable CreateGetTypesEnumerator (JniTypeSignature typeSignature) protected virtual IEnumerable GetTypesForSimpleReference (string jniSimpleReference) { AssertValid (); - - if (jniSimpleReference == null) - throw new ArgumentNullException (nameof (jniSimpleReference)); - if (jniSimpleReference != null && jniSimpleReference.IndexOf (".", StringComparison.Ordinal) >= 0) - throw new ArgumentException ("JNI type names do not contain '.', they use '/'. Are you sure you're using a JNI type name?", nameof (jniSimpleReference)); - if (jniSimpleReference != null && jniSimpleReference.StartsWith ("[", StringComparison.Ordinal)) - throw new ArgumentException ("Only simplified type references are supported.", nameof (jniSimpleReference)); - if (jniSimpleReference != null && jniSimpleReference.StartsWith ("L", StringComparison.Ordinal) && jniSimpleReference.EndsWith (";", StringComparison.Ordinal)) - throw new ArgumentException ("Only simplified type references are supported.", nameof (jniSimpleReference)); + AssertSimpleReference (jniSimpleReference); // Not sure why CS8604 is reported on following line when we check against null ~9 lines above... return CreateGetTypesForSimpleReferenceEnumerator (jniSimpleReference!); @@ -252,6 +329,43 @@ IEnumerable CreateGetTypesForSimpleReferenceEnumerator (string jniSimpleRe } #if NET + + public IReadOnlyList? GetStaticMethodFallbackTypes (string jniSimpleReference) + { + AssertValid (); + AssertSimpleReference (jniSimpleReference, nameof (jniSimpleReference)); + + return GetStaticMethodFallbackTypesCore (jniSimpleReference); + } + + protected virtual IReadOnlyList? GetStaticMethodFallbackTypesCore (string jniSimple) => null; + + public string? GetReplacementType (string jniSimpleReference) + { + AssertValid (); + AssertSimpleReference (jniSimpleReference, nameof (jniSimpleReference)); + + return GetReplacementTypeCore (jniSimpleReference); + } + + protected virtual string? GetReplacementTypeCore (string jniSimpleReference) => null; + + public ReplacementMethodInfo? GetReplacementMethodInfo (string jniSimpleReference, string jniMethodName, string jniMethodSignature) + { + AssertValid (); + AssertSimpleReference (jniSimpleReference, nameof (jniSimpleReference)); + if (string.IsNullOrEmpty (jniMethodName)) { + throw new ArgumentNullException (nameof (jniMethodName)); + } + if (string.IsNullOrEmpty (jniMethodSignature)) { + throw new ArgumentNullException (nameof (jniMethodSignature)); + } + + return GetReplacementMethodInfoCore (jniSimpleReference, jniMethodName, jniMethodSignature); + } + + protected virtual ReplacementMethodInfo? GetReplacementMethodInfoCore (string jniSimpleReference, string jniMethodName, string jniMethodSignature) => null; + public virtual void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan methods) { TryRegisterNativeMembers (nativeClass, type, methods); diff --git a/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs b/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs index a233ac81a..8f22e3269 100644 --- a/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs +++ b/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs @@ -402,9 +402,9 @@ public T CreateValue (ref JniObjectReference reference, JniObjectReferenceOpt targetType = targetType ?? typeof (T); if (typeof (IJavaPeerable).IsAssignableFrom (targetType)) { -#pragma warning disable CS8601 // Possible null reference assignment. +#pragma warning disable CS8600,CS8601 // Possible null reference assignment. return (T) JavaPeerableValueMarshaler.Instance.CreateGenericValue (ref reference, options, targetType); -#pragma warning restore CS8601 // Possible null reference assignment. +#pragma warning restore CS8600,CS8601 // Possible null reference assignment. } var marshaler = GetValueMarshaler (); @@ -481,9 +481,9 @@ public T GetValue (ref JniObjectReference reference, JniObjectReferenceOption } if (typeof (IJavaPeerable).IsAssignableFrom (targetType)) { -#pragma warning disable CS8601 // Possible null reference assignment. +#pragma warning disable CS8600,CS8601 // Possible null reference assignment. return (T) JavaPeerableValueMarshaler.Instance.CreateGenericValue (ref reference, options, targetType); -#pragma warning restore CS8601 // Possible null reference assignment. +#pragma warning restore CS8600,CS8601 // Possible null reference assignment. } var marshaler = GetValueMarshaler (); diff --git a/src/Java.Interop/Java.Interop/JniType.cs b/src/Java.Interop/Java.Interop/JniType.cs index 2639cdaee..5f0491620 100644 --- a/src/Java.Interop/Java.Interop/JniType.cs +++ b/src/Java.Interop/Java.Interop/JniType.cs @@ -25,6 +25,18 @@ public sealed class JniType : IDisposable { } } +#if NET + public static bool TryParse (string name, [NotNullWhen (true)] out JniType? type) + { + if (!JniEnvironment.Types.TryFindClass (name, out var peerReference)) { + type = null; + return false; + } + type = new JniType (ref peerReference, JniObjectReferenceOptions.CopyAndDispose); + return true; + } +#endif // NET + bool registered; JniObjectReference peerReference; @@ -236,6 +248,28 @@ public JniMethodInfo GetInstanceMethod (string name, string signature) return JniEnvironment.InstanceMethods.GetMethodID (PeerReference, name, signature); } +#if NET + internal bool TryGetInstanceMethod (string name, string signature, [NotNullWhen(true)] out JniMethodInfo? method) + { + AssertValid (); + + IntPtr thrown; + method = null; + var id = NativeMethods.java_interop_jnienv_get_method_id (JniEnvironment.EnvironmentPointer, out thrown, PeerReference.Handle, name, signature); + if (thrown != IntPtr.Zero) { + JniEnvironment.Exceptions.ExceptionClear (); + NativeMethods.java_interop_jnienv_delete_local_ref (JniEnvironment.EnvironmentPointer, thrown); + return false; + } + if (id == IntPtr.Zero) { + // …huh? Should only happen if `thrown != IntPtr.Zero`, handled above. + return false; + } + method = new JniMethodInfo (name, signature, id, isStatic: false); + return true; + } +#endif // NET + public JniMethodInfo GetCachedInstanceMethod ([NotNull] ref JniMethodInfo? cachedMethod, string name, string signature) { AssertValid (); @@ -256,6 +290,28 @@ public JniMethodInfo GetStaticMethod (string name, string signature) return JniEnvironment.StaticMethods.GetStaticMethodID (PeerReference, name, signature); } +#if NET + internal bool TryGetStaticMethod (string name, string signature, [NotNullWhen(true)] out JniMethodInfo? method) + { + AssertValid (); + + IntPtr thrown; + method = null; + var id = NativeMethods.java_interop_jnienv_get_static_method_id (JniEnvironment.EnvironmentPointer, out thrown, PeerReference.Handle, name, signature); + if (thrown != IntPtr.Zero) { + JniEnvironment.Exceptions.ExceptionClear (); + NativeMethods.java_interop_jnienv_delete_local_ref (JniEnvironment.EnvironmentPointer, thrown); + return false; + } + if (id == IntPtr.Zero) { + // …huh? Should only happen if `thrown != IntPtr.Zero`, handled above. + return false; + } + method = new JniMethodInfo (name, signature, id, isStatic: true); + return true; + } +#endif // NET + public JniMethodInfo GetCachedStaticMethod ([NotNull] ref JniMethodInfo? cachedMethod, string name, string signature) { AssertValid (); diff --git a/src/Java.Interop/Java.Interop/JniTypeSignatureAttribute.cs b/src/Java.Interop/Java.Interop/JniTypeSignatureAttribute.cs index 6ae3fac1f..4b5b6d8d9 100644 --- a/src/Java.Interop/Java.Interop/JniTypeSignatureAttribute.cs +++ b/src/Java.Interop/Java.Interop/JniTypeSignatureAttribute.cs @@ -11,14 +11,7 @@ public sealed class JniTypeSignatureAttribute : Attribute { public JniTypeSignatureAttribute (string simpleReference) { - if (simpleReference == null) - throw new ArgumentNullException (nameof (simpleReference)); - if (simpleReference.IndexOf (".", StringComparison.Ordinal) >= 0) - throw new ArgumentException ("JNI type names do not contain '.', they use '/'. Are you sure you're using a JNI type name?", nameof (simpleReference)); - if (simpleReference.StartsWith ("[", StringComparison.Ordinal)) - throw new ArgumentException ("Arrays cannot be present in simple type references.", nameof (simpleReference)); - if (simpleReference.StartsWith ("L", StringComparison.Ordinal) && simpleReference.EndsWith (";", StringComparison.Ordinal)) - throw new ArgumentException ("JNI type references are not supported.", nameof (simpleReference)); + JniRuntime.JniTypeManager.AssertSimpleReference (simpleReference, nameof (simpleReference)); SimpleReference = simpleReference; } diff --git a/tests/Java.Interop-Tests/Java.Interop-Tests.csproj b/tests/Java.Interop-Tests/Java.Interop-Tests.csproj index 9b2f4d6c9..eac512935 100644 --- a/tests/Java.Interop-Tests/Java.Interop-Tests.csproj +++ b/tests/Java.Interop-Tests/Java.Interop-Tests.csproj @@ -4,6 +4,7 @@ net472;net6.0 false true + 9.0 @@ -46,6 +47,10 @@ + + + + diff --git a/tests/Java.Interop-Tests/Java.Interop/JavaVMFixture.cs b/tests/Java.Interop-Tests/Java.Interop/JavaVMFixture.cs index e1a816c6b..855f52990 100644 --- a/tests/Java.Interop-Tests/Java.Interop/JavaVMFixture.cs +++ b/tests/Java.Interop-Tests/Java.Interop/JavaVMFixture.cs @@ -1,3 +1,4 @@ +#nullable enable using System; using System.Collections.Generic; using System.IO; @@ -10,20 +11,149 @@ namespace Java.InteropTests { partial class JavaVMFixture { + internal static TestJVM? VM; + internal static JavaVMFixtureTypeManager? TypeManager; + static partial void CreateJavaVM () { - var c = new TestJVM ( - jars: new[]{ "interop-test.jar" }, - typeMappings: new Dictionary () { + var o = new TestJVMOptions { + JarFilePaths = { + "interop-test.jar", + }, + TypeManager = new JavaVMFixtureTypeManager (), + }; + VM = new TestJVM (o); + TypeManager = (JavaVMFixtureTypeManager) VM.TypeManager; + JniRuntime.SetCurrent (VM); + } + } + + class JavaVMFixtureTypeManager : JniRuntime.JniTypeManager { + + Dictionary TypeMappings = new() { #if !NO_MARSHAL_MEMBER_BUILDER_SUPPORT - { TestType.JniTypeName, typeof (TestType) }, + [TestType.JniTypeName] = typeof (TestType), #endif // !NO_MARSHAL_MEMBER_BUILDER_SUPPORT - { GenericHolder.JniTypeName, typeof (GenericHolder<>) }, + [GenericHolder.JniTypeName] = typeof (GenericHolder<>), + [RenameClassBase.JniTypeName] = typeof (RenameClassBase), + [RenameClassDerived.JniTypeName] = typeof (RenameClassDerived), + }; + + public JavaVMFixtureTypeManager () + { + } + + protected override IEnumerable GetTypesForSimpleReference (string jniSimpleReference) + { + foreach (var t in base.GetTypesForSimpleReference (jniSimpleReference)) + yield return t; + Type target; +#pragma warning disable CS8600 // huh? + if (TypeMappings.TryGetValue (jniSimpleReference, out target)) + yield return target; +#pragma warning restore CS8600 + } + + protected override IEnumerable GetSimpleReferences (Type type) + { + return base.GetSimpleReferences (type) + .Concat (CreateSimpleReferencesEnumerator (type)); + } + + IEnumerable CreateSimpleReferencesEnumerator (Type type) + { + foreach (var e in TypeMappings) { + if (e.Value == type) { +#if NET + if (ReplacmentTypes.TryGetValue (e.Key, out var alt)) { + yield return alt; + continue; } - ); - JniRuntime.SetCurrent (c); +#endif // NET + yield return e.Key; + } + } } +#if NET + public string? RequestedFallbackTypesForSimpleReference; + protected override IReadOnlyList? GetStaticMethodFallbackTypesCore (string jniSimpleReference) + { + RequestedFallbackTypesForSimpleReference = jniSimpleReference; + Debug.WriteLine ($"# GetStaticMethodFallbackTypes (jniSimpleReference={jniSimpleReference})"); + + var slash = jniSimpleReference.LastIndexOf ('/'); + var desugarType = slash <= 0 + ? "Desugar" + jniSimpleReference + : jniSimpleReference.Substring (0, slash+1) + "Desugar" + jniSimpleReference.Substring (slash+1); + + // These types likely won't ever exist on Desktop, but providing + // "potentially non-existent" types ensures that we don't throw + // from places we don't want to internally throw. + return new[]{ + desugarType, + $"{jniSimpleReference}$-CC" + }; + } + + Dictionary ReplacmentTypes = new() { + ["com/xamarin/interop/RenameClassBase1"] = "com/xamarin/interop/RenameClassBase2", + }; + + protected override string? GetReplacementTypeCore (string jniSimpleReference) => + ReplacmentTypes.TryGetValue (jniSimpleReference, out var v) + ? v + : null; + + Dictionary<(string SourceType, string SourceName, string? SourceSignature), (string? TargetType, string? TargetName, string? TargetSignature, int? ParamCount, bool TurnStatic)> ReplacementMethods = new() { + [("java/lang/Object", "remappedToToString", "()Ljava/lang/String;")] = (null, "toString", null, null, false), + [("java/lang/Object", "remappedToStaticHashCode", null)] = ("com/xamarin/interop/ObjectHelper", "getHashCodeHelper", null, null, true), + [("java/lang/Runtime", "remappedToGetRuntime", null)] = (null, "getRuntime", null, null, false), + + // NOTE: key must use *post-renamed* value, not pre-renamed value + // NOTE: SourceSignature lacking return type; "closer in spirit" to what `remapping-config.json` allows + [("com/xamarin/interop/RenameClassBase2", "hashCode", "()")] = ("com/xamarin/interop/RenameClassBase2", "myNewHashCode", null, null, false), + }; + + protected override JniRuntime.ReplacementMethodInfo? GetReplacementMethodInfoCore (string jniSourceType, string jniMethodName, string jniMethodSignature) + { + // Console.Error.WriteLine ($"# jonp: looking for replacement method for (\"{jniSourceType}\", \"{jniMethodName}\", \"{jniMethodSignature}\")"); + if (!ReplacementMethods.TryGetValue ((jniSourceType, jniMethodName, jniMethodSignature), out var r) && + !ReplacementMethods.TryGetValue ((jniSourceType, jniMethodName, GetAlternateMethodSignature ()), out r) && + !ReplacementMethods.TryGetValue ((jniSourceType, jniMethodName, null), out r)) { + return null; + } + var targetSig = r.TargetSignature; + var paramCount = r.ParamCount; + if (targetSig == null && r.TurnStatic) { + targetSig = $"(L{jniSourceType};" + jniMethodSignature.Substring ("(".Length); + paramCount = paramCount ?? JniMemberSignature.GetParameterCountFromMethodSignature (jniMethodSignature); + paramCount++; + } + // Console.Error.WriteLine ($"# jonp: found replacement: ({GetValue (r.TargetType)}, {GetValue (r.TargetName)}, {GetValue (r.TargetSignature)}, {r.ParamCount?.ToString () ?? "null"}, {r.IsStatic})"); + return new JniRuntime.ReplacementMethodInfo { + SourceJniType = jniSourceType, + SourceJniMethodName = jniMethodName, + SourceJniMethodSignature = jniMethodSignature, + TargetJniType = r.TargetType ?? jniSourceType, + TargetJniMethodName = r.TargetName ?? jniMethodName, + TargetJniMethodSignature = targetSig ?? jniMethodSignature, + TargetJniMethodParameterCount = paramCount, + TargetJniMethodInstanceToStatic = r.TurnStatic, + }; + + string GetAlternateMethodSignature () + { + int i = jniMethodSignature.IndexOf (')'); + return jniMethodSignature.Substring (0, i+1); + } + + // string GetValue (string? value) + // { + // return value == null ? "null" : $"\"{value}\""; + // } + } +#endif // NET } } diff --git a/tests/Java.Interop-Tests/Java.Interop/JniPeerMembersTests.cs b/tests/Java.Interop-Tests/Java.Interop/JniPeerMembersTests.cs index 374a52a5b..788f5e61e 100644 --- a/tests/Java.Interop-Tests/Java.Interop/JniPeerMembersTests.cs +++ b/tests/Java.Interop-Tests/Java.Interop/JniPeerMembersTests.cs @@ -36,6 +36,79 @@ static Dictionary GetInstanceMethods (JniPeerMembers.JniI var f = typeof (JniPeerMembers.JniInstanceMethods).GetField ("InstanceMethods", BindingFlags.NonPublic | BindingFlags.Instance); return (Dictionary) f.GetValue (methods); } + +#if NET + [Test] + public void MethodLookupForNonexistentStaticMethodWillTryFallbacks () + { + try { + JavaLangRemappingTestRuntime.doesNotExist (); + Assert.Fail ("java.lang.Runtime.doesNotExist() exists?! Not expected to exist."); + } + catch (Exception e) { + Console.WriteLine ($"# jonp: MethodLookupForNonexistentStaticMethodWillTryFallbacks: e={e}"); + // On Desktop, expect `e` to be: + // ``` + // Java.Interop.JavaException: doesNotExist + // at Java.Interop.JniEnvironment.StaticMethods.GetMethodID(JniObjectReference type, String name, String signature) + // … + // at Java.InteropTests.JavaLangRuntime.doesNotExist() + // at Java.InteropTests.JniStaticMethodIDTest.MethodLookupForNonexistentMethodWillTryFallbacks() + // --- End of managed Java.Interop.JavaException stack trace --- + // java.lang.NoSuchMethodError: doesNotExist + // ``` + // On Android, expect `e` to be: + // ``` + // Java.Lang.NoSuchMethodError: no static method "Ljava/lang/Runtime;.doesNotExist()V" + // at Java.Interop.JniEnvironment.StaticMethods.GetStaticMethodID(JniObjectReference type, String name, String signature) + // at Java.Interop.JniType.GetStaticMethod(String name, String signature) + // at Java.Interop.JniPeerMembers.JniStaticMethods.GetMethodInfo(String method, String signature) + // at Java.Interop.JniPeerMembers.JniStaticMethods.GetMethodInfo(String encodedMember) + // at Java.Interop.JniPeerMembers.JniStaticMethods.InvokeVoidMethod(String encodedMember, JniArgumentValue* parameters) + // at Java.InteropTests.JavaLangRemappingTestRuntime.doesNotExist() + // at Java.InteropTests.JniPeerMembersTests.MethodLookupForNonexistentStaticMethodWillTryFallbacks() + // --- End of managed Java.Lang.NoSuchMethodError stack trace --- + // ``` + Assert.IsTrue (e.Message.Contains ("doesNotExist", StringComparison.Ordinal)); +#if !ANDROID // Android doesn't allow providing a custom TypeManager + Assert.AreEqual ("java/lang/Runtime", + JavaVMFixture.TypeManager.RequestedFallbackTypesForSimpleReference); +#endif // !ANDROID + } + } + + [Test] + public void ReplacementTypeUsedForMethodLookup () + { + using var o = new RenameClassDerived (); + int r = o.hashCode(); + Assert.AreEqual (33, r); + } + + [Test] + public void ReplaceInstanceMethodName () + { + using var o = new JavaLangRemappingTestObject (); + // Shouldn't throw; should instead invoke Object.toString() + var r = o.remappedToToString (); + JniObjectReference.Dispose (ref r); + } + + [Test] + public void ReplaceStaticMethodName () + { + var r = JavaLangRemappingTestRuntime.remappedToGetRuntime (); + JniObjectReference.Dispose (ref r); + } + + [Test] + public void ReplaceInstanceMethodWithStaticMethod () + { + using var o = new JavaLangRemappingTestObject (); + // Shouldn't throw; should instead invoke ObjectHelper.getHashCodeHelper(Object) + o.remappedToStaticHashCode (); + } +#endif // NET } [JniTypeSignature (JniTypeName)] @@ -57,5 +130,82 @@ public unsafe MyString (string value) _members.InstanceMethods.FinishGenericCreateInstance (id, this, value); } } -} + + [JniTypeSignature (JniTypeName)] + class JavaLangRemappingTestObject : JavaObject { + internal const string JniTypeName = "java/lang/Object"; + static readonly JniPeerMembers _members = new JniPeerMembers (JniTypeName, typeof (JavaLangRemappingTestObject)); + + public JavaLangRemappingTestObject () + { + } + + public unsafe void doesNotExist () + { + const string id = "doesNotExist.()V"; + _members.InstanceMethods.InvokeNonvirtualVoidMethod (id, this, null); + } + + public unsafe JniObjectReference remappedToToString () + { + const string id = "remappedToToString.()Ljava/lang/String;"; + return _members.InstanceMethods.InvokeNonvirtualObjectMethod (id, this, null); + } + + public unsafe int remappedToStaticHashCode () + { + const string id = "remappedToStaticHashCode.()I"; + return _members.InstanceMethods.InvokeVirtualInt32Method (id, this, null); + } + } + + [JniTypeSignature (JavaLangRemappingTestRuntime.JniTypeName)] + internal class JavaLangRemappingTestRuntime : JavaObject { + internal const string JniTypeName = "java/lang/Runtime"; + static readonly JniPeerMembers _members = new JniPeerMembers (JniTypeName, typeof (JavaLangRemappingTestRuntime)); + + public static unsafe JniObjectReference remappedToGetRuntime() + { + const string id = "remappedToGetRuntime.()Ljava/lang/Runtime;"; + return _members.StaticMethods.InvokeObjectMethod (id, null); + } + + public static unsafe void doesNotExist () + { + const string id = "doesNotExist.()V"; + _members.StaticMethods.InvokeVoidMethod (id, null); + } + } + + [JniTypeSignature (JniTypeName)] + class RenameClassBase : JavaObject { + internal const string JniTypeName = "com/xamarin/interop/RenameClassBase1"; + static readonly JniPeerMembers _members = new JniPeerMembers (JniTypeName, typeof (RenameClassBase)); + + public override JniPeerMembers JniPeerMembers => _members; + + public RenameClassBase () + { + } + + public virtual unsafe int hashCode () + { + const string id = "hashCode.()I"; + return _members.InstanceMethods.InvokeVirtualInt32Method (id, this, null); + } + } + + [JniTypeSignature (JniTypeName)] + class RenameClassDerived : RenameClassBase { + internal new const string JniTypeName = "com/xamarin/interop/RenameClassDerived"; + public RenameClassDerived () + { + } + + public override unsafe int hashCode () + { + return base.hashCode (); + } + } +} diff --git a/tests/Java.Interop-Tests/java/com/xamarin/interop/ObjectHelper.java b/tests/Java.Interop-Tests/java/com/xamarin/interop/ObjectHelper.java new file mode 100644 index 000000000..768161461 --- /dev/null +++ b/tests/Java.Interop-Tests/java/com/xamarin/interop/ObjectHelper.java @@ -0,0 +1,11 @@ +package com.xamarin.interop; + +public class ObjectHelper { + private ObjectHelper() + { + } + + public static int getHashCodeHelper (Object o) { + return o.hashCode(); + } +} diff --git a/tests/Java.Interop-Tests/java/com/xamarin/interop/RenameClassBase1.java b/tests/Java.Interop-Tests/java/com/xamarin/interop/RenameClassBase1.java new file mode 100644 index 000000000..e4e7c906c --- /dev/null +++ b/tests/Java.Interop-Tests/java/com/xamarin/interop/RenameClassBase1.java @@ -0,0 +1,39 @@ +package com.xamarin.interop; + +import java.util.ArrayList; + +import com.xamarin.java_interop.GCUserPeerable; + +public class RenameClassBase1 + implements GCUserPeerable +{ + static final String assemblyQualifiedName = "Java.InteropTests.RenameClassBase, Java.Interop-Tests"; + + ArrayList managedReferences = new ArrayList(); + + public RenameClassBase1 () { + System.out.println("RenameClassBase.()"); + if (RenameClassBase1.class == getClass ()) { + com.xamarin.java_interop.ManagedPeer.construct ( + this, + assemblyQualifiedName, + "" + ); + } + } + + public int hashCode () { + System.out.println("RenameClassBase1.hashCode()"); + return 16; + } + + public void jiAddManagedReference (java.lang.Object obj) + { + managedReferences.add (obj); + } + + public void jiClearManagedReferences () + { + managedReferences.clear (); + } +} diff --git a/tests/Java.Interop-Tests/java/com/xamarin/interop/RenameClassBase2.java b/tests/Java.Interop-Tests/java/com/xamarin/interop/RenameClassBase2.java new file mode 100644 index 000000000..c9f18dc9a --- /dev/null +++ b/tests/Java.Interop-Tests/java/com/xamarin/interop/RenameClassBase2.java @@ -0,0 +1,45 @@ +package com.xamarin.interop; + +import java.util.ArrayList; + +import com.xamarin.java_interop.GCUserPeerable; + +public class RenameClassBase2 + extends RenameClassBase1 + implements GCUserPeerable +{ + static final String assemblyQualifiedName = "Java.InteropTests.RenameClassBase, Java.Interop-Tests"; + + ArrayList managedReferences = new ArrayList(); + + public RenameClassBase2 () { + System.out.println("RenameClassBase.()"); + if (RenameClassBase2.class == getClass ()) { + com.xamarin.java_interop.ManagedPeer.construct ( + this, + assemblyQualifiedName, + "" + ); + } + } + + public int hashCode () { + System.out.println("RenameClassBase2.hashCode()"); + return 32; + } + + public int myNewHashCode() { + System.out.println("RenameClassBase2.myNewHashCode()"); + return 33; + } + + public void jiAddManagedReference (java.lang.Object obj) + { + managedReferences.add (obj); + } + + public void jiClearManagedReferences () + { + managedReferences.clear (); + } +} diff --git a/tests/Java.Interop-Tests/java/com/xamarin/interop/RenameClassDerived.java b/tests/Java.Interop-Tests/java/com/xamarin/interop/RenameClassDerived.java new file mode 100644 index 000000000..da9fc21ba --- /dev/null +++ b/tests/Java.Interop-Tests/java/com/xamarin/interop/RenameClassDerived.java @@ -0,0 +1,45 @@ +package com.xamarin.interop; + +import java.util.ArrayList; + +import com.xamarin.java_interop.GCUserPeerable; + +public class RenameClassDerived + extends RenameClassBase2 // Note: does NOT match C# binding! This is "post Bytecode rewriting" + implements GCUserPeerable +{ + static final String assemblyQualifiedName = "Java.InteropTests.RenameClassDerived, Java.Interop-Tests"; + + ArrayList managedReferences = new ArrayList(); + + public RenameClassDerived () { + System.out.println("RenameClassDerived.()"); + if (RenameClassDerived.class == getClass ()) { + com.xamarin.java_interop.ManagedPeer.construct ( + this, + assemblyQualifiedName, + "" + ); + } + } + + // Note: while *at runtime* `RenameClassBase1` is replaced with `RenameClassBase2`, + // Java Callable Wrapper generator doesn't know about that (yet?), and thus the + // *original* method name will be present. + // Not sure if this is actually a problem; perhaps Bytecode rewriting happens *after* + // Java Callable Wrapper generation? + public int hashCode () { + System.out.println("RenameClassDerived.hashCode()"); + return 64; + } + + public void jiAddManagedReference (java.lang.Object obj) + { + managedReferences.add (obj); + } + + public void jiClearManagedReferences () + { + managedReferences.clear (); + } +} diff --git a/tests/TestJVM/TestJVM.cs b/tests/TestJVM/TestJVM.cs index 4ac66ca63..c8d956e5f 100644 --- a/tests/TestJVM/TestJVM.cs +++ b/tests/TestJVM/TestJVM.cs @@ -14,39 +14,60 @@ namespace Java.InteropTests { + public class TestJVMOptions : JreRuntimeOptions { + + public TestJVMOptions (Assembly? callingAssembly = null) + { + CallingAssembly = callingAssembly ?? Assembly.GetCallingAssembly (); + } + + public ICollection JarFilePaths {get;} = new List (); + public Assembly CallingAssembly {get; set;} + public Dictionary? TypeMappings {get; set;} + } + public class TestJVM : JreRuntime { - static JreRuntimeOptions CreateBuilder (string[] jars, Assembly caller) + public TestJVM (TestJVMOptions builder) + : base (OverrideOptions (builder)) { - var dir = Path.GetDirectoryName (typeof (TestJVM).Assembly.Location); - var builder = new JreRuntimeOptions () { - JvmLibraryPath = GetJvmLibraryPath (), - JniAddNativeMethodRegistrationAttributePresent = true, - JniGlobalReferenceLogWriter = GetLogOutput ("JAVA_INTEROP_GREF_LOG", "g-", caller), - JniLocalReferenceLogWriter = GetLogOutput ("JAVA_INTEROP_LREF_LOG", "l-", caller), - }; - if (jars != null) { - foreach (var jar in jars) - builder.ClassPath.Add (Path.Combine (dir, jar)); - } + } + + static TestJVMOptions OverrideOptions (TestJVMOptions builder) + { + var dir = GetOutputDirectoryName (); + + builder.JvmLibraryPath = GetJvmLibraryPath (); + builder.JniAddNativeMethodRegistrationAttributePresent = true; + builder.JniGlobalReferenceLogWriter = GetLogOutput ("JAVA_INTEROP_GREF_LOG", "g-", builder.CallingAssembly); + builder.JniLocalReferenceLogWriter = GetLogOutput ("JAVA_INTEROP_LREF_LOG", "l-", builder.CallingAssembly); + + foreach (var jar in builder.JarFilePaths) + builder.ClassPath.Add (Path.Combine (dir, jar)); builder.AddOption ("-Xcheck:jni"); - builder.TypeManager = new TestTypeManager (); + builder.TypeManager = builder.TypeManager ?? new TestJvmTypeManager (builder.TypeMappings); return builder; } - static TextWriter GetLogOutput (string envVar, string prefix, Assembly caller) + static string GetOutputDirectoryName () + { + return Path.GetDirectoryName (typeof (TestJVM).Assembly.Location) ?? + Environment.CurrentDirectory; + } + + static TextWriter? GetLogOutput (string envVar, string prefix, Assembly caller) { var path = Environment.GetEnvironmentVariable (envVar); if (!string.IsNullOrEmpty (path)) return null; path = Path.Combine ( - Path.GetDirectoryName (typeof (TestJVM).Assembly.Location), + GetOutputDirectoryName (), prefix + Path.GetFileName (caller.Location) + ".txt"); return new StreamWriter (path, append: false, encoding: new UTF8Encoding (encoderShouldEmitUTF8Identifier: false)); } - public static string GetJvmLibraryPath () + public static string? GetJvmLibraryPath () { var jdkDir = ReadJavaSdkDirectoryFromJdkInfoProps (); if (jdkDir != null) { @@ -57,11 +78,14 @@ public static string GetJvmLibraryPath () return jdk?.JdkJvmPath; } - static string ReadJavaSdkDirectoryFromJdkInfoProps () + static string? ReadJavaSdkDirectoryFromJdkInfoProps () { var location = typeof (TestJVM).Assembly.Location; - var binDir = Path.GetDirectoryName (Path.GetDirectoryName (location)); + var binDir = Path.GetDirectoryName (Path.GetDirectoryName (location)) ?? Environment.CurrentDirectory; var testDir = Path.GetFileName (Path.GetDirectoryName (location)); + if (testDir == null) { + return null; + } if (!testDir.StartsWith ("Test", StringComparison.OrdinalIgnoreCase)) { return null; } @@ -89,47 +113,67 @@ static string ReadJavaSdkDirectoryFromJdkInfoProps () return jdkJvmPath.Value; } - Dictionary typeMappings; + public TestJVM (string[]? jars = null, Dictionary? typeMappings = null) + : this (CreateOptions (jars, Assembly.GetCallingAssembly (), typeMappings)) + { + } - public TestJVM (string[] jars = null, Dictionary typeMappings = null) - : base (CreateBuilder (jars, Assembly.GetCallingAssembly ())) + static TestJVMOptions CreateOptions (string[]? jarFiles, Assembly callingAssembly, Dictionary? typeMappings) { - this.typeMappings = typeMappings; + var o = new TestJVMOptions { + TypeMappings = typeMappings, + CallingAssembly = callingAssembly, + }; + if (jarFiles != null) { + foreach (var jar in jarFiles) { + o.JarFilePaths.Add (jar); + } + } + return o; } + } - class TestTypeManager : + public class TestJvmTypeManager : #if NET JreTypeManager #else // !NET - JniTypeManager + JniRuntime.JniTypeManager #endif // !NET + { + + Dictionary? typeMappings; + + public TestJvmTypeManager (Dictionary? typeMappings) { + this.typeMappings = typeMappings; + } - protected override IEnumerable GetTypesForSimpleReference (string jniSimpleReference) - { - foreach (var t in base.GetTypesForSimpleReference (jniSimpleReference)) - yield return t; - var mappings = ((TestJVM) Runtime).typeMappings; - Type target; - if (mappings != null && mappings.TryGetValue (jniSimpleReference, out target)) - yield return target; - } + protected override IEnumerable GetTypesForSimpleReference (string jniSimpleReference) + { + foreach (var t in base.GetTypesForSimpleReference (jniSimpleReference)) + yield return t; + if (typeMappings == null) + yield break; + Type target; +#pragma warning disable CS8600 // huh? + if (typeMappings.TryGetValue (jniSimpleReference, out target)) + yield return target; +#pragma warning restore CS8600 + } - protected override IEnumerable GetSimpleReferences (Type type) - { - return base.GetSimpleReferences (type) - .Concat (CreateSimpleReferencesEnumerator (type)); - } + protected override IEnumerable GetSimpleReferences (Type type) + { + return base.GetSimpleReferences (type) + .Concat (CreateSimpleReferencesEnumerator (type)); + } - IEnumerable CreateSimpleReferencesEnumerator (Type type) - { - var mappings = ((TestJVM) Runtime).typeMappings; - if (mappings == null) - yield break; - foreach (var e in mappings) { - if (e.Value == type) - yield return e.Key; - } + IEnumerable CreateSimpleReferencesEnumerator (Type type) + { + if (typeMappings == null) + yield break; + foreach (var e in typeMappings) { + if (e.Value == type) + yield return e.Key; } } } diff --git a/tests/TestJVM/TestJVM.csproj b/tests/TestJVM/TestJVM.csproj index b91b4da40..3c6332422 100644 --- a/tests/TestJVM/TestJVM.csproj +++ b/tests/TestJVM/TestJVM.csproj @@ -2,6 +2,8 @@ net472;net6.0 + 8.0 + enable false