Skip to content

Commit 855b7e9

Browse files
jpobstjonpryor
authored andcommitted
[generator] Use DIM to support static interface methods (#487)
Using the new Default Interface Members support (29f9707), and `generator --lang-features=default-interface-methods`, we can now also support static interface methods/properties. This Java interface: // Java public interface StaticMethodsInterface { static int foo () { return 10; } static int getValue () { return 3; } static void setValue (int value) { } } can now be bound as: // C# Binding public partial interface IStaticMethodsInterface { static new readonly JniPeerMembers _members = new JniPeerMembers ( "java/code/StaticMethodsInterface", typeof (IStaticMethodsInterface), isInterface: true); public static unsafe int Foo () { return _members.StaticMethods.InvokeInt32Method ("foo.()I", null); } public static unsafe int Value { get { return _members.StaticMethods.InvokeInt32Method ("getValue.()I", null); } set { JniArgumentValue* __args = stackalloc JniArgumentValue [1]; __args [0] = new JniArgumentValue (value); _members.StaticMethods.InvokeVoidMethod ("setValue.(I)V", __args); } } } Which can be invoked as: [Test] public void TestStaticMethods () { Assert.AreEqual (10, IStaticMethodsInterface.Foo ()); Assert.AreEqual (3, IStaticMethodsInterface.Value); Assert.DoesNotThrow (() => IStaticMethodsInterface.Value = 5); }
1 parent 3fee05c commit 855b7e9

File tree

8 files changed

+319
-4
lines changed

8 files changed

+319
-4
lines changed

tools/generator/Java.Interop.Tools.Generator.CodeGeneration/CodeGenerator.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -530,7 +530,7 @@ public void WriteInterfaceDeclaration (InterfaceGen @interface, string indent)
530530
writer.WriteLine ("{0}{1} partial interface {2}{3} {{", indent, @interface.Visibility, @interface.Name,
531531
@interface.IsConstSugar ? string.Empty : @interface.Interfaces.Count == 0 || sb.Length == 0 ? " : " + GetAllInterfaceImplements () : " : " + sb.ToString ());
532532

533-
if (opt.SupportDefaultInterfaceMethods && @interface.HasDefaultMethods)
533+
if (opt.SupportDefaultInterfaceMethods && (@interface.HasDefaultMethods || @interface.HasStaticMethods))
534534
WriteClassHandle (@interface, indent + "\t", @interface.Name);
535535

536536
WriteInterfaceFields (@interface, indent + "\t");
@@ -994,16 +994,18 @@ public void WriteInterfaceMethods (InterfaceGen @interface, string indent)
994994
WriteMethodDeclaration (m, indent, @interface, @interface.AssemblyQualifiedName + "Invoker");
995995
}
996996

997-
foreach (var m in @interface.Methods.Where (m => m.IsInterfaceDefaultMethod))
998-
WriteMethod (m, indent, @interface, true);
997+
if (opt.SupportDefaultInterfaceMethods)
998+
foreach (var m in @interface.Methods.Where (m => m.IsInterfaceDefaultMethod || m.IsStatic))
999+
WriteMethod (m, indent, @interface, true);
9991000
}
10001001

10011002
public void WriteInterfaceProperties (InterfaceGen @interface, string indent)
10021003
{
10031004
foreach (var prop in @interface.Properties.Where (p => !p.Getter.IsStatic && !p.Getter.IsInterfaceDefaultMethod))
10041005
WritePropertyDeclaration (prop, indent, @interface, @interface.AssemblyQualifiedName + "Invoker");
10051006

1006-
WriteImplementedProperties (@interface.Properties.Where (p => p.Getter.IsInterfaceDefaultMethod), indent, false, @interface);
1007+
if (opt.SupportDefaultInterfaceMethods)
1008+
WriteImplementedProperties (@interface.Properties.Where (p => p.Getter.IsInterfaceDefaultMethod || p.Getter.IsStatic), indent, false, @interface);
10071009
}
10081010

10091011
public void WriteInterfacePropertyInvokers (InterfaceGen @interface, IEnumerable<Property> properties, string indent, HashSet<string> members)

tools/generator/Java.Interop.Tools.Generator.ObjectModel/InterfaceGen.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ public IEnumerable<Field> GetGeneratableFields (CodeGenerationOptions options)
143143

144144
public bool HasDefaultMethods => GetAllMethods ().Any (m => m.IsInterfaceDefaultMethod);
145145

