Skip to content

Commit

Permalink
[generator] Use DIM to support static interface methods (#487)
Browse files Browse the repository at this point in the history
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);
	}
  • Loading branch information
jpobst authored and jonpryor committed Aug 30, 2019
1 parent 3fee05c commit 855b7e9
Show file tree
Hide file tree
Showing 8 changed files with 319 additions and 4 deletions.
Expand Up @@ -530,7 +530,7 @@ public void WriteInterfaceDeclaration (InterfaceGen @interface, string indent)
writer.WriteLine ("{0}{1} partial interface {2}{3} {{", indent, @interface.Visibility, @interface.Name,
@interface.IsConstSugar ? string.Empty : @interface.Interfaces.Count == 0 || sb.Length == 0 ? " : " + GetAllInterfaceImplements () : " : " + sb.ToString ());

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

WriteInterfaceFields (@interface, indent + "\t");
Expand Down Expand Up @@ -994,16 +994,18 @@ public void WriteInterfaceMethods (InterfaceGen @interface, string indent)
WriteMethodDeclaration (m, indent, @interface, @interface.AssemblyQualifiedName + "Invoker");
}

foreach (var m in @interface.Methods.Where (m => m.IsInterfaceDefaultMethod))
WriteMethod (m, indent, @interface, true);
if (opt.SupportDefaultInterfaceMethods)
foreach (var m in @interface.Methods.Where (m => m.IsInterfaceDefaultMethod || m.IsStatic))
WriteMethod (m, indent, @interface, true);
}

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

WriteImplementedProperties (@interface.Properties.Where (p => p.Getter.IsInterfaceDefaultMethod), indent, false, @interface);
if (opt.SupportDefaultInterfaceMethods)
WriteImplementedProperties (@interface.Properties.Where (p => p.Getter.IsInterfaceDefaultMethod || p.Getter.IsStatic), indent, false, @interface);
}

public void WriteInterfacePropertyInvokers (InterfaceGen @interface, IEnumerable<Property> properties, string indent, HashSet<string> members)
Expand Down
Expand Up @@ -143,6 +143,8 @@ public IEnumerable<Field> GetGeneratableFields (CodeGenerationOptions options)

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

public bool HasStaticMethods => GetAllMethods ().Any (m => m.IsStatic);

