From 1e14a61e03f1172e20fe2c4f7547b86a8f8df81a Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Fri, 21 Feb 2014 16:48:14 -0500 Subject: [PATCH] [Java.Interop] Remove IJavaObject constraint on JavaObjectArray. (Work in progress!) Allow e.g. JavaObjectArray (i.e. a non-IJavaObject type). This requires the introduction of JavaProxyObject, which allows the JVM to "hold onto" a non-IJavaObject type. This in turn requires the generation of the new "java-interop.jar" file, which MUST be included in the $CLASSPATH of all Java.Interop-using JVMs. TODO: * Support builtin types (JavaObjectArray causes things to blow up, as JNIEnv::FindClass("I") throws), * "Full" collection marshaling (JavaObjectArray should deep-marshal the array instead of using JavaProxyObject) Note: Xamarin.Android's JavaProxyObject equivalent -- Android.Runtime.JavaObject -- overrides Java.Lang.Object.Clone(). JavaProxyObject does not, because ICloneable isn't part of the PCL profile that Java.Interop is targeting, and thus there's no way to reference the ICloneable.Clone() method w/o resorting to Reflection. --- src/Java.Interop/Java.Interop.csproj | 14 +++ .../Java.Interop/JavaObjectArray.cs | 12 +-- .../Java.Interop/JavaProxyObject.cs | 93 +++++++++++++++++++ src/Java.Interop/Java.Interop/JniMarshal.cs | 26 ++++++ .../Tests/Java.Interop/JavaObjectArrayTest.cs | 33 +++---- .../android/internal/JavaProxyObject.java | 13 +++ tests/TestJVM/TestJVM.cs | 7 +- 7 files changed, 172 insertions(+), 26 deletions(-) create mode 100644 src/Java.Interop/Java.Interop/JavaProxyObject.cs create mode 100644 src/Java.Interop/Java.Interop/JniMarshal.cs create mode 100644 src/Java.Interop/java/com/xamarin/android/internal/JavaProxyObject.java diff --git a/src/Java.Interop/Java.Interop.csproj b/src/Java.Interop/Java.Interop.csproj index 7a7beff806e..2a8c5f497fb 100644 --- a/src/Java.Interop/Java.Interop.csproj +++ b/src/Java.Interop/Java.Interop.csproj @@ -67,11 +67,17 @@ + + + + + BuildJniEnvironment_g_cs; + BuildInteropJar; $(BuildDependsOn) @@ -81,4 +87,12 @@ + + + + + + + + \ No newline at end of file diff --git a/src/Java.Interop/Java.Interop/JavaObjectArray.cs b/src/Java.Interop/Java.Interop/JavaObjectArray.cs index d587a24e0c4..fb9e9e9f3f0 100644 --- a/src/Java.Interop/Java.Interop/JavaObjectArray.cs +++ b/src/Java.Interop/Java.Interop/JavaObjectArray.cs @@ -3,7 +3,6 @@ namespace Java.Interop { public class JavaObjectArray : JavaArray - where T : class, IJavaObject { public JavaObjectArray (JniReferenceSafeHandle handle, JniHandleOwnership transfer) : base (handle, transfer) @@ -25,23 +24,18 @@ public JavaObjectArray (int length) { } - // TODO: remove `IJavaObject` constraint public override T this [int index] { get { if (index < 0 || index >= Length) throw new ArgumentOutOfRangeException ("index", "index < 0 || index >= Length"); var lref = JniEnvironment.Arrays.GetObjectArrayElement (SafeHandle, index); - return (T) JniEnvironment.Current.JavaVM.GetObject (lref, JniHandleOwnership.Transfer, typeof (T)); + return JniMarshal.GetValue (lref, JniHandleOwnership.Transfer); } set { if (index < 0 || index >= Length) throw new ArgumentOutOfRangeException ("index", "index < 0 || index >= Length"); - if (value != null && !value.SafeHandle.IsInvalid) - value.RegisterWithVM (); - JniEnvironment.Arrays.SetObjectArrayElement (SafeHandle, index, - value == null || value.SafeHandle.IsInvalid - ? JniReferenceSafeHandle.Null - : value.SafeHandle); + using (var h = JniMarshal.CreateLocalRef (value)) + JniEnvironment.Arrays.SetObjectArrayElement (SafeHandle, index, h); } } } diff --git a/src/Java.Interop/Java.Interop/JavaProxyObject.cs b/src/Java.Interop/Java.Interop/JavaProxyObject.cs new file mode 100644 index 00000000000..cee20aea90d --- /dev/null +++ b/src/Java.Interop/Java.Interop/JavaProxyObject.cs @@ -0,0 +1,93 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Java.Interop { + + [JniTypeInfo (JavaProxyObject.JniTypeName)] + sealed class JavaProxyObject : JavaObject + { + internal const string JniTypeName = "com/xamarin/android/internal/JavaProxyObject"; + + static readonly JniType TypeRef; + static readonly ConditionalWeakTable CachedValues; + + static JavaProxyObject () + { + TypeRef = new JniType (JniTypeName); + TypeRef.RegisterWithVM (); + TypeRef.RegisterNativeMethods ( + new JniNativeMethodRegistration ("equals", "(Ljava/lang/Object;)Z", (Func) _Equals), + new JniNativeMethodRegistration ("hashCode", "()I", (Func) _GetHashCode), + new JniNativeMethodRegistration ("toString", "()Ljava/lang/String;", (Func) _ToString)); + CachedValues = new ConditionalWeakTable (); + } + + JavaProxyObject (object value) + { + if (value == null) + throw new ArgumentNullException ("value"); + Value = value; + } + + public object Value {get; private set;} + + public override int GetHashCode () + { + return Value.GetHashCode (); + } + + public override bool Equals (object obj) + { + var other = obj as JavaProxyObject; + if (other != null) + return object.Equals (Value, other.Value); + return object.Equals (Value, obj); + } + + public override string ToString () + { + return Value.ToString (); + } + + public static JavaProxyObject GetProxy (object value) + { + if (value == null) + return null; + + lock (CachedValues) { + JavaProxyObject proxy; + if (CachedValues.TryGetValue (value, out proxy)) + return proxy; + proxy = new JavaProxyObject (value); + proxy.RegisterWithVM (); + CachedValues.Add (value, proxy); + return proxy; + } + } + + static bool _Equals (IntPtr jnienv, IntPtr n_self, IntPtr n_value) + { + JniEnvironment.CheckCurrent (jnienv); + var self = JniEnvironment.Current.JavaVM.GetObject (n_self); + var value = JniEnvironment.Current.JavaVM.GetObject (n_value); + return self.Equals (value); + } + + static int _GetHashCode (IntPtr jnienv, IntPtr n_self) + { + JniEnvironment.CheckCurrent (jnienv); + var self = JniEnvironment.Current.JavaVM.GetObject (n_self); + return self.GetHashCode (); + } + + static IntPtr _ToString (IntPtr jnienv, IntPtr n_self) + { + JniEnvironment.CheckCurrent (jnienv); + var self = JniEnvironment.Current.JavaVM.GetObject (n_self); + var s = self.ToString (); + using (var r = JniEnvironment.Strings.NewString (s)) + return JniEnvironment.Handles.NewReturnToJniRef (r); + } + } +} + diff --git a/src/Java.Interop/Java.Interop/JniMarshal.cs b/src/Java.Interop/Java.Interop/JniMarshal.cs new file mode 100644 index 00000000000..67d65d50b70 --- /dev/null +++ b/src/Java.Interop/Java.Interop/JniMarshal.cs @@ -0,0 +1,26 @@ +using System; + +namespace Java.Interop { + + static class JniMarshal { + + public static T GetValue (JniReferenceSafeHandle handle, JniHandleOwnership transfer) + { + var target = JniEnvironment.Current.JavaVM.GetObject (handle, transfer, typeof (T)); + var proxy = target as JavaProxyObject; + if (proxy != null) + return (T) proxy.Value; + return (T) target; + } + + public static JniLocalReference CreateLocalRef (T value) + { + var o = (value as IJavaObject) ?? + JavaProxyObject.GetProxy (value); + if (o == null || o.SafeHandle.IsInvalid) + return new JniLocalReference (); + return o.SafeHandle.NewLocalRef (); + } + } +} + diff --git a/src/Java.Interop/Tests/Java.Interop/JavaObjectArrayTest.cs b/src/Java.Interop/Tests/Java.Interop/JavaObjectArrayTest.cs index 3d870edfae8..fd73325fe09 100644 --- a/src/Java.Interop/Tests/Java.Interop/JavaObjectArrayTest.cs +++ b/src/Java.Interop/Tests/Java.Interop/JavaObjectArrayTest.cs @@ -7,8 +7,7 @@ namespace Java.InteropTests { - public class JavaObjectArrayContractTest : JavaArrayContract - where T : class, IJavaObject, new() + public abstract class JavaObjectArrayContractTest : JavaArrayContract { protected override System.Collections.Generic.ICollection CreateCollection (System.Collections.Generic.IEnumerable values) { @@ -18,25 +17,27 @@ protected override System.Collections.Generic.ICollection CreateCollection (S array [i] = items [i]; return array; } + } - protected override T CreateValueA () - { - return new T (); - } - - protected override T CreateValueB () - { - return new T (); - } + [TestFixture] + public class JavaObjectArrayContractTest : JavaObjectArrayContractTest { + protected override JavaObject CreateValueA () {return new JavaObject ();} + protected override JavaObject CreateValueB () {return new JavaObject ();} + protected override JavaObject CreateValueC () {return new JavaObject ();} + } - protected override T CreateValueC () - { - return new T (); - } + [TestFixture] + public class JavaObjectArray_string_ContractTest : JavaObjectArrayContractTest { + protected override string CreateValueA () {return "a";} + protected override string CreateValueB () {return "b";} + protected override string CreateValueC () {return "c";} } [TestFixture] - public class JavaObjectArrayContractTest : JavaObjectArrayContractTest { + public class JavaObjectArray_object_ContractTest : JavaObjectArrayContractTest { + protected override object CreateValueA () {return new object ();} + protected override object CreateValueB () {return new object ();} + protected override object CreateValueC () {return new object ();} } } diff --git a/src/Java.Interop/java/com/xamarin/android/internal/JavaProxyObject.java b/src/Java.Interop/java/com/xamarin/android/internal/JavaProxyObject.java new file mode 100644 index 00000000000..a41498259e1 --- /dev/null +++ b/src/Java.Interop/java/com/xamarin/android/internal/JavaProxyObject.java @@ -0,0 +1,13 @@ +package com.xamarin.android.internal; + +/* package */ class JavaProxyObject extends java.lang.Object { + + @Override + public native boolean equals(Object obj); + + @Override + public native int hashCode(); + + @Override + public native String toString(); +} \ No newline at end of file diff --git a/tests/TestJVM/TestJVM.cs b/tests/TestJVM/TestJVM.cs index 404206f427f..7f42d87dc88 100644 --- a/tests/TestJVM/TestJVM.cs +++ b/tests/TestJVM/TestJVM.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Diagnostics; using System.Linq; @@ -16,7 +17,11 @@ public class TestJVM : JreVM { static JreVMBuilder CreateBuilder (string[] jars) { var builder = new JreVMBuilder (); - builder.AddSystemProperty ("java.class.path", string.Join (":", jars)); + var _jars = new List (jars) { + "java-interop.jar", + }; + _jars.AddRange (jars); + builder.AddSystemProperty ("java.class.path", string.Join (":", _jars)); return builder; }