Skip to content

Commit

Permalink
[Java.Interop] Remove IJavaObject constraint on JavaObjectArray.
Browse files Browse the repository at this point in the history
(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.
  • Loading branch information
jonpryor committed Feb 21, 2014
1 parent baf3c05 commit 1e14a61
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 26 deletions.
14 changes: 14 additions & 0 deletions src/Java.Interop/Java.Interop.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,17 @@
<Compile Include="Java.Interop\JavaObjectArray.cs" />
<Compile Include="Java.Interop\JniTypeInfoAttribute.cs" />
<Compile Include="Java.Interop\JniTypeInfo.cs" />
<Compile Include="Java.Interop\JavaProxyObject.cs" />
<Compile Include="Java.Interop\JniMarshal.cs" />
</ItemGroup>
<ItemGroup>
<CompileJavaInteropJar Include="java\com\xamarin\android\internal\JavaProxyObject.java" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<PropertyGroup>
<BuildDependsOn>
BuildJniEnvironment_g_cs;
BuildInteropJar;
$(BuildDependsOn)
</BuildDependsOn>
</PropertyGroup>
Expand All @@ -81,4 +87,12 @@
<Target Name="BuildJniEnvironment_g_cs" Inputs="$(OutputPath)\jnienv-gen.exe" Outputs="Java.Interop\JniEnvironment.g.cs">
<Exec Command="$(Runtime) &quot;$(OutputPath)\jnienv-gen.exe&quot; Java.Interop\JniEnvironment.g.cs" />
</Target>
<ItemGroup>
<JavaInteropJar Include="$(OutputPath)java-interop.jar" />
</ItemGroup>
<Target Name="BuildInteropJar" Inputs="@(CompileJavaInteropJar)" Outputs="@(JavaInteropJar)">
<MakeDir Directories="$(OutputPath)ji-classes" />
<Exec Command="javac -d &quot;$(OutputPath)ji-classes&quot; @(CompileJavaInteropJar -&gt; '%(Identity)', ' ')" />
<Exec Command="jar cf &quot;$(OutputPath)java-interop.jar&quot; -C &quot;$(OutputPath)ji-classes&quot; ." />
</Target>
</Project>
12 changes: 3 additions & 9 deletions src/Java.Interop/Java.Interop/JavaObjectArray.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
namespace Java.Interop
{
public class JavaObjectArray<T> : JavaArray<T>
where T : class, IJavaObject
{
public JavaObjectArray (JniReferenceSafeHandle handle, JniHandleOwnership transfer)
: base (handle, transfer)
Expand All @@ -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<T> (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);
}
}
}
Expand Down
93 changes: 93 additions & 0 deletions src/Java.Interop/Java.Interop/JavaProxyObject.cs
Original file line number Diff line number Diff line change
@@ -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<object, JavaProxyObject> CachedValues;

static JavaProxyObject ()
{
TypeRef = new JniType (JniTypeName);
TypeRef.RegisterWithVM ();
TypeRef.RegisterNativeMethods (
new JniNativeMethodRegistration ("equals", "(Ljava/lang/Object;)Z", (Func<IntPtr, IntPtr, IntPtr, bool>) _Equals),
new JniNativeMethodRegistration ("hashCode", "()I", (Func<IntPtr, IntPtr, int>) _GetHashCode),
new JniNativeMethodRegistration ("toString", "()Ljava/lang/String;", (Func<IntPtr, IntPtr, IntPtr>) _ToString));
CachedValues = new ConditionalWeakTable<object, JavaProxyObject> ();
}

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<JavaProxyObject> (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<JavaProxyObject> (n_self);
return self.GetHashCode ();
}

static IntPtr _ToString (IntPtr jnienv, IntPtr n_self)
{
JniEnvironment.CheckCurrent (jnienv);
var self = JniEnvironment.Current.JavaVM.GetObject<JavaProxyObject> (n_self);
var s = self.ToString ();
using (var r = JniEnvironment.Strings.NewString (s))
return JniEnvironment.Handles.NewReturnToJniRef (r);
}
}
}

26 changes: 26 additions & 0 deletions src/Java.Interop/Java.Interop/JniMarshal.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;

namespace Java.Interop {

static class JniMarshal {

public static T GetValue<T> (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> (T value)
{
var o = (value as IJavaObject) ??
JavaProxyObject.GetProxy (value);
if (o == null || o.SafeHandle.IsInvalid)
return new JniLocalReference ();
return o.SafeHandle.NewLocalRef ();
}
}
}

33 changes: 17 additions & 16 deletions src/Java.Interop/Tests/Java.Interop/JavaObjectArrayTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@

namespace Java.InteropTests
{
public class JavaObjectArrayContractTest<T> : JavaArrayContract<T>
where T : class, IJavaObject, new()
public abstract class JavaObjectArrayContractTest<T> : JavaArrayContract<T>
{
protected override System.Collections.Generic.ICollection<T> CreateCollection (System.Collections.Generic.IEnumerable<T> values)
{
Expand All @@ -18,25 +17,27 @@ protected override System.Collections.Generic.ICollection<T> 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<JavaObject> {
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<string> {
protected override string CreateValueA () {return "a";}
protected override string CreateValueB () {return "b";}
protected override string CreateValueC () {return "c";}
}

[TestFixture]
public class JavaObjectArrayContractTest : JavaObjectArrayContractTest<JavaObject> {
public class JavaObjectArray_object_ContractTest : JavaObjectArrayContractTest<object> {
protected override object CreateValueA () {return new object ();}
protected override object CreateValueB () {return new object ();}
protected override object CreateValueC () {return new object ();}
}
}

Original file line number Diff line number Diff line change
@@ -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();
}
7 changes: 6 additions & 1 deletion tests/TestJVM/TestJVM.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Diagnostics;
using System.Linq;
Expand All @@ -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<string> (jars) {
"java-interop.jar",
};
_jars.AddRange (jars);
builder.AddSystemProperty ("java.class.path", string.Join (":", _jars));
return builder;
}

Expand Down

0 comments on commit 1e14a61

Please sign in to comment.