From 855b7e92c5f91d01fc44adeae74ad511570bd43c Mon Sep 17 00:00:00 2001 From: Jonathan Pobst Date: Fri, 30 Aug 2019 10:12:02 -0500 Subject: [PATCH] [generator] Use DIM to support static interface methods (#487) Using the new Default Interface Members support (29f97075), 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); } --- .../CodeGenerator.cs | 10 +- .../InterfaceGen.cs | 2 + .../WriteStaticInterfaceMethod.txt | 101 ++++++++++++++++++ .../WriteStaticInterfaceProperty.txt | 31 ++++++ .../WriteStaticInterfaceMethod.txt | 101 ++++++++++++++++++ .../WriteStaticInterfaceProperty.txt | 31 ++++++ .../DefaultInterfaceMethodsTests.cs | 35 ++++++ tools/generator/Tests/generator-Tests.csproj | 12 +++ 8 files changed, 319 insertions(+), 4 deletions(-) create mode 100644 tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteStaticInterfaceMethod.txt create mode 100644 tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteStaticInterfaceProperty.txt create mode 100644 tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteStaticInterfaceMethod.txt create mode 100644 tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteStaticInterfaceProperty.txt diff --git a/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/CodeGenerator.cs b/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/CodeGenerator.cs index 0e43b432b..f6b9a7807 100644 --- a/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/CodeGenerator.cs +++ b/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/CodeGenerator.cs @@ -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"); @@ -994,8 +994,9 @@ 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) @@ -1003,7 +1004,8 @@ 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 properties, string indent, HashSet members) diff --git a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/InterfaceGen.cs b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/InterfaceGen.cs index e411c69a7..28d3acd68 100644 --- a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/InterfaceGen.cs +++ b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/InterfaceGen.cs @@ -143,6 +143,8 @@ public IEnumerable 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) diff --git a/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteStaticInterfaceMethod.txt b/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteStaticInterfaceMethod.txt new file mode 100644 index 000000000..fb7207ac3 --- /dev/null +++ b/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteStaticInterfaceMethod.txt @@ -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 (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); + } + +} + diff --git a/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteStaticInterfaceProperty.txt b/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteStaticInterfaceProperty.txt new file mode 100644 index 000000000..44ab8fe32 --- /dev/null +++ b/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteStaticInterfaceProperty.txt @@ -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 { + } + } + } + +} + diff --git a/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteStaticInterfaceMethod.txt b/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteStaticInterfaceMethod.txt new file mode 100644 index 000000000..34da51485 --- /dev/null +++ b/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteStaticInterfaceMethod.txt @@ -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 (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); + } + +} + diff --git a/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteStaticInterfaceProperty.txt b/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteStaticInterfaceProperty.txt new file mode 100644 index 000000000..7d2d99b86 --- /dev/null +++ b/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteStaticInterfaceProperty.txt @@ -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 { + } + } + } + +} + diff --git a/tools/generator/Tests/Unit-Tests/DefaultInterfaceMethodsTests.cs b/tools/generator/Tests/Unit-Tests/DefaultInterfaceMethodsTests.cs index a4189194b..142fbae9b 100644 --- a/tools/generator/Tests/Unit-Tests/DefaultInterfaceMethodsTests.cs +++ b/tools/generator/Tests/Unit-Tests/DefaultInterfaceMethodsTests.cs @@ -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 ()); + } } } diff --git a/tools/generator/Tests/generator-Tests.csproj b/tools/generator/Tests/generator-Tests.csproj index 8418a768b..68a150222 100644 --- a/tools/generator/Tests/generator-Tests.csproj +++ b/tools/generator/Tests/generator-Tests.csproj @@ -295,6 +295,12 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + PreserveNewest @@ -427,6 +433,12 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + PreserveNewest