diff --git a/src/Xamarin.SourceWriter/Models/PropertyWriter.cs b/src/Xamarin.SourceWriter/Models/PropertyWriter.cs index afa3269b4..c06eceb02 100644 --- a/src/Xamarin.SourceWriter/Models/PropertyWriter.cs +++ b/src/Xamarin.SourceWriter/Models/PropertyWriter.cs @@ -35,6 +35,7 @@ public class PropertyWriter : ISourceWriter public List SetterAttributes { get; } = new List (); public int Priority { get; set; } public string ExplicitInterfaceImplementation { get; set; } + public Visibility AutoSetterVisibility { get; set; } public void SetVisibility (string visibility) { @@ -165,6 +166,8 @@ protected virtual void WriteAutomaticPropertyBody (CodeWriter writer) writer.WriteLine (); writer.Indent (); need_unindent = true; + } else { + writer.Write (" "); } WriteGetterComments (writer); @@ -187,6 +190,14 @@ protected virtual void WriteAutomaticPropertyBody (CodeWriter writer) WriteSetterComments (writer); WriteSetterAttributes (writer); + + if (AutoSetterVisibility == Visibility.Private && !IsPrivate) + writer.Write ("private "); + else if (AutoSetterVisibility == Visibility.Protected && !IsProtected) + writer.Write ("protected "); + if (AutoSetterVisibility == Visibility.Internal && !IsInternal) + writer.Write ("internal "); + writer.Write ("set; "); if (need_unindent) { diff --git a/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/Common/WriteDuplicateInterfaceEventArgs.txt b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/Common/WriteDuplicateInterfaceEventArgs.txt new file mode 100644 index 000000000..cce5a205d --- /dev/null +++ b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/Common/WriteDuplicateInterfaceEventArgs.txt @@ -0,0 +1,191 @@ +// Metadata.xml XPath interface reference: path="/api/package[@name='java.code']/interface[@name='AnimatorListener']" +[Register ("java/code/AnimatorListener", "", "java.code.AnimatorListenerInvoker")] +public partial interface AnimatorListener : IJavaObject, IJavaPeerable { + // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='AnimatorListener']/method[@name='OnAnimationEnd' and count(parameter)=1 and parameter[1][@type='int']]" + [Register ("OnAnimationEnd", "(I)Z", "GetOnAnimationEnd_IHandler:java.code.AnimatorListenerInvoker, ")] + bool OnAnimationEnd (int param1); + + // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='AnimatorListener']/method[@name='OnAnimationEnd' and count(parameter)=2 and parameter[1][@type='int'] and parameter[2][@type='int']]" + [Register ("OnAnimationEnd", "(II)Z", "GetOnAnimationEnd_IIHandler:java.code.AnimatorListenerInvoker, ")] + bool OnAnimationEnd (int param1, int param2); + +} + +[global::Android.Runtime.Register ("java/code/AnimatorListener", DoNotGenerateAcw=true)] +internal partial class AnimatorListenerInvoker : global::Java.Lang.Object, AnimatorListener { + static readonly JniPeerMembers _members = new JniPeerMembers ("java/code/AnimatorListener", typeof (AnimatorListenerInvoker)); + + 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 AnimatorListener 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.AnimatorListener")); + 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 AnimatorListenerInvoker (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_OnAnimationEnd_I; +#pragma warning disable 0169 + static Delegate GetOnAnimationEnd_IHandler () + { + if (cb_OnAnimationEnd_I == null) + cb_OnAnimationEnd_I = JNINativeWrapper.CreateDelegate ((_JniMarshal_PPI_Z) n_OnAnimationEnd_I); + return cb_OnAnimationEnd_I; + } + + static bool n_OnAnimationEnd_I (IntPtr jnienv, IntPtr native__this, int param1) + { + var __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); + return __this.OnAnimationEnd (param1); + } +#pragma warning restore 0169 + + IntPtr id_OnAnimationEnd_I; + public unsafe bool OnAnimationEnd (int param1) + { + if (id_OnAnimationEnd_I == IntPtr.Zero) + id_OnAnimationEnd_I = JNIEnv.GetMethodID (class_ref, "OnAnimationEnd", "(I)Z"); + JValue* __args = stackalloc JValue [1]; + __args [0] = new JValue (param1); + return JNIEnv.CallBooleanMethod (((global::Java.Lang.Object) this).Handle, id_OnAnimationEnd_I, __args); + } + + static Delegate cb_OnAnimationEnd_II; +#pragma warning disable 0169 + static Delegate GetOnAnimationEnd_IIHandler () + { + if (cb_OnAnimationEnd_II == null) + cb_OnAnimationEnd_II = JNINativeWrapper.CreateDelegate ((_JniMarshal_PPII_Z) n_OnAnimationEnd_II); + return cb_OnAnimationEnd_II; + } + + static bool n_OnAnimationEnd_II (IntPtr jnienv, IntPtr native__this, int param1, int param2) + { + var __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); + return __this.OnAnimationEnd (param1, param2); + } +#pragma warning restore 0169 + + IntPtr id_OnAnimationEnd_II; + public unsafe bool OnAnimationEnd (int param1, int param2) + { + if (id_OnAnimationEnd_II == IntPtr.Zero) + id_OnAnimationEnd_II = JNIEnv.GetMethodID (class_ref, "OnAnimationEnd", "(II)Z"); + JValue* __args = stackalloc JValue [2]; + __args [0] = new JValue (param1); + __args [1] = new JValue (param2); + return JNIEnv.CallBooleanMethod (((global::Java.Lang.Object) this).Handle, id_OnAnimationEnd_II, __args); + } + +} + +// event args for java.code.AnimatorListener.OnAnimationEnd +public partial class AnimationEndEventArgs : global::System.EventArgs { + public AnimationEndEventArgs (bool handled, int param1) + { + this.Handled = handled; + this.Param1 = param1; + } + + public AnimationEndEventArgs (bool handled, int param1, int param2) + { + this.Handled = handled; + this.Param1 = param1; + this.Param2 = param2; + } + + public bool Handled { get; set; } + + public int Param1 { get; private set; } + + public int Param2 { get; private set; } + +} + +[global::Android.Runtime.Register ("mono/java/code/AnimatorListenerImplementor")] +internal sealed partial class AnimatorListenerImplementor : global::Java.Lang.Object, AnimatorListener { + + object sender; + + public AnimatorListenerImplementor (object sender) : base (global::Android.Runtime.JNIEnv.StartCreateInstance ("mono/java/code/AnimatorListenerImplementor", "()V"), JniHandleOwnership.TransferLocalRef) + { + global::Android.Runtime.JNIEnv.FinishCreateInstance (((global::Java.Lang.Object) this).Handle, "()V"); + this.sender = sender; + } + + #pragma warning disable 0649 + public EventHandler OnAnimationEndHandler; + #pragma warning restore 0649 + + public bool OnAnimationEnd (int param1) + { + var __h = OnAnimationEndHandler; + if (__h == null) + return false; + var __e = new AnimationEndEventArgs (true, param1); + __h (sender, __e); + return __e.Handled; + } + + #pragma warning disable 0649 + public EventHandler OnAnimationEndHandler; + #pragma warning restore 0649 + + public bool OnAnimationEnd (int param1, int param2) + { + var __h = OnAnimationEndHandler; + if (__h == null) + return false; + var __e = new AnimationEndEventArgs (true, param1, param2); + __h (sender, __e); + return __e.Handled; + } + + internal static bool __IsEmpty (AnimatorListenerImplementor value) + { + return value.OnAnimationEndHandler == null && value.OnAnimationEndHandler == null; + } + +} diff --git a/tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs b/tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs index f832421b5..ef2aa9880 100644 --- a/tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs +++ b/tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs @@ -195,6 +195,26 @@ public void ManagedOverrideProperty_Override () Assert.True (writer.ToString ().Contains ("public override unsafe int Name {")); } + + [Test] + public void WriteDuplicateInterfaceEventArgs () + { + // If we have 2 methods that would each create the same EventArgs class, + // make sure we combine them into 1 class with both members instead. + var iface = SupportTypeBuilder.CreateEmptyInterface ("java.code.AnimatorListener"); + + var method1 = SupportTypeBuilder.CreateMethod (iface, "OnAnimationEnd", options, "boolean", false, true, new Parameter ("param1", "int", "int", false)); + var method2 = SupportTypeBuilder.CreateMethod (iface, "OnAnimationEnd", options, "boolean", false, true, new Parameter ("param1", "int", "int", false), new Parameter ("param2", "int", "int", false)); + + iface.Methods.Add (method1); + iface.Methods.Add (method2); + + generator.Context.ContextTypes.Push (iface); + generator.WriteType (iface, string.Empty, new GenerationInfo ("", "", "MyAssembly")); + generator.Context.ContextTypes.Pop (); + + Assert.AreEqual (GetExpected (nameof (WriteDuplicateInterfaceEventArgs)), writer.ToString ().NormalizeLineEndings ()); + } } [TestFixture] diff --git a/tests/generator-Tests/expected.ji/GenericArguments/Com.Google.Android.Exoplayer.Drm.IExoMediaDrm.cs b/tests/generator-Tests/expected.ji/GenericArguments/Com.Google.Android.Exoplayer.Drm.IExoMediaDrm.cs index 4928e0dcc..202929cd4 100644 --- a/tests/generator-Tests/expected.ji/GenericArguments/Com.Google.Android.Exoplayer.Drm.IExoMediaDrm.cs +++ b/tests/generator-Tests/expected.ji/GenericArguments/Com.Google.Android.Exoplayer.Drm.IExoMediaDrm.cs @@ -123,42 +123,22 @@ public unsafe void OnEvent (global::Com.Google.Android.Exoplayer.Drm.IExoMediaDr public partial class ExoMediaDrmOnEventEventArgs : global::System.EventArgs { public ExoMediaDrmOnEventEventArgs (global::Com.Google.Android.Exoplayer.Drm.IExoMediaDrm p0, byte[] p1, int p2, int p3, byte[] p4) { - this.p0 = p0; - this.p1 = p1; - this.p2 = p2; - this.p3 = p3; - this.p4 = p4; + this.P0 = p0; + this.P1 = p1; + this.P2 = p2; + this.P3 = p3; + this.P4 = p4; } - global::Com.Google.Android.Exoplayer.Drm.IExoMediaDrm p0; + public global::Com.Google.Android.Exoplayer.Drm.IExoMediaDrm P0 { get; private set; } - public global::Com.Google.Android.Exoplayer.Drm.IExoMediaDrm P0 { - get { return p0; } - } - - byte[] p1; - - public byte[] P1 { - get { return p1; } - } - - int p2; + public byte[] P1 { get; private set; } - public int P2 { - get { return p2; } - } - - int p3; + public int P2 { get; private set; } - public int P3 { - get { return p3; } - } + public int P3 { get; private set; } - byte[] p4; - - public byte[] P4 { - get { return p4; } - } + public byte[] P4 { get; private set; } } diff --git a/tools/generator/SourceWriters/BoundInterface.cs b/tools/generator/SourceWriters/BoundInterface.cs index 3e3e773ce..c0439ca0e 100644 --- a/tools/generator/SourceWriters/BoundInterface.cs +++ b/tools/generator/SourceWriters/BoundInterface.cs @@ -104,8 +104,17 @@ void AddInterfaceEventHandler (InterfaceGen iface, CodeGenerationOptions opt, Co foreach (var method in iface.Methods.Where (m => m.EventName != string.Empty)) { if (method.RetVal.IsVoid || method.IsEventHandlerWithHandledProperty) { - if (!method.IsSimpleEventHandler || method.IsEventHandlerWithHandledProperty) - post_sibling_types.Add (new InterfaceEventArgsClass (iface, method, opt, context)); + if (!method.IsSimpleEventHandler || method.IsEventHandlerWithHandledProperty) { + var event_args_class = post_sibling_types.OfType ().SingleOrDefault (c => c.Name == iface.GetArgsName (method)); + + // Check if there's an existing EventArgs class to add to + if (event_args_class is null) { + event_args_class = new InterfaceEventArgsClass (iface, method); + post_sibling_types.Add (event_args_class); + } + + event_args_class.AddMembersFromMethod (iface, method, opt); + } } else { var del = new DelegateWriter { Name = iface.GetEventDelegateName (method), diff --git a/tools/generator/SourceWriters/BoundMethod.cs b/tools/generator/SourceWriters/BoundMethod.cs index aabec6f69..0525b31e4 100644 --- a/tools/generator/SourceWriters/BoundMethod.cs +++ b/tools/generator/SourceWriters/BoundMethod.cs @@ -12,8 +12,12 @@ public class BoundMethod : MethodWriter { readonly MethodCallback callback; + public Method JavaMethod { get; } + public BoundMethod (GenBase type, Method method, CodeGenerationOptions opt, bool generateCallbacks) { + JavaMethod = method; + if (generateCallbacks && method.IsVirtual) callback = new MethodCallback (type, method, opt, null, method.IsReturnCharSequence); diff --git a/tools/generator/SourceWriters/InterfaceEventArgsClass.cs b/tools/generator/SourceWriters/InterfaceEventArgsClass.cs index b76df1d33..3e1dbda38 100644 --- a/tools/generator/SourceWriters/InterfaceEventArgsClass.cs +++ b/tools/generator/SourceWriters/InterfaceEventArgsClass.cs @@ -10,7 +10,7 @@ namespace generator.SourceWriters { public class InterfaceEventArgsClass : ClassWriter { - public InterfaceEventArgsClass (InterfaceGen iface, Method method, CodeGenerationOptions opt, CodeGeneratorContext context) + public InterfaceEventArgsClass (InterfaceGen iface, Method method) { Name = iface.GetArgsName (method); Inherits = "global::System.EventArgs"; @@ -18,15 +18,23 @@ public InterfaceEventArgsClass (InterfaceGen iface, Method method, CodeGeneratio IsPublic = true; IsPartial = true; - UsePriorityOrder = true; - Comments.Add ($"// event args for {iface.JavaName}.{method.JavaName}"); - AddConstructor (iface, method, opt); - + // Add: public bool Handled { get; set; } if (method.IsEventHandlerWithHandledProperty) - Properties.Add (new HandledProperty ()); + Properties.Add (new PropertyWriter { + Name = "Handled", + PropertyType = TypeReferenceWriter.Bool, + IsPublic = true, + HasGet = true, + HasSet = true, + IsAutoProperty = true + }); + } + public void AddMembersFromMethod (InterfaceGen iface, Method method, CodeGenerationOptions opt) + { + AddConstructor (iface, method, opt); AddProperties (method, opt); } @@ -39,7 +47,7 @@ void AddConstructor (InterfaceGen iface, Method method, CodeGenerationOptions op if (method.IsEventHandlerWithHandledProperty) { ctor.Parameters.Add (new MethodParameterWriter ("handled", TypeReferenceWriter.Bool)); - ctor.Body.Add ("this.handled = handled;"); + ctor.Body.Add ("this.Handled = handled;"); } foreach (var p in method.Parameters) { @@ -47,7 +55,7 @@ void AddConstructor (InterfaceGen iface, Method method, CodeGenerationOptions op continue; ctor.Parameters.Add (new MethodParameterWriter (p.Name, new TypeReferenceWriter (opt.GetTypeReferenceName (p)))); - ctor.Body.Add ($"this.{opt.GetSafeIdentifier (p.Name)} = {opt.GetSafeIdentifier (p.Name)};"); + ctor.Body.Add ($"this.{p.PropertyName} = {opt.GetSafeIdentifier (p.Name)};"); } Constructors.Add (ctor); @@ -59,46 +67,22 @@ void AddProperties (Method method, CodeGenerationOptions opt) if (p.IsSender) continue; - Fields.Add (new FieldWriter { - Name = opt.GetSafeIdentifier (p.Name), - Type = new TypeReferenceWriter (opt.GetTypeReferenceName (p)) - }); + // We've already added this property from a different overload + if (Properties.Any (prop => prop.Name == p.PropertyName)) + continue; var prop = new PropertyWriter { Name = p.PropertyName, PropertyType = new TypeReferenceWriter (opt.GetTypeReferenceName (p)), IsPublic = true, - HasGet = true + HasGet = true, + HasSet = true, + IsAutoProperty = true, + AutoSetterVisibility = Visibility.Private }; - prop.GetBody.Add ($"return {opt.GetSafeIdentifier (p.Name)};"); - Properties.Add (prop); } } } - - public class HandledProperty : PropertyWriter - { - public HandledProperty () - { - Name = "Handled"; - PropertyType = TypeReferenceWriter.Bool; - - IsPublic = true; - - HasGet = true; - GetBody.Add ("return handled;"); - - HasSet = true; - SetBody.Add ("handled = value;"); - } - - public override void Write (CodeWriter writer) - { - writer.Write ("bool handled;"); - - base.Write (writer); - } - } }