From 4ed718c27c546521a45e132da5a45203baa4fe4c Mon Sep 17 00:00:00 2001 From: Jonathan Pobst Date: Tue, 4 May 2021 13:47:14 -0500 Subject: [PATCH] [generator] Ensure non-constant static interface fields are generated as interface properties. --- .../ObsoleteInterfaceAlternativeClass.txt | 12 ++ .../WriteInterfaceFieldAsDimProperty.txt | 126 ++++++++++++++++++ .../ObsoleteInterfaceAlternativeClass.txt | 12 ++ .../WriteInterfaceFieldAsDimProperty.txt | 126 ++++++++++++++++++ .../DefaultInterfaceMethodsTests.cs | 26 ++++ .../InterfaceGen.cs | 15 ++- .../generator/SourceWriters/BoundInterface.cs | 4 +- 7 files changed, 316 insertions(+), 5 deletions(-) create mode 100644 tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteInterfaceFieldAsDimProperty.txt create mode 100644 tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteInterfaceFieldAsDimProperty.txt diff --git a/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/ObsoleteInterfaceAlternativeClass.txt b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/ObsoleteInterfaceAlternativeClass.txt index fbe6e214c..f7419581f 100644 --- a/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/ObsoleteInterfaceAlternativeClass.txt +++ b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/ObsoleteInterfaceAlternativeClass.txt @@ -72,6 +72,18 @@ public partial interface IParent : IJavaObject, IJavaPeerable { [Obsolete ("deprecated")] public const string AlreadyObsolete = (string) "android.permission.ACCEPT_HANDOVER"; + + // Metadata.xml XPath field reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']/field[@name='API_NAME']" + [Register ("API_NAME")] + public static string ApiName { + get { + const string __id = "API_NAME.Ljava/lang/String;"; + + var __v = _members.StaticFields.GetObjectValue (__id); + return JNIEnv.GetString (__v.Handle, JniHandleOwnership.TransferLocalRef); + } + } + // Metadata.xml XPath method reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']/method[@name='comparing' and count(parameter)=0]" [Register ("comparing", "()I", "")] public static unsafe int Comparing () diff --git a/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteInterfaceFieldAsDimProperty.txt b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteInterfaceFieldAsDimProperty.txt new file mode 100644 index 000000000..567ac0ad2 --- /dev/null +++ b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteInterfaceFieldAsDimProperty.txt @@ -0,0 +1,126 @@ +[Register ("com/xamarin/android/MyInterface", DoNotGenerateAcw=true)] +public abstract class MyInterface : Java.Lang.Object { + internal MyInterface () + { + } + + // Metadata.xml XPath field reference: path="/api/package[@name='com.xamarin.android']/interface[@name='MyInterface']/field[@name='EGL_NATIVE_VISUAL_ID']" + [Register ("EGL_NATIVE_VISUAL_ID")] + public const int EglNativeVisualId = (int) 12334; + + + // Metadata.xml XPath field reference: path="/api/package[@name='com.xamarin.android']/interface[@name='MyInterface']/field[@name='EGL_NO_SURFACE']" + [Register ("EGL_NO_SURFACE")] + public static int EglNoSurface { + get { + const string __id = "EGL_NO_SURFACE.I"; + + var __v = _members.StaticFields.GetInt32Value (__id); + return __v; + } + set { + const string __id = "EGL_NO_SURFACE.I"; + + try { + _members.StaticFields.SetValue (__id, value); + } finally { + } + } + } + + static readonly JniPeerMembers _members = new JniPeerMembers ("com/xamarin/android/MyInterface", typeof (MyInterface)); + +} + +[Register ("com/xamarin/android/MyInterface", DoNotGenerateAcw=true)] +[global::System.Obsolete ("Use the 'MyInterface' type. This type will be removed in a future release.", error: true)] +public abstract class MyInterfaceConsts : MyInterface { + private MyInterfaceConsts () + { + } + +} + +// Metadata.xml XPath interface reference: path="/api/package[@name='com.xamarin.android']/interface[@name='MyInterface']" +[Register ("com/xamarin/android/MyInterface", "", "Com.Xamarin.Android.IMyInterfaceInvoker")] +public partial interface IMyInterface : IJavaObject, IJavaPeerable { + private static readonly JniPeerMembers _members = new JniPeerMembers ("com/xamarin/android/MyInterface", typeof (IMyInterface), isInterface: true); + + + // Metadata.xml XPath field reference: path="/api/package[@name='com.xamarin.android']/interface[@name='MyInterface']/field[@name='EGL_NO_SURFACE']" + [Register ("EGL_NO_SURFACE")] + public static int EglNoSurface { + get { + const string __id = "EGL_NO_SURFACE.I"; + + var __v = _members.StaticFields.GetInt32Value (__id); + return __v; + } + set { + const string __id = "EGL_NO_SURFACE.I"; + + try { + _members.StaticFields.SetValue (__id, value); + } finally { + } + } + } + +} + +[global::Android.Runtime.Register ("com/xamarin/android/MyInterface", DoNotGenerateAcw=true)] +internal partial class IMyInterfaceInvoker : global::Java.Lang.Object, IMyInterface { + static readonly JniPeerMembers _members = new JniPeerMembers ("com/xamarin/android/MyInterface", typeof (IMyInterfaceInvoker)); + + static IntPtr java_class_ref { + get { return _members.JniPeerType.PeerReference.Handle; } + } + + [global::System.Diagnostics.DebuggerBrowsable (global::System.Diagnostics.DebuggerBrowsableState.Never)] + [global::System.ComponentModel.EditorBrowsable (global::System.ComponentModel.EditorBrowsableState.Never)] + public override global::Java.Interop.JniPeerMembers JniPeerMembers { + get { return _members; } + } + + [global::System.Diagnostics.DebuggerBrowsable (global::System.Diagnostics.DebuggerBrowsableState.Never)] + [global::System.ComponentModel.EditorBrowsable (global::System.ComponentModel.EditorBrowsableState.Never)] + protected override IntPtr ThresholdClass { + get { return class_ref; } + } + + [global::System.Diagnostics.DebuggerBrowsable (global::System.Diagnostics.DebuggerBrowsableState.Never)] + [global::System.ComponentModel.EditorBrowsable (global::System.ComponentModel.EditorBrowsableState.Never)] + 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 ($"Unable to convert instance of type '{JNIEnv.GetClassNameFromInstance (handle)}' to type 'com.xamarin.android.MyInterface'."); + 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/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/ObsoleteInterfaceAlternativeClass.txt b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/ObsoleteInterfaceAlternativeClass.txt index 1066cf81b..51e2fe12d 100644 --- a/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/ObsoleteInterfaceAlternativeClass.txt +++ b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/ObsoleteInterfaceAlternativeClass.txt @@ -72,6 +72,18 @@ public partial interface IParent : IJavaObject, IJavaPeerable { [Obsolete ("deprecated")] public const string AlreadyObsolete = (string) "android.permission.ACCEPT_HANDOVER"; + + // Metadata.xml XPath field reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']/field[@name='API_NAME']" + [Register ("API_NAME")] + public static string ApiName { + get { + const string __id = "API_NAME.Ljava/lang/String;"; + + var __v = _members.StaticFields.GetObjectValue (__id); + return JNIEnv.GetString (__v.Handle, JniHandleOwnership.TransferLocalRef); + } + } + // Metadata.xml XPath method reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']/method[@name='comparing' and count(parameter)=0]" [Register ("comparing", "()I", "")] public static unsafe int Comparing () diff --git a/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteInterfaceFieldAsDimProperty.txt b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteInterfaceFieldAsDimProperty.txt new file mode 100644 index 000000000..53ec6dfe9 --- /dev/null +++ b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteInterfaceFieldAsDimProperty.txt @@ -0,0 +1,126 @@ +[Register ("com/xamarin/android/MyInterface", DoNotGenerateAcw=true)] +public abstract class MyInterface : Java.Lang.Object { + internal MyInterface () + { + } + + // Metadata.xml XPath field reference: path="/api/package[@name='com.xamarin.android']/interface[@name='MyInterface']/field[@name='EGL_NATIVE_VISUAL_ID']" + [Register ("EGL_NATIVE_VISUAL_ID")] + public const int EglNativeVisualId = (int) 12334; + + + // Metadata.xml XPath field reference: path="/api/package[@name='com.xamarin.android']/interface[@name='MyInterface']/field[@name='EGL_NO_SURFACE']" + [Register ("EGL_NO_SURFACE")] + public static int EglNoSurface { + get { + const string __id = "EGL_NO_SURFACE.I"; + + var __v = _members.StaticFields.GetInt32Value (__id); + return __v; + } + set { + const string __id = "EGL_NO_SURFACE.I"; + + try { + _members.StaticFields.SetValue (__id, value); + } finally { + } + } + } + + static readonly JniPeerMembers _members = new XAPeerMembers ("com/xamarin/android/MyInterface", typeof (MyInterface)); + +} + +[Register ("com/xamarin/android/MyInterface", DoNotGenerateAcw=true)] +[global::System.Obsolete ("Use the 'MyInterface' type. This type will be removed in a future release.", error: true)] +public abstract class MyInterfaceConsts : MyInterface { + private MyInterfaceConsts () + { + } + +} + +// Metadata.xml XPath interface reference: path="/api/package[@name='com.xamarin.android']/interface[@name='MyInterface']" +[Register ("com/xamarin/android/MyInterface", "", "Com.Xamarin.Android.IMyInterfaceInvoker")] +public partial interface IMyInterface : IJavaObject, IJavaPeerable { + private static readonly JniPeerMembers _members = new XAPeerMembers ("com/xamarin/android/MyInterface", typeof (IMyInterface), isInterface: true); + + + // Metadata.xml XPath field reference: path="/api/package[@name='com.xamarin.android']/interface[@name='MyInterface']/field[@name='EGL_NO_SURFACE']" + [Register ("EGL_NO_SURFACE")] + public static int EglNoSurface { + get { + const string __id = "EGL_NO_SURFACE.I"; + + var __v = _members.StaticFields.GetInt32Value (__id); + return __v; + } + set { + const string __id = "EGL_NO_SURFACE.I"; + + try { + _members.StaticFields.SetValue (__id, value); + } finally { + } + } + } + +} + +[global::Android.Runtime.Register ("com/xamarin/android/MyInterface", DoNotGenerateAcw=true)] +internal partial class IMyInterfaceInvoker : global::Java.Lang.Object, IMyInterface { + static readonly JniPeerMembers _members = new XAPeerMembers ("com/xamarin/android/MyInterface", typeof (IMyInterfaceInvoker)); + + static IntPtr java_class_ref { + get { return _members.JniPeerType.PeerReference.Handle; } + } + + [global::System.Diagnostics.DebuggerBrowsable (global::System.Diagnostics.DebuggerBrowsableState.Never)] + [global::System.ComponentModel.EditorBrowsable (global::System.ComponentModel.EditorBrowsableState.Never)] + public override global::Java.Interop.JniPeerMembers JniPeerMembers { + get { return _members; } + } + + [global::System.Diagnostics.DebuggerBrowsable (global::System.Diagnostics.DebuggerBrowsableState.Never)] + [global::System.ComponentModel.EditorBrowsable (global::System.ComponentModel.EditorBrowsableState.Never)] + protected override IntPtr ThresholdClass { + get { return class_ref; } + } + + [global::System.Diagnostics.DebuggerBrowsable (global::System.Diagnostics.DebuggerBrowsableState.Never)] + [global::System.ComponentModel.EditorBrowsable (global::System.ComponentModel.EditorBrowsableState.Never)] + 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 ($"Unable to convert instance of type '{JNIEnv.GetClassNameFromInstance (handle)}' to type 'com.xamarin.android.MyInterface'."); + 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/tests/generator-Tests/Unit-Tests/DefaultInterfaceMethodsTests.cs b/tests/generator-Tests/Unit-Tests/DefaultInterfaceMethodsTests.cs index 4f06bb1cc..86dc423ac 100644 --- a/tests/generator-Tests/Unit-Tests/DefaultInterfaceMethodsTests.cs +++ b/tests/generator-Tests/Unit-Tests/DefaultInterfaceMethodsTests.cs @@ -462,5 +462,31 @@ public void GenerateProperNestedInterfaceSignatures () Assert.True (generated.Contains ("GetOnActivityDestroyed_IHandler:Com.Xamarin.Android.Application/IActivityLifecycleInterface, MyAssembly")); Assert.False (generated.Contains ("GetOnActivityDestroyed_IHandler:Com.Xamarin.Android.Application.IActivityLifecycleInterface, MyAssembly")); } + + [Test] + public void WriteInterfaceFieldAsDimProperty () + { + // Ensure we write interface fields that are not constant, and thus must be written as properties + var xml = @" + + + + + + + "; + + var gens = ParseApiDefinition (xml); + var iface = gens.OfType ().Single (); + + var result = iface.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()); + Assert.True (result); + + generator.WriteType (iface, string.Empty, new GenerationInfo (string.Empty, string.Empty, "MyAssembly")); + + var generated = writer.ToString (); + + Assert.AreEqual (GetTargetedExpected (nameof (WriteInterfaceFieldAsDimProperty)), writer.ToString ().NormalizeLineEndings ()); + } } } diff --git a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/InterfaceGen.cs b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/InterfaceGen.cs index 6158c9f57..8b8b0ef6d 100644 --- a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/InterfaceGen.cs +++ b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/InterfaceGen.cs @@ -149,14 +149,23 @@ internal string GetEventDelegateName (Method m) // These are fields that we currently support generating on the interface with DIM public IEnumerable GetGeneratableFields (CodeGenerationOptions options) { - if (!options.SupportInterfaceConstants) - return Enumerable.Empty (); + var fields = new List (); - return Fields.Where (f => !f.NeedsProperty && !(f.DeprecatedComment?.Contains ("constant will be removed") == true)); + // Constant fields + if (options.SupportInterfaceConstants) + fields.AddRange (Fields.Where (f => !f.NeedsProperty && !(f.DeprecatedComment?.Contains ("constant will be removed") == true))); + + // Invoked fields exposed as properties + if (options.SupportDefaultInterfaceMethods) + fields.AddRange (Fields.Where (f => f.NeedsProperty && !(f.DeprecatedComment?.Contains ("constant will be removed") == true))); + + return fields; } public bool HasDefaultMethods => GetAllMethods ().Any (m => m.IsInterfaceDefaultMethod); + public bool HasFieldsAsProperties => Fields.Any (f => f.NeedsProperty); + public bool HasStaticMethods => GetAllMethods ().Any (m => m.IsStatic); public bool IsConstSugar (CodeGenerationOptions options) diff --git a/tools/generator/SourceWriters/BoundInterface.cs b/tools/generator/SourceWriters/BoundInterface.cs index 05194f4a7..1738d922a 100644 --- a/tools/generator/SourceWriters/BoundInterface.cs +++ b/tools/generator/SourceWriters/BoundInterface.cs @@ -153,14 +153,14 @@ void AddInheritedInterfaces (InterfaceGen iface, CodeGenerationOptions opt) void AddClassHandle (InterfaceGen iface, CodeGenerationOptions opt) { - if (opt.SupportDefaultInterfaceMethods && (iface.HasDefaultMethods || iface.HasStaticMethods)) + if (opt.SupportDefaultInterfaceMethods && (iface.HasDefaultMethods || iface.HasStaticMethods || iface.HasFieldsAsProperties)) Fields.Add (new PeerMembersField (opt, iface.RawJniName, iface.Name, true)); } void AddFields (InterfaceGen iface, CodeGenerationOptions opt, CodeGeneratorContext context) { // Interface fields are only supported with DIM - if (!opt.SupportInterfaceConstants) + if (!opt.SupportInterfaceConstants && !opt.SupportDefaultInterfaceMethods) return; var seen = new HashSet ();