diff --git a/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/ObsoleteInterfaceAlternativeClass.txt b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/ObsoleteInterfaceAlternativeClass.txt new file mode 100644 index 000000000..05e4d8654 --- /dev/null +++ b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/ObsoleteInterfaceAlternativeClass.txt @@ -0,0 +1,154 @@ +[Register ("com/xamarin/android/Parent", DoNotGenerateAcw=true)] +[global::System.Obsolete ("Use the 'Com.Xamarin.Android.IParent' type. This class will be removed in a future release.")] +public abstract class Parent : Java.Lang.Object { + + internal Parent () + { + } + + // Metadata.xml XPath field reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']/field[@name='ACCEPT_HANDOVER']" + [Register ("ACCEPT_HANDOVER")] + [Obsolete ("Use 'Com.Xamarin.Android.IParent.AcceptHandover'. This class will be removed in a future release.")] + public const string AcceptHandover = (string) "android.permission.ACCEPT_HANDOVER"; + + // Metadata.xml XPath field reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']/field[@name='ALREADY_OBSOLETE']" + [Register ("ALREADY_OBSOLETE")] + [Obsolete ("deprecated")] + public const string AlreadyObsolete = (string) "android.permission.ACCEPT_HANDOVER"; + + + // Metadata.xml XPath field reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']/field[@name='API_NAME']" + [Register ("API_NAME")] + [Obsolete ("Use 'Com.Xamarin.Android.IParent.ApiName'. This class will be removed in a future release.")] + public static string ApiName { + get { + const string __id = "API_NAME.Ljava/lang/String;"; + + var __v = _members.StaticFields.GetObjectValue (__id); + return JNIEnv.GetString (__v.Handle, JniHandleOwnership.TransferLocalRef); + } + } + // Metadata.xml XPath method reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']/method[@name='comparing' and count(parameter)=0]" + [Obsolete (@"Use 'Com.Xamarin.Android.IParent.Comparing'. This class will be removed in a future release.")] + [Register ("comparing", "()I", "")] + public static unsafe int Comparing () + { + const string __id = "comparing.()I"; + try { + var __rm = _members.StaticMethods.InvokeInt32Method (__id, null); + return __rm; + } finally { + } + } + + // Metadata.xml XPath method reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']/method[@name='comparingOld' and count(parameter)=0]" + [Obsolete (@"deprecated")] + [Register ("comparingOld", "()I", "")] + public static unsafe int ComparingOld () + { + const string __id = "comparingOld.()I"; + try { + var __rm = _members.StaticMethods.InvokeInt32Method (__id, null); + return __rm; + } finally { + } + } + + + static readonly JniPeerMembers _members = new JniPeerMembers ("com/xamarin/android/Parent", typeof (Parent)); +} + +// 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 { + private static readonly JniPeerMembers _members = new JniPeerMembers ("com/xamarin/android/Parent", typeof (IParent), isInterface: true); + + // Metadata.xml XPath field reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']/field[@name='ACCEPT_HANDOVER']" + [Register ("ACCEPT_HANDOVER")] + public const string AcceptHandover = (string) "android.permission.ACCEPT_HANDOVER"; + + // Metadata.xml XPath field reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']/field[@name='ALREADY_OBSOLETE']" + [Register ("ALREADY_OBSOLETE")] + [Obsolete ("deprecated")] + public const string AlreadyObsolete = (string) "android.permission.ACCEPT_HANDOVER"; + + // Metadata.xml XPath method reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']/method[@name='comparing' and count(parameter)=0]" + [Register ("comparing", "()I", "")] + public static unsafe int Comparing () + { + const string __id = "comparing.()I"; + try { + var __rm = _members.StaticMethods.InvokeInt32Method (__id, null); + return __rm; + } finally { + } + } + + // Metadata.xml XPath method reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']/method[@name='comparingOld' and count(parameter)=0]" + [Obsolete (@"deprecated")] + [Register ("comparingOld", "()I", "")] + public static unsafe int ComparingOld () + { + const string __id = "comparingOld.()I"; + try { + var __rm = _members.StaticMethods.InvokeInt32Method (__id, null); + return __rm; + } finally { + } + } + +} + +[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); + } + +} + diff --git a/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/ObsoleteInterfaceAlternativeClass.txt b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/ObsoleteInterfaceAlternativeClass.txt new file mode 100644 index 000000000..0b3d3963b --- /dev/null +++ b/tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/ObsoleteInterfaceAlternativeClass.txt @@ -0,0 +1,154 @@ +[Register ("com/xamarin/android/Parent", DoNotGenerateAcw=true)] +[global::System.Obsolete ("Use the 'Com.Xamarin.Android.IParent' type. This class will be removed in a future release.")] +public abstract class Parent : Java.Lang.Object { + + internal Parent () + { + } + + // Metadata.xml XPath field reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']/field[@name='ACCEPT_HANDOVER']" + [Register ("ACCEPT_HANDOVER")] + [Obsolete ("Use 'Com.Xamarin.Android.IParent.AcceptHandover'. This class will be removed in a future release.")] + public const string AcceptHandover = (string) "android.permission.ACCEPT_HANDOVER"; + + // Metadata.xml XPath field reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']/field[@name='ALREADY_OBSOLETE']" + [Register ("ALREADY_OBSOLETE")] + [Obsolete ("deprecated")] + public const string AlreadyObsolete = (string) "android.permission.ACCEPT_HANDOVER"; + + + // Metadata.xml XPath field reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']/field[@name='API_NAME']" + [Register ("API_NAME")] + [Obsolete ("Use 'Com.Xamarin.Android.IParent.ApiName'. This class will be removed in a future release.")] + public static string ApiName { + get { + const string __id = "API_NAME.Ljava/lang/String;"; + + var __v = _members.StaticFields.GetObjectValue (__id); + return JNIEnv.GetString (__v.Handle, JniHandleOwnership.TransferLocalRef); + } + } + // Metadata.xml XPath method reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']/method[@name='comparing' and count(parameter)=0]" + [Obsolete (@"Use 'Com.Xamarin.Android.IParent.Comparing'. This class will be removed in a future release.")] + [Register ("comparing", "()I", "")] + public static unsafe int Comparing () + { + const string __id = "comparing.()I"; + try { + var __rm = _members.StaticMethods.InvokeInt32Method (__id, null); + return __rm; + } finally { + } + } + + // Metadata.xml XPath method reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']/method[@name='comparingOld' and count(parameter)=0]" + [Obsolete (@"deprecated")] + [Register ("comparingOld", "()I", "")] + public static unsafe int ComparingOld () + { + const string __id = "comparingOld.()I"; + try { + var __rm = _members.StaticMethods.InvokeInt32Method (__id, null); + return __rm; + } finally { + } + } + + + static readonly JniPeerMembers _members = new XAPeerMembers ("com/xamarin/android/Parent", typeof (Parent)); +} + +// 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 { + private static readonly JniPeerMembers _members = new XAPeerMembers ("com/xamarin/android/Parent", typeof (IParent), isInterface: true); + + // Metadata.xml XPath field reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']/field[@name='ACCEPT_HANDOVER']" + [Register ("ACCEPT_HANDOVER")] + public const string AcceptHandover = (string) "android.permission.ACCEPT_HANDOVER"; + + // Metadata.xml XPath field reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']/field[@name='ALREADY_OBSOLETE']" + [Register ("ALREADY_OBSOLETE")] + [Obsolete ("deprecated")] + public const string AlreadyObsolete = (string) "android.permission.ACCEPT_HANDOVER"; + + // Metadata.xml XPath method reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']/method[@name='comparing' and count(parameter)=0]" + [Register ("comparing", "()I", "")] + public static unsafe int Comparing () + { + const string __id = "comparing.()I"; + try { + var __rm = _members.StaticMethods.InvokeInt32Method (__id, null); + return __rm; + } finally { + } + } + + // Metadata.xml XPath method reference: path="/api/package[@name='com.xamarin.android']/interface[@name='Parent']/method[@name='comparingOld' and count(parameter)=0]" + [Obsolete (@"deprecated")] + [Register ("comparingOld", "()I", "")] + public static unsafe int ComparingOld () + { + const string __id = "comparingOld.()I"; + try { + var __rm = _members.StaticMethods.InvokeInt32Method (__id, null); + return __rm; + } finally { + } + } + +} + +[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); + } + +} + diff --git a/tests/generator-Tests/Unit-Tests/DefaultInterfaceMethodsTests.cs b/tests/generator-Tests/Unit-Tests/DefaultInterfaceMethodsTests.cs index 761d64e2d..c4ec8e4fa 100644 --- a/tests/generator-Tests/Unit-Tests/DefaultInterfaceMethodsTests.cs +++ b/tests/generator-Tests/Unit-Tests/DefaultInterfaceMethodsTests.cs @@ -303,5 +303,65 @@ public void WriteNestedInterfaceClass () Assert.AreEqual (GetTargetedExpected (nameof (WriteNestedInterfaceClass)), writer.ToString ().NormalizeLineEndings ()); } + + [Test] + public void DontWriteInterfaceConstsClass () + { + // If SupportInterfaceConstants is true we no longer write the legacy + // XXXXConsts class that has been [Obsolete (iseeror: true)] for a while. + options.SupportInterfaceConstants = true; + + var xml = @" + + + + + + + + + "; + + var gens = ParseApiDefinition (xml); + var iface = gens.OfType ().Single (); + + iface.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()); + + generator.WriteInterface (iface, string.Empty, new GenerationInfo (string.Empty, string.Empty, "MyAssembly")); + + Assert.False (writer.ToString ().Contains ("class ParentConsts")); + } + + [Test] + public void ObsoleteInterfaceAlternativeClass () + { + // If SupportInterfaceConstants and SupportDefaultInterfaceMethods is true we want to + // [Obsolete] the members of the "interface alternative" class so we can eventually remove it. + options.SupportInterfaceConstants = true; + + var xml = @" + + + + + + + + + + + + + "; + + var gens = ParseApiDefinition (xml); + var iface = gens.OfType ().Single (); + + iface.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()); + + generator.WriteInterface (iface, string.Empty, new GenerationInfo (string.Empty, string.Empty, "MyAssembly")); + + Assert.AreEqual (GetTargetedExpected (nameof (ObsoleteInterfaceAlternativeClass)), writer.ToString ().NormalizeLineEndings ()); + } } } diff --git a/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/CodeGenerator.cs b/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/CodeGenerator.cs index 53625a1a8..6f187f809 100644 --- a/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/CodeGenerator.cs +++ b/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/CodeGenerator.cs @@ -423,6 +423,10 @@ internal virtual void WriteField (Field field, string indent, GenBase type) writer.WriteLine (); writer.WriteLine ("{0}// Metadata.xml XPath field reference: path=\"{1}/field[@name='{2}']\"", indent, type.MetadataXPathReference, field.JavaName); writer.WriteLine ("{0}[Register (\"{1}\"{2})]", indent, field.JavaName, field.AdditionalAttributeString ()); + if (field.IsDeprecated) { + var deprecatedError = field.IsDeprecatedError ? ", error: true" : string.Empty; + writer.WriteLine ("{0}[Obsolete (\"{1}\"{2})]", indent, field.DeprecatedComment, deprecatedError); + } writer.WriteLine ("{0}{1} {2}{3} {4} {{", indent, field.Visibility, field.IsStatic ? "static " : String.Empty, fieldType, field.Name); writer.WriteLine ("{0}\tget {{", indent); WriteFieldGetBody (field, indent + "\t\t", type); @@ -726,13 +730,20 @@ public void WriteInterfaceImplementedMembersAlternative (InterfaceGen @interface // IMyInterface -> MyInterface // In this case the XXXConsts class is [Obsolete]'d and simply inherits from the newer class // in order to maintain backward compatibility. + // If we're creating a binding that supports DIM, we remove the XXXConsts class as they've been + // [Obsolete:iserror] for a long time, and we add [Obsolete] to the interface "class". var staticMethods = @interface.Methods.Where (m => m.IsStatic); + var should_obsolete = opt.SupportInterfaceConstants && opt.SupportDefaultInterfaceMethods; if (@interface.Fields.Any () || staticMethods.Any ()) { string name = @interface.HasManagedName ? @interface.Name.Substring (1) + "Consts" : @interface.Name.Substring (1); writer.WriteLine ("{0}[Register (\"{1}\"{2}, DoNotGenerateAcw=true)]", indent, @interface.RawJniName, @interface.AdditionalAttributeString ()); + + if (should_obsolete) + writer.WriteLine ("{0}[global::System.Obsolete (\"Use the '{1}' type. This class will be removed in a future release.\")]", indent, @interface.FullName); + writer.WriteLine ("{0}public abstract class {1} : Java.Lang.Object {{", indent, name); writer.WriteLine (); writer.WriteLine ("{0}\tinternal {1} ()", indent, name); @@ -740,17 +751,31 @@ public void WriteInterfaceImplementedMembersAlternative (InterfaceGen @interface writer.WriteLine ("{0}\t}}", indent); var seen = new HashSet (); + + var original_fields = DeprecateFields (@interface, should_obsolete); bool needsClassRef = WriteFields (@interface.Fields, indent + "\t", @interface, seen) || staticMethods.Any (); + RestoreDeprecatedFields (original_fields); + foreach (var iface in @interface.GetAllImplementedInterfaces ().OfType ()) { writer.WriteLine (); writer.WriteLine ("{0}\t// The following are fields from: {1}", indent, iface.JavaName); + original_fields = DeprecateFields (iface, should_obsolete); bool v = WriteFields (iface.Fields, indent + "\t", iface, seen); + RestoreDeprecatedFields (original_fields); needsClassRef = needsClassRef || v; } - foreach (var m in @interface.Methods.Where (m => m.IsStatic)) + foreach (var m in @interface.Methods.Where (m => m.IsStatic)) { + var original = m.Deprecated; + + if (should_obsolete && string.IsNullOrWhiteSpace (m.Deprecated)) + m.Deprecated = $"Use '{@interface.FullName}.{m.AdjustedName}'. This class will be removed in a future release."; + WriteMethod (m, indent + "\t", @interface, true); + m.Deprecated = original; + } + if (needsClassRef) { writer.WriteLine (); WriteClassHandle (@interface, indent + "\t", name); @@ -759,7 +784,7 @@ public void WriteInterfaceImplementedMembersAlternative (InterfaceGen @interface writer.WriteLine ("{0}}}", indent, @interface.Name); writer.WriteLine (); - if (!@interface.HasManagedName) { + if (!@interface.HasManagedName && !opt.SupportInterfaceConstants) { writer.WriteLine ("{0}[Register (\"{1}\"{2}, DoNotGenerateAcw=true)]", indent, @interface.RawJniName, @interface.AdditionalAttributeString ()); writer.WriteLine ("{0}[global::System.Obsolete (\"Use the '{1}' type. This type will be removed in a future release.\", error: true)]", indent, name); writer.WriteLine ("{0}public abstract class {1}Consts : {1} {{", indent, name); @@ -773,6 +798,32 @@ public void WriteInterfaceImplementedMembersAlternative (InterfaceGen @interface } } + List<(Field field, bool deprecated, string comment)> DeprecateFields (InterfaceGen iface, bool shouldObsolete) + { + var original_fields = iface.Fields.Select (f => (f, f.IsDeprecated, f.DeprecatedComment)).ToList (); + + if (!shouldObsolete) + return original_fields; + + foreach (var f in iface.Fields) { + // Only use this derprecation if it's not already deprecated for another reason + if (!f.IsDeprecated) { + f.IsDeprecated = true; + f.DeprecatedComment = $"Use '{iface.FullName}.{f.Name}'. This class will be removed in a future release."; ; + } + } + + return original_fields; + } + + void RestoreDeprecatedFields (List<(Field field, bool deprecated, string comment)> fields) + { + foreach (var tuple in fields) { + tuple.field.IsDeprecated = tuple.deprecated; + tuple.field.DeprecatedComment = tuple.comment; + } + } + public void WriteInterfaceInvoker (InterfaceGen @interface, string indent) { writer.WriteLine ("{0}[global::Android.Runtime.Register (\"{1}\", DoNotGenerateAcw=true{2})]", indent, @interface.RawJniName, @interface.AdditionalAttributeString ());