146+
public bool HasStaticMethods => GetAllMethods ().Any (m => m.IsStatic);
147+
146148
public bool IsConstSugar {
147149
get {
148150
if (Methods.Count > 0 || Properties.Count > 0)
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
[Register ("java/code/IMyInterface", DoNotGenerateAcw=true)]
2+
public abstract class MyInterface : Java.Lang.Object {
3+
4+
internal MyInterface ()
5+
{
6+
}
7+
// Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='DoSomething' and count(parameter)=0]"
8+
[Register ("DoSomething", "()V", "")]
9+
public static unsafe void DoSomething ()
10+
{
11+
const string __id = "DoSomething.()V";
12+
try {
13+
_members.StaticMethods.InvokeVoidMethod (__id, null);
14+
} finally {
15+
}
16+
}
17+
18+
19+
static new readonly JniPeerMembers _members = new JniPeerMembers ("java/code/IMyInterface", typeof (MyInterface));
20+
}
21+
22+
[Register ("java/code/IMyInterface", DoNotGenerateAcw=true)]
23+
[global::System.Obsolete ("Use the 'MyInterface' type. This type will be removed in a future release.")]
24+
public abstract class MyInterfaceConsts : MyInterface {
25+
26+
private MyInterfaceConsts ()
27+
{
28+
}
29+
}
30+
31+
// Metadata.xml XPath interface reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']"
32+
[Register ("java/code/IMyInterface", "", "java.code.IMyInterfaceInvoker")]
33+
public partial interface IMyInterface : IJavaObject, IJavaPeerable {
34+
static new readonly JniPeerMembers _members = new JniPeerMembers ("java/code/IMyInterface", typeof (IMyInterface), isInterface: true);
35+
36+
// Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='DoSomething' and count(parameter)=0]"
37+
[Register ("DoSomething", "()V", "")]
38+
public static unsafe void DoSomething ()
39+
{
40+
const string __id = "DoSomething.()V";
41+
try {
42+
_members.StaticMethods.InvokeVoidMethod (__id, null);
43+
} finally {
44+
}
45+
}
46+
47+
}
48+
49+
[global::Android.Runtime.Register ("java/code/IMyInterface", DoNotGenerateAcw=true)]
50+
internal partial class IMyInterfaceInvoker : global::Java.Lang.Object, IMyInterface {
51+
52+
internal static new readonly JniPeerMembers _members = new JniPeerMembers ("java/code/IMyInterface", typeof (IMyInterfaceInvoker));
53+
54+
static IntPtr java_class_ref {
55+
get { return _members.JniPeerType.PeerReference.Handle; }
56+
}
57+
58+
public override global::Java.Interop.JniPeerMembers JniPeerMembers {
59+
get { return _members; }
60+
}
61+
62+
protected override IntPtr ThresholdClass {
63+
get { return class_ref; }
64+
}
65+
66+
protected override global::System.Type ThresholdType {
67+
get { return _members.ManagedPeerType; }
68+
}
69+
70+
IntPtr class_ref;
71+
72+
public static IMyInterface GetObject (IntPtr handle, JniHandleOwnership transfer)
73+
{
74+
return global::Java.Lang.Object.GetObject<IMyInterface> (handle, transfer);
75+
}
76+
77+
static IntPtr Validate (IntPtr handle)
78+
{
79+
if (!JNIEnv.IsInstanceOf (handle, java_class_ref))
80+
throw new InvalidCastException (string.Format ("Unable to convert instance of type '{0}' to type '{1}'.",
81+
JNIEnv.GetClassNameFromInstance (handle), "java.code.IMyInterface"));
82+
return handle;
83+
}
84+
85+
protected override void Dispose (bool disposing)
86+
{
87+
if (this.class_ref != IntPtr.Zero)
88+
JNIEnv.DeleteGlobalRef (this.class_ref);
89+
this.class_ref = IntPtr.Zero;
90+
base.Dispose (disposing);
91+
}
92+
93+
public IMyInterfaceInvoker (IntPtr handle, JniHandleOwnership transfer) : base (Validate (handle), transfer)
94+
{
95+
IntPtr local_ref = JNIEnv.GetObjectClass (((global::Java.Lang.Object) this).Handle);
96+
this.class_ref = JNIEnv.NewGlobalRef (local_ref);
97+
JNIEnv.DeleteLocalRef (local_ref);
98+
}
99+
100+
}
101+
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Metadata.xml XPath interface reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']"
2+
[Register ("java/code/IMyInterface", "", "java.code.IMyInterfaceInvoker")]
3+
public partial interface IMyInterface : IJavaObject, IJavaPeerable {
4+
static new readonly JniPeerMembers _members = new JniPeerMembers ("java/code/IMyInterface", typeof (IMyInterface), isInterface: true);
5+
6+
static unsafe int Value {
7+
// Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='get_Value' and count(parameter)=0]"
8+
[Register ("get_Value", "()I", "Getget_ValueHandler")]
9+
get {
10+
const string __id = "get_Value.()I";
11+
try {
12+
var __rm = _members.StaticMethods.InvokeInt32Method (__id, null);
13+
return __rm;
14+
} finally {
15+
}
16+
}
17+
// Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='set_Value' and count(parameter)=1 and parameter[1][@type='int']]"
18+
[Register ("set_Value", "(I)V", "Getset_Value_IHandler")]
19+
set {
20+
const string __id = "set_Value.(I)V";
21+
try {
22+
JniArgumentValue* __args = stackalloc JniArgumentValue [1];
23+
__args [0] = new JniArgumentValue (value);
24+
_members.StaticMethods.InvokeVoidMethod (__id, __args);
25+
} finally {
26+
}
27+
}
28+
}
29+
30+
}
31+
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
[Register ("java/code/IMyInterface", DoNotGenerateAcw=true)]
2+
public abstract class MyInterface : Java.Lang.Object {
3+
4+
internal MyInterface ()
5+
{
6+
}
7+
// Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='DoSomething' and count(parameter)=0]"
8+
[Register ("DoSomething", "()V", "")]
9+
public static unsafe void DoSomething ()
10+
{
11+
const string __id = "DoSomething.()V";
12+
try {
13+
_members.StaticMethods.InvokeVoidMethod (__id, null);
14+
} finally {
15+
}
16+
}
17+
18+
19+
static new readonly JniPeerMembers _members = new XAPeerMembers ("java/code/IMyInterface", typeof (MyInterface));
20+
}
21+
22+
[Register ("java/code/IMyInterface", DoNotGenerateAcw=true)]
23+
[global::System.Obsolete ("Use the 'MyInterface' type. This type will be removed in a future release.")]
24+
public abstract class MyInterfaceConsts : MyInterface {
25+
26+
private MyInterfaceConsts ()
27+
{
28+
}
29+
}
30+
31+
// Metadata.xml XPath interface reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']"
32+
[Register ("java/code/IMyInterface", "", "java.code.IMyInterfaceInvoker")]
33+
public partial interface IMyInterface : IJavaObject, IJavaPeerable {
34+
static new readonly JniPeerMembers _members = new XAPeerMembers ("java/code/IMyInterface", typeof (IMyInterface), isInterface: true);
35+
36+
// Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='DoSomething' and count(parameter)=0]"
37+
[Register ("DoSomething", "()V", "")]
38+
public static unsafe void DoSomething ()
39+
{
40+
const string __id = "DoSomething.()V";
41+
try {
42+
_members.StaticMethods.InvokeVoidMethod (__id, null);
43+
} finally {
44+
}
45+
}
46+
47+
}
48+
49+
[global::Android.Runtime.Register ("java/code/IMyInterface", DoNotGenerateAcw=true)]
50+
internal partial class IMyInterfaceInvoker : global::Java.Lang.Object, IMyInterface {
51+
52+
internal static new readonly JniPeerMembers _members = new XAPeerMembers ("java/code/IMyInterface", typeof (IMyInterfaceInvoker));
53+
54+
static IntPtr java_class_ref {
55+
get { return _members.JniPeerType.PeerReference.Handle; }
56+
}
57+
58+
public override global::Java.Interop.JniPeerMembers JniPeerMembers {
59+
get { return _members; }
60+
}
61+
62+
protected override IntPtr ThresholdClass {
63+
get { return class_ref; }
64+
}
65+
66+
protected override global::System.Type ThresholdType {
67+
get { return _members.ManagedPeerType; }
68+
}
69+
70+
IntPtr class_ref;
71+
72+
public static IMyInterface GetObject (IntPtr handle, JniHandleOwnership transfer)
73+
{
74+
return global::Java.Lang.Object.GetObject<IMyInterface> (handle, transfer);
75+
}
76+
77+
static IntPtr Validate (IntPtr handle)
78+
{
79+
if (!JNIEnv.IsInstanceOf (handle, java_class_ref))
80+
throw new InvalidCastException (string.Format ("Unable to convert instance of type '{0}' to type '{1}'.",
81+
JNIEnv.GetClassNameFromInstance (handle), "java.code.IMyInterface"));
82+
return handle;
83+
}
84+
85+
protected override void Dispose (bool disposing)
86+
{
87+
if (this.class_ref != IntPtr.Zero)
88+
JNIEnv.DeleteGlobalRef (this.class_ref);
89+
this.class_ref = IntPtr.Zero;
90+
base.Dispose (disposing);
91+
}
92+
93+
public IMyInterfaceInvoker (IntPtr handle, JniHandleOwnership transfer) : base (Validate (handle), transfer)
94+
{
95+
IntPtr local_ref = JNIEnv.GetObjectClass (((global::Java.Lang.Object) this).Handle);
96+
this.class_ref = JNIEnv.NewGlobalRef (local_ref);
97+
JNIEnv.DeleteLocalRef (local_ref);
98+
}
99+
100+
}
101+
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Metadata.xml XPath interface reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']"
2+
[Register ("java/code/IMyInterface", "", "java.code.IMyInterfaceInvoker")]
3+
public partial interface IMyInterface : IJavaObject, IJavaPeerable {
4+
static new readonly JniPeerMembers _members = new XAPeerMembers ("java/code/IMyInterface", typeof (IMyInterface), isInterface: true);
5+
6+
static unsafe int Value {
7+
// Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='get_Value' and count(parameter)=0]"
8+
[Register ("get_Value", "()I", "Getget_ValueHandler")]
9+
get {
10+
const string __id = "get_Value.()I";
11+
try {
12+
var __rm = _members.StaticMethods.InvokeInt32Method (__id, null);
13+
return __rm;
14+
} finally {
15+
}
16+
}
17+
// Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='set_Value' and count(parameter)=1 and parameter[1][@type='int']]"
18+
[Register ("set_Value", "(I)V", "Getset_Value_IHandler")]
19+
set {
20+
const string __id = "set_Value.(I)V";
21+
try {
22+
JniArgumentValue* __args = stackalloc JniArgumentValue [1];
23+
__args [0] = new JniArgumentValue (value);
24+
_members.StaticMethods.InvokeVoidMethod (__id, __args);
25+
} finally {
26+
}
27+
}
28+
}
29+
30+
}
31+

tools/generator/Tests/Unit-Tests/DefaultInterfaceMethodsTests.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,5 +148,40 @@ public void WriteSealedOverriddenDefaultMethod ()
148148
// The method should not be marked as 'virtual sealed'
149149
Assert.False (writer.ToString ().Contains ("virtual sealed"));
150150
}
151+
152+
[Test]
153+
public void WriteStaticInterfaceMethod ()
154+
{
155+
// Create an interface with a static method
156+
var iface = SupportTypeBuilder.CreateEmptyInterface ("java.code.IMyInterface");
157+
iface.Methods.Add (new TestMethod (iface, "DoSomething").SetStatic ());
158+
159+
iface.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ());
160+
161+
generator.WriteInterface (iface, string.Empty, new GenerationInfo (string.Empty, string.Empty, "MyAssembly"));
162+
163+
Assert.AreEqual (GetTargetedExpected (nameof (WriteStaticInterfaceMethod)), writer.ToString ().NormalizeLineEndings ());
164+
}
165+
166+
[Test]
167+
public void WriteStaticInterfaceProperty ()
168+
{
169+
// Create an interface with a static property
170+
var iface = SupportTypeBuilder.CreateEmptyInterface ("java.code.IMyInterface");
171+
var prop = SupportTypeBuilder.CreateProperty (iface, "Value", "int", options);
172+
173+
prop.Getter.IsStatic = true;
174+
prop.Getter.IsVirtual = false;
175+
prop.Setter.IsStatic = true;
176+
prop.Setter.IsVirtual = false;
177+
178+
iface.Properties.Add (prop);
179+
180+
iface.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ());
181+
182+
generator.WriteInterfaceDeclaration (iface, string.Empty);
183+
184+
Assert.AreEqual (GetTargetedExpected (nameof (WriteStaticInterfaceProperty)), writer.ToString ().NormalizeLineEndings ());
185+
}
151186
}
152187
}