public bool IsConstSugar {
get {
if (Methods.Count > 0 || Properties.Count > 0)
Expand Down
@@ -0,0 +1,101 @@
[Register ("java/code/IMyInterface", DoNotGenerateAcw=true)]
public abstract class MyInterface : Java.Lang.Object {

internal MyInterface ()
{
}
// Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='DoSomething' and count(parameter)=0]"
[Register ("DoSomething", "()V", "")]
public static unsafe void DoSomething ()
{
const string __id = "DoSomething.()V";
try {
_members.StaticMethods.InvokeVoidMethod (__id, null);
} finally {
}
}


static new readonly JniPeerMembers _members = new JniPeerMembers ("java/code/IMyInterface", typeof (MyInterface));
}

[Register ("java/code/IMyInterface", DoNotGenerateAcw=true)]
[global::System.Obsolete ("Use the 'MyInterface' type. This type will be removed in a future release.")]
public abstract class MyInterfaceConsts : MyInterface {

private MyInterfaceConsts ()
{
}
}

// Metadata.xml XPath interface reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']"
[Register ("java/code/IMyInterface", "", "java.code.IMyInterfaceInvoker")]
public partial interface IMyInterface : IJavaObject, IJavaPeerable {
static new readonly JniPeerMembers _members = new JniPeerMembers ("java/code/IMyInterface", typeof (IMyInterface), isInterface: true);

// Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='DoSomething' and count(parameter)=0]"
[Register ("DoSomething", "()V", "")]
public static unsafe void DoSomething ()
{
const string __id = "DoSomething.()V";
try {
_members.StaticMethods.InvokeVoidMethod (__id, null);
} finally {
}
}

}

[global::Android.Runtime.Register ("java/code/IMyInterface", DoNotGenerateAcw=true)]
internal partial class IMyInterfaceInvoker : global::Java.Lang.Object, IMyInterface {

internal static new readonly JniPeerMembers _members = new JniPeerMembers ("java/code/IMyInterface", typeof (IMyInterfaceInvoker));

static IntPtr java_class_ref {
get { return _members.JniPeerType.PeerReference.Handle; }
}

public override global::Java.Interop.JniPeerMembers JniPeerMembers {
get { return _members; }
}

protected override IntPtr ThresholdClass {
get { return class_ref; }
}

protected override global::System.Type ThresholdType {
get { return _members.ManagedPeerType; }
}

IntPtr class_ref;

public static IMyInterface GetObject (IntPtr handle, JniHandleOwnership transfer)
{
return global::Java.Lang.Object.GetObject<IMyInterface> (handle, transfer);
}

static IntPtr Validate (IntPtr handle)
{
if (!JNIEnv.IsInstanceOf (handle, java_class_ref))
throw new InvalidCastException (string.Format ("Unable to convert instance of type '{0}' to type '{1}'.",
JNIEnv.GetClassNameFromInstance (handle), "java.code.IMyInterface"));
return handle;
}

protected override void Dispose (bool disposing)
{
if (this.class_ref != IntPtr.Zero)
JNIEnv.DeleteGlobalRef (this.class_ref);
this.class_ref = IntPtr.Zero;
base.Dispose (disposing);
}

public IMyInterfaceInvoker (IntPtr handle, JniHandleOwnership transfer) : base (Validate (handle), transfer)
{
IntPtr local_ref = JNIEnv.GetObjectClass (((global::Java.Lang.Object) this).Handle);
this.class_ref = JNIEnv.NewGlobalRef (local_ref);
JNIEnv.DeleteLocalRef (local_ref);
}

}

@@ -0,0 +1,31 @@
// Metadata.xml XPath interface reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']"
[Register ("java/code/IMyInterface", "", "java.code.IMyInterfaceInvoker")]
public partial interface IMyInterface : IJavaObject, IJavaPeerable {
static new readonly JniPeerMembers _members = new JniPeerMembers ("java/code/IMyInterface", typeof (IMyInterface), isInterface: true);

static unsafe int Value {
// Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='get_Value' and count(parameter)=0]"
[Register ("get_Value", "()I", "Getget_ValueHandler")]
get {
const string __id = "get_Value.()I";
try {
var __rm = _members.StaticMethods.InvokeInt32Method (__id, null);
return __rm;
} finally {
}
}
// 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']]"
[Register ("set_Value", "(I)V", "Getset_Value_IHandler")]
set {
const string __id = "set_Value.(I)V";
try {
JniArgumentValue* __args = stackalloc JniArgumentValue [1];
__args [0] = new JniArgumentValue (value);
_members.StaticMethods.InvokeVoidMethod (__id, __args);
} finally {
}
}
}

}

@@ -0,0 +1,101 @@
[Register ("java/code/IMyInterface", DoNotGenerateAcw=true)]
public abstract class MyInterface : Java.Lang.Object {

internal MyInterface ()
{
}
// Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='DoSomething' and count(parameter)=0]"
[Register ("DoSomething", "()V", "")]
public static unsafe void DoSomething ()
{
const string __id = "DoSomething.()V";
try {
_members.StaticMethods.InvokeVoidMethod (__id, null);
} finally {
}
}


static new readonly JniPeerMembers _members = new XAPeerMembers ("java/code/IMyInterface", typeof (MyInterface));
}

[Register ("java/code/IMyInterface", DoNotGenerateAcw=true)]
[global::System.Obsolete ("Use the 'MyInterface' type. This type will be removed in a future release.")]
public abstract class MyInterfaceConsts : MyInterface {

private MyInterfaceConsts ()
{
}
}

// Metadata.xml XPath interface reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']"
[Register ("java/code/IMyInterface", "", "java.code.IMyInterfaceInvoker")]
public partial interface IMyInterface : IJavaObject, IJavaPeerable {
static new readonly JniPeerMembers _members = new XAPeerMembers ("java/code/IMyInterface", typeof (IMyInterface), isInterface: true);

// Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='DoSomething' and count(parameter)=0]"
[Register ("DoSomething", "()V", "")]
public static unsafe void DoSomething ()
{
const string __id = "DoSomething.()V";
try {
_members.StaticMethods.InvokeVoidMethod (__id, null);
} finally {
}
}

}

[global::Android.Runtime.Register ("java/code/IMyInterface", DoNotGenerateAcw=true)]
internal partial class IMyInterfaceInvoker : global::Java.Lang.Object, IMyInterface {

internal static new readonly JniPeerMembers _members = new XAPeerMembers ("java/code/IMyInterface", typeof (IMyInterfaceInvoker));

static IntPtr java_class_ref {
get { return _members.JniPeerType.PeerReference.Handle; }
}

public override global::Java.Interop.JniPeerMembers JniPeerMembers {
get { return _members; }
}

protected override IntPtr ThresholdClass {
get { return class_ref; }
}

protected override global::System.Type ThresholdType {
get { return _members.ManagedPeerType; }
}

IntPtr class_ref;

public static IMyInterface GetObject (IntPtr handle, JniHandleOwnership transfer)
{
return global::Java.Lang.Object.GetObject<IMyInterface> (handle, transfer);
}

static IntPtr Validate (IntPtr handle)
{
if (!JNIEnv.IsInstanceOf (handle, java_class_ref))
throw new InvalidCastException (string.Format ("Unable to convert instance of type '{0}' to type '{1}'.",
JNIEnv.GetClassNameFromInstance (handle), "java.code.IMyInterface"));
return handle;
}

protected override void Dispose (bool disposing)
{
if (this.class_ref != IntPtr.Zero)
JNIEnv.DeleteGlobalRef (this.class_ref);
this.class_ref = IntPtr.Zero;
base.Dispose (disposing);
}

public IMyInterfaceInvoker (IntPtr handle, JniHandleOwnership transfer) : base (Validate (handle), transfer)
{
IntPtr local_ref = JNIEnv.GetObjectClass (((global::Java.Lang.Object) this).Handle);
this.class_ref = JNIEnv.NewGlobalRef (local_ref);
JNIEnv.DeleteLocalRef (local_ref);
}

}

@@ -0,0 +1,31 @@
// Metadata.xml XPath interface reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']"
[Register ("java/code/IMyInterface", "", "java.code.IMyInterfaceInvoker")]
public partial interface IMyInterface : IJavaObject, IJavaPeerable {
static new readonly JniPeerMembers _members = new XAPeerMembers ("java/code/IMyInterface", typeof (IMyInterface), isInterface: true);

static unsafe int Value {
// Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='get_Value' and count(parameter)=0]"
[Register ("get_Value", "()I", "Getget_ValueHandler")]
get {
const string __id = "get_Value.()I";
try {
var __rm = _members.StaticMethods.InvokeInt32Method (__id, null);
return __rm;
} finally {
}
}
// 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']]"
[Register ("set_Value", "(I)V", "Getset_Value_IHandler")]
set {
const string __id = "set_Value.(I)V";
try {
JniArgumentValue* __args = stackalloc JniArgumentValue [1];
__args [0] = new JniArgumentValue (value);
_members.StaticMethods.InvokeVoidMethod (__id, __args);
} finally {
}
}
}

}

35 changes: 35 additions & 0 deletions tools/generator/Tests/Unit-Tests/DefaultInterfaceMethodsTests.cs
Expand Up @@ -148,5 +148,40 @@ public void WriteSealedOverriddenDefaultMethod ()
// The method should not be marked as 'virtual sealed'
Assert.False (writer.ToString ().Contains ("virtual sealed"));
}

[Test]
public void WriteStaticInterfaceMethod ()
{
// Create an interface with a static method
var iface = SupportTypeBuilder.CreateEmptyInterface ("java.code.IMyInterface");
iface.Methods.Add (new TestMethod (iface, "DoSomething").SetStatic ());

iface.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ());

generator.WriteInterface (iface, string.Empty, new GenerationInfo (string.Empty, string.Empty, "MyAssembly"));

Assert.AreEqual (GetTargetedExpected (nameof (WriteStaticInterfaceMethod)), writer.ToString ().NormalizeLineEndings ());
}

