diff --git a/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods_Invoke.tt b/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods_Invoke.tt index 5e5ae6994..0cfd669b5 100644 --- a/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods_Invoke.tt +++ b/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods_Invoke.tt @@ -44,7 +44,7 @@ namespace Java.Interop { JniPeerMembers.AssertSelf (self); var declaringType = DeclaringType; - if (Members.ShouldUseVirtualDispatch (self, declaringType)) { + if (Members.UsesVirtualDispatch (self, declaringType)) { var m = GetMethodInfo (encodedMember); <#= returnType.ReturnType != "void" ? "return " : "" #>JniEnvironment.InstanceMethods.Call<#= returnType.JniCallType #>Method (self.PeerReference, m, parameters); <#= returnType.ReturnType == "void" ? "return;" : "" #> diff --git a/tools/generator/CodeGenerationOptions.cs b/tools/generator/CodeGenerationOptions.cs index 55718a92f..c3797b4fd 100644 --- a/tools/generator/CodeGenerationOptions.cs +++ b/tools/generator/CodeGenerationOptions.cs @@ -48,6 +48,7 @@ internal CodeGenerator CreateCodeGenerator (TextWriter writer) public bool UseShortFileNames { get; set; } public int ProductVersion { get; set; } public bool SupportInterfaceConstants { get; set; } + public bool SupportDefaultInterfaceMethods { get; set; } public bool UseShallowReferencedTypes { get; set; } bool? buildingCoreAssembly; diff --git a/tools/generator/CodeGenerator.cs b/tools/generator/CodeGenerator.cs index 19966634e..65b795f60 100644 --- a/tools/generator/CodeGenerator.cs +++ b/tools/generator/CodeGenerator.cs @@ -66,6 +66,7 @@ static void Run (CodeGeneratorOptions options, DirectoryAssemblyResolver resolve UseShortFileNames = options.UseShortFileNames, ProductVersion = options.ProductVersion, SupportInterfaceConstants = options.SupportInterfaceConstants, + SupportDefaultInterfaceMethods = options.SupportDefaultInterfaceMethods, }; // Load reference libraries @@ -143,7 +144,7 @@ static void Run (CodeGeneratorOptions options, DirectoryAssemblyResolver resolve // disable interface default methods here, especially before validation. gens = gens.Where (g => !g.IsObfuscated && g.Visibility != "private").ToList (); foreach (var gen in gens) { - gen.StripNonBindables (); + gen.StripNonBindables (opt); if (gen.IsGeneratable) AddTypeToTable (opt, gen); } diff --git a/tools/generator/CodeGeneratorOptions.cs b/tools/generator/CodeGeneratorOptions.cs index f9c1e5ff7..ae78d0557 100644 --- a/tools/generator/CodeGeneratorOptions.cs +++ b/tools/generator/CodeGeneratorOptions.cs @@ -40,6 +40,7 @@ public CodeGeneratorOptions () public bool OnlyRunApiXmlAdjuster { get; set; } public string ApiXmlAdjusterOutput { get; set; } public bool SupportInterfaceConstants { get; set; } + public bool SupportDefaultInterfaceMethods { get; set; } public static CodeGeneratorOptions Parse (string[] args) { @@ -87,8 +88,11 @@ public static CodeGeneratorOptions Parse (string[] args) "SDK Platform {VERSION}/API level.", v => opts.ApiLevel = v }, { "lang-features=", - "For internal use. (Flags: interface-constants)", - v => opts.SupportInterfaceConstants = v?.Contains ("interface-constants") == true }, + "For internal use. (Flags: interface-constants,default-interface-methods)", + v => { + opts.SupportInterfaceConstants = v?.Contains ("interface-constants") == true; + opts.SupportDefaultInterfaceMethods = v?.Contains ("default-interface-methods") == true; + }}, { "preserve-enums", "For internal use.", v => opts.PreserveEnums = v != null }, @@ -157,6 +161,11 @@ public static CodeGeneratorOptions Parse (string[] args) opts.ApiDescriptionFile = apis [0]; + if (opts.SupportDefaultInterfaceMethods && opts.CodeGenerationTarget == CodeGenerationTarget.XamarinAndroid) { + Console.Error.WriteLine (Report.Format (true, Report.ErrorInvalidArgument, "lang-features=default-interface-methods is not compatible with codegen-target=xamarinandroid.")); + return null; + } + return opts; } diff --git a/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/CodeGenerator.cs b/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/CodeGenerator.cs index 78a109c50..e36a6a9b0 100644 --- a/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/CodeGenerator.cs +++ b/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/CodeGenerator.cs @@ -35,7 +35,7 @@ protected CodeGenerator (TextWriter writer, CodeGenerationOptions options) internal abstract void WriteConstructorBody (Ctor ctor, string indent, StringCollection call_cleanup); internal abstract void WriteMethodIdField (Method method, string indent); - internal abstract void WriteMethodBody (Method method, string indent); + internal abstract void WriteMethodBody (Method method, string indent, GenBase type); internal abstract void WriteFieldIdField (Field field, string indent); internal abstract void WriteFieldGetBody (Field field, string indent, GenBase type); @@ -139,7 +139,7 @@ public void WriteClass (ClassGen @class, string indent, GenerationInfo gen_info) WriteClassConstructors (@class, indent + "\t"); - WriteClassProperties (@class, indent + "\t"); + WriteImplementedProperties (@class.Properties, indent + "\t", @class.IsFinal, @class); WriteClassMethods (@class, indent + "\t"); if (@class.IsAbstract) @@ -245,7 +245,7 @@ public void WriteClassInvokerMembers (ClassGen @class, string indent, HashSet !@class.ContainsProperty (p.Name, false, false)), indent, members); - WriteClassMethodInvokers (@class, iface.Methods.Where (m => !m.IsInterfaceDefaultMethod && !@class.ContainsMethod (m, false, false) && !@class.IsCovariantMethod (m) && !@class.ExplicitlyImplementedInterfaceMethods.Contains (m.GetSignature ())), indent, members, iface); + WriteClassMethodInvokers (@class, iface.Methods.Where (m => (opt.SupportDefaultInterfaceMethods || !m.IsInterfaceDefaultMethod) && !@class.ContainsMethod (m, false, false) && !@class.IsCovariantMethod (m) && !@class.ExplicitlyImplementedInterfaceMethods.Contains (m.GetSignature ())), indent, members, iface); } if (@class.BaseGen != null && @class.BaseGen.FullName != "Java.Lang.Object") @@ -275,6 +275,8 @@ public void WriteClassMethodInvokers (ClassGen @class, IEnumerable metho public void WriteClassMethods (ClassGen @class, string indent) { + var methodsToDeclare = @class.Methods.AsEnumerable (); + // This does not exclude overrides (unlike virtual methods) because we're not sure // if calling the base interface default method via JNI expectedly dispatches to // the derived method. @@ -282,15 +284,17 @@ public void WriteClassMethods (ClassGen @class, string indent) .SelectMany (i => i.Methods) .Where (m => m.IsInterfaceDefaultMethod) .Where (m => !@class.ContainsMethod (m, false, false)); - var overrides = defaultMethods.Where (m => m.IsInterfaceDefaultMethodOverride); + var overrides = defaultMethods.Where (m => m.OverriddenInterfaceMethod != null); var overridens = defaultMethods.Where (m => overrides.Where (_ => _.Name == m.Name && _.JniSignature == m.JniSignature) .Any (mm => mm.DeclaringType.GetAllDerivedInterfaces ().Contains (m.DeclaringType))); - foreach (Method m in @class.Methods.Concat (defaultMethods.Except (overridens)).Where (m => m.DeclaringType.IsGeneratable)) { + methodsToDeclare = opt.SupportDefaultInterfaceMethods ? methodsToDeclare : methodsToDeclare.Concat (defaultMethods.Except (overridens)).Where (m => m.DeclaringType.IsGeneratable); + + foreach (var m in methodsToDeclare) { bool virt = m.IsVirtual; m.IsVirtual = !@class.IsFinal && virt; - if (m.IsAbstract && !m.IsInterfaceDefaultMethodOverride && !m.IsInterfaceDefaultMethod) + if (m.IsAbstract && m.OverriddenInterfaceMethod == null && (opt.SupportDefaultInterfaceMethods || !m.IsInterfaceDefaultMethod)) WriteMethodAbstractDeclaration (m, indent, null, @class); else WriteMethod (m, indent, @class, true); @@ -306,18 +310,18 @@ public void WriteClassMethods (ClassGen @class, string indent) } } - public void WriteClassProperties (ClassGen @class, string indent) + public void WriteImplementedProperties (IEnumerable targetProperties, string indent, bool isFinal, GenBase gen) { - foreach (Property prop in @class.Properties) { + foreach (var prop in targetProperties) { bool get_virt = prop.Getter.IsVirtual; bool set_virt = prop.Setter == null ? false : prop.Setter.IsVirtual; - prop.Getter.IsVirtual = !@class.IsFinal && get_virt; + prop.Getter.IsVirtual = !isFinal && get_virt; if (prop.Setter != null) - prop.Setter.IsVirtual = !@class.IsFinal && set_virt; + prop.Setter.IsVirtual = !isFinal && set_virt; if (prop.Getter.IsAbstract) - WritePropertyAbstractDeclaration (prop, indent, @class); + WritePropertyAbstractDeclaration (prop, indent, gen); else - WriteProperty (prop, @class, indent); + WriteProperty (prop, gen, indent); prop.Getter.IsVirtual = get_virt; if (prop.Setter != null) prop.Setter.IsVirtual = set_virt; @@ -476,7 +480,7 @@ public void WriteInterface (InterfaceGen @interface, string indent, GenerationIn // For each interface, generate either an abstract method or an explicit implementation method. public void WriteInterfaceAbstractMembers (InterfaceGen @interface, ClassGen gen, string indent) { - foreach (Method m in @interface.Methods.Where (m => !m.IsInterfaceDefaultMethod && !m.IsStatic)) { + foreach (var m in @interface.Methods.Where (m => !m.IsInterfaceDefaultMethod && !m.IsStatic)) { bool mapped = false; string sig = m.GetSignature (); if (Context.ContextGeneratedMethods.Any (_ => _.Name == m.Name && _.JniSignature == m.JniSignature)) @@ -494,7 +498,7 @@ public void WriteInterfaceAbstractMembers (InterfaceGen @interface, ClassGen gen WriteMethodAbstractDeclaration (m, indent, @interface, gen); Context.ContextGeneratedMethods.Add (m); } - foreach (Property prop in @interface.Properties.Where (p => !p.Getter.IsStatic)) { + foreach (var prop in @interface.Properties.Where (p => !p.Getter.IsInterfaceDefaultMethod && !p.Getter.IsStatic)) { if (gen.ContainsProperty (prop.Name, false)) continue; WritePropertyAbstractDeclaration (prop, indent, gen); @@ -525,6 +529,10 @@ public void WriteInterfaceDeclaration (InterfaceGen @interface, string indent) writer.WriteLine ("{0}{1}", indent, @interface.TypeParameters.ToGeneratedAttributeString ()); writer.WriteLine ("{0}{1} partial interface {2}{3} {{", indent, @interface.Visibility, @interface.Name, @interface.IsConstSugar ? string.Empty : @interface.Interfaces.Count == 0 || sb.Length == 0 ? " : " + GetAllInterfaceImplements () : " : " + sb.ToString ()); + + if (opt.SupportDefaultInterfaceMethods && @interface.HasDefaultMethods) + WriteClassHandle (@interface, indent + "\t", @interface.Name); + WriteInterfaceFields (@interface, indent + "\t"); writer.WriteLine (); WriteInterfaceProperties (@interface, indent + "\t"); @@ -789,14 +797,14 @@ public void WriteInterfaceInvoker (InterfaceGen @interface, string indent) writer.WriteLine (); HashSet members = new HashSet (); - WriteInterfacePropertyInvokers (@interface, @interface.Properties.Where (p => !p.Getter.IsStatic), indent + "\t", members); - WriteInterfaceMethodInvokers (@interface, @interface.Methods.Where (m => !m.IsStatic), indent + "\t", members); + WriteInterfacePropertyInvokers (@interface, @interface.Properties.Where (p => !p.Getter.IsStatic && !p.Getter.IsInterfaceDefaultMethod), indent + "\t", members); + WriteInterfaceMethodInvokers (@interface, @interface.Methods.Where (m => !m.IsStatic && !m.IsInterfaceDefaultMethod), indent + "\t", members); if (@interface.FullName == "Java.Lang.ICharSequence") WriteCharSequenceEnumerator (indent + "\t"); foreach (InterfaceGen iface in @interface.GetAllDerivedInterfaces ()) { - WriteInterfacePropertyInvokers (@interface, iface.Properties.Where (p => !p.Getter.IsStatic), indent + "\t", members); - WriteInterfaceMethodInvokers (@interface, iface.Methods.Where (m => !m.IsStatic && !@interface.IsCovariantMethod (m) && !(iface.FullName.StartsWith ("Java.Lang.ICharSequence") && m.Name.EndsWith ("Formatted"))), indent + "\t", members); + WriteInterfacePropertyInvokers (@interface, iface.Properties.Where (p => !p.Getter.IsStatic && !p.Getter.IsInterfaceDefaultMethod), indent + "\t", members); + WriteInterfaceMethodInvokers (@interface, iface.Methods.Where (m => !m.IsStatic && !m.IsInterfaceDefaultMethod && !@interface.IsCovariantMethod (m) && !(iface.FullName.StartsWith ("Java.Lang.ICharSequence") && m.Name.EndsWith ("Formatted"))), indent + "\t", members); if (iface.FullName == "Java.Lang.ICharSequence") WriteCharSequenceEnumerator (indent + "\t"); } @@ -968,17 +976,23 @@ public void WriteInterfaceMethodInvokers (InterfaceGen @interface, IEnumerable !m.IsStatic)) { + foreach (var m in @interface.Methods.Where (m => !m.IsStatic && !m.IsInterfaceDefaultMethod)) { if (m.Name == @interface.Name || @interface.ContainsProperty (m.Name, true)) m.Name = "Invoke" + m.Name; + WriteMethodDeclaration (m, indent, @interface, @interface.AssemblyQualifiedName + "Invoker"); } + + foreach (var m in @interface.Methods.Where (m => m.IsInterfaceDefaultMethod)) + WriteMethod (m, indent, @interface, true); } public void WriteInterfaceProperties (InterfaceGen @interface, string indent) { - foreach (Property prop in @interface.Properties.Where (p => !p.Getter.IsStatic)) + foreach (var prop in @interface.Properties.Where (p => !p.Getter.IsStatic && !p.Getter.IsInterfaceDefaultMethod)) WritePropertyDeclaration (prop, indent, @interface, @interface.AssemblyQualifiedName + "Invoker"); + + WriteImplementedProperties (@interface.Properties.Where (p => p.Getter.IsInterfaceDefaultMethod), indent, false, @interface); } public void WriteInterfacePropertyInvokers (InterfaceGen @interface, IEnumerable properties, string indent, HashSet members) @@ -1063,7 +1077,7 @@ public void WriteMethodExplicitInterfaceInvoker (Method method, string indent, G writer.WriteLine ("{0}unsafe {1} {2}.{3} ({4})", indent, opt.GetOutputName (method.RetVal.FullName), opt.GetOutputName (iface.FullName), method.Name, method.GetSignature (opt)); writer.WriteLine ("{0}{{", indent); - WriteMethodBody (method, indent + "\t"); + WriteMethodBody (method, indent + "\t", iface); writer.WriteLine ("{0}}}", indent); writer.WriteLine (); } @@ -1255,14 +1269,23 @@ public void WriteMethodExtensionOverload (Method method, string indent, string s string ret = opt.GetOutputName (method.RetVal.FullName.Replace ("Java.Lang.ICharSequence", "string")); writer.WriteLine (); - writer.WriteLine ("{0}public static {1} {2} (this {3} self, {4})", - indent, ret, method.Name, selfType, - method.GetSignature (opt).Replace ("Java.Lang.ICharSequence", "string").Replace ("global::string", "string")); + + var parameters = method.GetSignature (opt).Replace ("Java.Lang.ICharSequence", "string").Replace ("global::string", "string"); + writer.WriteLine ("{0}public static {1} {2} (this {3} self{4}{5})", indent, ret, method.Name, selfType, parameters.Length > 0 ? ", " : "", parameters); + writer.WriteLine ("{0}{{", indent); WriteMethodStringOverloadBody (method, indent + "\t", true); writer.WriteLine ("{0}}}", indent); } + static string GetDeclaringTypeOfExplicitInterfaceMethod (Method method) + { + return method.OverriddenInterfaceMethod != null ? + GetDeclaringTypeOfExplicitInterfaceMethod (method.OverriddenInterfaceMethod) : + method.DeclaringType.FullName; + } + + public void WriteMethodAsyncWrapper (Method method, string indent) { if (!method.Asyncify) @@ -1315,11 +1338,20 @@ public void WriteMethod (Method method, string indent, GenBase type, bool genera bool gen_string_overload = !method.IsOverride && method.Parameters.HasCharSequence && !type.ContainsMethod (name_and_jnisig); string static_arg = method.IsStatic ? " static" : String.Empty; - string virt_ov = method.IsOverride ? " override" : method.IsVirtual ? " virtual" : String.Empty; + + var is_explicit = opt.SupportDefaultInterfaceMethods && type is InterfaceGen && method.OverriddenInterfaceMethod != null; + var virt_ov = is_explicit ? string.Empty : method.IsOverride ? (opt.SupportDefaultInterfaceMethods && method.OverriddenInterfaceMethod != null ? " virtual" : " override") : method.IsVirtual ? " virtual" : string.Empty; + string seal = method.IsOverride && method.IsFinal ? " sealed" : null; + + // When using DIM, don't generate "virtual sealed" methods, remove both modifiers instead + if (opt.SupportDefaultInterfaceMethods && method.OverriddenInterfaceMethod != null && virt_ov == " virtual" && seal == " sealed") { + virt_ov = string.Empty; + seal = string.Empty; + } + if ((string.IsNullOrEmpty (virt_ov) || virt_ov == " virtual") && type.RequiresNew (method.AdjustedName)) { virt_ov = " new" + virt_ov; } - string seal = method.IsOverride && method.IsFinal ? " sealed" : null; string ret = opt.GetOutputName (method.RetVal.FullName); WriteMethodIdField (method, indent); if (method.DeclaringType.IsGeneratable) @@ -1329,11 +1361,24 @@ public void WriteMethod (Method method, string indent, GenBase type, bool genera if (method.IsReturnEnumified) writer.WriteLine ("{0}[return:global::Android.Runtime.GeneratedEnum]", indent); writer.WriteLine ("{0}[Register (\"{1}\", \"{2}\", \"{3}\"{4})]", - indent, method.JavaName, method.JniSignature, method.IsVirtual ? method.ConnectorName : String.Empty, method.AdditionalAttributeString ()); + indent, method.JavaName, method.JniSignature, method.IsVirtual ? method.GetConnectorNameFull (opt) : String.Empty, method.AdditionalAttributeString ()); WriteMethodCustomAttributes (method, indent); - writer.WriteLine ("{0}{1}{2}{3}{4} unsafe {5} {6} ({7})", indent, method.Visibility, static_arg, virt_ov, seal, ret, method.AdjustedName, method.GetSignature (opt)); + + var visibility = type is InterfaceGen && !method.IsStatic ? string.Empty : method.Visibility; + + writer.WriteLine ("{0}{1}{2}{3}{4} unsafe {5} {6}{7} ({8})", + indent, + visibility, + static_arg, + virt_ov, + seal, + ret, + is_explicit ? GetDeclaringTypeOfExplicitInterfaceMethod (method.OverriddenInterfaceMethod) + '.' : string.Empty, + method.AdjustedName, + method.GetSignature (opt)); + writer.WriteLine ("{0}{{", indent); - WriteMethodBody (method, indent + "\t"); + WriteMethodBody (method, indent + "\t", type); writer.WriteLine ("{0}}}", indent); writer.WriteLine (); @@ -1418,7 +1463,7 @@ public void WriteProperty (Property property, GenBase gen, string indent, bool w WriteMethodIdField (property.Getter, indent); if (property.Setter != null) WriteMethodIdField (property.Setter, indent); - string visibility = property.Getter.IsAbstract && property.Getter.RetVal.IsGeneric ? "protected" : (property.Setter ?? property.Getter).Visibility; + string visibility = gen is InterfaceGen ? string.Empty : property.Getter.IsAbstract && property.Getter.RetVal.IsGeneric ? "protected" : (property.Setter ?? property.Getter).Visibility; // Unlike [Register], mcs does not allow applying [Obsolete] on property accessors, so we can apply them only under limited condition... if (property.Getter.Deprecated != null && (property.Setter == null || property.Setter.Deprecated != null)) writer.WriteLine ("{0}[Obsolete (@\"{1}\")]", indent, property.Getter.Deprecated.Replace ("\"", "\"\"").Trim () + (property.Setter != null && property.Setter.Deprecated != property.Getter.Deprecated ? " " + property.Setter.Deprecated.Replace ("\"", "\"\"").Trim () : null)); @@ -1426,19 +1471,19 @@ public void WriteProperty (Property property, GenBase gen, string indent, bool w writer.WriteLine ("{0}{1}{2} unsafe {3} {4} {{", indent, visibility, virtual_override, opt.GetOutputName (property.Getter.ReturnType), decl_name); if (gen.IsGeneratable) writer.WriteLine ("{0}\t// Metadata.xml XPath method reference: path=\"{1}/method[@name='{2}'{3}]\"", indent, gen.MetadataXPathReference, property.Getter.JavaName, property.Getter.Parameters.GetMethodXPathPredicate ()); - writer.WriteLine ("{0}\t[Register (\"{1}\", \"{2}\", \"{3}\"{4})]", indent, property.Getter.JavaName, property.Getter.JniSignature, property.Getter.ConnectorName, property.Getter.AdditionalAttributeString ()); + writer.WriteLine ("{0}\t[Register (\"{1}\", \"{2}\", \"{3}\"{4})]", indent, property.Getter.JavaName, property.Getter.JniSignature, property.Getter.GetConnectorNameFull (opt), property.Getter.AdditionalAttributeString ()); writer.WriteLine ("{0}\tget {{", indent); - WriteMethodBody (property.Getter, indent + "\t\t"); + WriteMethodBody (property.Getter, indent + "\t\t", gen); writer.WriteLine ("{0}\t}}", indent); if (property.Setter != null) { if (gen.IsGeneratable) writer.WriteLine ("{0}\t// Metadata.xml XPath method reference: path=\"{1}/method[@name='{2}'{3}]\"", indent, gen.MetadataXPathReference, property.Setter.JavaName, property.Setter.Parameters.GetMethodXPathPredicate ()); WriteMethodCustomAttributes (property.Setter, indent); - writer.WriteLine ("{0}\t[Register (\"{1}\", \"{2}\", \"{3}\"{4})]", indent, property.Setter.JavaName, property.Setter.JniSignature, property.Setter.ConnectorName, property.Setter.AdditionalAttributeString ()); + writer.WriteLine ("{0}\t[Register (\"{1}\", \"{2}\", \"{3}\"{4})]", indent, property.Setter.JavaName, property.Setter.JniSignature, property.Setter.GetConnectorNameFull (opt), property.Setter.AdditionalAttributeString ()); writer.WriteLine ("{0}\tset {{", indent); string pname = property.Setter.Parameters [0].Name; property.Setter.Parameters [0].Name = "value"; - WriteMethodBody (property.Setter, indent + "\t\t"); + WriteMethodBody (property.Setter, indent + "\t\t", gen); property.Setter.Parameters [0].Name = pname; writer.WriteLine ("{0}\t}}", indent); } else if (property.GenerateDispatchingSetter) { @@ -1484,12 +1529,12 @@ public void WritePropertyAbstractDeclaration (Property property, string indent, if (property.Getter.IsReturnEnumified) writer.WriteLine ("{0}[return:global::Android.Runtime.GeneratedEnum]", indent); WriteMethodCustomAttributes (property.Getter, indent); - writer.WriteLine ("{0}\t[Register (\"{1}\", \"{2}\", \"{3}\"{4})] get;", indent, property.Getter.JavaName, property.Getter.JniSignature, property.Getter.ConnectorName, property.Getter.AdditionalAttributeString ()); + writer.WriteLine ("{0}\t[Register (\"{1}\", \"{2}\", \"{3}\"{4})] get;", indent, property.Getter.JavaName, property.Getter.JniSignature, property.Getter.GetConnectorNameFull (opt), property.Getter.AdditionalAttributeString ()); if (property.Setter != null) { if (gen.IsGeneratable) writer.WriteLine ("{0}\t// Metadata.xml XPath method reference: path=\"{1}/method[@name='{2}'{3}]\"", indent, gen.MetadataXPathReference, property.Setter.JavaName, property.Setter.Parameters.GetMethodXPathPredicate ()); WriteMethodCustomAttributes (property.Setter, indent); - writer.WriteLine ("{0}\t[Register (\"{1}\", \"{2}\", \"{3}\"{4})] set;", indent, property.Setter.JavaName, property.Setter.JniSignature, property.Setter.ConnectorName, property.Setter.AdditionalAttributeString ()); + writer.WriteLine ("{0}\t[Register (\"{1}\", \"{2}\", \"{3}\"{4})] set;", indent, property.Setter.JavaName, property.Setter.JniSignature, property.Setter.GetConnectorNameFull (opt), property.Setter.AdditionalAttributeString ()); } writer.WriteLine ("{0}}}", indent); writer.WriteLine (); diff --git a/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/JavaInteropCodeGenerator.cs b/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/JavaInteropCodeGenerator.cs index 15613e4f2..3c89671a1 100644 --- a/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/JavaInteropCodeGenerator.cs +++ b/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/JavaInteropCodeGenerator.cs @@ -134,7 +134,7 @@ internal override void WriteMethodIdField (Method method, string indent) // No method id_ field required; it's now an `id` constant in the binding. } - internal override void WriteMethodBody (Method method, string indent) + internal override void WriteMethodBody (Method method, string indent, GenBase type) { writer.WriteLine ("{0}const string __id = \"{1}.{2}\";", indent, method.JavaName, method.JniSignature); foreach (string prep in method.Parameters.GetCallPrep (opt)) @@ -159,7 +159,7 @@ internal override void WriteMethodBody (Method method, string indent) writer.WriteLine ("_members.InstanceMethods.InvokeNonvirtual{0}Method (__id, this{1});", invokeType, method.Parameters.GetCallArgs (opt, invoker: false)); - } else if (method.IsVirtual && !method.IsAbstract) { + } else if ((method.IsVirtual && !method.IsAbstract) || method.IsInterfaceDefaultMethod) { writer.WriteLine ("_members.InstanceMethods.InvokeVirtual{0}Method (__id, this{1});", invokeType, method.Parameters.GetCallArgs (opt, invoker: false)); diff --git a/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/XamarinAndroidCodeGenerator.cs b/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/XamarinAndroidCodeGenerator.cs index a24fe21c6..1de68e0c4 100644 --- a/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/XamarinAndroidCodeGenerator.cs +++ b/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/XamarinAndroidCodeGenerator.cs @@ -123,7 +123,7 @@ void GenerateJNICall (Method method, string indent, string call, bool declare_re writer.WriteLine ("{0}return {1};", indent, method.RetVal.FromNative (opt, call, true)); } - internal override void WriteMethodBody (Method method, string indent) + internal override void WriteMethodBody (Method method, string indent, GenBase type) { writer.WriteLine ("{0}if ({1} == IntPtr.Zero)", indent, method.EscapedIdName); writer.WriteLine ("{0}\t{1} = JNIEnv.Get{2}MethodID (class_ref, \"{3}\", \"{4}\");", indent, method.EscapedIdName, method.IsStatic ? "Static" : String.Empty, method.JavaName, method.JniSignature); diff --git a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBase.cs b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBase.cs index 1628785ba..ffa0e1751 100644 --- a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBase.cs +++ b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBase.cs @@ -290,7 +290,7 @@ public virtual void FixupExplicitImplementation () public void FixupMethodOverrides (CodeGenerationOptions opt) { - foreach (var m in Methods.Where (m => !m.IsInterfaceDefaultMethod)) { + foreach (var m in Methods.Where (m => !m.IsStatic && !m.IsInterfaceDefaultMethod)) { for (var bt = GetBaseGen (opt); bt != null; bt = bt.GetBaseGen (opt)) { var bm = bt.Methods.FirstOrDefault (mm => mm.Name == m.Name && mm.Visibility == m.Visibility && ParameterList.Equals (mm.Parameters, m.Parameters)); if (bm != null && bm.RetVal.FullName == m.RetVal.FullName) { // if return type is different, it could be still "new", not "override". @@ -301,11 +301,22 @@ public void FixupMethodOverrides (CodeGenerationOptions opt) } // Interface default methods can be overriden. We want to process them differently. - foreach (var m in Methods.Where (m => m.IsInterfaceDefaultMethod)) { - foreach (var bt in GetAllDerivedInterfaces ()) { - var bm = bt.Methods.FirstOrDefault (mm => mm.Name == m.Name && ParameterList.Equals (mm.Parameters, m.Parameters)); + var checkDimOverrideTargets = opt.SupportDefaultInterfaceMethods ? Methods : Methods.Where (m => m.IsInterfaceDefaultMethod); + + // We need to check all the implemented interfaces of all the base types. + var allIfaces = new List (); + + for (var gen = this; gen != null; gen = gen.BaseGen) + gen.GetAllDerivedInterfaces (allIfaces); + + foreach (var m in checkDimOverrideTargets.Where (m => !m.IsStatic)) { + foreach (var bt in allIfaces.Distinct ()) { + // We mark a method as an override if (1) it is a DIM, or (2) if the base method is DIM + // (i.e. we don't mark as override if a class method "implements" normal iface method.) + var bm = bt.Methods.FirstOrDefault (mm => (m.IsInterfaceDefaultMethod || !mm.IsAbstract) && mm.Name == m.Name && ParameterList.Equals (mm.Parameters, m.Parameters)); + if (bm != null) { - m.IsInterfaceDefaultMethodOverride = true; + m.OverriddenInterfaceMethod = bm; break; } } @@ -411,7 +422,7 @@ void visit (ISymbol isym) } public IEnumerable GetAllMethods () => - Methods.Concat (Properties.Select (p => p.Getter)).Concat (Properties.Select (p => p.Setter).Where (m => m != null)); + Methods.Concat (Properties.Select (p => p.Getter)).Concat (Properties.Select (p => p.Setter)).Where (m => m != null); GenBase GetBaseGen (CodeGenerationOptions opt) { @@ -659,9 +670,12 @@ protected virtual bool OnValidate (CodeGenerationOptions opt, GenericParameterDe } Fields = valid_fields; - int method_cnt = Methods.Count; + // If we can't validate a default interface method it's ok to ignore it and still bind the interface + var method_cnt = Methods.Where (m => !m.IsInterfaceDefaultMethod).Count (); + Methods = Methods.Where (m => ValidateMethod (opt, m, context)).ToList (); - MethodValidationFailed = method_cnt != Methods.Count; + MethodValidationFailed = method_cnt != Methods.Where (m => !m.IsInterfaceDefaultMethod).Count (); + foreach (Method m in Methods) { if (m.IsVirtual) HasVirtualMethods = true; @@ -734,15 +748,16 @@ bool ReturnTypeMatches (Method m, Method mm) public bool ShouldGenerateAnnotationAttribute => IsAnnotation; - public void StripNonBindables () + public void StripNonBindables (CodeGenerationOptions opt) { - // As of now, if we generate bindings for interface default methods, that means users will - // have to "implement" those methods because they are declared and you have to implement - // any declared methods in C#. That is going to be problematic a lot. - Methods = Methods.Where (m => !m.IsInterfaceDefaultMethod).ToList (); + // Strip out default interface methods if not desired + if (!opt.SupportDefaultInterfaceMethods) + Methods = Methods.Where (m => !m.IsInterfaceDefaultMethod).ToList (); + NestedTypes = NestedTypes.Where (n => !n.IsObfuscated && n.Visibility != "private").ToList (); + foreach (var n in NestedTypes) - n.StripNonBindables (); + n.StripNonBindables (opt); } static readonly HashSet ThrowableRequiresNew = new HashSet ( diff --git a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/InterfaceGen.cs b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/InterfaceGen.cs index e0bbba10b..ce36922f6 100644 --- a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/InterfaceGen.cs +++ b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/InterfaceGen.cs @@ -117,6 +117,8 @@ public IEnumerable GetGeneratableFields (CodeGenerationOptions options) return Fields.Where (f => !f.NeedsProperty && !(f.DeprecatedComment?.Contains ("constant will be removed") == true)); } + public bool HasDefaultMethods => GetAllMethods ().Any (m => m.IsInterfaceDefaultMethod); + public bool IsConstSugar { get { if (Methods.Count > 0 || Properties.Count > 0) diff --git a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Method.cs b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Method.cs index 3017e4124..9c37eae99 100644 --- a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Method.cs +++ b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Method.cs @@ -20,7 +20,7 @@ public Method (GenBase declaringType) : base (declaringType) public bool IsAbstract { get; set; } public bool IsFinal { get; set; } public bool IsInterfaceDefaultMethod { get; set; } - public bool IsInterfaceDefaultMethodOverride { get; set; } + public Method OverriddenInterfaceMethod { get; set; } public bool IsReturnEnumified { get; set; } public bool IsStatic { get; set; } public bool IsVirtual { get; set; } @@ -122,6 +122,9 @@ internal string GetAdapterName (CodeGenerationOptions opt, string adapter) return adapter + AssemblyName; } + // Connectors for DIM are defined on the interface, not the implementing type + public string GetConnectorNameFull (CodeGenerationOptions opt) => ConnectorName + (opt.SupportDefaultInterfaceMethods && IsInterfaceDefaultMethod ? $":{DeclaringType.FullName}, " + (AssemblyName ?? opt.AssemblyName) : string.Empty); + internal string GetDelegateType () { var parms = Parameters.DelegateTypeParams; diff --git a/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/XamarinAndroid/WriteInterfaceMethod.txt b/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/Common/WriteInterfaceRedeclaredDefaultMethod.txt similarity index 55% rename from tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/XamarinAndroid/WriteInterfaceMethod.txt rename to tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/Common/WriteInterfaceRedeclaredDefaultMethod.txt index e82dbe04a..5b678cfd5 100644 --- a/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/XamarinAndroid/WriteInterfaceMethod.txt +++ b/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/Common/WriteInterfaceRedeclaredDefaultMethod.txt @@ -1,10 +1,9 @@ -// Metadata.xml XPath interface reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']" -[Register ("java/code/IMyInterface", "", "java.code.IMyInterfaceInvoker")] -public partial interface IMyInterface : IJavaObject { +// Metadata.xml XPath interface reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface2']" +[Register ("java/code/IMyInterface2", "", "java.code.IMyInterface2Invoker")] +public partial interface IMyInterface2 : java.code.IMyInterface { // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='DoSomething' and count(parameter)=0]" - [global::Java.Interop.JavaInterfaceDefaultMethod] - [Register ("DoSomething", "()V", "GetDoSomethingHandler:java.code.IMyInterfaceInvoker, ")] + [Register ("DoSomething", "()V", "GetDoSomethingHandler:java.code.IMyInterface2Invoker, MyAssembly")] void DoSomething (); } diff --git a/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteDefaultInterfaceMethodInvoker.txt b/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteDefaultInterfaceMethodInvoker.txt new file mode 100644 index 000000000..98f44e7f2 --- /dev/null +++ b/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteDefaultInterfaceMethodInvoker.txt @@ -0,0 +1,77 @@ +[global::Android.Runtime.Register ("java/code/IMyInterface", DoNotGenerateAcw=true)] +internal partial class IMyInterfaceInvoker : global::Java.Lang.Object, IMyInterface { + + internal static new readonly JniPeerMembers _members = new JniPeerMembers ("java/code/IMyInterface", typeof (IMyInterfaceInvoker)); + + 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; } + } + + IntPtr class_ref; + + public static IMyInterface 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.IMyInterface")); + 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 IMyInterfaceInvoker (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_DoDeclaration; +#pragma warning disable 0169 + static Delegate GetDoDeclarationHandler () + { + if (cb_DoDeclaration == null) + cb_DoDeclaration = JNINativeWrapper.CreateDelegate ((Action) n_DoDeclaration); + return cb_DoDeclaration; + } + + static void n_DoDeclaration (IntPtr jnienv, IntPtr native__this) + { + java.code.IMyInterface __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); + __this.DoDeclaration (); + } +#pragma warning restore 0169 + + IntPtr id_DoDeclaration; + public unsafe void DoDeclaration () + { + if (id_DoDeclaration == IntPtr.Zero) + id_DoDeclaration = JNIEnv.GetMethodID (class_ref, "DoDeclaration", "()V"); + JNIEnv.CallVoidMethod (((global::Java.Lang.Object) this).Handle, id_DoDeclaration); + } + +} + diff --git a/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteInterfaceDefaultMethod.txt b/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteInterfaceDefaultMethod.txt new file mode 100644 index 000000000..6eece822a --- /dev/null +++ b/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteInterfaceDefaultMethod.txt @@ -0,0 +1,34 @@ +// Metadata.xml XPath interface reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']" +[Register ("java/code/IMyInterface", "", "java.code.IMyInterfaceInvoker")] +public partial interface IMyInterface : IJavaObject, IJavaPeerable { + static new readonly JniPeerMembers _members = new JniPeerMembers ("java/code/IMyInterface", typeof (IMyInterface)); + + static Delegate cb_DoSomething; +#pragma warning disable 0169 + static Delegate GetDoSomethingHandler () + { + if (cb_DoSomething == null) + cb_DoSomething = JNINativeWrapper.CreateDelegate ((Action) n_DoSomething); + return cb_DoSomething; + } + + static void n_DoSomething (IntPtr jnienv, IntPtr native__this) + { + java.code.IMyInterface __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); + __this.DoSomething (); + } +#pragma warning restore 0169 + + // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='DoSomething' and count(parameter)=0]" + [Register ("DoSomething", "()V", "GetDoSomethingHandler:java.code.IMyInterface, MyAssembly")] + virtual unsafe void DoSomething () + { + const string __id = "DoSomething.()V"; + try { + _members.InstanceMethods.InvokeVirtualVoidMethod (__id, this, null); + } finally { + } + } + +} + diff --git a/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteInterfaceDefaultProperty.txt b/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteInterfaceDefaultProperty.txt new file mode 100644 index 000000000..0a9514cbf --- /dev/null +++ b/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteInterfaceDefaultProperty.txt @@ -0,0 +1,63 @@ +// Metadata.xml XPath interface reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']" +[Register ("java/code/IMyInterface", "", "java.code.IMyInterfaceInvoker")] +public partial interface IMyInterface : IJavaObject, IJavaPeerable { + static new readonly JniPeerMembers _members = new JniPeerMembers ("java/code/IMyInterface", typeof (IMyInterface)); + + static Delegate cb_get_Value; +#pragma warning disable 0169 + static Delegate Getget_ValueHandler () + { + if (cb_get_Value == null) + cb_get_Value = JNINativeWrapper.CreateDelegate ((Func) n_get_Value); + return cb_get_Value; + } + + static int n_get_Value (IntPtr jnienv, IntPtr native__this) + { + java.code.IMyInterface __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); + return __this.Value; + } +#pragma warning restore 0169 + + static Delegate cb_set_Value_I; +#pragma warning disable 0169 + static Delegate Getset_Value_IHandler () + { + if (cb_set_Value_I == null) + cb_set_Value_I = JNINativeWrapper.CreateDelegate ((Action) n_set_Value_I); + return cb_set_Value_I; + } + + static void n_set_Value_I (IntPtr jnienv, IntPtr native__this, int value) + { + java.code.IMyInterface __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); + __this.Value = value; + } +#pragma warning restore 0169 + + virtual unsafe int Value { + // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='get_Value' and count(parameter)=0]" + [Register ("get_Value", "()I", "Getget_ValueHandler:java.code.IMyInterface, MyAssembly")] + get { + const string __id = "get_Value.()I"; + try { + var __rm = _members.InstanceMethods.InvokeVirtualInt32Method (__id, this, null); + return __rm; + } finally { + } + } + // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='set_Value' and count(parameter)=1 and parameter[1][@type='int']]" + [Register ("set_Value", "(I)V", "Getset_Value_IHandler:java.code.IMyInterface, MyAssembly")] + set { + const string __id = "set_Value.(I)V"; + try { + JniArgumentValue* __args = stackalloc JniArgumentValue [1]; + __args [0] = new JniArgumentValue (value); + _members.InstanceMethods.InvokeVirtualVoidMethod (__id, this, __args); + } finally { + } + } + } + +} + diff --git a/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteInterfaceDefaultPropertyGetterOnly.txt b/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteInterfaceDefaultPropertyGetterOnly.txt new file mode 100644 index 000000000..6eb8aa760 --- /dev/null +++ b/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteInterfaceDefaultPropertyGetterOnly.txt @@ -0,0 +1,36 @@ +// Metadata.xml XPath interface reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']" +[Register ("java/code/IMyInterface", "", "java.code.IMyInterfaceInvoker")] +public partial interface IMyInterface : IJavaObject, IJavaPeerable { + static new readonly JniPeerMembers _members = new JniPeerMembers ("java/code/IMyInterface", typeof (IMyInterface)); + + static Delegate cb_get_Value; +#pragma warning disable 0169 + static Delegate Getget_ValueHandler () + { + if (cb_get_Value == null) + cb_get_Value = JNINativeWrapper.CreateDelegate ((Func) n_get_Value); + return cb_get_Value; + } + + static int n_get_Value (IntPtr jnienv, IntPtr native__this) + { + java.code.IMyInterface __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); + return __this.Value; + } +#pragma warning restore 0169 + + virtual unsafe int Value { + // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='get_Value' and count(parameter)=0]" + [Register ("get_Value", "()I", "Getget_ValueHandler:java.code.IMyInterface, MyAssembly")] + get { + const string __id = "get_Value.()I"; + try { + var __rm = _members.InstanceMethods.InvokeVirtualInt32Method (__id, this, null); + return __rm; + } finally { + } + } + } + +} + diff --git a/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteInterfaceMethod.txt b/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteInterfaceMethod.txt deleted file mode 100644 index b616be47a..000000000 --- a/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteInterfaceMethod.txt +++ /dev/null @@ -1,11 +0,0 @@ -// Metadata.xml XPath interface reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']" -[Register ("java/code/IMyInterface", "", "java.code.IMyInterfaceInvoker")] -public partial interface IMyInterface : IJavaObject, IJavaPeerable { - - // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='DoSomething' and count(parameter)=0]" - [global::Java.Interop.JavaInterfaceDefaultMethod] - [Register ("DoSomething", "()V", "GetDoSomethingHandler:java.code.IMyInterfaceInvoker, ")] - void DoSomething (); - -} - diff --git a/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteDefaultInterfaceMethodInvoker.txt b/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteDefaultInterfaceMethodInvoker.txt new file mode 100644 index 000000000..550d22615 --- /dev/null +++ b/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteDefaultInterfaceMethodInvoker.txt @@ -0,0 +1,77 @@ +[global::Android.Runtime.Register ("java/code/IMyInterface", DoNotGenerateAcw=true)] +internal partial class IMyInterfaceInvoker : global::Java.Lang.Object, IMyInterface { + + internal static new readonly JniPeerMembers _members = new XAPeerMembers ("java/code/IMyInterface", typeof (IMyInterfaceInvoker)); + + 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; } + } + + IntPtr class_ref; + + public static IMyInterface 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.IMyInterface")); + 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 IMyInterfaceInvoker (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_DoDeclaration; +#pragma warning disable 0169 + static Delegate GetDoDeclarationHandler () + { + if (cb_DoDeclaration == null) + cb_DoDeclaration = JNINativeWrapper.CreateDelegate ((Action) n_DoDeclaration); + return cb_DoDeclaration; + } + + static void n_DoDeclaration (IntPtr jnienv, IntPtr native__this) + { + java.code.IMyInterface __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); + __this.DoDeclaration (); + } +#pragma warning restore 0169 + + IntPtr id_DoDeclaration; + public unsafe void DoDeclaration () + { + if (id_DoDeclaration == IntPtr.Zero) + id_DoDeclaration = JNIEnv.GetMethodID (class_ref, "DoDeclaration", "()V"); + JNIEnv.CallVoidMethod (((global::Java.Lang.Object) this).Handle, id_DoDeclaration); + } + +} + diff --git a/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteInterfaceDefaultMethod.txt b/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteInterfaceDefaultMethod.txt new file mode 100644 index 000000000..ff77d6c71 --- /dev/null +++ b/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteInterfaceDefaultMethod.txt @@ -0,0 +1,34 @@ +// Metadata.xml XPath interface reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']" +[Register ("java/code/IMyInterface", "", "java.code.IMyInterfaceInvoker")] +public partial interface IMyInterface : IJavaObject, IJavaPeerable { + static new readonly JniPeerMembers _members = new XAPeerMembers ("java/code/IMyInterface", typeof (IMyInterface)); + + static Delegate cb_DoSomething; +#pragma warning disable 0169 + static Delegate GetDoSomethingHandler () + { + if (cb_DoSomething == null) + cb_DoSomething = JNINativeWrapper.CreateDelegate ((Action) n_DoSomething); + return cb_DoSomething; + } + + static void n_DoSomething (IntPtr jnienv, IntPtr native__this) + { + java.code.IMyInterface __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); + __this.DoSomething (); + } +#pragma warning restore 0169 + + // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='DoSomething' and count(parameter)=0]" + [Register ("DoSomething", "()V", "GetDoSomethingHandler:java.code.IMyInterface, MyAssembly")] + virtual unsafe void DoSomething () + { + const string __id = "DoSomething.()V"; + try { + _members.InstanceMethods.InvokeVirtualVoidMethod (__id, this, null); + } finally { + } + } + +} + diff --git a/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteInterfaceDefaultProperty.txt b/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteInterfaceDefaultProperty.txt new file mode 100644 index 000000000..a0c56ba92 --- /dev/null +++ b/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteInterfaceDefaultProperty.txt @@ -0,0 +1,63 @@ +// Metadata.xml XPath interface reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']" +[Register ("java/code/IMyInterface", "", "java.code.IMyInterfaceInvoker")] +public partial interface IMyInterface : IJavaObject, IJavaPeerable { + static new readonly JniPeerMembers _members = new XAPeerMembers ("java/code/IMyInterface", typeof (IMyInterface)); + + static Delegate cb_get_Value; +#pragma warning disable 0169 + static Delegate Getget_ValueHandler () + { + if (cb_get_Value == null) + cb_get_Value = JNINativeWrapper.CreateDelegate ((Func) n_get_Value); + return cb_get_Value; + } + + static int n_get_Value (IntPtr jnienv, IntPtr native__this) + { + java.code.IMyInterface __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); + return __this.Value; + } +#pragma warning restore 0169 + + static Delegate cb_set_Value_I; +#pragma warning disable 0169 + static Delegate Getset_Value_IHandler () + { + if (cb_set_Value_I == null) + cb_set_Value_I = JNINativeWrapper.CreateDelegate ((Action) n_set_Value_I); + return cb_set_Value_I; + } + + static void n_set_Value_I (IntPtr jnienv, IntPtr native__this, int value) + { + java.code.IMyInterface __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); + __this.Value = value; + } +#pragma warning restore 0169 + + virtual unsafe int Value { + // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='get_Value' and count(parameter)=0]" + [Register ("get_Value", "()I", "Getget_ValueHandler:java.code.IMyInterface, MyAssembly")] + get { + const string __id = "get_Value.()I"; + try { + var __rm = _members.InstanceMethods.InvokeVirtualInt32Method (__id, this, null); + return __rm; + } finally { + } + } + // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='set_Value' and count(parameter)=1 and parameter[1][@type='int']]" + [Register ("set_Value", "(I)V", "Getset_Value_IHandler:java.code.IMyInterface, MyAssembly")] + set { + const string __id = "set_Value.(I)V"; + try { + JniArgumentValue* __args = stackalloc JniArgumentValue [1]; + __args [0] = new JniArgumentValue (value); + _members.InstanceMethods.InvokeVirtualVoidMethod (__id, this, __args); + } finally { + } + } + } + +} + diff --git a/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteInterfaceDefaultPropertyGetterOnly.txt b/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteInterfaceDefaultPropertyGetterOnly.txt new file mode 100644 index 000000000..a0f879bd5 --- /dev/null +++ b/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteInterfaceDefaultPropertyGetterOnly.txt @@ -0,0 +1,36 @@ +// Metadata.xml XPath interface reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']" +[Register ("java/code/IMyInterface", "", "java.code.IMyInterfaceInvoker")] +public partial interface IMyInterface : IJavaObject, IJavaPeerable { + static new readonly JniPeerMembers _members = new XAPeerMembers ("java/code/IMyInterface", typeof (IMyInterface)); + + static Delegate cb_get_Value; +#pragma warning disable 0169 + static Delegate Getget_ValueHandler () + { + if (cb_get_Value == null) + cb_get_Value = JNINativeWrapper.CreateDelegate ((Func) n_get_Value); + return cb_get_Value; + } + + static int n_get_Value (IntPtr jnienv, IntPtr native__this) + { + java.code.IMyInterface __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); + return __this.Value; + } +#pragma warning restore 0169 + + virtual unsafe int Value { + // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']/method[@name='get_Value' and count(parameter)=0]" + [Register ("get_Value", "()I", "Getget_ValueHandler:java.code.IMyInterface, MyAssembly")] + get { + const string __id = "get_Value.()I"; + try { + var __rm = _members.InstanceMethods.InvokeVirtualInt32Method (__id, this, null); + return __rm; + } finally { + } + } + } + +} + diff --git a/tools/generator/Tests/Unit-Tests/CodeGeneratorTests.cs b/tools/generator/Tests/Unit-Tests/CodeGeneratorTests.cs index c051f11d1..2f405b876 100644 --- a/tools/generator/Tests/Unit-Tests/CodeGeneratorTests.cs +++ b/tools/generator/Tests/Unit-Tests/CodeGeneratorTests.cs @@ -163,7 +163,7 @@ public void WriteClassProperties () var @class = SupportTypeBuilder.CreateClass ("java.code.MyClass", options); generator.Context.ContextTypes.Push (@class); - generator.WriteClassProperties (@class, string.Empty); + generator.WriteImplementedProperties (@class.Properties, string.Empty, @class.IsFinal, @class); generator.Context.ContextTypes.Pop (); Assert.AreEqual (GetTargetedExpected (nameof (WriteClassProperties)), writer.ToString ().NormalizeLineEndings ()); @@ -663,7 +663,7 @@ public void WriteMethodBody () var method = new TestMethod (@class, "bar"); Assert.IsTrue (method.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()), "method.Validate failed!"); - generator.WriteMethodBody (method, string.Empty); + generator.WriteMethodBody (method, string.Empty, @class); Assert.AreEqual (GetTargetedExpected (nameof (WriteMethodBody)), writer.ToString ().NormalizeLineEndings ()); } diff --git a/tools/generator/Tests/Unit-Tests/DefaultInterfaceMethodsTests.cs b/tools/generator/Tests/Unit-Tests/DefaultInterfaceMethodsTests.cs index eb38f2cdd..a4189194b 100644 --- a/tools/generator/Tests/Unit-Tests/DefaultInterfaceMethodsTests.cs +++ b/tools/generator/Tests/Unit-Tests/DefaultInterfaceMethodsTests.cs @@ -1,19 +1,20 @@ using System; using MonoDroid.Generation; using NUnit.Framework; +using Xamarin.Android.Binder; namespace generatortests { [TestFixture] class JavaInteropDefaultInterfaceMethodsTests : DefaultInterfaceMethodsTests { - protected override Xamarin.Android.Binder.CodeGenerationTarget Target => Xamarin.Android.Binder.CodeGenerationTarget.JavaInterop1; + protected override CodeGenerationTarget Target => CodeGenerationTarget.JavaInterop1; } [TestFixture] - class XamarinAndroidDefaultInterfaceMethodsTests : DefaultInterfaceMethodsTests + class XAJavaInteropDefaultInterfaceMethodsTests : DefaultInterfaceMethodsTests { - protected override Xamarin.Android.Binder.CodeGenerationTarget Target => Xamarin.Android.Binder.CodeGenerationTarget.XamarinAndroid; + protected override CodeGenerationTarget Target => CodeGenerationTarget.XAJavaInterop1; } abstract class DefaultInterfaceMethodsTests : CodeGeneratorTestBase @@ -22,52 +23,130 @@ protected override CodeGenerationOptions CreateOptions () { var options = base.CreateOptions (); - options.SupportInterfaceConstants = true; + options.AssemblyName = "MyAssembly"; + options.SupportDefaultInterfaceMethods = true; return options; } [Test] - public void WriteInterfaceFields () + public void WriteInterfaceDefaultMethod () { - // This is an interface that has both fields and method declarations + // Create an interface with a default method var iface = SupportTypeBuilder.CreateEmptyInterface("java.code.IMyInterface"); - iface.Fields.Add (new TestField ("int", "MyConstantField").SetConstant ().SetValue ("7")); - iface.Methods.Add (new TestMethod (iface, "DoSomething").SetAbstract ()); + iface.Methods.Add (new TestMethod (iface, "DoSomething").SetDefaultInterfaceMethod ()); iface.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()); - generator.Context.ContextTypes.Push (iface); generator.WriteInterfaceDeclaration (iface, string.Empty); - generator.Context.ContextTypes.Pop (); - Assert.AreEqual (GetTargetedExpected (nameof (WriteInterfaceFields)), writer.ToString ().NormalizeLineEndings ()); + Assert.AreEqual (GetTargetedExpected (nameof (WriteInterfaceDefaultMethod)), writer.ToString ().NormalizeLineEndings ()); + } + + [Test] + public void WriteInterfaceRedeclaredDefaultMethod () + { + // Create an interface with a default method + var iface = SupportTypeBuilder.CreateEmptyInterface ("java.code.IMyInterface"); + iface.Methods.Add (new TestMethod (iface, "DoSomething").SetDefaultInterfaceMethod ()); + options.SymbolTable.AddType (iface); + + // Create a second interface that inherits the first, declaring the method as not default + var iface2 = SupportTypeBuilder.CreateEmptyInterface ("java.code.IMyInterface2"); + iface2.AddImplementedInterface ("java.code.IMyInterface"); + iface2.Methods.Add (new TestMethod (iface, "DoSomething")); + + iface.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()); + iface2.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()); + + generator.WriteInterfaceDeclaration (iface2, string.Empty); + + // IMyInterface2 should generate the method as abstract, not a default method + Assert.AreEqual (GetExpected (nameof (WriteInterfaceRedeclaredDefaultMethod)), writer.ToString ().NormalizeLineEndings ()); } [Test] - public void WriteConstSugarInterfaceFields () + public void WriteInterfaceDefaultProperty () { - // This is an interface that only has fields (IsConstSugar) - // We treat a little differenly because they don't need to interop with Java + // Create an interface with a default method var iface = SupportTypeBuilder.CreateEmptyInterface ("java.code.IMyInterface"); + var prop = SupportTypeBuilder.CreateProperty (iface, "Value", "int", options); - // These interface fields are supported and should be in the output - iface.Fields.Add (new TestField ("int", "MyConstantField").SetConstant ().SetValue ("7")); - iface.Fields.Add (new TestField ("java.lang.String", "MyConstantStringField").SetConstant ().SetValue ("\"hello\"")); - iface.Fields.Add (new TestField ("int", "MyDeprecatedField").SetConstant ().SetValue ("7").SetDeprecated ()); + prop.Getter.IsInterfaceDefaultMethod = true; + prop.Setter.IsInterfaceDefaultMethod = true; - // These interface fields are not supported and should be ignored - iface.Fields.Add (new TestField ("int", "MyDeprecatedEnumField").SetConstant ().SetValue ("MyEnumValue").SetDeprecated ("This constant will be removed in the future version.")); - iface.Fields.Add (new TestField ("int", "MyStaticField").SetStatic ().SetValue ("7")); + iface.Properties.Add (prop); + + iface.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()); + + generator.WriteInterfaceDeclaration (iface, string.Empty); + + Assert.AreEqual (GetTargetedExpected (nameof (WriteInterfaceDefaultProperty)), writer.ToString ().NormalizeLineEndings ()); + } + + [Test] + public void WriteInterfaceDefaultPropertyGetterOnly () + { + // Create an interface with a default method + var iface = SupportTypeBuilder.CreateEmptyInterface ("java.code.IMyInterface"); + var prop = SupportTypeBuilder.CreateProperty (iface, "Value", "int", options); + + prop.Getter.IsInterfaceDefaultMethod = true; + prop.Setter = null; + + iface.Properties.Add (prop); iface.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()); - generator.Context.ContextTypes.Push (iface); generator.WriteInterfaceDeclaration (iface, string.Empty); + + Assert.AreEqual (GetTargetedExpected (nameof (WriteInterfaceDefaultPropertyGetterOnly)), writer.ToString ().NormalizeLineEndings ()); + } + + + [Test] + public void WriteDefaultInterfaceMethodInvoker () + { + // Create an interface with a default method + var iface = SupportTypeBuilder.CreateEmptyInterface ("java.code.IMyInterface"); + + iface.Methods.Add (new TestMethod (iface, "DoDeclaration")); + iface.Methods.Add (new TestMethod (iface, "DoDefault").SetDefaultInterfaceMethod ()); + + iface.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()); + + generator.Context.ContextTypes.Push (iface); + generator.WriteInterfaceInvoker (iface, string.Empty); + generator.Context.ContextTypes.Pop (); + + Assert.AreEqual (GetTargetedExpected (nameof (WriteDefaultInterfaceMethodInvoker)), writer.ToString ().NormalizeLineEndings ()); + } + + [Test] + public void WriteSealedOverriddenDefaultMethod () + { + // Create an interface with a default method + var iface = SupportTypeBuilder.CreateEmptyInterface ("java.code.IMyInterface"); + iface.Methods.Add (new TestMethod (iface, "DoSomething").SetDefaultInterfaceMethod ()); + options.SymbolTable.AddType (iface); + + // Create a type that inherits the interface, overriding the method as final + var klass = new TestClass ("java.code.IMyInterface", "java.code.MyClass"); + klass.AddImplementedInterface ("java.code.IMyInterface"); + klass.Methods.Add (new TestMethod (iface, "DoSomething").SetFinal ()); + + iface.Validate (options, new GenericParameterDefinitionList (), generator.Context); + klass.Validate (options, new GenericParameterDefinitionList (), generator.Context); + + klass.FixupMethodOverrides (options); + + generator.Context.ContextTypes.Push (klass); + generator.WriteClass (klass, string.Empty, new GenerationInfo (string.Empty, string.Empty, "MyAssembly")); generator.Context.ContextTypes.Pop (); - Assert.AreEqual (GetTargetedExpected (nameof (WriteConstSugarInterfaceFields)), writer.ToString ().NormalizeLineEndings ()); + // The method should not be marked as 'virtual sealed' + Assert.False (writer.ToString ().Contains ("virtual sealed")); } } } diff --git a/tools/generator/Tests/Unit-Tests/InterfaceConstantsTests.cs b/tools/generator/Tests/Unit-Tests/InterfaceConstantsTests.cs new file mode 100644 index 000000000..1bcfdc24d --- /dev/null +++ b/tools/generator/Tests/Unit-Tests/InterfaceConstantsTests.cs @@ -0,0 +1,73 @@ +using System; +using MonoDroid.Generation; +using NUnit.Framework; + +namespace generatortests +{ + [TestFixture] + class JavaInteropInterfaceConstantsTests : InterfaceConstantsTests + { + protected override Xamarin.Android.Binder.CodeGenerationTarget Target => Xamarin.Android.Binder.CodeGenerationTarget.JavaInterop1; + } + + [TestFixture] + class XamarinAndroidInterfaceConstantsTests : InterfaceConstantsTests + { + protected override Xamarin.Android.Binder.CodeGenerationTarget Target => Xamarin.Android.Binder.CodeGenerationTarget.XamarinAndroid; + } + + abstract class InterfaceConstantsTests : CodeGeneratorTestBase + { + protected override CodeGenerationOptions CreateOptions () + { + var options = base.CreateOptions (); + + options.SupportInterfaceConstants = true; + + return options; + } + + [Test] + public void WriteInterfaceFields () + { + // This is an interface that has both fields and method declarations + var iface = SupportTypeBuilder.CreateEmptyInterface("java.code.IMyInterface"); + + iface.Fields.Add (new TestField ("int", "MyConstantField").SetConstant ().SetValue ("7")); + iface.Methods.Add (new TestMethod (iface, "DoSomething").SetAbstract ()); + + iface.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()); + + generator.Context.ContextTypes.Push (iface); + generator.WriteInterfaceDeclaration (iface, string.Empty); + generator.Context.ContextTypes.Pop (); + + Assert.AreEqual (GetTargetedExpected (nameof (WriteInterfaceFields)), writer.ToString ().NormalizeLineEndings ()); + } + + [Test] + public void WriteConstSugarInterfaceFields () + { + // This is an interface that only has fields (IsConstSugar) + // We treat a little differenly because they don't need to interop with Java + var iface = SupportTypeBuilder.CreateEmptyInterface ("java.code.IMyInterface"); + + // These interface fields are supported and should be in the output + iface.Fields.Add (new TestField ("int", "MyConstantField").SetConstant ().SetValue ("7")); + iface.Fields.Add (new TestField ("java.lang.String", "MyConstantStringField").SetConstant ().SetValue ("\"hello\"")); + iface.Fields.Add (new TestField ("int", "MyDeprecatedField").SetConstant ().SetValue ("7").SetDeprecated ()); + + // These interface fields are not supported and should be ignored + iface.Fields.Add (new TestField ("int", "MyDeprecatedEnumField").SetConstant ().SetValue ("MyEnumValue").SetDeprecated ("This constant will be removed in the future version.")); + iface.Fields.Add (new TestField ("int", "MyStaticField").SetStatic ().SetValue ("7")); + + iface.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ()); + + generator.Context.ContextTypes.Push (iface); + generator.WriteInterfaceDeclaration (iface, string.Empty); + generator.Context.ContextTypes.Pop (); + + Assert.AreEqual (GetTargetedExpected (nameof (WriteConstSugarInterfaceFields)), writer.ToString ().NormalizeLineEndings ()); + } + } +} diff --git a/tools/generator/Tests/Unit-Tests/InterfaceTests.cs b/tools/generator/Tests/Unit-Tests/InterfaceTests.cs new file mode 100644 index 000000000..d619910fd --- /dev/null +++ b/tools/generator/Tests/Unit-Tests/InterfaceTests.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using MonoDroid.Generation; +using NUnit.Framework; + +namespace generatortests +{ + public class InterfaceTests + { + [Test] + public void ValidateInterfaceMethods () + { + var options = new CodeGenerationOptions { SupportDefaultInterfaceMethods = true }; + var iface = SupportTypeBuilder.CreateEmptyInterface ("My.Test.Interface"); + + iface.Methods.Add (SupportTypeBuilder.CreateMethod (iface, "DoAbstractThing", options)); + iface.Methods.Add (SupportTypeBuilder.CreateMethod (iface, "DoDefaultThing", options).SetDefaultInterfaceMethod ()); + + // The interface should be valid + Assert.True (iface.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ())); + + Assert.AreEqual (2, iface.Methods.Count); + } + + [Test] + public void ValidateInvalidDefaultInterfaceMethods () + { + var options = new CodeGenerationOptions { SupportDefaultInterfaceMethods = true }; + var iface = SupportTypeBuilder.CreateEmptyInterface ("My.Test.Interface"); + + iface.Methods.Add (SupportTypeBuilder.CreateMethod (iface, "DoAbstractThing", options)); + iface.Methods.Add (SupportTypeBuilder.CreateMethod (iface, "DoDefaultThing", options, "potato").SetDefaultInterfaceMethod ()); + + // The interface should still be valid despite the default method being invalid + Assert.True (iface.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ())); + + // The invalid default method should be removed, leaving just the valid abstract method + Assert.AreEqual (1, iface.Methods.Count); + } + + [Test] + public void ValidateInvalidAbstractInterfaceMethods () + { + var options = new CodeGenerationOptions { SupportDefaultInterfaceMethods = true }; + var iface = SupportTypeBuilder.CreateEmptyInterface ("My.Test.Interface"); + + iface.Methods.Add (SupportTypeBuilder.CreateMethod (iface, "DoAbstractThing", options, "potato")); + iface.Methods.Add (SupportTypeBuilder.CreateMethod (iface, "DoDefaultThing", options).SetDefaultInterfaceMethod ()); + + // The interface should be invalid because an abstract method is invalid + Assert.False (iface.Validate (options, new GenericParameterDefinitionList (), new CodeGeneratorContext ())); + + // The invalid abstract method should be removed, leaving just the valid default method + Assert.AreEqual (1, iface.Methods.Count); + } + } +} diff --git a/tools/generator/Tests/Unit-Tests/SupportTypes.cs b/tools/generator/Tests/Unit-Tests/SupportTypes.cs index 3dd968019..9ce051837 100644 --- a/tools/generator/Tests/Unit-Tests/SupportTypes.cs +++ b/tools/generator/Tests/Unit-Tests/SupportTypes.cs @@ -155,6 +155,15 @@ public TestMethod SetReturnEnumified () IsReturnEnumified = true; return this; } + + public TestMethod SetDefaultInterfaceMethod () + { + IsAbstract = false; + IsStatic = false; + IsInterfaceDefaultMethod = true; + + return this; + } } class TestCtor : Ctor diff --git a/tools/generator/Tests/generator-Tests.csproj b/tools/generator/Tests/generator-Tests.csproj index b95a0f447..a46c7f2c4 100644 --- a/tools/generator/Tests/generator-Tests.csproj +++ b/tools/generator/Tests/generator-Tests.csproj @@ -74,6 +74,8 @@ + + @@ -176,6 +178,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -215,6 +220,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -242,6 +250,15 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + PreserveNewest @@ -323,6 +340,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -353,6 +373,15 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + PreserveNewest diff --git a/tools/generator/Utilities/Report.cs b/tools/generator/Utilities/Report.cs index 0db1bbd17..4561f043c 100644 --- a/tools/generator/Utilities/Report.cs +++ b/tools/generator/Utilities/Report.cs @@ -13,6 +13,7 @@ public class Report public const int ErrorParser = 0x4200; public const int ErrorApiFixup = 0x4300; public const int ErrorCodeGenerator = 0x4400; + public const int ErrorInvalidArgument = 0x4500; public const int WarningClassGen = 0x8100; public const int WarningCodeGenerator = 0x8200; public const int WarningCtor = 0x8300;