From 28b1fc974c1c50896899c4e41ebfbf6ead1ebb0a Mon Sep 17 00:00:00 2001 From: Jonathan Pobst Date: Mon, 2 Mar 2020 15:31:03 -0600 Subject: [PATCH] [generator] Support using DIM to nest interface types. (#589) Context: https://github.com/xamarin/Java.Interop/issues/509 Consider the following Java code: package example; public interface Parent { public interface Child { } } Before C#8, interfaces could not contain types. To bind `example.Parent.Child`, we had to "un-nest" the interface, *prefixing* the interface name with the name of the enclosing type: namespace Example { public interface IParent { } public interface IParentChild { } } With C#8, interfaces can now contain types. We can now emit: namespace Example { public interface IParent { public interface IChild { } } } Enable this new style of binding when `generator --lang-features=nested-interface-types` is used. This is *disabled* by default. However, even when `nested-interface-types` is enabled, not all interfaces should follow this new strategy. In the case of `Mono.Android.dll`, nested interface types should *only* be used for types added in API-R/API-30. Add support for a new `unnest` Metadata attribute which can be placed on nested types to explicitly emit the C#7-compatible bindings: true In a future integration with xamarin-android, [`src/Mono.Android/metadata`][0] can be updated so that all APIs prior to API-30 are unnested, and all APIs starting with API-30 are nested: true true true true This will ensure we preserve API compatibility. [0]: https://github.com/xamarin/xamarin-android/blob/b366ac1e6d411dc052dc8711d881d71b7e967685/src/Mono.Android/metadata --- .../WriteNestedInterfaceTypes.txt | 179 ++++++++++++++++++ .../WriteUnnestedInterfaceTypes.txt | 179 ++++++++++++++++++ .../WriteNestedInterfaceTypes.txt | 179 ++++++++++++++++++ .../WriteUnnestedInterfaceTypes.txt | 179 ++++++++++++++++++ .../Unit-Tests/CodeGeneratorTests.cs | 2 +- .../DefaultInterfaceMethodsTests.cs | 57 +++++- .../Unit-Tests/InterfaceConstantsTests.cs | 4 +- .../Unit-Tests/KotlinFixupsTests.cs | 4 +- .../Unit-Tests/XmlApiImporterTests.cs | 12 +- tests/generator-Tests/Unit-Tests/XmlTests.cs | 18 +- tools/generator/CodeGenerationOptions.cs | 1 + tools/generator/CodeGenerator.cs | 1 + tools/generator/CodeGeneratorOptions.cs | 2 + .../CodeGenerator.cs | 15 +- .../XmlApiImporter.cs | 16 +- .../GenBase.cs | 6 + .../InterfaceGen.cs | 9 + .../Parser.cs | 4 +- 18 files changed, 833 insertions(+), 34 deletions(-) create mode 100644 tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteNestedInterfaceTypes.txt create mode 100644 tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteUnnestedInterfaceTypes.txt create mode 100644 tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteNestedInterfaceTypes.txt create mode 100644 tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteUnnestedInterfaceTypes.txt diff --git a/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteNestedInterfaceTypes.txt b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteNestedInterfaceTypes.txt new file mode 100644 index 000000000..ba46e94b8 --- /dev/null +++ b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteNestedInterfaceTypes.txt @@ -0,0 +1,179 @@ +// Metadata.xml XPath interface reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']" +[Register ("com/xamarin/android/Parent", "", "Com.Xamarin.Android.IParentInvoker")] +public partial interface IParent : IJavaObject, IJavaPeerable { + + int Bar { + // Metadata.xml XPath method reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']/method[@name='getBar' and count(parameter)=0]" + [Register ("getBar", "()I", "GetGetBarHandler:Com.Xamarin.Android.IParentInvoker, MyAssembly")] get; + } + + // Metadata.xml XPath interface reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent.Child']" + [Register ("com/xamarin/android/Parent$Child", "", "Com.Xamarin.Android.IParent/IChildInvoker")] + public partial interface IChild : IJavaObject, IJavaPeerable { + + int Bar { + // Metadata.xml XPath method reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent.Child']/method[@name='getBar' and count(parameter)=0]" + [Register ("getBar", "()I", "GetGetBarHandler:Com.Xamarin.Android.IParent/IChildInvoker, MyAssembly")] get; + } + + } + + [global::Android.Runtime.Register ("com/xamarin/android/Parent$Child", DoNotGenerateAcw=true)] + internal partial class IChildInvoker : global::Java.Lang.Object, IChild { + + static readonly JniPeerMembers _members = new JniPeerMembers ("com/xamarin/android/Parent$Child", typeof (IChildInvoker)); + + 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; } + } + + new IntPtr class_ref; + + public static IChild 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), "com.xamarin.android.Parent.Child")); + 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 IChildInvoker (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); + } + + static Delegate cb_getBar; +#pragma warning disable 0169 + static Delegate GetGetBarHandler () + { + if (cb_getBar == null) + cb_getBar = JNINativeWrapper.CreateDelegate ((Func) n_GetBar); + return cb_getBar; + } + + static int n_GetBar (IntPtr jnienv, IntPtr native__this) + { + Com.Xamarin.Android.IParent.IChild __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); + return __this.Bar; + } +#pragma warning restore 0169 + + IntPtr id_getBar; + public unsafe int Bar { + get { + if (id_getBar == IntPtr.Zero) + id_getBar = JNIEnv.GetMethodID (class_ref, "getBar", "()I"); + return JNIEnv.CallIntMethod (((global::Java.Lang.Object) this).Handle, id_getBar); + } + } + + } + + +} + +[global::Android.Runtime.Register ("com/xamarin/android/Parent", DoNotGenerateAcw=true)] +internal partial class IParentInvoker : global::Java.Lang.Object, IParent { + + static readonly JniPeerMembers _members = new JniPeerMembers ("com/xamarin/android/Parent", typeof (IParentInvoker)); + + 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; } + } + + new IntPtr class_ref; + + public static IParent 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), "com.xamarin.android.Parent")); + 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 IParentInvoker (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); + } + + static Delegate cb_getBar; +#pragma warning disable 0169 + static Delegate GetGetBarHandler () + { + if (cb_getBar == null) + cb_getBar = JNINativeWrapper.CreateDelegate ((Func) n_GetBar); + return cb_getBar; + } + + static int n_GetBar (IntPtr jnienv, IntPtr native__this) + { + Com.Xamarin.Android.IParent __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); + return __this.Bar; + } +#pragma warning restore 0169 + + IntPtr id_getBar; + public unsafe int Bar { + get { + if (id_getBar == IntPtr.Zero) + id_getBar = JNIEnv.GetMethodID (class_ref, "getBar", "()I"); + return JNIEnv.CallIntMethod (((global::Java.Lang.Object) this).Handle, id_getBar); + } + } + +} + diff --git a/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteUnnestedInterfaceTypes.txt b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteUnnestedInterfaceTypes.txt new file mode 100644 index 000000000..7ecbd9e50 --- /dev/null +++ b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteUnnestedInterfaceTypes.txt @@ -0,0 +1,179 @@ +// Metadata.xml XPath interface reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent.Child']" +[Register ("com/xamarin/android/Parent$Child", "", "Com.Xamarin.Android.IParentChildInvoker")] +public partial interface IParentChild : IJavaObject, IJavaPeerable { + + int Bar { + // Metadata.xml XPath method reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent.Child']/method[@name='getBar' and count(parameter)=0]" + [Register ("getBar", "()I", "GetGetBarHandler:Com.Xamarin.Android.IParentChildInvoker, MyAssembly")] get; + } + +} + +[global::Android.Runtime.Register ("com/xamarin/android/Parent$Child", DoNotGenerateAcw=true)] +internal partial class IParentChildInvoker : global::Java.Lang.Object, IParentChild { + + static readonly JniPeerMembers _members = new JniPeerMembers ("com/xamarin/android/Parent$Child", typeof (IParentChildInvoker)); + + 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; } + } + + new IntPtr class_ref; + + public static IParentChild 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), "com.xamarin.android.Parent.Child")); + 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 IParentChildInvoker (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); + } + + static Delegate cb_getBar; +#pragma warning disable 0169 + static Delegate GetGetBarHandler () + { + if (cb_getBar == null) + cb_getBar = JNINativeWrapper.CreateDelegate ((Func) n_GetBar); + return cb_getBar; + } + + static int n_GetBar (IntPtr jnienv, IntPtr native__this) + { + Com.Xamarin.Android.IParentChild __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); + return __this.Bar; + } +#pragma warning restore 0169 + + IntPtr id_getBar; + public unsafe int Bar { + get { + if (id_getBar == IntPtr.Zero) + id_getBar = JNIEnv.GetMethodID (class_ref, "getBar", "()I"); + return JNIEnv.CallIntMethod (((global::Java.Lang.Object) this).Handle, id_getBar); + } + } + +} + + +// Metadata.xml XPath interface reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']" +[Register ("com/xamarin/android/Parent", "", "Com.Xamarin.Android.IParentInvoker")] +public partial interface IParent : IJavaObject, IJavaPeerable { + + int Bar { + // Metadata.xml XPath method reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']/method[@name='getBar' and count(parameter)=0]" + [Register ("getBar", "()I", "GetGetBarHandler:Com.Xamarin.Android.IParentInvoker, MyAssembly")] get; + } + +} + +[global::Android.Runtime.Register ("com/xamarin/android/Parent", DoNotGenerateAcw=true)] +internal partial class IParentInvoker : global::Java.Lang.Object, IParent { + + static readonly JniPeerMembers _members = new JniPeerMembers ("com/xamarin/android/Parent", typeof (IParentInvoker)); + + 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; } + } + + new IntPtr class_ref; + + public static IParent 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), "com.xamarin.android.Parent")); + 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 IParentInvoker (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); + } + + static Delegate cb_getBar; +#pragma warning disable 0169 + static Delegate GetGetBarHandler () + { + if (cb_getBar == null) + cb_getBar = JNINativeWrapper.CreateDelegate ((Func) n_GetBar); + return cb_getBar; + } + + static int n_GetBar (IntPtr jnienv, IntPtr native__this) + { + Com.Xamarin.Android.IParent __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); + return __this.Bar; + } +#pragma warning restore 0169 + + IntPtr id_getBar; + public unsafe int Bar { + get { + if (id_getBar == IntPtr.Zero) + id_getBar = JNIEnv.GetMethodID (class_ref, "getBar", "()I"); + return JNIEnv.CallIntMethod (((global::Java.Lang.Object) this).Handle, id_getBar); + } + } + +} + diff --git a/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteNestedInterfaceTypes.txt b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteNestedInterfaceTypes.txt new file mode 100644 index 000000000..f13d20716 --- /dev/null +++ b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteNestedInterfaceTypes.txt @@ -0,0 +1,179 @@ +// Metadata.xml XPath interface reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']" +[Register ("com/xamarin/android/Parent", "", "Com.Xamarin.Android.IParentInvoker")] +public partial interface IParent : IJavaObject, IJavaPeerable { + + int Bar { + // Metadata.xml XPath method reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']/method[@name='getBar' and count(parameter)=0]" + [Register ("getBar", "()I", "GetGetBarHandler:Com.Xamarin.Android.IParentInvoker, MyAssembly")] get; + } + + // Metadata.xml XPath interface reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent.Child']" + [Register ("com/xamarin/android/Parent$Child", "", "Com.Xamarin.Android.IParent/IChildInvoker")] + public partial interface IChild : IJavaObject, IJavaPeerable { + + int Bar { + // Metadata.xml XPath method reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent.Child']/method[@name='getBar' and count(parameter)=0]" + [Register ("getBar", "()I", "GetGetBarHandler:Com.Xamarin.Android.IParent/IChildInvoker, MyAssembly")] get; + } + + } + + [global::Android.Runtime.Register ("com/xamarin/android/Parent$Child", DoNotGenerateAcw=true)] + internal partial class IChildInvoker : global::Java.Lang.Object, IChild { + + static readonly JniPeerMembers _members = new XAPeerMembers ("com/xamarin/android/Parent$Child", typeof (IChildInvoker)); + + 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; } + } + + new IntPtr class_ref; + + public static IChild 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), "com.xamarin.android.Parent.Child")); + 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 IChildInvoker (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); + } + + static Delegate cb_getBar; +#pragma warning disable 0169 + static Delegate GetGetBarHandler () + { + if (cb_getBar == null) + cb_getBar = JNINativeWrapper.CreateDelegate ((Func) n_GetBar); + return cb_getBar; + } + + static int n_GetBar (IntPtr jnienv, IntPtr native__this) + { + Com.Xamarin.Android.IParent.IChild __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); + return __this.Bar; + } +#pragma warning restore 0169 + + IntPtr id_getBar; + public unsafe int Bar { + get { + if (id_getBar == IntPtr.Zero) + id_getBar = JNIEnv.GetMethodID (class_ref, "getBar", "()I"); + return JNIEnv.CallIntMethod (((global::Java.Lang.Object) this).Handle, id_getBar); + } + } + + } + + +} + +[global::Android.Runtime.Register ("com/xamarin/android/Parent", DoNotGenerateAcw=true)] +internal partial class IParentInvoker : global::Java.Lang.Object, IParent { + + static readonly JniPeerMembers _members = new XAPeerMembers ("com/xamarin/android/Parent", typeof (IParentInvoker)); + + 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; } + } + + new IntPtr class_ref; + + public static IParent 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), "com.xamarin.android.Parent")); + 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 IParentInvoker (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); + } + + static Delegate cb_getBar; +#pragma warning disable 0169 + static Delegate GetGetBarHandler () + { + if (cb_getBar == null) + cb_getBar = JNINativeWrapper.CreateDelegate ((Func) n_GetBar); + return cb_getBar; + } + + static int n_GetBar (IntPtr jnienv, IntPtr native__this) + { + Com.Xamarin.Android.IParent __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); + return __this.Bar; + } +#pragma warning restore 0169 + + IntPtr id_getBar; + public unsafe int Bar { + get { + if (id_getBar == IntPtr.Zero) + id_getBar = JNIEnv.GetMethodID (class_ref, "getBar", "()I"); + return JNIEnv.CallIntMethod (((global::Java.Lang.Object) this).Handle, id_getBar); + } + } + +} + diff --git a/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteUnnestedInterfaceTypes.txt b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteUnnestedInterfaceTypes.txt new file mode 100644 index 000000000..0966e21b1 --- /dev/null +++ b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteUnnestedInterfaceTypes.txt @@ -0,0 +1,179 @@ +// Metadata.xml XPath interface reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent.Child']" +[Register ("com/xamarin/android/Parent$Child", "", "Com.Xamarin.Android.IParentChildInvoker")] +public partial interface IParentChild : IJavaObject, IJavaPeerable { + + int Bar { + // Metadata.xml XPath method reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent.Child']/method[@name='getBar' and count(parameter)=0]" + [Register ("getBar", "()I", "GetGetBarHandler:Com.Xamarin.Android.IParentChildInvoker, MyAssembly")] get; + } + +} + +[global::Android.Runtime.Register ("com/xamarin/android/Parent$Child", DoNotGenerateAcw=true)] +internal partial class IParentChildInvoker : global::Java.Lang.Object, IParentChild { + + static readonly JniPeerMembers _members = new XAPeerMembers ("com/xamarin/android/Parent$Child", typeof (IParentChildInvoker)); + + 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; } + } + + new IntPtr class_ref; + + public static IParentChild 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), "com.xamarin.android.Parent.Child")); + 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 IParentChildInvoker (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); + } + + static Delegate cb_getBar; +#pragma warning disable 0169 + static Delegate GetGetBarHandler () + { + if (cb_getBar == null) + cb_getBar = JNINativeWrapper.CreateDelegate ((Func) n_GetBar); + return cb_getBar; + } + + static int n_GetBar (IntPtr jnienv, IntPtr native__this) + { + Com.Xamarin.Android.IParentChild __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); + return __this.Bar; + } +#pragma warning restore 0169 + + IntPtr id_getBar; + public unsafe int Bar { + get { + if (id_getBar == IntPtr.Zero) + id_getBar = JNIEnv.GetMethodID (class_ref, "getBar", "()I"); + return JNIEnv.CallIntMethod (((global::Java.Lang.Object) this).Handle, id_getBar); + } + } + +} + + +// Metadata.xml XPath interface reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']" +[Register ("com/xamarin/android/Parent", "", "Com.Xamarin.Android.IParentInvoker")] +public partial interface IParent : IJavaObject, IJavaPeerable { + + int Bar { + // Metadata.xml XPath method reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']/method[@name='getBar' and count(parameter)=0]" + [Register ("getBar", "()I", "GetGetBarHandler:Com.Xamarin.Android.IParentInvoker, MyAssembly")] get; + } + +} + +[global::Android.Runtime.Register ("com/xamarin/android/Parent", DoNotGenerateAcw=true)] +internal partial class IParentInvoker : global::Java.Lang.Object, IParent { + + static readonly JniPeerMembers _members = new XAPeerMembers ("com/xamarin/android/Parent", typeof (IParentInvoker)); + + 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; } + } + + new IntPtr class_ref; + + public static IParent 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), "com.xamarin.android.Parent")); + 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 IParentInvoker (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); + } + + static Delegate cb_getBar; +#pragma warning disable 0169 + static Delegate GetGetBarHandler () + { + if (cb_getBar == null) + cb_getBar = JNINativeWrapper.CreateDelegate ((Func) n_GetBar); + return cb_getBar; + } + + static int n_GetBar (IntPtr jnienv, IntPtr native__this) + { + Com.Xamarin.Android.IParent __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); + return __this.Bar; + } +#pragma warning restore 0169 + + IntPtr id_getBar; + public unsafe int Bar { + get { + if (id_getBar == IntPtr.Zero) + id_getBar = JNIEnv.GetMethodID (class_ref, "getBar", "()I"); + return JNIEnv.CallIntMethod (((global::Java.Lang.Object) this).Handle, id_getBar); + } + } + +} + diff --git a/tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs b/tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs index b3de17fa1..2ea239e11 100644 --- a/tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs +++ b/tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs @@ -511,7 +511,7 @@ public void WriteInterfaceDeclaration () var iface = SupportTypeBuilder.CreateInterface ("java.code.IMyInterface", options); generator.Context.ContextTypes.Push (iface); - generator.WriteInterfaceDeclaration (iface, string.Empty); + generator.WriteInterfaceDeclaration (iface, string.Empty, new GenerationInfo (null, null, null)); generator.Context.ContextTypes.Pop (); Assert.AreEqual (GetTargetedExpected (nameof (WriteInterfaceDeclaration)), writer.ToString ().NormalizeLineEndings ()); diff --git a/tests/generator-Tests/Unit-Tests/DefaultInterfaceMethodsTests.cs b/tests/generator-Tests/Unit-Tests/DefaultInterfaceMethodsTests.cs index d60f047bc..2438d7328 100644 --- a/tests/generator-Tests/Unit-Tests/DefaultInterfaceMethodsTests.cs +++ b/tests/generator-Tests/Unit-Tests/DefaultInterfaceMethodsTests.cs @@ -40,7 +40,7 @@ public void WriteInterfaceDefaultMethod () iface.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()); - generator.WriteInterfaceDeclaration (iface, string.Empty); + generator.WriteInterfaceDeclaration (iface, string.Empty, new GenerationInfo (null, null, null)); Assert.AreEqual (GetTargetedExpected (nameof (WriteInterfaceDefaultMethod)), writer.ToString ().NormalizeLineEndings ()); } @@ -61,7 +61,7 @@ public void WriteInterfaceRedeclaredDefaultMethod () iface.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()); iface2.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()); - generator.WriteInterfaceDeclaration (iface2, string.Empty); + generator.WriteInterfaceDeclaration (iface2, string.Empty, new GenerationInfo (null, null, null)); // IMyInterface2 should generate the method as abstract, not a default method Assert.AreEqual (GetExpected (nameof (WriteInterfaceRedeclaredDefaultMethod)), writer.ToString ().NormalizeLineEndings ()); @@ -81,7 +81,7 @@ public void WriteInterfaceDefaultProperty () iface.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()); - generator.WriteInterfaceDeclaration (iface, string.Empty); + generator.WriteInterfaceDeclaration (iface, string.Empty, new GenerationInfo (null, null, null)); Assert.AreEqual (GetTargetedExpected (nameof (WriteInterfaceDefaultProperty)), writer.ToString ().NormalizeLineEndings ()); } @@ -100,7 +100,7 @@ public void WriteInterfaceDefaultPropertyGetterOnly () iface.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()); - generator.WriteInterfaceDeclaration (iface, string.Empty); + generator.WriteInterfaceDeclaration (iface, string.Empty, new GenerationInfo (null, null, null)); Assert.AreEqual (GetTargetedExpected (nameof (WriteInterfaceDefaultPropertyGetterOnly)), writer.ToString ().NormalizeLineEndings ()); } @@ -222,9 +222,56 @@ public void WriteStaticInterfaceProperty () iface.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()); - generator.WriteInterfaceDeclaration (iface, string.Empty); + generator.WriteInterfaceDeclaration (iface, string.Empty, new GenerationInfo (null, null, null)); Assert.AreEqual (GetTargetedExpected (nameof (WriteStaticInterfaceProperty)), writer.ToString ().NormalizeLineEndings ()); } + + readonly string nested_interface_api = @" + + + + + + + + + + + + "; + + [Test] + public void WriteUnnestedInterfaceTypes () + { + // Ensure we don't break the original un-nested interface types + var gens = ParseApiDefinition (nested_interface_api); + + var parent_iface = gens.OfType ().Single (); + + parent_iface.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()); + + generator.WriteInterface (parent_iface, string.Empty, new GenerationInfo (string.Empty, string.Empty, "MyAssembly")); + + Assert.AreEqual (GetTargetedExpected (nameof (WriteUnnestedInterfaceTypes)), writer.ToString ().NormalizeLineEndings ()); + } + + [Test] + public void WriteNestedInterfaceTypes () + { + // Traditionally this would have created namespace.IParent and namespace.IParentChild + // With nested types this creates namespace.IParent and namespace.IParent.IChild + options.SupportNestedInterfaceTypes = true; + + var gens = ParseApiDefinition (nested_interface_api); + + var parent_iface = gens.OfType ().Single (); + + parent_iface.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()); + + generator.WriteInterface (parent_iface, string.Empty, new GenerationInfo (string.Empty, string.Empty, "MyAssembly")); + + Assert.AreEqual (GetTargetedExpected (nameof (WriteNestedInterfaceTypes)), writer.ToString ().NormalizeLineEndings ()); + } } } diff --git a/tests/generator-Tests/Unit-Tests/InterfaceConstantsTests.cs b/tests/generator-Tests/Unit-Tests/InterfaceConstantsTests.cs index 1bcfdc24d..924d98622 100644 --- a/tests/generator-Tests/Unit-Tests/InterfaceConstantsTests.cs +++ b/tests/generator-Tests/Unit-Tests/InterfaceConstantsTests.cs @@ -39,7 +39,7 @@ public void WriteInterfaceFields () iface.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()); generator.Context.ContextTypes.Push (iface); - generator.WriteInterfaceDeclaration (iface, string.Empty); + generator.WriteInterfaceDeclaration (iface, string.Empty, new GenerationInfo (null, null, null)); generator.Context.ContextTypes.Pop (); Assert.AreEqual (GetTargetedExpected (nameof (WriteInterfaceFields)), writer.ToString ().NormalizeLineEndings ()); @@ -64,7 +64,7 @@ public void WriteConstSugarInterfaceFields () iface.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()); generator.Context.ContextTypes.Push (iface); - generator.WriteInterfaceDeclaration (iface, string.Empty); + generator.WriteInterfaceDeclaration (iface, string.Empty, new GenerationInfo (null, null, null)); generator.Context.ContextTypes.Pop (); Assert.AreEqual (GetTargetedExpected (nameof (WriteConstSugarInterfaceFields)), writer.ToString ().NormalizeLineEndings ()); diff --git a/tests/generator-Tests/Unit-Tests/KotlinFixupsTests.cs b/tests/generator-Tests/Unit-Tests/KotlinFixupsTests.cs index 1c4ac1a89..ad3f650de 100644 --- a/tests/generator-Tests/Unit-Tests/KotlinFixupsTests.cs +++ b/tests/generator-Tests/Unit-Tests/KotlinFixupsTests.cs @@ -14,7 +14,7 @@ public class KotlinFixupsTests public void CreateMethod_EnsureKotlinImplFix () { var xml = XDocument.Parse (""); - var klass = XmlApiImporter.CreateClass (xml.Root, xml.Root.Element ("class")); + var klass = XmlApiImporter.CreateClass (xml.Root, xml.Root.Element ("class"), new CodeGenerationOptions ()); KotlinFixups.Fixup (new [] { (GenBase)klass }.ToList ()); @@ -27,7 +27,7 @@ public void CreateMethod_EnsureKotlinImplFix () public void CreateMethod_EnsureKotlinHashcodeFix () { var xml = XDocument.Parse (""); - var klass = XmlApiImporter.CreateClass (xml.Root, xml.Root.Element ("class")); + var klass = XmlApiImporter.CreateClass (xml.Root, xml.Root.Element ("class"), new CodeGenerationOptions ()); KotlinFixups.Fixup (new [] { (GenBase) klass }.ToList ()); diff --git a/tests/generator-Tests/Unit-Tests/XmlApiImporterTests.cs b/tests/generator-Tests/Unit-Tests/XmlApiImporterTests.cs index c8161224a..079d2653d 100644 --- a/tests/generator-Tests/Unit-Tests/XmlApiImporterTests.cs +++ b/tests/generator-Tests/Unit-Tests/XmlApiImporterTests.cs @@ -8,11 +8,13 @@ namespace generatortests [TestFixture] public class XmlApiImporterTests { + CodeGenerationOptions opt = new CodeGenerationOptions (); + [Test] public void CreateClass_EnsureValidName () { var xml = XDocument.Parse (""); - var klass = XmlApiImporter.CreateClass (xml.Root, xml.Root.Element ("class")); + var klass = XmlApiImporter.CreateClass (xml.Root, xml.Root.Element ("class"), opt); Assert.AreEqual ("_3", klass.Name); } @@ -21,7 +23,7 @@ public void CreateClass_EnsureValidName () public void CreateCtor_EnsureValidName () { var xml = XDocument.Parse (""); - var klass = XmlApiImporter.CreateClass (xml.Root, xml.Root.Element ("class")); + var klass = XmlApiImporter.CreateClass (xml.Root, xml.Root.Element ("class"), opt); Assert.AreEqual ("_3", klass.Ctors[0].Name); } @@ -66,7 +68,7 @@ public void CreateField_HandleDollarSignNumber () public void CreateInterface_EnsureValidName () { var xml = XDocument.Parse (""); - var iface = XmlApiImporter.CreateInterface (xml.Root, xml.Root.Element ("interface")); + var iface = XmlApiImporter.CreateInterface (xml.Root, xml.Root.Element ("interface"), opt); Assert.AreEqual ("I_3", iface.Name); } @@ -75,7 +77,7 @@ public void CreateInterface_EnsureValidName () public void CreateMethod_EnsureValidName () { var xml = XDocument.Parse (""); - var klass = XmlApiImporter.CreateClass (xml.Root, xml.Root.Element ("class")); + var klass = XmlApiImporter.CreateClass (xml.Root, xml.Root.Element ("class"), opt); Assert.AreEqual ("_3", klass.Methods [0].Name); } @@ -84,7 +86,7 @@ public void CreateMethod_EnsureValidName () public void CreateMethod_EnsureValidNameHyphen () { var xml = XDocument.Parse (""); - var klass = XmlApiImporter.CreateClass (xml.Root, xml.Root.Element ("class")); + var klass = XmlApiImporter.CreateClass (xml.Root, xml.Root.Element ("class"), opt); Assert.AreEqual ("_3", klass.Methods [0].Name); } diff --git a/tests/generator-Tests/Unit-Tests/XmlTests.cs b/tests/generator-Tests/Unit-Tests/XmlTests.cs index ebcaf431f..878d7511f 100644 --- a/tests/generator-Tests/Unit-Tests/XmlTests.cs +++ b/tests/generator-Tests/Unit-Tests/XmlTests.cs @@ -52,7 +52,7 @@ public void SetUp () options = new CodeGenerationOptions (); var javaLang = xml.Element ("api").Element ("package"); foreach (var type in javaLang.Elements("class")) { - var @class = XmlApiImporter.CreateClass (javaLang, type); + var @class = XmlApiImporter.CreateClass (javaLang, type, options); Assert.IsTrue (@class.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()), "@class.Validate failed!"); options.SymbolTable.AddType (@class); } @@ -64,7 +64,7 @@ public void SetUp () public void Class () { var element = package.Element ("class"); - var @class = XmlApiImporter.CreateClass (package, element); + var @class = XmlApiImporter.CreateClass (package, element, options); Assert.IsTrue (@class.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()), "@class.Validate failed!"); Assert.AreEqual ("public", @class.Visibility); @@ -81,7 +81,7 @@ public void Class () public void Method () { var element = package.Element ("class"); - var @class = XmlApiImporter.CreateClass (package, element); + var @class = XmlApiImporter.CreateClass (package, element, options); var method = XmlApiImporter.CreateMethod (@class, element.Element ("method")); Assert.IsTrue (method.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()), "method.Validate failed!"); @@ -101,7 +101,7 @@ public void Method () public void Method_Matches_True () { var element = package.Element ("class"); - var @class = XmlApiImporter.CreateClass (package, element); + var @class = XmlApiImporter.CreateClass (package, element, options); var unknownTypes = element.Elements ("method").Where (e => e.Attribute ("name").Value == "unknownTypes").First (); var methodA = XmlApiImporter.CreateMethod (@class, unknownTypes); var methodB = XmlApiImporter.CreateMethod (@class, unknownTypes); @@ -112,7 +112,7 @@ public void Method_Matches_True () public void Method_Matches_False () { var element = package.Element ("class"); - var @class = XmlApiImporter.CreateClass (package, element); + var @class = XmlApiImporter.CreateClass (package, element, options); var unknownTypesA = element.Elements ("method").Where (e => e.Attribute ("name").Value == "unknownTypes").First (); var unknownTypesB = element.Elements ("method").Where (e => e.Attribute ("name").Value == "unknownTypesReturn").First (); unknownTypesB.Attribute ("name").Value = "unknownTypes"; @@ -126,7 +126,7 @@ public void Method_Matches_False () public void MethodWithParameters () { var element = package.Element ("class"); - var @class = XmlApiImporter.CreateClass (package, element); + var @class = XmlApiImporter.CreateClass (package, element, options); var method = XmlApiImporter.CreateMethod (@class, element.Elements ("method").Where (e => e.Attribute ("name").Value == "barWithParams").First ()); Assert.IsTrue (method.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()), "method.Validate failed!"); Assert.AreEqual ("(ZID)Ljava/lang/String;", method.JniSignature); @@ -156,7 +156,7 @@ public void MethodWithParameters () public void Ctor () { var element = package.Element ("class"); - var @class = XmlApiImporter.CreateClass (package, element); + var @class = XmlApiImporter.CreateClass (package, element, options); var ctor = XmlApiImporter.CreateCtor (@class, element.Element ("constructor")); Assert.IsTrue (ctor.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()), "ctor.Validate failed!"); @@ -170,7 +170,7 @@ public void Ctor () public void Field () { var element = package.Element ("class"); - var @class = XmlApiImporter.CreateClass (package, element); + var @class = XmlApiImporter.CreateClass (package, element, options); var field = XmlApiImporter.CreateField (element.Element ("field")); Assert.IsTrue (field.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()), "field.Validate failed!"); @@ -186,7 +186,7 @@ public void Field () public void Interface () { var element = package.Element ("interface"); - var @interface = XmlApiImporter.CreateInterface (package, element); + var @interface = XmlApiImporter.CreateInterface (package, element, options); Assert.IsTrue (@interface.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()), "interface.Validate failed!"); Assert.AreEqual ("public", @interface.Visibility); diff --git a/tools/generator/CodeGenerationOptions.cs b/tools/generator/CodeGenerationOptions.cs index c3797b4fd..762d58268 100644 --- a/tools/generator/CodeGenerationOptions.cs +++ b/tools/generator/CodeGenerationOptions.cs @@ -49,6 +49,7 @@ internal CodeGenerator CreateCodeGenerator (TextWriter writer) public int ProductVersion { get; set; } public bool SupportInterfaceConstants { get; set; } public bool SupportDefaultInterfaceMethods { get; set; } + public bool SupportNestedInterfaceTypes { get; set; } public bool UseShallowReferencedTypes { get; set; } bool? buildingCoreAssembly; diff --git a/tools/generator/CodeGenerator.cs b/tools/generator/CodeGenerator.cs index 21c7d8cb7..33df6cd92 100644 --- a/tools/generator/CodeGenerator.cs +++ b/tools/generator/CodeGenerator.cs @@ -68,6 +68,7 @@ static void Run (CodeGeneratorOptions options, DirectoryAssemblyResolver resolve ProductVersion = options.ProductVersion, SupportInterfaceConstants = options.SupportInterfaceConstants, SupportDefaultInterfaceMethods = options.SupportDefaultInterfaceMethods, + SupportNestedInterfaceTypes = options.SupportNestedInterfaceTypes, }; var resolverCache = new TypeDefinitionCache (); diff --git a/tools/generator/CodeGeneratorOptions.cs b/tools/generator/CodeGeneratorOptions.cs index ae78d0557..c3b085dcf 100644 --- a/tools/generator/CodeGeneratorOptions.cs +++ b/tools/generator/CodeGeneratorOptions.cs @@ -41,6 +41,7 @@ public CodeGeneratorOptions () public string ApiXmlAdjusterOutput { get; set; } public bool SupportInterfaceConstants { get; set; } public bool SupportDefaultInterfaceMethods { get; set; } + public bool SupportNestedInterfaceTypes { get; set; } public static CodeGeneratorOptions Parse (string[] args) { @@ -92,6 +93,7 @@ public static CodeGeneratorOptions Parse (string[] args) v => { opts.SupportInterfaceConstants = v?.Contains ("interface-constants") == true; opts.SupportDefaultInterfaceMethods = v?.Contains ("default-interface-methods") == true; + opts.SupportNestedInterfaceTypes = v?.Contains ("nested-interface-types") == true; }}, { "preserve-enums", "For internal use.", diff --git a/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/CodeGenerator.cs b/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/CodeGenerator.cs index 832834a01..53625a1a8 100644 --- a/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/CodeGenerator.cs +++ b/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/CodeGenerator.cs @@ -453,8 +453,8 @@ public void WriteInterface (InterfaceGen @interface, string indent, GenerationIn { Context.ContextTypes.Push (@interface); - // interfaces don't nest, so generate as siblings - foreach (GenBase nest in @interface.NestedTypes) { + // Generate sibling types for nested types we don't want to nest + foreach (var nest in @interface.NestedTypes.Where (t => t.Unnest)) { WriteType (nest, indent, gen_info); writer.WriteLine (); } @@ -466,7 +466,7 @@ public void WriteInterface (InterfaceGen @interface, string indent, GenerationIn if (@interface.IsConstSugar && @interface.GetGeneratableFields (opt).Count () == 0) return; - WriteInterfaceDeclaration (@interface, indent); + WriteInterfaceDeclaration (@interface, indent, gen_info); // If this interface is just constant fields we don't need to write all the invoker bits if (@interface.IsConstSugar) @@ -507,7 +507,7 @@ public void WriteInterfaceAbstractMembers (InterfaceGen @interface, ClassGen gen } } - public void WriteInterfaceDeclaration (InterfaceGen @interface, string indent) + public void WriteInterfaceDeclaration (InterfaceGen @interface, string indent, GenerationInfo gen_info) { StringBuilder sb = new StringBuilder (); foreach (ISymbol isym in @interface.Interfaces) { @@ -544,6 +544,13 @@ public void WriteInterfaceDeclaration (InterfaceGen @interface, string indent) writer.WriteLine (); WriteInterfaceProperties (@interface, indent + "\t"); WriteInterfaceMethods (@interface, indent + "\t"); + + // Generate nested types for supported nested types + foreach (var nest in @interface.NestedTypes.Where (t => !t.Unnest)) { + WriteType (nest, indent + "\t", gen_info); + writer.WriteLine (); + } + writer.WriteLine (indent + "}"); writer.WriteLine (); } diff --git a/tools/generator/Java.Interop.Tools.Generator.Importers/XmlApiImporter.cs b/tools/generator/Java.Interop.Tools.Generator.Importers/XmlApiImporter.cs index f0a09eaaf..09cebba0b 100644 --- a/tools/generator/Java.Interop.Tools.Generator.Importers/XmlApiImporter.cs +++ b/tools/generator/Java.Interop.Tools.Generator.Importers/XmlApiImporter.cs @@ -12,13 +12,17 @@ class XmlApiImporter { static readonly Regex api_level = new Regex (@"api-(\d+).xml"); - public static ClassGen CreateClass (XElement pkg, XElement elem) + public static ClassGen CreateClass (XElement pkg, XElement elem, CodeGenerationOptions options) { var klass = new ClassGen (CreateGenBaseSupport (pkg, elem, false)) { BaseType = elem.XGetAttribute ("extends"), FromXml = true, IsAbstract = elem.XGetAttribute ("abstract") == "true", - IsFinal = elem.XGetAttribute ("final") == "true" + IsFinal = elem.XGetAttribute ("final") == "true", + // Only use an explicitly set XML attribute + Unnest = elem.XGetAttribute ("unnest") == "true" ? true : + elem.XGetAttribute ("unnest") == "false" ? false : + !options.SupportNestedInterfaceTypes }; foreach (var child in elem.Elements ()) { @@ -188,11 +192,15 @@ public static GenBaseSupport CreateGenBaseSupport (XElement pkg, XElement elem, return support; } - public static InterfaceGen CreateInterface (XElement pkg, XElement elem) + public static InterfaceGen CreateInterface (XElement pkg, XElement elem, CodeGenerationOptions options) { var iface = new InterfaceGen (CreateGenBaseSupport (pkg, elem, true)) { ArgsType = elem.XGetAttribute ("argsType"), - HasManagedName = elem.Attribute ("managedName") != null + HasManagedName = elem.Attribute ("managedName") != null, + // Only use an explicitly set XML attribute + Unnest = elem.XGetAttribute ("unnest") == "true" ? true : + elem.XGetAttribute ("unnest") == "false" ? false : + !options.SupportNestedInterfaceTypes }; foreach (var child in elem.Elements ()) { diff --git a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBase.cs b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBase.cs index 485519bea..977ee8430 100644 --- a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBase.cs +++ b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBase.cs @@ -817,6 +817,12 @@ public void StripNonBindables (CodeGenerationOptions opt) public GenericParameterDefinitionList TypeParameters => support.TypeParameters; + // Prior to DIM, interfaces could not contain nested types so we generated them + // as sibling types. When DIM is enabled we can now generate them properly nested. + // However this is an API break for existing bindings. Setting this property + // to true opts this interface into the sibling compatibility behavior. + public bool Unnest { get; set; } + public virtual void UpdateEnums (CodeGenerationOptions opt, AncestorDescendantCache cache) { if (enum_updated || !IsGeneratable) diff --git a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/InterfaceGen.cs b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/InterfaceGen.cs index f4dc45488..e1c6f38f1 100644 --- a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/InterfaceGen.cs +++ b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/InterfaceGen.cs @@ -21,6 +21,15 @@ public override void AddNestedType (GenBase gen) if (nest_name.IndexOf (".") < 0) { if (gen is InterfaceGen) { + + // We don't need to mangle the name if we support nested interface types + // ex: my.namespace.IParent.IChild + if (!gen.Unnest) { + gen.FullName = FullName + "." + gen.Name; + return; + } + + // ex: my.namespace.IParentChild gen.FullName = FullName + gen.Name.Substring (1); gen.Name = Name + gen.Name.Substring (1); } else { diff --git a/tools/generator/Java.Interop.Tools.Generator.Transformation/Parser.cs b/tools/generator/Java.Interop.Tools.Generator.Transformation/Parser.cs index d00e7a34c..3df44a5a3 100644 --- a/tools/generator/Java.Interop.Tools.Generator.Transformation/Parser.cs +++ b/tools/generator/Java.Interop.Tools.Generator.Transformation/Parser.cs @@ -105,12 +105,12 @@ List ParsePackage (XElement ns, Predicate p) case "class": if (elem.XGetAttribute ("obfuscated") == "true") continue; - gen = XmlApiImporter.CreateClass (ns, elem); + gen = XmlApiImporter.CreateClass (ns, elem, opt); break; case "interface": if (elem.XGetAttribute ("obfuscated") == "true") continue; - gen = XmlApiImporter.CreateInterface (ns, elem); + gen = XmlApiImporter.CreateInterface (ns, elem, opt); break; default: Report.Warning (0, Report.WarningParser + 3, "Unexpected node in package element: {0}.", elem.Name);