tools/generator/Tests/generator-Tests.csproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,12 @@
295295
<Content Include="Unit-Tests\CodeGeneratorExpectedResults\JavaInterop1\WriteMethodWithVoidReturn.txt">
296296
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
297297
</Content>
298+
<Content Include="Unit-Tests\CodeGeneratorExpectedResults\JavaInterop1\WriteStaticInterfaceMethod.txt">
299+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
300+
</Content>
301+
<Content Include="Unit-Tests\CodeGeneratorExpectedResults\JavaInterop1\WriteStaticInterfaceProperty.txt">
302+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
303+
</Content>
298304
<Content Include="Unit-Tests\CodeGeneratorExpectedResults\XAJavaInterop1\WriteClass.txt">
299305
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
300306
</Content>
@@ -427,6 +433,12 @@
427433
<Content Include="Unit-Tests\CodeGeneratorExpectedResults\XAJavaInterop1\WriteProperty.txt">
428434
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
429435
</Content>
436+
<Content Include="Unit-Tests\CodeGeneratorExpectedResults\XAJavaInterop1\WriteStaticInterfaceMethod.txt">
437+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
438+
</Content>
439+
<Content Include="Unit-Tests\CodeGeneratorExpectedResults\XAJavaInterop1\WriteStaticInterfaceProperty.txt">
440+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
441+
</Content>
430442
<Content Include="Unit-Tests\CodeGeneratorExpectedResults\XamarinAndroid\WriteClass.txt">
431443
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
432444
</Content>

0 commit comments

Comments
 (0)