Skip to content

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

File tree

7 files changed

+270
-71
lines changed

7 files changed

+270
-71
lines changed

src/Xamarin.SourceWriter/Models/PropertyWriter.cs

+11
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public class PropertyWriter : ISourceWriter
3535
public List<AttributeWriter> SetterAttributes { get; } = new List<AttributeWriter> ();
3636
public int Priority { get; set; }
3737
public string ExplicitInterfaceImplementation { get; set; }
38+
public Visibility AutoSetterVisibility { get; set; }
3839

3940
public void SetVisibility (string visibility)
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+
else if (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) {
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

+20
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+
public void WriteDuplicateInterfaceEventArgs ()
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+
var iface = SupportTypeBuilder.CreateEmptyInterface ("java.code.AnimatorListener");
205+
206+
var method1 = SupportTypeBuilder.CreateMethod (iface, "OnAnimationEnd", options, "boolean", false, true, new Parameter ("param1", "int", "int", false));
207+
var method2 = SupportTypeBuilder.CreateMethod (iface, "OnAnimationEnd", options, "boolean", false, true, new Parameter ("param1", "int", "int", false), new Parameter ("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, new GenerationInfo ("", "", "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

+10-30
Original file line numberDiff line numberDiff line change
@@ -123,42 +123,22 @@ public unsafe void OnEvent (global::Com.Google.Android.Exoplayer.Drm.IExoMediaDr
123123
public partial class ExoMediaDrmOnEventEventArgs : global::System.EventArgs {
124124
public ExoMediaDrmOnEventEventArgs (global::Com.Google.Android.Exoplayer.Drm.IExoMediaDrm p0, byte[] p1, int p2, int p3, 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.IExoMediaDrm p0;
133+
public global::Com.Google.Android.Exoplayer.Drm.IExoMediaDrm P0 { get; private set; }
134134

135-
public global::Com.Google.Android.Exoplayer.Drm.IExoMediaDrm P0 {
136-
get { return p0; }
137-
}
138-
139-
byte[] p1;
140-
141-
public byte[] P1 {
142-
get { return p1; }
143-
}
144-
145-
int p2;
135+
public byte[] P1 { get; private set; }
146136

147-
public int P2 {
148-
get { return p2; }
149-
}
150-
151-
int p3;
137+
public int P2 { get; private set; }
152138

153-
public int P3 {
154-
get { return p3; }
155-
}
139+
public int P3 { get; private set; }
156140

157-
byte[] p4;
158-
159-
public byte[] P4 {
160-
get { return p4; }
161-
}
141+
public byte[] P4 { get; private set; }
162142

163143
}
164144

tools/generator/SourceWriters/BoundInterface.cs

+11-2
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,17 @@ void AddInterfaceEventHandler (InterfaceGen iface, CodeGenerationOptions opt, Co
104104

105105
foreach (var method in iface.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 (new InterfaceEventArgsClass (iface, method, opt, context));
107+
if (!method.IsSimpleEventHandler || method.IsEventHandlerWithHandledProperty) {
108+
var event_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_class is null) {
112+
event_args_class = new InterfaceEventArgsClass (iface, method);
113+
post_sibling_types.Add (event_args_class);
114+
}
115+
116+
event_args_class.AddMembersFromMethod (iface, method, opt);
117+
}
109118
} else {
110119
var del = new DelegateWriter {
111120
Name = iface.GetEventDelegateName (method),

tools/generator/SourceWriters/BoundMethod.cs

+4
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,12 @@ public class BoundMethod : MethodWriter
1212
{
1313
readonly MethodCallback callback;
1414

15+
public Method JavaMethod { get; }
16+
1517
public BoundMethod (GenBase type, Method method, CodeGenerationOptions opt, bool generateCallbacks)
1618
{
19+
JavaMethod = method;
20+
1721
if (generateCallbacks && method.IsVirtual)
1822
callback = new MethodCallback (type, method, opt, null, method.IsReturnCharSequence);
1923

0 commit comments

Comments
 (0)