Skip to content
This repository was archived by the owner on Jul 6, 2026. It is now read-only.

Commit ee7afee

Browse files
authored
[generator] Prevent generating duplicate EventArgs classes (#726)
Fixes: #461 Given this Java listener interface: // Java interface AnimatorListener { void onAnimationEnd (int param1); void onAnimationEnd (int param1, int param2); } Previously we would generate *two* `partial class AnimationEndEventArgs` declarations: // C# binding event args for java.code.AnimatorListener.onAnimationEnd public partial class AnimationEndEventArgs : global::System.EventArgs { public AnimationEndEventArgs (int param1) { this.param1 = param1; } int param1; public int Param1 { get { return param1; } } } // C# binding event args for java.code.AnimatorListener.onAnimationEnd public partial class AnimationEndEventArgs : global::System.EventArgs { public AnimationEndEventArgs (int param1, int param2) { this.param1 = param1; this.param2 = param2; } int param1; public int Param1 { get { return param1; } } int param2; public int Param2 { get { return param2; } } } This causes a compilation error: error CS0102: The type 'AnimationEndEventArgs' already contains a definition for 'param1' We need to detect these situations and instead combine multiple `EventArgs` classes into one, with a constructor for each method: // event args for java.code.AnimatorListener.OnAnimationEnd public partial class AnimationEndEventArgs : global::System.EventArgs { public AnimationEndEventArgs (int param1) { this.Param1 = param1; } public AnimationEndEventArgs (int param1, int param2) { this.Param1 = param1; this.Param2 = param2; } public int Param1 { get; private set; } public int Param2 { get; private set; } } Additionally, clean up the generated code a little bit by using auto properties instead of fields.
1 parent 1f21f38 commit ee7afee

7 files changed

Lines changed: 270 additions & 71 deletions

File tree

‎src/Xamarin.SourceWriter/Models/PropertyWriter.cs‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public class PropertyWriter : ISourceWriter
3535
publicList<AttributeWriter>SetterAttributes{get;}=newList<AttributeWriter>();
3636
publicintPriority{get;set;}
3737
publicstringExplicitInterfaceImplementation{get;set;}
38+
publicVisibilityAutoSetterVisibility{get;set;}
3839

3940
publicvoidSetVisibility(stringvisibility)
4041
{
@@ -165,6 +166,8 @@ protected virtual void WriteAutomaticPropertyBody (CodeWriter writer)
165166
writer.WriteLine();
166167
writer.Indent();
167168
need_unindent=true;
169+
}else{
170+
writer.Write(" ");
168171
}
169172

170173
WriteGetterComments(writer);
@@ -187,6 +190,14 @@ protected virtual void WriteAutomaticPropertyBody (CodeWriter writer)
187190

188191
WriteSetterComments(writer);
189192
WriteSetterAttributes(writer);
193+
194+
if(AutoSetterVisibility==Visibility.Private&&!IsPrivate)
195+
writer.Write("private ");
196+
elseif(AutoSetterVisibility==Visibility.Protected&&!IsProtected)
197+
writer.Write("protected ");
198+
if(AutoSetterVisibility==Visibility.Internal&&!IsInternal)
199+
writer.Write("internal ");
200+
190201
writer.Write("set; ");
191202

192203
if(need_unindent){
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
// Metadata.xml XPath interface reference: path="/api/package[@name='java.code']/interface[@name='AnimatorListener']"
2+
[Register ("java/code/AnimatorListener", "", "java.code.AnimatorListenerInvoker")]
3+
public partial interface AnimatorListener : IJavaObject, IJavaPeerable {
4+
// 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']]"
5+
[Register ("OnAnimationEnd", "(I)Z", "GetOnAnimationEnd_IHandler:java.code.AnimatorListenerInvoker, ")]
6+
bool OnAnimationEnd (int param1);
7+
8+
// 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']]"
9+
[Register ("OnAnimationEnd", "(II)Z", "GetOnAnimationEnd_IIHandler:java.code.AnimatorListenerInvoker, ")]
10+
bool OnAnimationEnd (int param1, int param2);
11+
12+
}
13+
14+
[global::Android.Runtime.Register ("java/code/AnimatorListener", DoNotGenerateAcw=true)]
15+
internal partial class AnimatorListenerInvoker : global::Java.Lang.Object, AnimatorListener {
16+
static readonly JniPeerMembers _members = new JniPeerMembers ("java/code/AnimatorListener", typeof (AnimatorListenerInvoker));
17+
18+
static IntPtr java_class_ref {
19+
get { return _members.JniPeerType.PeerReference.Handle; }
20+
}
21+
22+
[global::System.Diagnostics.DebuggerBrowsable (global::System.Diagnostics.DebuggerBrowsableState.Never)]
23+
[global::System.ComponentModel.EditorBrowsable (global::System.ComponentModel.EditorBrowsableState.Never)]
24+
public override global::Java.Interop.JniPeerMembers JniPeerMembers {
25+
get { return _members; }
26+
}
27+
28+
[global::System.Diagnostics.DebuggerBrowsable (global::System.Diagnostics.DebuggerBrowsableState.Never)]
29+
[global::System.ComponentModel.EditorBrowsable (global::System.ComponentModel.EditorBrowsableState.Never)]
30+
protected override IntPtr ThresholdClass {
31+
get { return class_ref; }
32+
}
33+
34+
[global::System.Diagnostics.DebuggerBrowsable (global::System.Diagnostics.DebuggerBrowsableState.Never)]
35+
[global::System.ComponentModel.EditorBrowsable (global::System.ComponentModel.EditorBrowsableState.Never)]
36+
protected override global::System.Type ThresholdType {
37+
get { return _members.ManagedPeerType; }
38+
}
39+
40+
IntPtr class_ref;
41+
42+
public static AnimatorListener GetObject (IntPtr handle, JniHandleOwnership transfer)
43+
{
44+
return global::Java.Lang.Object.GetObject<AnimatorListener> (handle, transfer);
45+
}
46+
47+
static IntPtr Validate (IntPtr handle)
48+
{
49+
if (!JNIEnv.IsInstanceOf (handle, java_class_ref))
50+
throw new InvalidCastException (string.Format ("Unable to convert instance of type '{0}' to type '{1}'.", JNIEnv.GetClassNameFromInstance (handle), "java.code.AnimatorListener"));
51+
return handle;
52+
}
53+
54+
protected override void Dispose (bool disposing)
55+
{
56+
if (this.class_ref != IntPtr.Zero)
57+
JNIEnv.DeleteGlobalRef (this.class_ref);
58+
this.class_ref = IntPtr.Zero;
59+
base.Dispose (disposing);
60+
}
61+
62+
public AnimatorListenerInvoker (IntPtr handle, JniHandleOwnership transfer) : base (Validate (handle), transfer)
63+
{
64+
IntPtr local_ref = JNIEnv.GetObjectClass (((global::Java.Lang.Object) this).Handle);
65+
this.class_ref = JNIEnv.NewGlobalRef (local_ref);
66+
JNIEnv.DeleteLocalRef (local_ref);
67+
}
68+
69+
static Delegate cb_OnAnimationEnd_I;
70+
#pragma warning disable 0169
71+
static Delegate GetOnAnimationEnd_IHandler ()
72+
{
73+
if (cb_OnAnimationEnd_I == null)
74+
cb_OnAnimationEnd_I = JNINativeWrapper.CreateDelegate ((_JniMarshal_PPI_Z) n_OnAnimationEnd_I);
75+
return cb_OnAnimationEnd_I;
76+
}
77+
78+
static bool n_OnAnimationEnd_I (IntPtr jnienv, IntPtr native__this, int param1)
79+
{
80+
var __this = global::Java.Lang.Object.GetObject<java.code.AnimatorListener> (jnienv, native__this, JniHandleOwnership.DoNotTransfer);
81+
return __this.OnAnimationEnd (param1);
82+
}
83+
#pragma warning restore 0169
84+
85+
IntPtr id_OnAnimationEnd_I;
86+
public unsafe bool OnAnimationEnd (int param1)
87+
{
88+
if (id_OnAnimationEnd_I == IntPtr.Zero)
89+
id_OnAnimationEnd_I = JNIEnv.GetMethodID (class_ref, "OnAnimationEnd", "(I)Z");
90+
JValue* __args = stackalloc JValue [1];
91+
__args [0] = new JValue (param1);
92+
return JNIEnv.CallBooleanMethod (((global::Java.Lang.Object) this).Handle, id_OnAnimationEnd_I, __args);
93+
}
94+
95+
static Delegate cb_OnAnimationEnd_II;
96+
#pragma warning disable 0169
97+
static Delegate GetOnAnimationEnd_IIHandler ()
98+
{
99+
if (cb_OnAnimationEnd_II == null)
100+
cb_OnAnimationEnd_II = JNINativeWrapper.CreateDelegate ((_JniMarshal_PPII_Z) n_OnAnimationEnd_II);
101+
return cb_OnAnimationEnd_II;
102+
}
103+
104+
static bool n_OnAnimationEnd_II (IntPtr jnienv, IntPtr native__this, int param1, int param2)
105+
{
106+
var __this = global::Java.Lang.Object.GetObject<java.code.AnimatorListener> (jnienv, native__this, JniHandleOwnership.DoNotTransfer);
107+
return __this.OnAnimationEnd (param1, param2);
108+
}
109+
#pragma warning restore 0169
110+
111+
IntPtr id_OnAnimationEnd_II;
112+
public unsafe bool OnAnimationEnd (int param1, int param2)
113+
{
114+
if (id_OnAnimationEnd_II == IntPtr.Zero)
115+
id_OnAnimationEnd_II = JNIEnv.GetMethodID (class_ref, "OnAnimationEnd", "(II)Z");
116+
JValue* __args = stackalloc JValue [2];
117+
__args [0] = new JValue (param1);
118+
__args [1] = new JValue (param2);
119+
return JNIEnv.CallBooleanMethod (((global::Java.Lang.Object) this).Handle, id_OnAnimationEnd_II, __args);
120+
}
121+
122+
}
123+
124+
// event args for java.code.AnimatorListener.OnAnimationEnd
125+
public partial class AnimationEndEventArgs : global::System.EventArgs {
126+
public AnimationEndEventArgs (bool handled, int param1)
127+
{
128+
this.Handled = handled;
129+
this.Param1 = param1;
130+
}
131+
132+
public AnimationEndEventArgs (bool handled, int param1, int param2)
133+
{
134+
this.Handled = handled;
135+
this.Param1 = param1;
136+
this.Param2 = param2;
137+
}
138+
139+
public bool Handled { get; set; }
140+
141+
public int Param1 { get; private set; }
142+
143+
public int Param2 { get; private set; }
144+
145+
}
146+
147+
[global::Android.Runtime.Register ("mono/java/code/AnimatorListenerImplementor")]
148+
internal sealed partial class AnimatorListenerImplementor : global::Java.Lang.Object, AnimatorListener {
149+
150+
object sender;
151+
152+
public AnimatorListenerImplementor (object sender) : base (global::Android.Runtime.JNIEnv.StartCreateInstance ("mono/java/code/AnimatorListenerImplementor", "()V"), JniHandleOwnership.TransferLocalRef)
153+
{
154+
global::Android.Runtime.JNIEnv.FinishCreateInstance (((global::Java.Lang.Object) this).Handle, "()V");
155+
this.sender = sender;
156+
}
157+
158+
#pragma warning disable 0649
159+
public EventHandler<AnimationEndEventArgs> OnAnimationEndHandler;
160+
#pragma warning restore 0649
161+
162+
public bool OnAnimationEnd (int param1)
163+
{
164+
var __h = OnAnimationEndHandler;
165+
if (__h == null)
166+
return false;
167+
var __e = new AnimationEndEventArgs (true, param1);
168+
__h (sender, __e);
169+
return __e.Handled;
170+
}
171+
172+
#pragma warning disable 0649
173+
public EventHandler<AnimationEndEventArgs> OnAnimationEndHandler;
174+
#pragma warning restore 0649
175+
176+
public bool OnAnimationEnd (int param1, int param2)
177+
{
178+
var __h = OnAnimationEndHandler;
179+
if (__h == null)
180+
return false;
181+
var __e = new AnimationEndEventArgs (true, param1, param2);
182+
__h (sender, __e);
183+
return __e.Handled;
184+
}
185+
186+
internal static bool __IsEmpty (AnimatorListenerImplementor value)
187+
{
188+
return value.OnAnimationEndHandler == null && value.OnAnimationEndHandler == null;
189+
}
190+
191+
}

‎tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,26 @@ public void ManagedOverrideProperty_Override ()
195195

196196
Assert.True(writer.ToString().Contains("public override unsafe int Name {"));
197197
}
198+
199+
[Test]
200+
publicvoidWriteDuplicateInterfaceEventArgs()
201+
{
202+
// If we have 2 methods that would each create the same EventArgs class,
203+
// make sure we combine them into 1 class with both members instead.
204+
variface=SupportTypeBuilder.CreateEmptyInterface("java.code.AnimatorListener");
205+
206+
varmethod1=SupportTypeBuilder.CreateMethod(iface,"OnAnimationEnd",options,"boolean",false,true,newParameter("param1","int","int",false));
207+
varmethod2=SupportTypeBuilder.CreateMethod(iface,"OnAnimationEnd",options,"boolean",false,true,newParameter("param1","int","int",false),newParameter("param2","int","int",false));
208+
209+
iface.Methods.Add(method1);
210+
iface.Methods.Add(method2);
211+
212+
generator.Context.ContextTypes.Push(iface);
213+
generator.WriteType(iface,string.Empty,newGenerationInfo("","","MyAssembly"));
214+
generator.Context.ContextTypes.Pop();
215+
216+
Assert.AreEqual(GetExpected(nameof(WriteDuplicateInterfaceEventArgs)),writer.ToString().NormalizeLineEndings());
217+
}
198218
}
199219

