Skip to content

Commit 1e14a61

Browse files
committed
[Java.Interop] Remove IJavaObject constraint on JavaObjectArray.
(Work in progress!) Allow e.g. JavaObjectArray<object> (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<int> causes things to blow up, as JNIEnv::FindClass("I") throws), * "Full" collection marshaling (JavaObjectArray<int[]> 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.
1 parent baf3c05 commit 1e14a61

File tree

7 files changed

+172
-26
lines changed

7 files changed

+172
-26
lines changed

src/Java.Interop/Java.Interop.csproj

+14
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,17 @@
6767
<Compile Include="Java.Interop\JavaObjectArray.cs" />
6868
<Compile Include="Java.Interop\JniTypeInfoAttribute.cs" />
6969
<Compile Include="Java.Interop\JniTypeInfo.cs" />
70+
<Compile Include="Java.Interop\JavaProxyObject.cs" />
71+
<Compile Include="Java.Interop\JniMarshal.cs" />
72+
</ItemGroup>
73+
<ItemGroup>
74+
<CompileJavaInteropJar Include="java\com\xamarin\android\internal\JavaProxyObject.java" />
7075
</ItemGroup>
7176
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
7277
<PropertyGroup>
7378
<BuildDependsOn>
7479
BuildJniEnvironment_g_cs;
80+
BuildInteropJar;
7581
$(BuildDependsOn)
7682
</BuildDependsOn>
7783
</PropertyGroup>
@@ -81,4 +87,12 @@
8187
<Target Name="BuildJniEnvironment_g_cs" Inputs="$(OutputPath)\jnienv-gen.exe" Outputs="Java.Interop\JniEnvironment.g.cs">
8288
<Exec Command="$(Runtime) &quot;$(OutputPath)\jnienv-gen.exe&quot; Java.Interop\JniEnvironment.g.cs" />
8389
</Target>
90+
<ItemGroup>
91+
<JavaInteropJar Include="$(OutputPath)java-interop.jar" />
92+
</ItemGroup>
93+
<Target Name="BuildInteropJar" Inputs="@(CompileJavaInteropJar)" Outputs="@(JavaInteropJar)">
94+
<MakeDir Directories="$(OutputPath)ji-classes" />
95+
<Exec Command="javac -d &quot;$(OutputPath)ji-classes&quot; @(CompileJavaInteropJar -&gt; '%(Identity)', ' ')" />
96+
<Exec Command="jar cf &quot;$(OutputPath)java-interop.jar&quot; -C &quot;$(OutputPath)ji-classes&quot; ." />
97+
</Target>
8498
</Project>

src/Java.Interop/Java.Interop/JavaObjectArray.cs

+3-9
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
namespace Java.Interop
44
{
55
public class JavaObjectArray<T> : JavaArray<T>
6-
where T : class, IJavaObject
76
{
87
public JavaObjectArray (JniReferenceSafeHandle handle, JniHandleOwnership transfer)
98
: base (handle, transfer)
@@ -25,23 +24,18 @@ public JavaObjectArray (int length)
2524
{
2625
}
2726

28-
// TODO: remove `IJavaObject` constraint
2927
public override T this [int index] {
3028
get {
3129
if (index < 0 || index >= Length)
3230
throw new ArgumentOutOfRangeException ("index", "index < 0 || index >= Length");
3331
var lref = JniEnvironment.Arrays.GetObjectArrayElement (SafeHandle, index);
34-
return (T) JniEnvironment.Current.JavaVM.GetObject (lref, JniHandleOwnership.Transfer, typeof (T));
32+
return JniMarshal.GetValue<T> (lref, JniHandleOwnership.Transfer);
3533
}
3634
set {
3735
if (index < 0 || index >= Length)
3836
throw new ArgumentOutOfRangeException ("index", "index < 0 || index >= Length");
39-
if (value != null && !value.SafeHandle.IsInvalid)
40-
value.RegisterWithVM ();
41-
JniEnvironment.Arrays.SetObjectArrayElement (SafeHandle, index,
42-
value == null || value.SafeHandle.IsInvalid
43-
? JniReferenceSafeHandle.Null
44-
: value.SafeHandle);
37+
using (var h = JniMarshal.CreateLocalRef (value))
38+
JniEnvironment.Arrays.SetObjectArrayElement (SafeHandle, index, h);
4539
}
4640
}
4741
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
using System;
2+
using System.Runtime.CompilerServices;
3+
4+
namespace Java.Interop {
5+
6+
[JniTypeInfo (JavaProxyObject.JniTypeName)]
7+
sealed class JavaProxyObject : JavaObject
8+
{
9+
internal const string JniTypeName = "com/xamarin/android/internal/JavaProxyObject";
10+
11+
static readonly JniType TypeRef;
12+
static readonly ConditionalWeakTable<object, JavaProxyObject> CachedValues;
13+
14+
static JavaProxyObject ()
15+
{
16+
TypeRef = new JniType (JniTypeName);
17+
TypeRef.RegisterWithVM ();
18+
TypeRef.RegisterNativeMethods (
19+
new JniNativeMethodRegistration ("equals", "(Ljava/lang/Object;)Z", (Func<IntPtr, IntPtr, IntPtr, bool>) _Equals),
20+
new JniNativeMethodRegistration ("hashCode", "()I", (Func<IntPtr, IntPtr, int>) _GetHashCode),
21+
new JniNativeMethodRegistration ("toString", "()Ljava/lang/String;", (Func<IntPtr, IntPtr, IntPtr>) _ToString));
22+
CachedValues = new ConditionalWeakTable<object, JavaProxyObject> ();
23+
}
24+
25+
JavaProxyObject (object value)
26+
{
27+
if (value == null)
28+
throw new ArgumentNullException ("value");
29+
Value = value;
30+
}
31+
32+
public object Value {get; private set;}
33+
34+
public override int GetHashCode ()
35+
{
36+
return Value.GetHashCode ();
37+
}
38+
39+
public override bool Equals (object obj)
40+
{
41+
var other = obj as JavaProxyObject;
42+
if (other != null)
43+
return object.Equals (Value, other.Value);
44+
return object.Equals (Value, obj);
45+
}
46+
47+
public override string ToString ()
48+
{
49+
return Value.ToString ();
50+
}
51+
52+
public static JavaProxyObject GetProxy (object value)
53+
{
54+
if (value == null)
55+
return null;
56+
57+
lock (CachedValues) {
58+
JavaProxyObject proxy;
59+
if (CachedValues.TryGetValue (value, out proxy))
60+
return proxy;
61+
proxy = new JavaProxyObject (value);
62+
proxy.RegisterWithVM ();
63+
CachedValues.Add (value, proxy);
64+
return proxy;
65+
}
66+
}
67+
68+
static bool _Equals (IntPtr jnienv, IntPtr n_self, IntPtr n_value)
69+
{
70+
JniEnvironment.CheckCurrent (jnienv);
71+
var self = JniEnvironment.Current.JavaVM.GetObject<JavaProxyObject> (n_self);
72+
var value = JniEnvironment.Current.JavaVM.GetObject (n_value);
73+
return self.Equals (value);
74+
}
75+
76+
static int _GetHashCode (IntPtr jnienv, IntPtr n_self)
77+
{
78+
JniEnvironment.CheckCurrent (jnienv);
79+
var self = JniEnvironment.Current.JavaVM.GetObject<JavaProxyObject> (n_self);
80+
return self.GetHashCode ();
81+
}
82+
83+
static IntPtr _ToString (IntPtr jnienv, IntPtr n_self)
84+
{
85+
JniEnvironment.CheckCurrent (jnienv);
86+
var self = JniEnvironment.Current.JavaVM.GetObject<JavaProxyObject> (n_self);
87+
var s = self.ToString ();
88+
using (var r = JniEnvironment.Strings.NewString (s))
89+
return JniEnvironment.Handles.NewReturnToJniRef (r);
90+
}
91+
}
92+
}
93+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using System;
2+
3+
namespace Java.Interop {
4+
5+
static class JniMarshal {
6+
7+
public static T GetValue<T> (JniReferenceSafeHandle handle, JniHandleOwnership transfer)
8+
{
9+
var target = JniEnvironment.Current.JavaVM.GetObject (handle, transfer, typeof (T));
10+
var proxy = target as JavaProxyObject;
11+
if (proxy != null)
12+
return (T) proxy.Value;
13+
return (T) target;
14+
}
15+
16+
public static JniLocalReference CreateLocalRef<T> (T value)
17+
{
18+
var o = (value as IJavaObject) ??
19+
JavaProxyObject.GetProxy (value);
20+
if (o == null || o.SafeHandle.IsInvalid)
21+
return new JniLocalReference ();
22+
return o.SafeHandle.NewLocalRef ();
23+
}
24+
}
25+
}
26+

src/Java.Interop/Tests/Java.Interop/JavaObjectArrayTest.cs

+17-16
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@
77

88
namespace Java.InteropTests
99
{
10-
public class JavaObjectArrayContractTest<T> : JavaArrayContract<T>
11-
where T : class, IJavaObject, new()
10+
public abstract class JavaObjectArrayContractTest<T> : JavaArrayContract<T>
1211
{
1312
protected override System.Collections.Generic.ICollection<T> CreateCollection (System.Collections.Generic.IEnumerable<T> values)
1413
{
@@ -18,25 +17,27 @@ protected override System.Collections.Generic.ICollection<T> CreateCollection (S
1817
array [i] = items [i];
1918
return array;
2019
}
20+
}
2121

22-
protected override T CreateValueA ()
23-
{
24-
return new T ();
25-
}
26-
27-
protected override T CreateValueB ()
28-
{
29-
return new T ();
30-
}
22+
[TestFixture]
23+
public class JavaObjectArrayContractTest : JavaObjectArrayContractTest<JavaObject> {
24+
protected override JavaObject CreateValueA () {return new JavaObject ();}
25+
protected override JavaObject CreateValueB () {return new JavaObject ();}
26+
protected override JavaObject CreateValueC () {return new JavaObject ();}
27+
}
3128

32-
protected override T CreateValueC ()
33-
{
34-
return new T ();
35-
}
29+
[TestFixture]
30+
public class JavaObjectArray_string_ContractTest : JavaObjectArrayContractTest<string> {
31+
protected override string CreateValueA () {return "a";}
32+
protected override string CreateValueB () {return "b";}
33+
protected override string CreateValueC () {return "c";}
3634
}
3735

3836
[TestFixture]
39-
public class JavaObjectArrayContractTest : JavaObjectArrayContractTest<JavaObject> {
37+
public class JavaObjectArray_object_ContractTest : JavaObjectArrayContractTest<object> {
38+
protected override object CreateValueA () {return new object ();}
39+
protected override object CreateValueB () {return new object ();}
40+
protected override object CreateValueC () {return new object ();}
4041
}
4142
}
4243

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.xamarin.android.internal;
2+
3+
/* package */ class JavaProxyObject extends java.lang.Object {
4+
5+
@Override
6+
public native boolean equals(Object obj);
7+
8+
@Override
9+
public native int hashCode();
10+
11+
@Override
12+
public native String toString();
13+
}

tests/TestJVM/TestJVM.cs

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.IO;
34
using System.Diagnostics;
45
using System.Linq;
@@ -16,7 +17,11 @@ public class TestJVM : JreVM {
1617
static JreVMBuilder CreateBuilder (string[] jars)
1718
{
1819
var builder = new JreVMBuilder ();
19-
builder.AddSystemProperty ("java.class.path", string.Join (":", jars));
20+
var _jars = new List<string> (jars) {
21+
"java-interop.jar",
22+
};
23+
_jars.AddRange (jars);
24+
builder.AddSystemProperty ("java.class.path", string.Join (":", _jars));
2025
return builder;
2126
}
2227

0 commit comments

Comments
 (0)