[Test]
public void WriteStaticInterfaceProperty ()
{
// Create an interface with a static property
var iface = SupportTypeBuilder.CreateEmptyInterface ("java.code.IMyInterface");
var prop = SupportTypeBuilder.CreateProperty (iface, "Value", "int", options);

prop.Getter.IsStatic = true;
prop.Getter.IsVirtual = false;
prop.Setter.IsStatic = true;
prop.Setter.IsVirtual = false;

iface.Properties.Add (prop);

iface.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ());

generator.WriteInterfaceDeclaration (iface, string.Empty);

Assert.AreEqual (GetTargetedExpected (nameof (WriteStaticInterfaceProperty)), writer.ToString ().NormalizeLineEndings ());
}
}
}
12 changes: 12 additions & 0 deletions tools/generator/Tests/generator-Tests.csproj
Expand Up @@ -295,6 +295,12 @@
<Content Include="Unit-Tests\CodeGeneratorExpectedResults\JavaInterop1\WriteMethodWithVoidReturn.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Unit-Tests\CodeGeneratorExpectedResults\JavaInterop1\WriteStaticInterfaceMethod.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Unit-Tests\CodeGeneratorExpectedResults\JavaInterop1\WriteStaticInterfaceProperty.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Unit-Tests\CodeGeneratorExpectedResults\XAJavaInterop1\WriteClass.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
Expand Down Expand Up @@ -427,6 +433,12 @@
<Content Include="Unit-Tests\CodeGeneratorExpectedResults\XAJavaInterop1\WriteProperty.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Unit-Tests\CodeGeneratorExpectedResults\XAJavaInterop1\WriteStaticInterfaceMethod.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Unit-Tests\CodeGeneratorExpectedResults\XAJavaInterop1\WriteStaticInterfaceProperty.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Unit-Tests\CodeGeneratorExpectedResults\XamarinAndroid\WriteClass.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
Expand Down

0 comments on commit 855b7e9

Please sign in to comment.