200220
[TestFixture]

‎tests/generator-Tests/expected.ji/GenericArguments/Com.Google.Android.Exoplayer.Drm.IExoMediaDrm.cs‎

Lines changed: 10 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -123,42 +123,22 @@ public unsafe void OnEvent (global::Com.Google.Android.Exoplayer.Drm.IExoMediaDr
123123
publicpartialclassExoMediaDrmOnEventEventArgs:global::System.EventArgs{
124124
publicExoMediaDrmOnEventEventArgs(global::Com.Google.Android.Exoplayer.Drm.IExoMediaDrmp0,byte[]p1,intp2,intp3,byte[]p4)
125125
{
126-
this.p0=p0;
127-
this.p1=p1;
128-
this.p2=p2;
129-
this.p3=p3;
130-
this.p4=p4;
126+
this.P0=p0;
127+
this.P1=p1;
128+
this.P2=p2;
129+
this.P3=p3;
130+
this.P4=p4;
131131
}
132132

133-
global::Com.Google.Android.Exoplayer.Drm.IExoMediaDrmp0;
133+
publicglobal::Com.Google.Android.Exoplayer.Drm.IExoMediaDrmP0{get;privateset;}
134134

135-
publicglobal::Com.Google.Android.Exoplayer.Drm.IExoMediaDrmP0{
136-
get{returnp0;}
137-
}
138-
139-
byte[]p1;
140-
141-
publicbyte[]P1{
142-
get{returnp1;}
143-
}
144-
145-
intp2;
135+
publicbyte[]P1{get;privateset;}
146136

147-
publicintP2{
148-
get{returnp2;}
149-
}
150-
151-
intp3;
137+
publicintP2{get;privateset;}
152138

153-
publicintP3{
154-
get{returnp3;}
155-
}
139+
publicintP3{get;privateset;}
156140

157-
byte[]p4;
158-
159-
publicbyte[]P4{
160-
get{returnp4;}
161-
}
141+
publicbyte[]P4{get;privateset;}
162142

163143
}
164144

‎tools/generator/SourceWriters/BoundInterface.cs‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,17 @@ void AddInterfaceEventHandler (InterfaceGen iface, CodeGenerationOptions opt, Co
104104

105105
foreach(varmethodiniface.Methods.Where(m =>m.EventName!=string.Empty)){
106106
if(method.RetVal.IsVoid||method.IsEventHandlerWithHandledProperty){
107-
if(!method.IsSimpleEventHandler||method.IsEventHandlerWithHandledProperty)
108-
post_sibling_types.Add(newInterfaceEventArgsClass(iface,method,opt,context));
107+
if(!method.IsSimpleEventHandler||method.IsEventHandlerWithHandledProperty){
108+
varevent_args_class=post_sibling_types.OfType<InterfaceEventArgsClass>().SingleOrDefault(c =>c.Name==iface.GetArgsName(method));
109+
110+
// Check if there's an existing EventArgs class to add to
111+
if(event_args_classisnull){
112+
event_args_class=newInterfaceEventArgsClass(iface,method);
113+
post_sibling_types.Add(event_args_class);
114+
}
115+
116+
event_args_class.AddMembersFromMethod(iface,method,opt);
117+
}
109118
}else{
110119
vardel=newDelegateWriter{
111120
Name=iface.GetEventDelegateName(method),

‎tools/generator/SourceWriters/BoundMethod.cs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,12 @@ public class BoundMethod : MethodWriter
1212
{
1313
readonlyMethodCallbackcallback;
1414

15+
publicMethodJavaMethod{get;}
16+
1517
publicBoundMethod(GenBasetype,Methodmethod,CodeGenerationOptionsopt,boolgenerateCallbacks)
1618
{
19+
JavaMethod=method;
20+
1721
if(generateCallbacks&&method.IsVirtual)
1822
callback=newMethodCallback(type,method,opt,null,method.IsReturnCharSequence);
1923

0 commit comments

Comments
 (0)