Skip to content

Commit 105d544

Browse files
authored
[generator] Remove interface alternatives w/ interface-constants (#600)
Context: #509 When using `generator --lang-features=interface-constants`, remove or obsolete interface alternatives. Due to our legacy workarounds for the C# shortcoming of not being able to place constants or members on interfaces, every interface with those members creates two additional "alternative" classes to provide users access to the members originally on the interface. For example, consider the Java interface [`android.os.Parcelable`][0]: // Java package android.os; public interface Parcelable { public static final int CONTENTS_FILE_DESCRIPTOR = 1; public static final int PARCELABLE_WRITE_RETURN_VALUE = 1; int describeContents(); void writeToParcel(Parcel dest, int flags); // ... } The binding in a C#7 world is: // C# Binding namespace Android.OS { public partial interface IParcelable { int DescribeContents(); void WriteToParcel(Parcel dest, [GeneratedEnum] ParcelableWriteFlags flags); } public abstract partial class Parcelable { internal Parcelable () {} public const int ContentsFileDescriptor = (int) 1; public const ParcelableWriteFlags ParcelableWriteReturnValue = (ParcelableWriteFlags) 1; } [Obsolete ("…", error: true)] public abstract partial class ParcelableConsts : Parcelable { private ParcelableConsts () {} public const int ContentsFileDescriptor = (int) 1; public const ParcelableWriteFlags ParcelableWriteReturnValue = (ParcelableWriteFlags) 1; } } `Parcelable` contains the interface constants which couldn't exist on `IParcelable`, and `ParcelableConsts` exists for backward compatibility (as we used `*Consts` types before we introduced the non-`*Consts` types), and is `[Obsolete(error:true)]`. In a C#8 world with `generator --lang-features=interface-constants` we no longer need to preserve these alternative types. The `*Consts` types have been `[Obsolete]` for over 5 years, but have only been `[Obsolete(error:true)]` since d16-4, released 2019-Dec-03. We feel it hasn't been an error long enough to remove it for *all* API levels, so we will stop emitting it for API-R. The `Parcelable` and similar alternative types are now redundant when `--lang-features=interface-constants,default-interface-methods` is used, as all members of the Java interface can now exist in the C# binding of that interface. Here, we will `[Obsolete]` the class and all members of the class, with a message referring to the new types and members to use. This results in an API-R binding of: // C# Binding, API-R namespace Android.OS { public partial interface IParcelable { int DescribeContents(); void WriteToParcel(Parcel dest, [GeneratedEnum] ParcelableWriteFlags flags); public const int ContentsFileDescriptor = (int) 1; public const ParcelableWriteFlags ParcelableWriteReturnValue = (ParcelableWriteFlags) 1; } [Obsolete ("Use 'Android.OS.IParcelable'. This class will be removed in a future release.")] public abstract partial class Parcelable { internal Parcelable () {} [Obsolete ("Use 'Android.OS.IParcelable.ContentsFileDescriptor'. This class will be removed in a future release.")] public const int ContentsFileDescriptor = (int) 1; [Obsolete ("Use 'Android.OS.IParcelable.ParcelableWriteReturnValue'. This class will be removed in a future release.")] public const ParcelableWriteFlags ParcelableWriteReturnValue = (ParcelableWriteFlags) 1; } } Additionally, fix a bug where fields that are marked `[Obsolete]` that are being changed into property getters were not having the obsolete attribute applied. [0]: https://developer.android.com/reference/android/os/Parcelable
1 parent b255981 commit 105d544

File tree

4 files changed

+421
-2
lines changed

4 files changed

+421
-2
lines changed
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
[Register ("com/xamarin/android/Parent", DoNotGenerateAcw=true)]
2+
[global::System.Obsolete ("Use the 'Com.Xamarin.Android.IParent' type. This class will be removed in a future release.")]
3+
public abstract class Parent : Java.Lang.Object {
4+
5+
internal Parent ()
6+
{
7+
}
8+
9+
// Metadata.xml XPath field reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']/field[@name='ACCEPT_HANDOVER']"
10+
[Register ("ACCEPT_HANDOVER")]
11+
[Obsolete ("Use 'Com.Xamarin.Android.IParent.AcceptHandover'. This class will be removed in a future release.")]
12+
public const string AcceptHandover = (string) "android.permission.ACCEPT_HANDOVER";
13+
14+
// Metadata.xml XPath field reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']/field[@name='ALREADY_OBSOLETE']"
15+
[Register ("ALREADY_OBSOLETE")]
16+
[Obsolete ("deprecated")]
17+
public const string AlreadyObsolete = (string) "android.permission.ACCEPT_HANDOVER";
18+
19+
20+
// Metadata.xml XPath field reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']/field[@name='API_NAME']"
21+
[Register ("API_NAME")]
22+
[Obsolete ("Use 'Com.Xamarin.Android.IParent.ApiName'. This class will be removed in a future release.")]
23+
public static string ApiName {
24+
get {
25+
const string __id = "API_NAME.Ljava/lang/String;";
26+
27+
var __v = _members.StaticFields.GetObjectValue (__id);
28+
return JNIEnv.GetString (__v.Handle, JniHandleOwnership.TransferLocalRef);
29+
}
30+
}
31+
// Metadata.xml XPath method reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']/method[@name='comparing' and count(parameter)=0]"
32+
[Obsolete (@"Use 'Com.Xamarin.Android.IParent.Comparing'. This class will be removed in a future release.")]
33+
[Register ("comparing", "()I", "")]
34+
public static unsafe int Comparing ()
35+
{
36+
const string __id = "comparing.()I";
37+
try {
38+
var __rm = _members.StaticMethods.InvokeInt32Method (__id, null);
39+
return __rm;
40+
} finally {
41+
}
42+
}
43+
44+
// Metadata.xml XPath method reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']/method[@name='comparingOld' and count(parameter)=0]"
45+
[Obsolete (@"deprecated")]
46+
[Register ("comparingOld", "()I", "")]
47+
public static unsafe int ComparingOld ()
48+
{
49+
const string __id = "comparingOld.()I";
50+
try {
51+
var __rm = _members.StaticMethods.InvokeInt32Method (__id, null);
52+
return __rm;
53+
} finally {
54+
}
55+
}
56+
57+
58+
static readonly JniPeerMembers _members = new JniPeerMembers ("com/xamarin/android/Parent", typeof (Parent));
59+
}
60+
61+
// Metadata.xml XPath interface reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']"
62+
[Register ("com/xamarin/android/Parent", "", "Com.Xamarin.Android.IParentInvoker")]
63+
public partial interface IParent : IJavaObject, IJavaPeerable {
64+
private static readonly JniPeerMembers _members = new JniPeerMembers ("com/xamarin/android/Parent", typeof (IParent), isInterface: true);
65+
66+
// Metadata.xml XPath field reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']/field[@name='ACCEPT_HANDOVER']"
67+
[Register ("ACCEPT_HANDOVER")]
68+
public const string AcceptHandover = (string) "android.permission.ACCEPT_HANDOVER";
69+
70+
// Metadata.xml XPath field reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']/field[@name='ALREADY_OBSOLETE']"
71+
[Register ("ALREADY_OBSOLETE")]
72+
[Obsolete ("deprecated")]
73+
public const string AlreadyObsolete = (string) "android.permission.ACCEPT_HANDOVER";
74+
75+
// Metadata.xml XPath method reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']/method[@name='comparing' and count(parameter)=0]"
76+
[Register ("comparing", "()I", "")]
77+
public static unsafe int Comparing ()
78+
{
79+
const string __id = "comparing.()I";
80+
try {
81+
var __rm = _members.StaticMethods.InvokeInt32Method (__id, null);
82+
return __rm;
83+
} finally {
84+
}
85+
}
86+
87+
// Metadata.xml XPath method reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']/method[@name='comparingOld' and count(parameter)=0]"
88+
[Obsolete (@"deprecated")]
89+
[Register ("comparingOld", "()I", "")]
90+
public static unsafe int ComparingOld ()
91+
{
92+
const string __id = "comparingOld.()I";
93+
try {
94+
var __rm = _members.StaticMethods.InvokeInt32Method (__id, null);
95+
return __rm;
96+
} finally {
97+
}
98+
}
99+
100+
}
101+
102+
[global::Android.Runtime.Register ("com/xamarin/android/Parent", DoNotGenerateAcw=true)]
103+
internal partial class IParentInvoker : global::Java.Lang.Object, IParent {
104+
105+
static readonly JniPeerMembers _members = new JniPeerMembers ("com/xamarin/android/Parent", typeof (IParentInvoker));
106+
107+
static IntPtr java_class_ref {
108+
get { return _members.JniPeerType.PeerReference.Handle; }
109+
}
110+
111+
public override global::Java.Interop.JniPeerMembers JniPeerMembers {
112+
get { return _members; }
113+
}
114+
115+
protected override IntPtr ThresholdClass {
116+
get { return class_ref; }
117+
}
118+
119+
protected override global::System.Type ThresholdType {
120+
get { return _members.ManagedPeerType; }
121+
}
122+
123+
new IntPtr class_ref;
124+
125+
public static IParent GetObject (IntPtr handle, JniHandleOwnership transfer)
126+
{
127+
return global::Java.Lang.Object.GetObject<IParent> (handle, transfer);
128+
}
129+
130+
static IntPtr Validate (IntPtr handle)
131+
{
132+
if (!JNIEnv.IsInstanceOf (handle, java_class_ref))
133+
throw new InvalidCastException (string.Format ("Unable to convert instance of type '{0}' to type '{1}'.",
134+
JNIEnv.GetClassNameFromInstance (handle), "com.xamarin.android.Parent"));
135+
return handle;
136+
}
137+
138+
protected override void Dispose (bool disposing)
139+
{
140+
if (this.class_ref != IntPtr.Zero)
141+
JNIEnv.DeleteGlobalRef (this.class_ref);
142+
this.class_ref = IntPtr.Zero;
143+
base.Dispose (disposing);
144+
}
145+
146+
public IParentInvoker (IntPtr handle, JniHandleOwnership transfer) : base (Validate (handle), transfer)
147+
{
148+
IntPtr local_ref = JNIEnv.GetObjectClass (((global::Java.Lang.Object) this).Handle);
149+
this.class_ref = JNIEnv.NewGlobalRef (local_ref);
150+
JNIEnv.DeleteLocalRef (local_ref);
151+
}
152+
153+
}
154+
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
[Register ("com/xamarin/android/Parent", DoNotGenerateAcw=true)]
2+
[global::System.Obsolete ("Use the 'Com.Xamarin.Android.IParent' type. This class will be removed in a future release.")]
3+
public abstract class Parent : Java.Lang.Object {
4+
5+
internal Parent ()
6+
{
7+
}
8+
9+
// Metadata.xml XPath field reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']/field[@name='ACCEPT_HANDOVER']"
10+
[Register ("ACCEPT_HANDOVER")]
11+
[Obsolete ("Use 'Com.Xamarin.Android.IParent.AcceptHandover'. This class will be removed in a future release.")]
12+
public const string AcceptHandover = (string) "android.permission.ACCEPT_HANDOVER";
13+
14+
// Metadata.xml XPath field reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']/field[@name='ALREADY_OBSOLETE']"
15+
[Register ("ALREADY_OBSOLETE")]
16+
[Obsolete ("deprecated")]
17+
public const string AlreadyObsolete = (string) "android.permission.ACCEPT_HANDOVER";
18+
19+
20+
// Metadata.xml XPath field reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']/field[@name='API_NAME']"
21+
[Register ("API_NAME")]
22+
[Obsolete ("Use 'Com.Xamarin.Android.IParent.ApiName'. This class will be removed in a future release.")]
23+
public static string ApiName {
24+
get {
25+
const string __id = "API_NAME.Ljava/lang/String;";
26+
27+
var __v = _members.StaticFields.GetObjectValue (__id);
28+
return JNIEnv.GetString (__v.Handle, JniHandleOwnership.TransferLocalRef);
29+
}
30+
}
31+
// Metadata.xml XPath method reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']/method[@name='comparing' and count(parameter)=0]"
32+
[Obsolete (@"Use 'Com.Xamarin.Android.IParent.Comparing'. This class will be removed in a future release.")]
33+
[Register ("comparing", "()I", "")]
34+
public static unsafe int Comparing ()
35+
{
36+
const string __id = "comparing.()I";
37+
try {
38+
var __rm = _members.StaticMethods.InvokeInt32Method (__id, null);
39+
return __rm;
40+
} finally {
41+
}
42+
}
43+
44+
// Metadata.xml XPath method reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']/method[@name='comparingOld' and count(parameter)=0]"
45+
[Obsolete (@"deprecated")]
46+
[Register ("comparingOld", "()I", "")]
47+
public static unsafe int ComparingOld ()
48+
{
49+
const string __id = "comparingOld.()I";
50+
try {
51+
var __rm = _members.StaticMethods.InvokeInt32Method (__id, null);
52+
return __rm;
53+
} finally {
54+
}
55+
}
56+
57+
58+
static readonly JniPeerMembers _members = new XAPeerMembers ("com/xamarin/android/Parent", typeof (Parent));
59+
}
60+
61+
// Metadata.xml XPath interface reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']"
62+
[Register ("com/xamarin/android/Parent", "", "Com.Xamarin.Android.IParentInvoker")]
63+
public partial interface IParent : IJavaObject, IJavaPeerable {
64+
private static readonly JniPeerMembers _members = new XAPeerMembers ("com/xamarin/android/Parent", typeof (IParent), isInterface: true);
65+
66+
// Metadata.xml XPath field reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']/field[@name='ACCEPT_HANDOVER']"
67+
[Register ("ACCEPT_HANDOVER")]
68+
public const string AcceptHandover = (string) "android.permission.ACCEPT_HANDOVER";
69+
70+
// Metadata.xml XPath field reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']/field[@name='ALREADY_OBSOLETE']"
71+
[Register ("ALREADY_OBSOLETE")]
72+
[Obsolete ("deprecated")]
73+
public const string AlreadyObsolete = (string) "android.permission.ACCEPT_HANDOVER";
74+
75+
// Metadata.xml XPath method reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']/method[@name='comparing' and count(parameter)=0]"
76+
[Register ("comparing", "()I", "")]
77+
public static unsafe int Comparing ()
78+
{
79+
const string __id = "comparing.()I";
80+
try {
81+
var __rm = _members.StaticMethods.InvokeInt32Method (__id, null);
82+
return __rm;
83+
} finally {
84+
}
85+
}
86+
87+
// Metadata.xml XPath method reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']/method[@name='comparingOld' and count(parameter)=0]"
88+
[Obsolete (@"deprecated")]
89+
[Register ("comparingOld", "()I", "")]
90+
public static unsafe int ComparingOld ()
91+
{
92+
const string __id = "comparingOld.()I";
93+
try {
94+
var __rm = _members.StaticMethods.InvokeInt32Method (__id, null);
95+
return __rm;
96+
} finally {
97+
}
98+
}
99+
100+
}
101+
102+
[global::Android.Runtime.Register ("com/xamarin/android/Parent", DoNotGenerateAcw=true)]
103+
internal partial class IParentInvoker : global::Java.Lang.Object, IParent {
104+
105+
static readonly JniPeerMembers _members = new XAPeerMembers ("com/xamarin/android/Parent", typeof (IParentInvoker));
106+
107+
static IntPtr java_class_ref {
108+
get { return _members.JniPeerType.PeerReference.Handle; }
109+
}
110+
111+
public override global::Java.Interop.JniPeerMembers JniPeerMembers {
112+
get { return _members; }
113+
}
114+
115+
protected override IntPtr ThresholdClass {
116+
get { return class_ref; }
117+
}
118+
119+
protected override global::System.Type ThresholdType {
120+
get { return _members.ManagedPeerType; }
121+
}
122+
123+
new IntPtr class_ref;
124+
125+
public static IParent GetObject (IntPtr handle, JniHandleOwnership transfer)
126+
{
127+
return global::Java.Lang.Object.GetObject<IParent> (handle, transfer);
128+
}
129+
130+
static IntPtr Validate (IntPtr handle)
131+
{
132+
if (!JNIEnv.IsInstanceOf (handle, java_class_ref))
133+
throw new InvalidCastException (string.Format ("Unable to convert instance of type '{0}' to type '{1}'.",
134+
JNIEnv.GetClassNameFromInstance (handle), "com.xamarin.android.Parent"));
135+
return handle;
136+
}
137+
138+
protected override void Dispose (bool disposing)
139+
{
140+
if (this.class_ref != IntPtr.Zero)
141+
JNIEnv.DeleteGlobalRef (this.class_ref);
142+
this.class_ref = IntPtr.Zero;
143+
base.Dispose (disposing);
144+
}
145+
146+
public IParentInvoker (IntPtr handle, JniHandleOwnership transfer) : base (Validate (handle), transfer)
147+
{
148+
IntPtr local_ref = JNIEnv.GetObjectClass (((global::Java.Lang.Object) this).Handle);
149+
this.class_ref = JNIEnv.NewGlobalRef (local_ref);
150+
JNIEnv.DeleteLocalRef (local_ref);
151+
}
152+
153+
}
154+

tests/generator-Tests/Unit-Tests/DefaultInterfaceMethodsTests.cs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,5 +303,65 @@ public void WriteNestedInterfaceClass ()
303303

304304
Assert.AreEqual (GetTargetedExpected (nameof (WriteNestedInterfaceClass)), writer.ToString ().NormalizeLineEndings ());
305305
}
306+
307+
[Test]
308+
public void DontWriteInterfaceConstsClass ()
309+
{
310+
// If SupportInterfaceConstants is true we no longer write the legacy
311+
// XXXXConsts class that has been [Obsolete (iseeror: true)] for a while.
312+
options.SupportInterfaceConstants = true;
313+
314+
var xml = @"<api>
315+
<package name='java.lang' jni-name='java/lang'>
316+
<class abstract='false' deprecated='not deprecated' final='false' name='Object' static='false' visibility='public' jni-signature='Ljava/lang/EmptyOverrideClass;' />
317+
</package>
318+
<package name='com.xamarin.android' jni-name='com/xamarin/android'>
319+
<interface abstract='true' deprecated='not deprecated' final='false' name='Parent' static='false' visibility='public' jni-signature='Lcom/xamarin/android/Parent;'>
320+
<field deprecated='not deprecated' final='true' name='ACCEPT_HANDOVER' jni-signature='Ljava/lang/String;' static='true' transient='false' type='java.lang.String' type-generic-aware='java.lang.String' value='&quot;android.permission.ACCEPT_HANDOVER&quot;' visibility='public' volatile='false'></field>
321+
</interface>
322+
</package>
323+
</api>";
324+
325+
var gens = ParseApiDefinition (xml);
326+
var iface = gens.OfType<InterfaceGen> ().Single ();
327+
328+
iface.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ());
329+
330+
generator.WriteInterface (iface, string.Empty, new GenerationInfo (string.Empty, string.Empty, "MyAssembly"));
331+
332+
Assert.False (writer.ToString ().Contains ("class ParentConsts"));
333+
}
334+
335+
[Test]
336+
public void ObsoleteInterfaceAlternativeClass ()
337+
{
338+
// If SupportInterfaceConstants and SupportDefaultInterfaceMethods is true we want to
339+
// [Obsolete] the members of the "interface alternative" class so we can eventually remove it.
340+
options.SupportInterfaceConstants = true;
341+
342+
var xml = @"<api>
343+
<package name='java.lang' jni-name='java/lang'>
344+
<class abstract='false' deprecated='not deprecated' final='false' name='Object' static='false' visibility='public' jni-signature='Ljava/lang/EmptyOverrideClass;' />
345+
</package>
346+
<package name='com.xamarin.android' jni-name='com/xamarin/android'>
347+
<interface abstract='true' deprecated='not deprecated' final='false' name='Parent' static='false' visibility='public' jni-signature='Lcom/xamarin/android/Parent;'>
348+
<field deprecated='not deprecated' final='true' name='ACCEPT_HANDOVER' jni-signature='Ljava/lang/String;' static='true' transient='false' type='java.lang.String' type-generic-aware='java.lang.String' value='&quot;android.permission.ACCEPT_HANDOVER&quot;' visibility='public' volatile='false'></field>
349+
<field deprecated='deprecated' final='true' name='ALREADY_OBSOLETE' jni-signature='Ljava/lang/String;' static='true' transient='false' type='java.lang.String' type-generic-aware='java.lang.String' value='&quot;android.permission.ACCEPT_HANDOVER&quot;' visibility='public' volatile='false'></field>
350+
<field deprecated='not deprecated' final='true' name='API_NAME' jni-signature='Ljava/lang/String;' static='true' transient='false' type='java.lang.String' type-generic-aware='java.lang.String' visibility='public' volatile='false'></field>
351+
<method abstract='false' deprecated='not deprecated' final='false' name='comparing' jni-signature='()I' bridge='false' native='false' return='int' jni-return='I' static='true' synchronized='false' synthetic='false' visibility='public' />
352+
<method abstract='false' deprecated='deprecated' final='false' name='comparingOld' jni-signature='()I' bridge='false' native='false' return='int' jni-return='I' static='true' synchronized='false' synthetic='false' visibility='public' />
353+
</interface>
354+
</package>
355+
</api>";
356+
357+
var gens = ParseApiDefinition (xml);
358+
var iface = gens.OfType<InterfaceGen> ().Single ();
359+
360+
iface.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ());
361+
362+
generator.WriteInterface (iface, string.Empty, new GenerationInfo (string.Empty, string.Empty, "MyAssembly"));
363+
364+
Assert.AreEqual (GetTargetedExpected (nameof (ObsoleteInterfaceAlternativeClass)), writer.ToString ().NormalizeLineEndings ());
365+
}
306366
}
307367
}

0 commit comments

Comments
 (0)