From fa7078e5c0b71c42a439babcbc3630f14b3cc807 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 29 Mar 2018 10:38:34 -0500 Subject: [PATCH] [generator] refactoring for methods In the same fashion that 7a96efe refactored fields, this commit aims at refactoring methods. I also added various unit tests to add coverage around the new `TestMethod` support type. Refactoring in generator: - Dropped the method "support" classes in favor of extension methods from `ManagedExtensions` and `XmlExtensions`. These existed due to C# single-inheritance getting in the way of the relationship between methods, properties, constructors, etc. - Moved methods from `Method` and `MethodBase` to `CodeGenerator` and renamed them from `Generate*` to `WriteMethod*`. I tried to not modify the code in these methods other than to add a parameter for the incoming method. - Output of generator *should* remain the same. - `*Method` classes are now alot closer to POCOs Other changes: - Dropped usage of `ContextTypes` stack in `XamarinAndroidCodeGenerator` - Added a base test class for `JavaInteropCodeGeneratorTests` and `XamarinAndroidCodeGeneratorTests` because there were several tests that were completely identical (due to `CodeGenerator` base class) - Changed `StreamWriter` to `TextWriter` as needed - Now up to 88 tests! Most new ones are ~1ms Future plans (trying to keep this commit of reasonable size): - The `CodeGenerator` base class is starting to get large. As refactoring continues, I would like to make a class for `FieldGenerator`, `MethodGenerator`, etc. and `CodeGenerator` would get properties named `Fields` and `Methods` that would return these child generator classes - This is exactly what grendel did with his prototype, so we should adopt this improvement: https://github.com/grendello/JavaBindingGenerator/blob/fb8864d1842bdf0b8d45eb04b89ef2bd57de0014/src/Java.Interop.Bindings/Compiler/MethodCodeGenerator.cs --- tools/generator/ClassGen.cs | 20 +- tools/generator/CodeGenerator.cs | 343 +++++++++++++++ tools/generator/Ctor.cs | 29 +- tools/generator/InterfaceGen.cs | 14 +- tools/generator/ManagedExtensions.cs | 50 +++ tools/generator/Method.cs | 401 ++---------------- tools/generator/MethodBase.cs | 141 +----- tools/generator/Property.cs | 34 +- .../Tests/Unit-Tests/CodeGeneratorTests.cs | 102 +++++ .../JavaInteropCodeGeneratorTests.cs | 325 +++++++++++--- .../Tests/Unit-Tests/SupportTypes.cs | 110 +++++ .../XamarinAndroidCodeGeneratorTests.cs | 352 ++++++++++++--- tools/generator/Tests/generator-Tests.csproj | 1 + .../generator/XamarinAndroidCodeGenerator.cs | 6 +- tools/generator/XmlExtensions.cs | 22 + tools/generator/generator.csproj | 2 + 16 files changed, 1283 insertions(+), 669 deletions(-) create mode 100644 tools/generator/ManagedExtensions.cs create mode 100644 tools/generator/Tests/Unit-Tests/CodeGeneratorTests.cs create mode 100644 tools/generator/XmlExtensions.cs diff --git a/tools/generator/ClassGen.cs b/tools/generator/ClassGen.cs index d0590bf21..9f022d67c 100644 --- a/tools/generator/ClassGen.cs +++ b/tools/generator/ClassGen.cs @@ -15,15 +15,7 @@ using System.Xml.Linq; namespace MonoDroid.Generation { -#if HAVE_CECIL - static class ManagedExtensions - { - public static string FullNameCorrected (this TypeReference t) - { - return t.FullName.Replace ('/', '.'); - } - } - +#if HAVE_CECIL public class ManagedClassGen : ClassGen { TypeDefinition t; TypeReference nominal_base_type; @@ -352,9 +344,9 @@ void GenMethods (StreamWriter sw, string indent, CodeGenerationOptions opt) bool virt = m.IsVirtual; m.IsVirtual = !IsFinal && virt; if (m.IsAbstract && !m.IsInterfaceDefaultMethodOverride && !m.IsInterfaceDefaultMethod) - m.GenerateAbstractDeclaration (sw, indent, opt, null, this); + opt.CodeGenerator.WriteMethodAbstractDeclaration (m, sw, indent, opt, null, this); else - m.Generate (sw, indent, opt, this, true); + opt.CodeGenerator.WriteMethod (m, sw, indent, opt, this, true); opt.ContextGeneratedMethods.Add (m); m.IsVirtual = virt; } @@ -497,7 +489,7 @@ public override void Generate (StreamWriter sw, string indent, CodeGenerationOpt if (m.IsInterfaceDefaultMethod || m.IsStatic) continue; if (m.IsGeneric) - m.GenerateExplicitIface (sw, indent + "\t", opt, gs); + opt.CodeGenerator.WriteMethodExplicitIface (m, sw, indent + "\t", opt, gs); } var adapter = gs.Gen.AssemblyQualifiedName + "Invoker"; @@ -631,11 +623,11 @@ void GenerateInvoker (StreamWriter sw, IEnumerable methods, string inden continue; if (IsExplicitlyImplementedMethod (sig)) { // sw.WriteLine ("// This invoker explicitly implements this method"); - m.GenerateExplicitInterfaceInvoker (sw, indent, opt, gen); + opt.CodeGenerator.WriteMethodExplicitInterfaceInvoker (m, sw, indent, opt, gen); } else { // sw.WriteLine ("// This invoker overrides {0} method", gen.FullName); m.IsOverride = true; - m.Generate (sw, indent, opt, this, false); + opt.CodeGenerator.WriteMethod (m, sw, indent, opt, this, false); m.IsOverride = false; } } diff --git a/tools/generator/CodeGenerator.cs b/tools/generator/CodeGenerator.cs index 146251b9a..46c423add 100644 --- a/tools/generator/CodeGenerator.cs +++ b/tools/generator/CodeGenerator.cs @@ -751,6 +751,349 @@ internal virtual void WriteField (Field field, TextWriter w writer.WriteLine ("{0}{1} const {2} {3} = ({2}) {4};", indent, field.Visibility, opt.GetOutputName (field.Symbol.FullName), field.Name, field.Value.Contains ('-') && field.Symbol.FullName.Contains ('.') ? '(' + field.Value + ')' : field.Value); } } + + #region "if you're changing this part, also change method in https://github.com/xamarin/xamarin-android/blob/master/src/Mono.Android.Export/CallbackCode.cs" + public virtual void WriteMethodCallback (Method method, TextWriter writer, string indent, CodeGenerationOptions opt, GenBase type, string property_name, bool as_formatted = false) + { + string delegate_type = method.GetDelegateType (); + writer.WriteLine ("{0}static Delegate {1};", indent, method.EscapedCallbackName); + writer.WriteLine ("#pragma warning disable 0169"); + writer.WriteLine ("{0}static Delegate {1} ()", indent, method.ConnectorName); + writer.WriteLine ("{0}{{", indent); + writer.WriteLine ("{0}\tif ({1} == null)", indent, method.EscapedCallbackName); + writer.WriteLine ("{0}\t\t{1} = JNINativeWrapper.CreateDelegate (({2}) n_{3});", indent, method.EscapedCallbackName, delegate_type, method.Name + method.IDSignature); + writer.WriteLine ("{0}\treturn {1};", indent, method.EscapedCallbackName); + writer.WriteLine ("{0}}}", indent); + writer.WriteLine (); + writer.WriteLine ("{0}static {1} n_{2} (IntPtr jnienv, IntPtr native__this{3})", indent, method.RetVal.NativeType, method.Name + method.IDSignature, method.Parameters.GetCallbackSignature (opt)); + writer.WriteLine ("{0}{{", indent); + writer.WriteLine ("{0}\t{1} __this = global::Java.Lang.Object.GetObject<{1}> (jnienv, native__this, JniHandleOwnership.DoNotTransfer);", indent, opt.GetOutputName (type.FullName)); + foreach (string s in method.Parameters.GetCallbackPrep (opt)) + writer.WriteLine ("{0}\t{1}", indent, s); + if (String.IsNullOrEmpty (property_name)) { + string call = "__this." + method.Name + (as_formatted ? "Formatted" : String.Empty) + " (" + method.Parameters.GetCall (opt) + ")"; + if (method.IsVoid) + writer.WriteLine ("{0}\t{1};", indent, call); + else + writer.WriteLine ("{0}\t{1} {2};", indent, method.Parameters.HasCleanup ? method.RetVal.NativeType + " __ret =" : "return", method.RetVal.ToNative (opt, call)); + } else { + if (method.IsVoid) + writer.WriteLine ("{0}\t__this.{1} = {2};", indent, property_name, method.Parameters.GetCall (opt)); + else + writer.WriteLine ("{0}\t{1} {2};", indent, method.Parameters.HasCleanup ? method.RetVal.NativeType + " __ret =" : "return", method.RetVal.ToNative (opt, "__this." + property_name)); + } + foreach (string cleanup in method.Parameters.GetCallbackCleanup (opt)) + writer.WriteLine ("{0}\t{1}", indent, cleanup); + if (!method.IsVoid && method.Parameters.HasCleanup) + writer.WriteLine ("{0}\treturn __ret;", indent); + writer.WriteLine ("{0}}}", indent); + writer.WriteLine ("#pragma warning restore 0169"); + writer.WriteLine (); + } + #endregion + + public void WriteMethodCustomAttributes (Method method, TextWriter writer, string indent) + { + if (method.GenericArguments != null && method.GenericArguments.Any ()) + writer.WriteLine ("{0}{1}", indent, method.GenericArguments.ToGeneratedAttributeString ()); + if (method.CustomAttributes != null) + writer.WriteLine ("{0}{1}", indent, method.CustomAttributes); + if (method.Annotation != null) + writer.WriteLine ("{0}{1}", indent, method.Annotation); + } + + public void WriteMethodExplicitInterfaceImplementation (Method method, TextWriter writer, string indent, CodeGenerationOptions opt, GenBase iface) + { + //writer.WriteLine ("// explicitly implemented method from " + iface.FullName); + WriteMethodCustomAttributes (method, writer, indent); + writer.WriteLine ("{0}{1} {2}.{3} ({4})", indent, opt.GetOutputName (method.RetVal.FullName), opt.GetOutputName (iface.FullName), method.Name, GenBase.GetSignature (method, opt)); + writer.WriteLine ("{0}{{", indent); + writer.WriteLine ("{0}\treturn {1} ({2});", indent, method.Name, method.Parameters.GetCall (opt)); + writer.WriteLine ("{0}}}", indent); + writer.WriteLine (); + } + + public void WriteMethodExplicitInterfaceInvoker (Method method, TextWriter writer, string indent, CodeGenerationOptions opt, GenBase iface) + { + //writer.WriteLine ("\t\t// explicitly implemented invoker method from " + iface.FullName); + WriteMethodIdField (method, writer, indent, opt); + writer.WriteLine ("{0}unsafe {1} {2}.{3} ({4})", + indent, opt.GetOutputName (method.RetVal.FullName), opt.GetOutputName (iface.FullName), method.Name, GenBase.GetSignature (method, opt)); + writer.WriteLine ("{0}{{", indent); + WriteMethodBody (method, writer, indent + "\t", opt); + writer.WriteLine ("{0}}}", indent); + writer.WriteLine (); + } + + public void WriteMethodAbstractDeclaration (Method method, TextWriter writer, string indent, CodeGenerationOptions opt, InterfaceGen gen, GenBase impl) + { + if (method.RetVal.IsGeneric && gen != null) { + WriteMethodCustomAttributes (method, writer, indent); + writer.WriteLine ("{0}{1} {2}.{3} ({4})", indent, opt.GetOutputName (method.RetVal.FullName), opt.GetOutputName (gen.FullName), method.Name, GenBase.GetSignature (method, opt)); + writer.WriteLine ("{0}{{", indent); + writer.WriteLine ("{0}\tthrow new NotImplementedException ();", indent); + writer.WriteLine ("{0}}}", indent); + writer.WriteLine (); + } else { + bool gen_as_formatted = method.IsReturnCharSequence; + string name = method.AdjustedName; + WriteMethodCallback (method, writer, indent, opt, impl, null, gen_as_formatted); + if (method.DeclaringType.IsGeneratable) + writer.WriteLine ("{0}// Metadata.xml XPath method reference: path=\"{1}\"", indent, method.GetMetadataXPathReference (method.DeclaringType)); + writer.WriteLine ("{0}[Register (\"{1}\", \"{2}\", \"{3}\"{4})]", indent, method.JavaName, method.JniSignature, method.ConnectorName, method.AdditionalAttributeString ()); + WriteMethodCustomAttributes (method, writer, indent); + writer.WriteLine ("{0}{1} abstract {2} {3} ({4});", indent, method.Visibility, opt.GetOutputName (method.RetVal.FullName), name, GenBase.GetSignature (method, opt)); + writer.WriteLine (); + + if (gen_as_formatted || method.Parameters.HasCharSequence) + WriteMethodStringOverload (method, writer, indent, opt); + } + + WriteMethodAsyncWrapper (method, writer, indent, opt); + } + + public void WriteMethodDeclaration (Method method, TextWriter writer, string indent, CodeGenerationOptions opt, GenBase type, string adapter) + { + if (method.DeclaringType.IsGeneratable) + writer.WriteLine ("{0}// Metadata.xml XPath method reference: path=\"{1}\"", indent, method.GetMetadataXPathReference (method.DeclaringType)); + if (method.Deprecated != null) + writer.WriteLine ("[Obsolete (@\"{0}\")]", method.Deprecated.Replace ("\"", "\"\"")); + if (method.IsReturnEnumified) + writer.WriteLine ("{0}[return:global::Android.Runtime.GeneratedEnum]", indent); + if (method.IsInterfaceDefaultMethod) + writer.WriteLine ("{0}[global::Java.Interop.JavaInterfaceDefaultMethod]", indent); + writer.WriteLine ("{0}[Register (\"{1}\", \"{2}\", \"{3}:{4}\"{5})]", indent, method.JavaName, method.JniSignature, method.ConnectorName, method.GetAdapterName (opt, adapter), method.AdditionalAttributeString ()); + WriteMethodCustomAttributes (method, writer, indent); + writer.WriteLine ("{0}{1} {2} ({3});", indent, opt.GetOutputName (method.RetVal.FullName), method.AdjustedName, GenBase.GetSignature (method, opt)); + writer.WriteLine (); + } + + public void WriteMethodEventDelegate (Method method, TextWriter writer, string indent, CodeGenerationOptions opt) + { + writer.WriteLine ("{0}public delegate {1} {2}EventHandler ({3});", indent, opt.GetOutputName (method.RetVal.FullName), method.Name, GenBase.GetSignature (method, opt)); + writer.WriteLine (); + } + + // This is supposed to generate instantiated generic method output, but I don't think it is done yet. + public void WriteMethodExplicitIface (Method method, TextWriter writer, string indent, CodeGenerationOptions opt, GenericSymbol gen) + { + writer.WriteLine ("{0}// This method is explicitly implemented as a member of an instantiated {1}", indent, gen.FullName); + WriteMethodCustomAttributes (method, writer, indent); + writer.WriteLine ("{0}{1} {2}.{3} ({4})", indent, opt.GetOutputName (method.RetVal.FullName), opt.GetOutputName (gen.Gen.FullName), method.Name, GenBase.GetSignature (method, opt)); + writer.WriteLine ("{0}{{", indent); + Dictionary mappings = new Dictionary (); + for (int i = 0; i < gen.TypeParams.Length; i++) + mappings [gen.Gen.TypeParameters [i].Name] = gen.TypeParams [i].FullName; + WriteMethodGenericBody (method, writer, indent + "\t", opt, null, String.Empty, mappings); + writer.WriteLine ("{0}}}", indent); + writer.WriteLine (); + } + + void WriteMethodGenericBody (Method method, TextWriter writer, string indent, CodeGenerationOptions opt, string property_name, string container_prefix, Dictionary mappings) + { + if (String.IsNullOrEmpty (property_name)) { + string call = container_prefix + method.Name + " (" + method.Parameters.GetGenericCall (opt, mappings) + ")"; + writer.WriteLine ("{0}{1}{2};", indent, method.IsVoid ? String.Empty : "return ", method.RetVal.GetGenericReturn (opt, call, mappings)); + } else { + if (method.IsVoid) // setter + writer.WriteLine ("{0}{1} = {2};", indent, container_prefix + property_name, method.Parameters.GetGenericCall (opt, mappings)); + else // getter + writer.WriteLine ("{0}return {1};", indent, method.RetVal.GetGenericReturn (opt, container_prefix + property_name, mappings)); + } + } + + public void WriteMethodIdField (Method method, TextWriter writer, string indent, CodeGenerationOptions opt, bool invoker = false) + { + if (invoker) { + writer.WriteLine ("{0}IntPtr {1};", indent, method.EscapedIdName); + return; + } + WriteMethodIdField (method, writer, indent, opt); + } + + public void WriteMethodInvoker (Method method, TextWriter writer, string indent, CodeGenerationOptions opt, GenBase type) + { + WriteMethodCallback (method, writer, indent, opt, type, null, method.IsReturnCharSequence); + WriteMethodIdField (method, writer, indent, opt, invoker: true); + writer.WriteLine ("{0}public unsafe {1}{2} {3} ({4})", + indent, method.IsStatic ? "static " : string.Empty, opt.GetOutputName (method.RetVal.FullName), method.AdjustedName, GenBase.GetSignature (method, opt)); + writer.WriteLine ("{0}{{", indent); + WriteMethodInvokerBody (method, writer, indent + "\t", opt); + writer.WriteLine ("{0}}}", indent); + writer.WriteLine (); + } + + public void WriteMethodInvokerBody (Method method, TextWriter writer, string indent, CodeGenerationOptions opt) + { + writer.WriteLine ("{0}if ({1} == IntPtr.Zero)", indent, method.EscapedIdName); + writer.WriteLine ("{0}\t{1} = JNIEnv.GetMethodID (class_ref, \"{2}\", \"{3}\");", indent, method.EscapedIdName, method.JavaName, method.JniSignature); + foreach (string prep in method.Parameters.GetCallPrep (opt)) + writer.WriteLine ("{0}{1}", indent, prep); + method.Parameters.WriteCallArgs (writer, indent, opt, invoker: true); + string env_method = "Call" + method.RetVal.CallMethodPrefix + "Method"; + string call = "JNIEnv." + env_method + " (" + + opt.ContextType.GetObjectHandleProperty ("this") + ", " + method.EscapedIdName + method.Parameters.GetCallArgs (opt, invoker: true) + ")"; + if (method.IsVoid) + writer.WriteLine ("{0}{1};", indent, call); + else + writer.WriteLine ("{0}{1}{2};", indent, method.Parameters.HasCleanup ? opt.GetOutputName (method.RetVal.FullName) + " __ret = " : "return ", method.RetVal.FromNative (opt, call, true)); + + foreach (string cleanup in method.Parameters.GetCallCleanup (opt)) + writer.WriteLine ("{0}{1}", indent, cleanup); + + if (!method.IsVoid && method.Parameters.HasCleanup) + writer.WriteLine ("{0}return __ret;", indent); + } + + void WriteMethodStringOverloadBody (Method method, TextWriter writer, string indent, CodeGenerationOptions opt, bool haveSelf) + { + var call = new System.Text.StringBuilder (); + foreach (Parameter p in method.Parameters) { + string pname = p.Name; + if (p.Type == "Java.Lang.ICharSequence") { + pname = p.GetName ("jls_"); + writer.WriteLine ("{0}global::Java.Lang.String {1} = {2} == null ? null : new global::Java.Lang.String ({2});", indent, pname, p.Name); + } else if (p.Type == "Java.Lang.ICharSequence[]" || p.Type == "params Java.Lang.ICharSequence[]") { + pname = p.GetName ("jlca_"); + writer.WriteLine ("{0}global::Java.Lang.ICharSequence[] {1} = CharSequence.ArrayFromStringArray({2});", indent, pname, p.Name); + } + if (call.Length > 0) + call.Append (", "); + call.Append (pname); + } + writer.WriteLine ("{0}{1}{2}{3} ({4});", indent, method.RetVal.IsVoid ? String.Empty : opt.GetOutputName (method.RetVal.FullName) + " __result = ", haveSelf ? "self." : "", method.AdjustedName, call.ToString ()); + switch (method.RetVal.FullName) { + case "void": + break; + case "Java.Lang.ICharSequence[]": + writer.WriteLine ("{0}var __rsval = CharSequence.ArrayToStringArray (__result);", indent); + break; + case "Java.Lang.ICharSequence": + writer.WriteLine ("{0}var __rsval = __result?.ToString ();", indent); + break; + default: + writer.WriteLine ("{0}var __rsval = __result;", indent); + break; + } + foreach (Parameter p in method.Parameters) { + if (p.Type == "Java.Lang.ICharSequence") + writer.WriteLine ("{0}{1}?.Dispose ();", indent, p.GetName ("jls_")); + else if (p.Type == "Java.Lang.ICharSequence[]") + writer.WriteLine ("{0}if ({1} != null) foreach (global::Java.Lang.String s in {1}) s?.Dispose ();", indent, p.GetName ("jlca_")); + } + if (!method.RetVal.IsVoid) { + writer.WriteLine ($"{indent}return __rsval;"); + } + } + + void WriteMethodStringOverload (Method method, TextWriter writer, string indent, CodeGenerationOptions opt) + { + string static_arg = method.IsStatic ? " static" : String.Empty; + string ret = opt.GetOutputName (method.RetVal.FullName.Replace ("Java.Lang.ICharSequence", "string")); + if (method.Deprecated != null) + writer.WriteLine ("{0}[Obsolete (@\"{1}\")]", indent, method.Deprecated.Replace ("\"", "\"\"").Trim ()); + writer.WriteLine ("{0}{1}{2} {3} {4} ({5})", indent, method.Visibility, static_arg, ret, method.Name, GenBase.GetSignature (method, opt).Replace ("Java.Lang.ICharSequence", "string").Replace ("global::string", "string")); + writer.WriteLine ("{0}{{", indent); + WriteMethodStringOverloadBody (method, writer, indent + "\t", opt, false); + writer.WriteLine ("{0}}}", indent); + writer.WriteLine (); + } + + public void WriteMethodExtensionOverload (Method method, TextWriter writer, string indent, CodeGenerationOptions opt, string selfType) + { + if (!method.CanHaveStringOverload) + return; + + 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, + GenBase.GetSignature (method, opt).Replace ("Java.Lang.ICharSequence", "string").Replace ("global::string", "string")); + writer.WriteLine ("{0}{{", indent); + WriteMethodStringOverloadBody (method, writer, indent + "\t", opt, true); + writer.WriteLine ("{0}}}", indent); + } + + public void WriteMethodAsyncWrapper (Method method, TextWriter writer, string indent, CodeGenerationOptions opt) + { + if (!method.Asyncify) + return; + + string static_arg = method.IsStatic ? " static" : String.Empty; + string ret; + + if (method.IsVoid) + ret = "global::System.Threading.Tasks.Task"; + else + ret = "global::System.Threading.Tasks.Task<" + opt.GetOutputName (method.RetVal.FullName) + ">"; + + writer.WriteLine ("{0}{1}{2} {3} {4}Async ({5})", indent, method.Visibility, static_arg, ret, method.AdjustedName, GenBase.GetSignature (method, opt)); + writer.WriteLine ("{0}{{", indent); + writer.WriteLine ("{0}\treturn global::System.Threading.Tasks.Task.Run (() => {1} ({2}));", indent, method.AdjustedName, method.Parameters.GetCall (opt)); + writer.WriteLine ("{0}}}", indent); + writer.WriteLine (); + } + + public void WriteMethodExtensionAsyncWrapper (Method method, TextWriter writer, string indent, CodeGenerationOptions opt, string selfType) + { + if (!method.Asyncify) + return; + + string ret; + + if (method.IsVoid) + ret = "global::System.Threading.Tasks.Task"; + else + ret = "global::System.Threading.Tasks.Task<" + opt.GetOutputName (method.RetVal.FullName) + ">"; + + writer.WriteLine ("{0}public static {1} {2}Async (this {3} self{4}{5})", indent, ret, method.AdjustedName, selfType, method.Parameters.Count > 0 ? ", " : string.Empty, GenBase.GetSignature (method, opt)); + writer.WriteLine ("{0}{{", indent); + writer.WriteLine ("{0}\treturn global::System.Threading.Tasks.Task.Run (() => self.{1} ({2}));", indent, method.AdjustedName, method.Parameters.GetCall (opt)); + writer.WriteLine ("{0}}}", indent); + writer.WriteLine (); + } + + public void WriteMethod (Method method, TextWriter writer, string indent, CodeGenerationOptions opt, GenBase type, bool generate_callbacks) + { + if (!method.IsValid) + return; + + bool gen_as_formatted = method.IsReturnCharSequence; + if (generate_callbacks && method.IsVirtual) + WriteMethodCallback (method, writer, indent, opt, type, null, gen_as_formatted); + + string name_and_jnisig = method.JavaName + method.JniSignature.Replace ("java/lang/CharSequence", "java/lang/String"); + 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; + 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, writer, indent, opt); + if (method.DeclaringType.IsGeneratable) + writer.WriteLine ("{0}// Metadata.xml XPath method reference: path=\"{1}\"", indent, method.GetMetadataXPathReference (method.DeclaringType)); + if (method.Deprecated != null) + writer.WriteLine ("{0}[Obsolete (@\"{1}\")]", indent, method.Deprecated.Replace ("\"", "\"\"")); + 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 ()); + WriteMethodCustomAttributes (method, writer, indent); + writer.WriteLine ("{0}{1}{2}{3}{4} unsafe {5} {6} ({7})", indent, method.Visibility, static_arg, virt_ov, seal, ret, method.AdjustedName, GenBase.GetSignature (method, opt)); + writer.WriteLine ("{0}{{", indent); + WriteMethodBody (method, writer, indent + "\t", opt); + writer.WriteLine ("{0}}}", indent); + writer.WriteLine (); + + //NOTE: Invokers are the only place false is passed for generate_callbacks, they do not need string overloads + if (generate_callbacks && (gen_string_overload || gen_as_formatted)) + WriteMethodStringOverload (method, writer, indent, opt); + + WriteMethodAsyncWrapper (method, writer, indent, opt); + } } } diff --git a/tools/generator/Ctor.cs b/tools/generator/Ctor.cs index 54d35ee02..6e40e5c5f 100644 --- a/tools/generator/Ctor.cs +++ b/tools/generator/Ctor.cs @@ -15,14 +15,10 @@ public class ManagedCtor : Ctor { bool is_acw; public ManagedCtor (GenBase declaringType, MethodDefinition m) - : this (declaringType, m, new ManagedMethodBaseSupport (m)) - { - } - - ManagedCtor (GenBase declaringType, MethodDefinition m, ManagedMethodBaseSupport support) - : base (declaringType, support) + : base (declaringType) { this.m = m; + GenericArguments = m.GenericArguments (); name = m.Name; // If 'elem' is a constructor for a non-static nested type, then // the type of the containing class must be inserted as the first @@ -31,7 +27,7 @@ public ManagedCtor (GenBase declaringType, MethodDefinition m) Parameters.AddFirst (Parameter.FromManagedType (m.DeclaringType.DeclaringType, DeclaringType.JavaName)); var regatt = m.CustomAttributes.FirstOrDefault (a => a.AttributeType.FullName == "Android.Runtime.RegisterAttribute"); is_acw = regatt != null; - foreach (var p in support.GetParameters (regatt)) + foreach (var p in m.GetParameters (regatt)) Parameters.Add (p); } @@ -52,17 +48,26 @@ public override string Name { public override string CustomAttributes { get { return null; } } + + public override string AssemblyName => m.DeclaringType.Module.Assembly.FullName; + + public override string Visibility => m.Visibility (); + + public override string Deprecated => m.Deprecated (); } #endif // HAVE_CECIL public class XmlCtor : Ctor { + XElement elem; string name; bool nonStaticNestedType; bool missing_enclosing_class; string custom_attributes; - public XmlCtor (GenBase declaringType, XElement elem) : base (declaringType, new XmlMethodBaseSupport (elem)) + public XmlCtor (GenBase declaringType, XElement elem) : base (declaringType) { + this.elem = elem; + GenericArguments = elem.GenericArguments (); name = elem.XGetAttribute ("name"); int idx = name.LastIndexOf ('.'); if (idx > 0) @@ -125,12 +130,16 @@ protected override bool OnValidate (CodeGenerationOptions opt, GenericParameterD public override string CustomAttributes { get { return custom_attributes; } } + + public override string Deprecated => elem.Deprecated (); + + public override string Visibility => elem.Visibility (); } public abstract class Ctor : MethodBase { - protected Ctor (GenBase declaringType, IMethodBaseSupport support) - : base (declaringType, support) + protected Ctor (GenBase declaringType) + : base (declaringType) { } diff --git a/tools/generator/InterfaceGen.cs b/tools/generator/InterfaceGen.cs index 23227bd9d..e35e69b6d 100644 --- a/tools/generator/InterfaceGen.cs +++ b/tools/generator/InterfaceGen.cs @@ -200,15 +200,15 @@ void GenMethods (StreamWriter sw, string indent, CodeGenerationOptions opt) foreach (Method m in Methods.Where (m => !m.IsStatic)) { if (m.Name == Name || ContainsProperty (m.Name, true)) m.Name = "Invoke" + m.Name; - m.GenerateDeclaration (sw, indent, opt, this, AssemblyQualifiedName + "Invoker"); + opt.CodeGenerator.WriteMethodDeclaration (m, sw, indent, opt, this, AssemblyQualifiedName + "Invoker"); } } void GenExtensionMethods (StreamWriter sw, string indent, CodeGenerationOptions opt) { foreach (Method m in Methods.Where (m => !m.IsStatic)) { - m.GenerateExtensionOverload (sw, indent, opt, FullName); - m.GenerateExtensionAsyncWrapper (sw, indent, opt, FullName); + opt.CodeGenerator.WriteMethodExtensionOverload (m, sw, indent, opt, FullName); + opt.CodeGenerator.WriteMethodExtensionAsyncWrapper (m, sw, indent, opt, FullName); } } @@ -288,7 +288,7 @@ void GenerateInvoker (StreamWriter sw, IEnumerable methods, string inden if (members.Contains (sig)) continue; members.Add (sig); - m.GenerateInvoker (sw, indent, opt, this); + opt.CodeGenerator.WriteMethodInvoker (m, sw, indent, opt, this); } } @@ -632,9 +632,9 @@ public void GenerateAbstractMembers (ClassGen gen, StreamWriter sw, string inden if (mapped) continue; if (gen.ExplicitlyImplementedInterfaceMethods.Contains (sig)) - m.GenerateExplicitInterfaceImplementation (sw, indent, opt, this); + opt.CodeGenerator.WriteMethodExplicitInterfaceImplementation (m, sw, indent, opt, this); else - m.GenerateAbstractDeclaration (sw, indent, opt, this, gen); + opt.CodeGenerator.WriteMethodAbstractDeclaration (m, sw, indent, opt, this, gen); opt.ContextGeneratedMethods.Add (m); } foreach (Property prop in Properties.Where (p => !p.Getter.IsStatic)) { @@ -714,7 +714,7 @@ public override void Generate (StreamWriter sw, string indent, CodeGenerationOpt } foreach (var m in Methods.Where (m => m.IsStatic)) - m.Generate (sw, indent + "\t", opt, this, true); + opt.CodeGenerator.WriteMethod (m, sw, indent + "\t", opt, this, true); if (needsClassRef) { sw.WriteLine (); diff --git a/tools/generator/ManagedExtensions.cs b/tools/generator/ManagedExtensions.cs new file mode 100644 index 000000000..ac7e3e380 --- /dev/null +++ b/tools/generator/ManagedExtensions.cs @@ -0,0 +1,50 @@ +using Java.Interop.Tools.TypeNameMappings; +using Mono.Cecil; +using System.Collections.Generic; +using System.Linq; + +namespace MonoDroid.Generation +{ +#if HAVE_CECIL + internal static class ManagedExtensions + { + public static string FullNameCorrected (this TypeReference t) => t.FullName.Replace ('/', '.'); + + public static GenericParameterDefinitionList GenericArguments (this MethodDefinition m) => + m.HasGenericParameters ? GenericParameterDefinitionList.FromMetadata (m.GenericParameters) : null; + + public static string Deprecated (this MethodDefinition m) + { + var v = m.CustomAttributes.FirstOrDefault (a => a.AttributeType.FullName == "System.ObsoleteAttribute"); + return v != null ? (string)v.ConstructorArguments [0].Value ?? "deprecated" : null; + } + + public static string Visibility (this MethodDefinition m) => + m.IsPublic ? "public" : m.IsFamilyOrAssembly ? "protected internal" : m.IsFamily ? "protected" : m.IsAssembly ? "internal" : "private"; + + public static IEnumerable GetParameters (this MethodDefinition m, CustomAttribute regatt) + { + var jnisig = (string)(regatt.ConstructorArguments.Count > 1 ? regatt.ConstructorArguments [1].Value : regatt.Properties.First (p => p.Name == "JniSignature").Argument.Value); + var types = jnisig == null ? null : JavaNativeTypeManager.FromSignature (jnisig); + var e = types?.GetEnumerator (); + + foreach (var p in m.Parameters) { + if (e != null && !e.MoveNext ()) + e = null; + // Here we do some tricky thing: + // Both java.io.InputStream and java.io.OutputStream could be mapped to + // System.IO.Stream. And when there is Stream in parameters, we have to + // determine which direction of the Stream it was - in or out. + // To do that, we inspect JNI Signature to handle that. + // + // We could *always* use this JNI information, *IF* there were no + // int->enum conversion. Sadly this is not true, we still have to expect + // custom enum types and cannot simply use JNI signature here. + var rawtype = e?.Current.Type; + var type = p.ParameterType.FullName == "System.IO.Stream" && e != null ? e.Current.Type : null; + yield return Parameter.FromManagedParameter (p, type, rawtype); + } + } + } +#endif +} diff --git a/tools/generator/Method.cs b/tools/generator/Method.cs index 3469140ec..d0d9940e2 100644 --- a/tools/generator/Method.cs +++ b/tools/generator/Method.cs @@ -1,17 +1,14 @@ +using Java.Interop.Tools.TypeNameMappings; +using Mono.Cecil; +using MonoDroid.Utils; using System; -using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Xml; using System.Text.RegularExpressions; -using Mono.Cecil; - -using Xamarin.Android.Tools; - -using MonoDroid.Utils; using System.Xml.Linq; +using Xamarin.Android.Tools; -namespace MonoDroid.Generation { +namespace MonoDroid.Generation +{ #if HAVE_CECIL public class ManagedMethod : Method { MethodDefinition m; @@ -21,31 +18,34 @@ public class ManagedMethod : Method { bool is_interface_default_method; public ManagedMethod (GenBase declaringType, MethodDefinition m) - : this (declaringType, m, new ManagedMethodBaseSupport (m)) - { - } - - ManagedMethod (GenBase declaringType, MethodDefinition m, ManagedMethodBaseSupport support) - : base (declaringType, support) + : base (declaringType) { this.m = m; + GenericArguments = m.GenericArguments (); var regatt = m.CustomAttributes.FirstOrDefault (a => a.AttributeType.FullName == "Android.Runtime.RegisterAttribute"); is_acw = regatt != null; is_interface_default_method = m.CustomAttributes .Any (ca => ca.AttributeType.FullName == "Java.Interop.JavaInterfaceDefaultMethodAttribute"); java_name = regatt != null ? ((string) regatt.ConstructorArguments [0].Value) : m.Name; - foreach (var p in support.GetParameters (regatt)) + foreach (var p in m.GetParameters (regatt)) Parameters.Add (p); if (regatt != null) { - var rt = support.GetJniReturnType (regatt); + var jnisig = (string)(regatt.ConstructorArguments.Count > 1 ? regatt.ConstructorArguments [1].Value : regatt.Properties.First (p => p.Name == "JniSignature").Argument.Value); + var rt = JavaNativeTypeManager.ReturnTypeFromSignature (jnisig); if (rt != null) java_return = rt.Type; } FillReturnType (); } + public override string AssemblyName => m.DeclaringType.Module.Assembly.FullName; + + public override string Deprecated => m.Deprecated (); + + public override string Visibility => m.Visibility (); + public override bool IsAcw { get { return is_acw; } } @@ -128,9 +128,10 @@ public class XmlMethod : Method { XElement elem; public XmlMethod (GenBase declaringType, XElement elem) - : base (declaringType, new XmlMethodBaseSupport (elem)) + : base (declaringType) { this.elem = elem; + GenericArguments = elem.GenericArguments (); is_static = elem.XGetAttribute ("static") == "true"; is_virtual = !is_static && elem.XGetAttribute ("final") == "false"; if (elem.Attribute ("managedName") != null) @@ -152,6 +153,9 @@ public XmlMethod (GenBase declaringType, XElement elem) } // core XML-based properties + public override string Deprecated => elem.Deprecated (); + + public override string Visibility => elem.Visibility (); public override string ArgsType { get { @@ -255,8 +259,8 @@ public override string CustomAttributes { public abstract class Method : MethodBase { - protected Method (GenBase declaringType, IMethodBaseSupport support) - : base (declaringType, support) + protected Method (GenBase declaringType) + : base (declaringType) { } @@ -427,7 +431,7 @@ public string ConnectorName { } string escaped_cb_name; - string EscapedCallbackName { + internal string EscapedCallbackName { get { if (escaped_cb_name == null) escaped_cb_name = "cb_" + JavaName + IDSignature; @@ -445,7 +449,7 @@ internal string EscapedIdName { } string delegate_type; - string GetDelegateType () + internal string GetDelegateType () { if (delegate_type == null) { string parms = Parameters.DelegateTypeParams; @@ -462,368 +466,15 @@ internal string AdjustedName { get { return IsReturnCharSequence ? Name + "Formatted" : Name; } } - public void GenerateCallback (StreamWriter sw, string indent, CodeGenerationOptions opt, GenBase type, string property_name) - { - GenerateCallback (sw, indent, opt, type, property_name, false); - } - - #region "if you're changing this part, also change method in CallbackCode.cs" - void GenerateCallback (StreamWriter sw, string indent, CodeGenerationOptions opt, GenBase type, string property_name, bool as_formatted) - { - string delegate_type = GetDelegateType (); - sw.WriteLine ("{0}static Delegate {1};", indent, EscapedCallbackName); - sw.WriteLine ("#pragma warning disable 0169"); - sw.WriteLine ("{0}static Delegate {1} ()", indent, ConnectorName); - sw.WriteLine ("{0}{{", indent); - sw.WriteLine ("{0}\tif ({1} == null)", indent, EscapedCallbackName); - sw.WriteLine ("{0}\t\t{1} = JNINativeWrapper.CreateDelegate (({2}) n_{3});", indent, EscapedCallbackName, delegate_type, Name + IDSignature); - sw.WriteLine ("{0}\treturn {1};", indent, EscapedCallbackName); - sw.WriteLine ("{0}}}", indent); - sw.WriteLine (); - sw.WriteLine ("{0}static {1} n_{2} (IntPtr jnienv, IntPtr native__this{3})", indent, RetVal.NativeType, Name + IDSignature, Parameters.GetCallbackSignature (opt)); - sw.WriteLine ("{0}{{", indent); - sw.WriteLine ("{0}\t{1} __this = global::Java.Lang.Object.GetObject<{1}> (jnienv, native__this, JniHandleOwnership.DoNotTransfer);", indent, opt.GetOutputName (type.FullName)); - foreach (string s in Parameters.GetCallbackPrep (opt)) - sw.WriteLine ("{0}\t{1}", indent, s); - if (String.IsNullOrEmpty (property_name)) { - string call = "__this." + Name + (as_formatted ? "Formatted" : String.Empty) + " (" + Parameters.GetCall (opt) + ")"; - if (IsVoid) - sw.WriteLine ("{0}\t{1};", indent, call); - else - sw.WriteLine ("{0}\t{1} {2};", indent, Parameters.HasCleanup ? RetVal.NativeType + " __ret =" : "return", RetVal.ToNative (opt, call)); - } else { - if (IsVoid) - sw.WriteLine ("{0}\t__this.{1} = {2};", indent, property_name, Parameters.GetCall (opt)); - else - sw.WriteLine ("{0}\t{1} {2};", indent, Parameters.HasCleanup ? RetVal.NativeType + " __ret =" : "return", RetVal.ToNative (opt, "__this." + property_name)); - } - foreach (string cleanup in Parameters.GetCallbackCleanup (opt)) - sw.WriteLine ("{0}\t{1}", indent, cleanup); - if (!IsVoid && Parameters.HasCleanup) - sw.WriteLine ("{0}\treturn __ret;", indent); - sw.WriteLine ("{0}}}", indent); - sw.WriteLine ("#pragma warning restore 0169"); - sw.WriteLine (); - } - #endregion - - public void GenerateCustomAttributes (StreamWriter sw, string indent) - { - if (this.GenericArguments != null && this.GenericArguments.Any ()) - sw.WriteLine ("{0}{1}", indent, GenericArguments.ToGeneratedAttributeString ()); - if (CustomAttributes != null) - sw.WriteLine ("{0}{1}", indent, CustomAttributes); - if (Annotation != null) - sw.WriteLine ("{0}{1}", indent, Annotation); - } - - public void GenerateBody (StreamWriter sw, string indent, CodeGenerationOptions opt) - { - opt.CodeGenerator.WriteMethodBody (this, sw, indent, opt); - } - - public void GenerateExplicitInterfaceImplementation (StreamWriter sw, string indent, CodeGenerationOptions opt, GenBase iface) - { -// sw.WriteLine ("// explicitly implemented method from " + iface.FullName); - GenerateCustomAttributes (sw, indent); - sw.WriteLine ("{0}{1} {2}.{3} ({4})", indent, opt.GetOutputName (RetVal.FullName), opt.GetOutputName (iface.FullName), Name, GenBase.GetSignature (this, opt)); - sw.WriteLine ("{0}{{", indent); - sw.WriteLine ("{0}\treturn {1} ({2});", indent, Name, Parameters.GetCall (opt)); - sw.WriteLine ("{0}}}", indent); - sw.WriteLine (); - } - - public void GenerateExplicitInterfaceInvoker (StreamWriter sw, string indent, CodeGenerationOptions opt, GenBase iface) - { - //sw.WriteLine ("\t\t// explicitly implemented invoker method from " + iface.FullName); - GenerateIdField (sw, indent, opt); - sw.WriteLine ("{0}unsafe {1} {2}.{3} ({4})", - indent, opt.GetOutputName (RetVal.FullName), opt.GetOutputName (iface.FullName), Name, GenBase.GetSignature (this, opt)); - sw.WriteLine ("{0}{{", indent); - GenerateBody (sw, indent + "\t", opt); - sw.WriteLine ("{0}}}", indent); - sw.WriteLine (); - } - - public void GenerateAbstractDeclaration (StreamWriter sw, string indent, CodeGenerationOptions opt, InterfaceGen gen, GenBase impl) - { - if (RetVal.IsGeneric && gen != null) { - GenerateCustomAttributes (sw, indent); - sw.WriteLine ("{0}{1} {2}.{3} ({4})", indent, opt.GetOutputName (RetVal.FullName), opt.GetOutputName (gen.FullName), Name, GenBase.GetSignature (this, opt)); - sw.WriteLine ("{0}{{", indent); - sw.WriteLine ("{0}\tthrow new NotImplementedException ();", indent); - sw.WriteLine ("{0}}}", indent); - sw.WriteLine (); - } else { - bool gen_as_formatted = IsReturnCharSequence; - string name = AdjustedName; - GenerateCallback (sw, indent, opt, impl, null, gen_as_formatted); - if (DeclaringType.IsGeneratable) - sw.WriteLine ("{0}// Metadata.xml XPath method reference: path=\"{1}\"", indent, GetMetadataXPathReference (this.DeclaringType)); - sw.WriteLine ("{0}[Register (\"{1}\", \"{2}\", \"{3}\"{4})]", indent, JavaName, JniSignature, ConnectorName, this.AdditionalAttributeString ()); - GenerateCustomAttributes (sw, indent); - sw.WriteLine ("{0}{1} abstract {2} {3} ({4});", indent, Visibility, opt.GetOutputName (RetVal.FullName), name, GenBase.GetSignature (this, opt)); - sw.WriteLine (); - - if (gen_as_formatted || Parameters.HasCharSequence) - GenerateStringOverload (sw, indent, opt); - } - - GenerateAsyncWrapper (sw, indent, opt); - } - - public void GenerateDeclaration (StreamWriter sw, string indent, CodeGenerationOptions opt, GenBase type, string adapter) - { - if (DeclaringType.IsGeneratable) - sw.WriteLine ("{0}// Metadata.xml XPath method reference: path=\"{1}\"", indent, GetMetadataXPathReference (this.DeclaringType)); - if (Deprecated != null) - sw.WriteLine ("[Obsolete (@\"{0}\")]", Deprecated.Replace ("\"", "\"\"")); - if (IsReturnEnumified) - sw.WriteLine ("{0}[return:global::Android.Runtime.GeneratedEnum]", indent); - if (IsInterfaceDefaultMethod) - sw.WriteLine ("{0}[global::Java.Interop.JavaInterfaceDefaultMethod]", indent); - sw.WriteLine ("{0}[Register (\"{1}\", \"{2}\", \"{3}:{4}\"{5})]", indent, JavaName, JniSignature, ConnectorName, GetAdapterName (opt, adapter), this.AdditionalAttributeString ()); - GenerateCustomAttributes (sw, indent); - sw.WriteLine ("{0}{1} {2} ({3});", indent, opt.GetOutputName (RetVal.FullName), AdjustedName, GenBase.GetSignature (this, opt)); - sw.WriteLine (); - } - - public void GenerateEventDelegate (StreamWriter sw, string indent, CodeGenerationOptions opt) - { - sw.WriteLine ("{0}public delegate {1} {2}EventHandler ({3});", indent, opt.GetOutputName (RetVal.FullName), Name, GenBase.GetSignature (this, opt)); - sw.WriteLine (); - } - - // This is supposed to generate instantiated generic method output, but I don't think it is done yet. - public void GenerateExplicitIface (StreamWriter sw, string indent, CodeGenerationOptions opt, GenericSymbol gen) - { - sw.WriteLine ("{0}// This method is explicitly implemented as a member of an instantiated {1}", indent, gen.FullName); - GenerateCustomAttributes (sw, indent); - sw.WriteLine ("{0}{1} {2}.{3} ({4})", indent, opt.GetOutputName (RetVal.FullName), opt.GetOutputName (gen.Gen.FullName), Name, GenBase.GetSignature (this, opt)); - sw.WriteLine ("{0}{{", indent); - Dictionary mappings = new Dictionary (); - for (int i = 0; i < gen.TypeParams.Length; i++) - mappings [gen.Gen.TypeParameters[i].Name] = gen.TypeParams [i].FullName; - GenerateGenericBody (sw, indent + "\t", opt, null, String.Empty, mappings); - sw.WriteLine ("{0}}}", indent); - sw.WriteLine (); - } - - void GenerateGenericBody (StreamWriter sw, string indent, CodeGenerationOptions opt, string property_name, string container_prefix, Dictionary mappings) - { - if (String.IsNullOrEmpty (property_name)) { - string call = container_prefix + Name + " (" + Parameters.GetGenericCall (opt, mappings) + ")"; - sw.WriteLine ("{0}{1}{2};", indent, IsVoid ? String.Empty : "return ", RetVal.GetGenericReturn (opt, call, mappings)); - } else { - if (IsVoid) // setter - sw.WriteLine ("{0}{1} = {2};", indent, container_prefix + property_name, Parameters.GetGenericCall (opt, mappings)); - else // getter - sw.WriteLine ("{0}return {1};", indent, RetVal.GetGenericReturn (opt, container_prefix + property_name, mappings)); - } - } - - public void GenerateIdField (StreamWriter sw, string indent, CodeGenerationOptions opt, bool invoker = false) - { - if (invoker) { - sw.WriteLine ("{0}IntPtr {1};", indent, EscapedIdName); - return; - } - opt.CodeGenerator.WriteMethodIdField (this, sw, indent, opt); - } - - public void GenerateInvoker (StreamWriter sw, string indent, CodeGenerationOptions opt, GenBase type) - { - GenerateCallback (sw, indent, opt, type, null, IsReturnCharSequence); - GenerateIdField (sw, indent, opt, invoker:true); - sw.WriteLine ("{0}public unsafe {1}{2} {3} ({4})", - indent, IsStatic ? "static " : string.Empty, opt.GetOutputName (RetVal.FullName), AdjustedName, GenBase.GetSignature (this, opt)); - sw.WriteLine ("{0}{{", indent); - GenerateInvokerBody (sw, indent + "\t", opt); - sw.WriteLine ("{0}}}", indent); - sw.WriteLine (); - } - - public void GenerateInvokerBody (StreamWriter sw, string indent, CodeGenerationOptions opt) - { - sw.WriteLine ("{0}if ({1} == IntPtr.Zero)", indent, EscapedIdName); - sw.WriteLine ("{0}\t{1} = JNIEnv.GetMethodID (class_ref, \"{2}\", \"{3}\");", indent, EscapedIdName, JavaName, JniSignature); - foreach (string prep in Parameters.GetCallPrep (opt)) - sw.WriteLine ("{0}{1}", indent, prep); - Parameters.WriteCallArgs (sw, indent, opt, invoker:true); - string env_method = "Call" + RetVal.CallMethodPrefix + "Method"; - string call = "JNIEnv." + env_method + " (" + - opt.ContextType.GetObjectHandleProperty ("this") + ", " + EscapedIdName + Parameters.GetCallArgs (opt, invoker:true) + ")"; - if (IsVoid) - sw.WriteLine ("{0}{1};", indent, call); - else - sw.WriteLine ("{0}{1}{2};", indent, Parameters.HasCleanup ? opt.GetOutputName (RetVal.FullName) + " __ret = " : "return ", RetVal.FromNative (opt, call, true)); - - foreach (string cleanup in Parameters.GetCallCleanup (opt)) - sw.WriteLine ("{0}{1}", indent, cleanup); - - if (!IsVoid && Parameters.HasCleanup) - sw.WriteLine ("{0}return __ret;", indent); - } - public string GetSignature () { return String.Format ("n_{0}:{1}:{2}", JavaName, JniSignature, ConnectorName); } - void GenerateStringOverloadBody (StreamWriter sw, string indent, CodeGenerationOptions opt, bool haveSelf) - { - var call = new System.Text.StringBuilder (); - foreach (Parameter p in Parameters) { - string pname = p.Name; - if (p.Type == "Java.Lang.ICharSequence") { - pname = p.GetName ("jls_"); - sw.WriteLine ("{0}global::Java.Lang.String {1} = {2} == null ? null : new global::Java.Lang.String ({2});", indent, pname, p.Name); - } else if (p.Type == "Java.Lang.ICharSequence[]" || p.Type == "params Java.Lang.ICharSequence[]") { - pname = p.GetName ("jlca_"); - sw.WriteLine ("{0}global::Java.Lang.ICharSequence[] {1} = CharSequence.ArrayFromStringArray({2});", indent, pname, p.Name); - } - if (call.Length > 0) - call.Append (", "); - call.Append (pname); - } - sw.WriteLine ("{0}{1}{2}{3} ({4});", indent, RetVal.IsVoid ? String.Empty : opt.GetOutputName (RetVal.FullName) + " __result = ", haveSelf ? "self." : "", AdjustedName, call.ToString ()); - switch (RetVal.FullName) { - case "void": - break; - case "Java.Lang.ICharSequence[]": - sw.WriteLine ("{0}var __rsval = CharSequence.ArrayToStringArray (__result);", indent); - break; - case "Java.Lang.ICharSequence": - sw.WriteLine ("{0}var __rsval = __result?.ToString ();", indent); - break; - default: - sw.WriteLine ("{0}var __rsval = __result;", indent); - break; - } - foreach (Parameter p in Parameters) { - if (p.Type == "Java.Lang.ICharSequence") - sw.WriteLine ("{0}{1}?.Dispose ();", indent, p.GetName ("jls_")); - else if (p.Type == "Java.Lang.ICharSequence[]") - sw.WriteLine ("{0}if ({1} != null) foreach (global::Java.Lang.String s in {1}) s?.Dispose ();", indent, p.GetName ("jlca_")); - } - if (!RetVal.IsVoid) { - sw.WriteLine ($"{indent}return __rsval;"); - } - } - - void GenerateStringOverload (StreamWriter sw, string indent, CodeGenerationOptions opt) - { - string static_arg = IsStatic ? " static" : String.Empty; - string ret = opt.GetOutputName (RetVal.FullName.Replace ("Java.Lang.ICharSequence", "string")); - if (Deprecated != null) - sw.WriteLine ("{0}[Obsolete (@\"{1}\")]", indent, Deprecated.Replace ("\"", "\"\"").Trim ()); - sw.WriteLine ("{0}{1}{2} {3} {4} ({5})", indent, Visibility, static_arg, ret, Name, GenBase.GetSignature (this, opt).Replace ("Java.Lang.ICharSequence", "string").Replace ("global::string", "string")); - sw.WriteLine ("{0}{{", indent); - GenerateStringOverloadBody (sw, indent + "\t", opt, false); - sw.WriteLine ("{0}}}", indent); - sw.WriteLine (); - } - public bool CanHaveStringOverload { get { return IsReturnCharSequence || Parameters.HasCharSequence; } } - public void GenerateExtensionOverload (StreamWriter sw, string indent, CodeGenerationOptions opt, string selfType) - { - if (!CanHaveStringOverload) - return; - - string ret = opt.GetOutputName (RetVal.FullName.Replace ("Java.Lang.ICharSequence", "string")); - sw.WriteLine (); - sw.WriteLine ("{0}public static {1} {2} (this {3} self, {4})", - indent, ret, Name, selfType, - GenBase.GetSignature (this, opt).Replace ("Java.Lang.ICharSequence", "string").Replace ("global::string", "string")); - sw.WriteLine ("{0}{{", indent); - GenerateStringOverloadBody (sw, indent + "\t", opt, true); - sw.WriteLine ("{0}}}", indent); - } - - public void GenerateAsyncWrapper (StreamWriter sw, string indent, CodeGenerationOptions opt) - { - if (!Asyncify) - return; - - string static_arg = IsStatic ? " static" : String.Empty; - string ret; - - if (IsVoid) - ret = "global::System.Threading.Tasks.Task"; - else - ret = "global::System.Threading.Tasks.Task<" + opt.GetOutputName (RetVal.FullName) + ">"; - - sw.WriteLine ("{0}{1}{2} {3} {4}Async ({5})", indent, Visibility, static_arg, ret, AdjustedName, GenBase.GetSignature (this, opt)); - sw.WriteLine ("{0}{{", indent); - sw.WriteLine ("{0}\treturn global::System.Threading.Tasks.Task.Run (() => {1} ({2}));", indent, AdjustedName, Parameters.GetCall (opt)); - sw.WriteLine ("{0}}}", indent); - sw.WriteLine (); - } - - public void GenerateExtensionAsyncWrapper (StreamWriter sw, string indent, CodeGenerationOptions opt, string selfType) - { - if (!Asyncify) - return; - - string ret; - - if (IsVoid) - ret = "global::System.Threading.Tasks.Task"; - else - ret = "global::System.Threading.Tasks.Task<" + opt.GetOutputName (RetVal.FullName) + ">"; - - sw.WriteLine ("{0}public static {1} {2}Async (this {3} self{4}{5})", indent, ret, AdjustedName, selfType, Parameters.Count > 0 ? ", " : string.Empty, GenBase.GetSignature (this, opt)); - sw.WriteLine ("{0}{{", indent); - sw.WriteLine ("{0}\treturn global::System.Threading.Tasks.Task.Run (() => self.{1} ({2}));", indent, AdjustedName, Parameters.GetCall (opt)); - sw.WriteLine ("{0}}}", indent); - sw.WriteLine (); - } - - public void Generate (StreamWriter sw, string indent, CodeGenerationOptions opt, GenBase type, bool generate_callbacks) - { - if (!IsValid) - return; - - bool gen_as_formatted = IsReturnCharSequence; - if (generate_callbacks && IsVirtual) - GenerateCallback (sw, indent, opt, type, null, gen_as_formatted); - - string name_and_jnisig = JavaName + JniSignature.Replace ("java/lang/CharSequence", "java/lang/String"); - bool gen_string_overload = !IsOverride && Parameters.HasCharSequence && !type.ContainsMethod (name_and_jnisig); - - string static_arg = IsStatic ? " static" : String.Empty; - string virt_ov = IsOverride ? " override" : IsVirtual ? " virtual" : String.Empty; - if ((string.IsNullOrEmpty (virt_ov) || virt_ov == " virtual") && type.RequiresNew (AdjustedName)) { - virt_ov = " new" + virt_ov; - } - string seal = IsOverride && IsFinal ? " sealed" : null; - string ret = opt.GetOutputName (RetVal.FullName); - GenerateIdField (sw, indent, opt); - if (DeclaringType.IsGeneratable) - sw.WriteLine ("{0}// Metadata.xml XPath method reference: path=\"{1}\"", indent, GetMetadataXPathReference (this.DeclaringType)); - if (Deprecated != null) - sw.WriteLine ("{0}[Obsolete (@\"{1}\")]", indent, Deprecated.Replace ("\"", "\"\"")); - if (IsReturnEnumified) - sw.WriteLine ("{0}[return:global::Android.Runtime.GeneratedEnum]", indent); - sw.WriteLine ("{0}[Register (\"{1}\", \"{2}\", \"{3}\"{4})]", - indent, JavaName, JniSignature, IsVirtual ? ConnectorName : String.Empty, this.AdditionalAttributeString ()); - GenerateCustomAttributes (sw, indent); - sw.WriteLine ("{0}{1}{2}{3}{4} unsafe {5} {6} ({7})", indent, Visibility, static_arg, virt_ov, seal, ret, AdjustedName, GenBase.GetSignature (this, opt)); - sw.WriteLine ("{0}{{", indent); - GenerateBody (sw, indent + "\t", opt); - sw.WriteLine ("{0}}}", indent); - sw.WriteLine (); - - //NOTE: Invokers are the only place false is passed for generate_callbacks, they do not need string overloads - if (generate_callbacks && (gen_string_overload || gen_as_formatted)) - GenerateStringOverload (sw, indent, opt); - - GenerateAsyncWrapper (sw, indent, opt); - } - internal string GetAdapterName (CodeGenerationOptions opt, string adapter) { if (String.IsNullOrEmpty (adapter)) diff --git a/tools/generator/MethodBase.cs b/tools/generator/MethodBase.cs index 05fe7a8ae..5a26be9ca 100644 --- a/tools/generator/MethodBase.cs +++ b/tools/generator/MethodBase.cs @@ -1,147 +1,33 @@ using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Xml; -using Mono.Cecil; -using MonoDroid.Utils; - -using Java.Interop.Tools.TypeNameMappings; - -using Xamarin.Android.Tools; -using System.Xml.Linq; - -namespace MonoDroid.Generation { - - public interface IMethodBaseSupport { - string AssemblyName { get; } - string Deprecated { get; } - GenericParameterDefinitionList GenericArguments { get; } - string Visibility { get; } - } - -#if HAVE_CECIL - public class ManagedMethodBaseSupport : IMethodBaseSupport { - MethodDefinition m; - public ManagedMethodBaseSupport (MethodDefinition m) - { - this.m = m; - generic_arguments = m.HasGenericParameters ? GenericParameterDefinitionList.FromMetadata (m.GenericParameters) : null; - } - - public string AssemblyName { - get { return m.DeclaringType.Module.Assembly.FullName; } - } - - public string Deprecated { - get { - var v = m.CustomAttributes.FirstOrDefault (a => a.AttributeType.FullName == "System.ObsoleteAttribute"); - return v != null ? (string) v.ConstructorArguments [0].Value ?? "deprecated" : null; - } - } - - GenericParameterDefinitionList generic_arguments; - public GenericParameterDefinitionList GenericArguments { - get { return generic_arguments; } - } - - public string Visibility { - get { return m.IsPublic ? "public" : m.IsFamilyOrAssembly ? "protected internal" : m.IsFamily ? "protected" : m.IsAssembly ? "internal" : "private"; } - } - - internal JniTypeName GetJniReturnType (CustomAttribute regatt) - { - var jnisig = (string) (regatt.ConstructorArguments.Count > 1 ? regatt.ConstructorArguments [1].Value : regatt.Properties.First (p => p.Name == "JniSignature").Argument.Value); - return JavaNativeTypeManager.ReturnTypeFromSignature (jnisig); - } - - public IEnumerable GetParameters (CustomAttribute regatt) - { - var jnisig = (string) (regatt.ConstructorArguments.Count > 1 ? regatt.ConstructorArguments [1].Value : regatt.Properties.First (p => p.Name == "JniSignature").Argument.Value); - var types = jnisig == null ? null : JavaNativeTypeManager.FromSignature (jnisig); - var e = types != null ? types.GetEnumerator () : null; - - foreach (var p in m.Parameters) { - if (e != null && !e.MoveNext ()) - e = null; - // Here we do some tricky thing: - // Both java.io.InputStream and java.io.OutputStream could be mapped to - // System.IO.Stream. And when there is Stream in parameters, we have to - // determine which direction of the Stream it was - in or out. - // To do that, we inspect JNI Signature to handle that. - // - // We could *always* use this JNI information, *IF* there were no - // int->enum conversion. Sadly this is not true, we still have to expect - // custom enum types and cannot simply use JNI signature here. - var rawtype = e != null ? e.Current.Type : null; - var type = p.ParameterType.FullName == "System.IO.Stream" && e != null ? e.Current.Type : null; - yield return Parameter.FromManagedParameter (p, type, rawtype); - } - } - } -#endif // HAVE_CECIL - - public class XmlMethodBaseSupport : IMethodBaseSupport { - - XElement elem; - GenericParameterDefinitionList generic_arguments; - - public XmlMethodBaseSupport (XElement elem) - { - this.elem = elem; - var tps = elem.Element ("typeParameters"); - generic_arguments = tps != null ? GenericParameterDefinitionList.FromXml (tps) : null; - } - - public XElement Element { - get { return elem; } - } - - public string AssemblyName { - get { return null; } - } - - public string Deprecated { - get { return elem.XGetAttribute ("deprecated") != "not deprecated" ? elem.XGetAttribute ("deprecated") : null; } - } - - public GenericParameterDefinitionList GenericArguments { - get { return generic_arguments; } - } - - public string Visibility { - get { return elem.XGetAttribute ("visibility"); } - } - } +namespace MonoDroid.Generation +{ public abstract class MethodBase : ApiVersionsSupport.IApiAvailability { ParameterList parms; - IMethodBaseSupport support; - protected MethodBase (GenBase declaringType, IMethodBaseSupport support) + protected MethodBase (GenBase declaringType) { DeclaringType = declaringType; - this.support = support; parms = new ParameterList (); } + public virtual string AssemblyName { + get { return null; } + } + public virtual bool IsAcw { get { return true; } } public GenBase DeclaringType { get; private set; } - - public string AssemblyName { - get { return support.AssemblyName; } - } protected bool HasParameters { get { return parms.Count > 0; } } - public string Deprecated { - get { return support.Deprecated; } + public abstract string Deprecated { + get; } public virtual bool IsGeneric { @@ -149,7 +35,7 @@ public virtual bool IsGeneric { } string id_sig; - protected string IDSignature { + internal string IDSignature { get { if (id_sig == null) id_sig = HasParameters ? "_" + Parameters.JniSignature.Replace ("/", "_").Replace ("`", "_").Replace (";", "_").Replace ("$", "_").Replace ("[", "array") : String.Empty; @@ -164,11 +50,12 @@ public ParameterList Parameters { } public GenericParameterDefinitionList GenericArguments { - get { return support.GenericArguments; } + get; + internal protected set; } - public string Visibility { - get { return support.Visibility; } + public abstract string Visibility { + get; } public int ApiAvailableSince { get; set; } diff --git a/tools/generator/Property.cs b/tools/generator/Property.cs index ce9421a5c..7bc13a640 100644 --- a/tools/generator/Property.cs +++ b/tools/generator/Property.cs @@ -47,9 +47,9 @@ public void GenerateCallbacks (StreamWriter sw, string indent, CodeGenerationOpt public void GenerateCallbacks (StreamWriter sw, string indent, CodeGenerationOptions opt, GenBase gen, string name) { - Getter.GenerateCallback (sw, indent, opt, gen, name); + opt.CodeGenerator.WriteMethodCallback (Getter, sw, indent, opt, gen, name); if (Setter != null) - Setter.GenerateCallback (sw, indent, opt, gen, name); + opt.CodeGenerator.WriteMethodCallback (Setter, sw, indent, opt, gen, name); } public void GenerateAbstractDeclaration (StreamWriter sw, string indent, CodeGenerationOptions opt, GenBase gen) @@ -83,12 +83,12 @@ public void GenerateAbstractDeclaration (StreamWriter sw, string indent, CodeGen sw.WriteLine ("{0}\t// Metadata.xml XPath method reference: path=\"{1}/method[@name='{2}'{3}]\"", indent, gen.MetadataXPathReference, Getter.JavaName, Getter.Parameters.GetMethodXPathPredicate ()); if (Getter.IsReturnEnumified) sw.WriteLine ("{0}[return:global::Android.Runtime.GeneratedEnum]", indent); - Getter.GenerateCustomAttributes (sw, indent); + opt.CodeGenerator.WriteMethodCustomAttributes (Getter, sw, indent); sw.WriteLine ("{0}\t[Register (\"{1}\", \"{2}\", \"{3}\"{4})] get;", indent, Getter.JavaName, Getter.JniSignature, Getter.ConnectorName, Getter.AdditionalAttributeString ()); if (Setter != null) { if (gen.IsGeneratable) sw.WriteLine ("{0}\t// Metadata.xml XPath method reference: path=\"{1}/method[@name='{2}'{3}]\"", indent, gen.MetadataXPathReference, Setter.JavaName, Setter.Parameters.GetMethodXPathPredicate ()); - Setter.GenerateCustomAttributes (sw, indent); + opt.CodeGenerator.WriteMethodCustomAttributes (Setter, sw, indent); sw.WriteLine ("{0}\t[Register (\"{1}\", \"{2}\", \"{3}\"{4})] set;", indent, Setter.JavaName, Setter.JniSignature, Setter.ConnectorName, Setter.AdditionalAttributeString ()); } sw.WriteLine ("{0}}}", indent); @@ -121,18 +121,18 @@ public void GenerateDeclaration (StreamWriter sw, string indent, CodeGenerationO public void GenerateInvoker (StreamWriter sw, string indent, CodeGenerationOptions opt, GenBase container) { GenerateCallbacks (sw, indent, opt, container); - Getter.GenerateIdField (sw, indent, opt, invoker: true); + opt.CodeGenerator.WriteMethodIdField (Getter, sw, indent, opt, invoker: true); if (Setter != null) - Setter.GenerateIdField (sw, indent, opt, invoker: true); + opt.CodeGenerator.WriteMethodIdField (Setter, sw, indent, opt, invoker: true); sw.WriteLine ("{0}public unsafe {1} {2} {{", indent, opt.GetOutputName (Getter.ReturnType), AdjustedName); sw.WriteLine ("{0}\tget {{", indent); - Getter.GenerateInvokerBody (sw, indent + "\t\t", opt); + opt.CodeGenerator.WriteMethodInvokerBody (Getter, sw, indent + "\t\t", opt); sw.WriteLine ("{0}\t}}", indent); if (Setter != null) { string pname = Setter.Parameters [0].Name; Setter.Parameters [0].Name = "value"; sw.WriteLine ("{0}\tset {{", indent); - Setter.GenerateInvokerBody (sw, indent + "\t\t", opt); + opt.CodeGenerator.WriteMethodInvokerBody (Setter, sw, indent + "\t\t", opt); sw.WriteLine ("{0}\t}}", indent); Setter.Parameters [0].Name = pname; } @@ -248,11 +248,11 @@ public void Generate (GenBase gen, StreamWriter sw, string indent, CodeGeneratio bool is_virtual = Getter.IsVirtual && (Setter == null || Setter.IsVirtual); if (with_callbacks && is_virtual) { virtual_override = needNew + " virtual"; - Getter.GenerateCallback (sw, indent, opt, gen, AdjustedName); + opt.CodeGenerator.WriteMethodCallback (Getter, sw, indent, opt, gen, AdjustedName); } if (with_callbacks && is_virtual && Setter != null) { virtual_override = needNew + " virtual"; - Setter.GenerateCallback (sw, indent, opt, gen, AdjustedName); + opt.CodeGenerator.WriteMethodCallback (Setter, sw, indent, opt, gen, AdjustedName); } virtual_override = force_override ? " override" : virtual_override; if ((Getter ?? Setter).IsStatic) @@ -260,31 +260,31 @@ public void Generate (GenBase gen, StreamWriter sw, string indent, CodeGeneratio // It should be using AdjustedName instead of Name, but ICharSequence ("Formatted") properties are not caught by this... else if (gen.BaseSymbol != null && gen.BaseSymbol.GetPropertyByName (Name, true) != null) virtual_override = needNew + " override"; - - Getter.GenerateIdField (sw, indent, opt); + + opt.CodeGenerator.WriteMethodIdField (Getter, sw, indent, opt); if (Setter != null) - Setter.GenerateIdField (sw, indent, opt); + opt.CodeGenerator.WriteMethodIdField (Setter, sw, indent, opt); string visibility = Getter.IsAbstract && Getter.RetVal.IsGeneric ? "protected" : (Setter ?? Getter).Visibility; // Unlike [Register], mcs does not allow applying [Obsolete] on property accessors, so we can apply them only under limited condition... if (Getter.Deprecated != null && (Setter == null || Setter.Deprecated != null)) sw.WriteLine ("{0}[Obsolete (@\"{1}\")]", indent, Getter.Deprecated.Replace ("\"", "\"\"").Trim () + (Setter != null && Setter.Deprecated != Getter.Deprecated ? " " + Setter.Deprecated.Replace ("\"", "\"\"").Trim () : null)); - Getter.GenerateCustomAttributes (sw, indent); + opt.CodeGenerator.WriteMethodCustomAttributes (Getter, sw, indent); sw.WriteLine ("{0}{1}{2} unsafe {3} {4} {{", indent, visibility, virtual_override, opt.GetOutputName (Getter.ReturnType), decl_name); if (gen.IsGeneratable) sw.WriteLine ("{0}\t// Metadata.xml XPath method reference: path=\"{1}/method[@name='{2}'{3}]\"", indent, gen.MetadataXPathReference, Getter.JavaName, Getter.Parameters.GetMethodXPathPredicate ()); sw.WriteLine ("{0}\t[Register (\"{1}\", \"{2}\", \"{3}\"{4})]", indent, Getter.JavaName, Getter.JniSignature, Getter.ConnectorName, Getter.AdditionalAttributeString ()); sw.WriteLine ("{0}\tget {{", indent); - Getter.GenerateBody (sw, indent + "\t\t", opt); + opt.CodeGenerator.WriteMethodBody (Getter, sw, indent + "\t\t", opt); sw.WriteLine ("{0}\t}}", indent); if (Setter != null) { if (gen.IsGeneratable) sw.WriteLine ("{0}\t// Metadata.xml XPath method reference: path=\"{1}/method[@name='{2}'{3}]\"", indent, gen.MetadataXPathReference, Setter.JavaName, Setter.Parameters.GetMethodXPathPredicate ()); - Setter.GenerateCustomAttributes (sw, indent); + opt.CodeGenerator.WriteMethodCustomAttributes (Setter, sw, indent); sw.WriteLine ("{0}\t[Register (\"{1}\", \"{2}\", \"{3}\"{4})]", indent, Setter.JavaName, Setter.JniSignature, Setter.ConnectorName, Setter.AdditionalAttributeString ()); sw.WriteLine ("{0}\tset {{", indent); string pname = Setter.Parameters [0].Name; Setter.Parameters [0].Name = "value"; - Setter.GenerateBody (sw, indent + "\t\t", opt); + opt.CodeGenerator.WriteMethodBody (Setter, sw, indent + "\t\t", opt); Setter.Parameters [0].Name = pname; sw.WriteLine ("{0}\t}}", indent); } else if (GenerateDispatchingSetter) { diff --git a/tools/generator/Tests/Unit-Tests/CodeGeneratorTests.cs b/tools/generator/Tests/Unit-Tests/CodeGeneratorTests.cs new file mode 100644 index 000000000..26590c036 --- /dev/null +++ b/tools/generator/Tests/Unit-Tests/CodeGeneratorTests.cs @@ -0,0 +1,102 @@ +using MonoDroid.Generation; +using NUnit.Framework; +using System.IO; +using System.Text; + +namespace generatortests +{ + abstract class CodeGeneratorTests + { + protected CodeGenerator generator; + protected StringBuilder builder; + protected StringWriter writer; + protected CodeGenerationOptions options; + + [SetUp] + public void SetUp () + { + builder = new StringBuilder (); + writer = new StringWriter (builder); + options = new CodeGenerationOptions { + CodeGenerationTarget = Target, + }; + generator = options.CodeGenerator; + } + + [TearDown] + public void TearDown () + { + writer.Dispose (); + } + + protected abstract Xamarin.Android.Binder.CodeGenerationTarget Target { get; } + + [Test] + public void WriteEnumifiedField () + { + var @class = new TestClass ("java.lang.Object", "com.mypackage.foo"); + var field = new TestField ("int", "bar").SetEnumified (); + Assert.IsTrue (field.Validate (options, new GenericParameterDefinitionList ()), "field.Validate failed!"); + generator.WriteField (field, writer, string.Empty, options, @class); + + StringAssert.Contains ("[global::Android.Runtime.GeneratedEnum]", builder.ToString (), "Should contain GeneratedEnumAttribute!"); + } + + [Test] + public void WriteDeprecatedField () + { + var comment = "Don't use this!"; + var @class = new TestClass ("java.lang.Object", "com.mypackage.foo"); + var field = new TestField ("int", "bar").SetConstant ("1234").SetDeprecated (comment); + Assert.IsTrue (field.Validate (options, new GenericParameterDefinitionList ()), "field.Validate failed!"); + generator.WriteField (field, writer, string.Empty, options, @class); + + StringAssert.Contains ($"[Obsolete (\"{comment}\")]", builder.ToString (), "Should contain ObsoleteAttribute!"); + } + + [Test] + public void WriteProtectedField () + { + var @class = new TestClass ("java.lang.Object", "com.mypackage.foo"); + var field = new TestField ("int", "bar").SetVisibility ("protected"); + Assert.IsTrue (field.Validate (options, new GenericParameterDefinitionList ()), "field.Validate failed!"); + generator.WriteField (field, writer, string.Empty, options, @class); + + StringAssert.Contains ("protected int bar {", builder.ToString (), "Property should be protected!"); + } + + [Test] + public void WriteDeprecatedMethod () + { + var comment = "Don't use this!"; + var @class = new TestClass ("java.lang.Object", "com.mypackage.foo"); + var method = new TestMethod (@class, "bar").SetDeprecated (comment); + Assert.IsTrue (method.Validate (options, new GenericParameterDefinitionList ()), "method.Validate failed!"); + generator.WriteMethod (method, writer, string.Empty, options, @class, true); + + StringAssert.Contains ($"[Obsolete (@\"{comment}\")]", builder.ToString (), "Should contain ObsoleteAttribute!"); + } + + [Test] + public void WritedMethodWithManagedReturn () + { + var @class = new TestClass ("java.lang.Object", "com.mypackage.foo"); + var method = new TestMethod (@class, "bar", @return: "int").SetManagedReturn ("long"); + Assert.IsTrue (method.Validate (options, new GenericParameterDefinitionList ()), "method.Validate failed!"); + generator.WriteMethod (method, writer, string.Empty, options, @class, true); + + StringAssert.Contains ("public virtual unsafe long bar ()", builder.ToString (), "Should contain return long!"); + } + + [Test] + public void WritedMethodWithEnumifiedReturn () + { + var @class = new TestClass ("java.lang.Object", "com.mypackage.foo"); + var method = new TestMethod (@class, "bar", @return: "int").SetManagedReturn ("int").SetReturnEnumified (); + Assert.IsTrue (method.Validate (options, new GenericParameterDefinitionList ()), "method.Validate failed!"); + generator.WriteMethod (method, writer, string.Empty, options, @class, true); + + StringAssert.Contains ("[return:global::Android.Runtime.GeneratedEnum]", builder.ToString (), "Should contain GeneratedEnumAttribute!"); + } + } +} diff --git a/tools/generator/Tests/Unit-Tests/JavaInteropCodeGeneratorTests.cs b/tools/generator/Tests/Unit-Tests/JavaInteropCodeGeneratorTests.cs index ef137a3c4..8cc33ce4b 100644 --- a/tools/generator/Tests/Unit-Tests/JavaInteropCodeGeneratorTests.cs +++ b/tools/generator/Tests/Unit-Tests/JavaInteropCodeGeneratorTests.cs @@ -1,34 +1,13 @@ using MonoDroid.Generation; using NUnit.Framework; -using System.IO; -using System.Text; +using Xamarin.Android.Binder; namespace generatortests { [TestFixture] - class JavaInteropCodeGeneratorTests + class JavaInteropCodeGeneratorTests : CodeGeneratorTests { - CodeGenerator generator; - StringBuilder builder; - StringWriter writer; - CodeGenerationOptions options; - - [SetUp] - public void SetUp () - { - builder = new StringBuilder (); - writer = new StringWriter (builder); - options = new CodeGenerationOptions { - CodeGenerationTarget = Xamarin.Android.Binder.CodeGenerationTarget.JavaInterop1, - }; - generator = options.CodeGenerator; - } - - [TearDown] - public void TearDown () - { - writer.Dispose (); - } + protected override CodeGenerationTarget Target => CodeGenerationTarget.JavaInterop1; [Test] public void WriteClassHandle () @@ -174,40 +153,6 @@ public int bar { ", builder.ToString ()); } - [Test] - public void WriteEnumifiedField () - { - var @class = new TestClass ("java.lang.Object", "com.mypackage.foo"); - var field = new TestField ("int", "bar").SetEnumified (); - Assert.IsTrue (field.Validate (options, new GenericParameterDefinitionList ()), "field.Validate failed!"); - generator.WriteField (field, writer, string.Empty, options, @class); - - StringAssert.Contains ("[global::Android.Runtime.GeneratedEnum]", builder.ToString (), "Should contain GeneratedEnumAttribute!"); - } - - [Test] - public void WriteDeprecatedField () - { - var comment = "Don't use this!"; - var @class = new TestClass ("java.lang.Object", "com.mypackage.foo"); - var field = new TestField ("int", "bar").SetConstant ("1234").SetDeprecated (comment); - Assert.IsTrue (field.Validate (options, new GenericParameterDefinitionList ()), "field.Validate failed!"); - generator.WriteField (field, writer, string.Empty, options, @class); - - StringAssert.Contains ($"[Obsolete (\"{comment}\")]", builder.ToString (), "Should contain ObsoleteAttribute!"); - } - - [Test] - public void WriteProtectedField () - { - var @class = new TestClass ("java.lang.Object", "com.mypackage.foo"); - var field = new TestField ("int", "bar").SetVisibility ("protected"); - Assert.IsTrue (field.Validate (options, new GenericParameterDefinitionList ()), "field.Validate failed!"); - generator.WriteField (field, writer, string.Empty, options, @class); - - StringAssert.Contains ("protected int bar {", builder.ToString (), "Property should be protected!"); - } - [Test] public void WriteConstantField () { @@ -257,5 +202,269 @@ public void WriteConstantFieldWithIntValue () public const int bar = (int) 1234; ", builder.ToString ()); } + + [Test] + public void WriteMethodIdField () + { + var @class = new TestClass ("java.lang.Object", "com.mypackage.foo"); + var method = new TestMethod (@class, "bar"); + generator.WriteMethodIdField (method, writer, string.Empty, options); + + //NOTE: not needed for JavaInteropCodeGenerator + Assert.AreEqual ("", builder.ToString ()); + } + + [Test] + public void WriteMethodBody () + { + var @class = new TestClass ("java.lang.Object", "com.mypackage.foo"); + var method = new TestMethod (@class, "bar"); + Assert.IsTrue (method.Validate (options, new GenericParameterDefinitionList ()), "method.Validate failed!"); + generator.WriteMethodBody (method, writer, string.Empty, options); + + Assert.AreEqual (@"const string __id = ""bar.()V""; +try { + _members.InstanceMethods.InvokeVirtualVoidMethod (__id, this, null); +} finally { +} +", builder.ToString ()); + } + + [Test] + public void WriteVoidMethod () + { + var @class = new TestClass ("java.lang.Object", "com.mypackage.foo"); + var method = new TestMethod (@class, "bar"); + Assert.IsTrue (method.Validate (options, new GenericParameterDefinitionList ()), "method.Validate failed!"); + generator.WriteMethod (method, writer, string.Empty, options, @class, true); + + Assert.AreEqual (@"static Delegate cb_bar; +#pragma warning disable 0169 +static Delegate GetbarHandler () +{ + if (cb_bar == null) + cb_bar = JNINativeWrapper.CreateDelegate ((Action) n_bar); + return cb_bar; +} + +static void n_bar (IntPtr jnienv, IntPtr native__this) +{ + com.mypackage.foo __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); + __this.bar (); +} +#pragma warning restore 0169 + +// Metadata.xml XPath method reference: path=""/api/package[@name='com.mypackage']/class[@name='foo']/method[@name='bar' and count(parameter)=0]"" +[Register (""bar"", ""()V"", ""GetbarHandler"")] +public virtual unsafe void bar () +{ + const string __id = ""bar.()V""; + try { + _members.InstanceMethods.InvokeVirtualVoidMethod (__id, this, null); + } finally { + } +} + +", builder.ToString ()); + } + + [Test] + public void WriteIntMethod () + { + var @class = new TestClass ("java.lang.Object", "com.mypackage.foo"); + var method = new TestMethod (@class, "bar", @return: "int"); + Assert.IsTrue (method.Validate (options, new GenericParameterDefinitionList ()), "method.Validate failed!"); + generator.WriteMethod (method, writer, string.Empty, options, @class, true); + + Assert.AreEqual (@"static Delegate cb_bar; +#pragma warning disable 0169 +static Delegate GetbarHandler () +{ + if (cb_bar == null) + cb_bar = JNINativeWrapper.CreateDelegate ((Func) n_bar); + return cb_bar; +} + +static int n_bar (IntPtr jnienv, IntPtr native__this) +{ + com.mypackage.foo __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); + return __this.bar (); +} +#pragma warning restore 0169 + +// Metadata.xml XPath method reference: path=""/api/package[@name='com.mypackage']/class[@name='foo']/method[@name='bar' and count(parameter)=0]"" +[Register (""bar"", ""()I"", ""GetbarHandler"")] +public virtual unsafe int bar () +{ + const string __id = ""bar.()I""; + try { + var __rm = _members.InstanceMethods.InvokeVirtualInt32Method (__id, this, null); + return __rm; + } finally { + } +} + +", builder.ToString ()); + } + + [Test] + public void WriteStringMethod () + { + var @class = new TestClass ("java.lang.Object", "com.mypackage.foo"); + var method = new TestMethod (@class, "bar", @return: "java.lang.String"); + Assert.IsTrue (method.Validate (options, new GenericParameterDefinitionList ()), "method.Validate failed!"); + generator.WriteMethod (method, writer, string.Empty, options, @class, true); + + Assert.AreEqual (@"static Delegate cb_bar; +#pragma warning disable 0169 +static Delegate GetbarHandler () +{ + if (cb_bar == null) + cb_bar = JNINativeWrapper.CreateDelegate ((Func) n_bar); + return cb_bar; +} + +static IntPtr n_bar (IntPtr jnienv, IntPtr native__this) +{ + com.mypackage.foo __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); + return JNIEnv.NewString (__this.bar ()); +} +#pragma warning restore 0169 + +// Metadata.xml XPath method reference: path=""/api/package[@name='com.mypackage']/class[@name='foo']/method[@name='bar' and count(parameter)=0]"" +[Register (""bar"", ""()Ljava/lang/String;"", ""GetbarHandler"")] +public virtual unsafe string bar () +{ + const string __id = ""bar.()Ljava/lang/String;""; + try { + var __rm = _members.InstanceMethods.InvokeVirtualObjectMethod (__id, this, null); + return JNIEnv.GetString (__rm.Handle, JniHandleOwnership.TransferLocalRef); + } finally { + } +} + +", builder.ToString ()); + } + + [Test] + public void WriteFinalVoidMethod () + { + var @class = new TestClass ("java.lang.Object", "com.mypackage.foo"); + var method = new TestMethod (@class, "bar").SetFinal (); + Assert.IsTrue (method.Validate (options, new GenericParameterDefinitionList ()), "method.Validate failed!"); + generator.WriteMethod (method, writer, string.Empty, options, @class, true); + + Assert.AreEqual (@"// Metadata.xml XPath method reference: path=""/api/package[@name='com.mypackage']/class[@name='foo']/method[@name='bar' and count(parameter)=0]"" +[Register (""bar"", ""()V"", """")] +public unsafe void bar () +{ + const string __id = ""bar.()V""; + try { + _members.InstanceMethods.InvokeNonvirtualVoidMethod (__id, this, null); + } finally { + } +} + +", builder.ToString ()); + } + + [Test] + public void WriteAbstractVoidMethod () + { + var @class = new TestClass ("java.lang.Object", "com.mypackage.foo"); + var method = new TestMethod (@class, "bar").SetAbstract (); + Assert.IsTrue (method.Validate (options, new GenericParameterDefinitionList ()), "method.Validate failed!"); + generator.WriteMethod (method, writer, string.Empty, options, @class, true); + + Assert.AreEqual (@"static Delegate cb_bar; +#pragma warning disable 0169 +static Delegate GetbarHandler () +{ + if (cb_bar == null) + cb_bar = JNINativeWrapper.CreateDelegate ((Action) n_bar); + return cb_bar; +} + +static void n_bar (IntPtr jnienv, IntPtr native__this) +{ + com.mypackage.foo __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); + __this.bar (); +} +#pragma warning restore 0169 + +// Metadata.xml XPath method reference: path=""/api/package[@name='com.mypackage']/class[@name='foo']/method[@name='bar' and count(parameter)=0]"" +[Register (""bar"", ""()V"", ""GetbarHandler"")] +public virtual unsafe void bar () +{ + const string __id = ""bar.()V""; + try { + _members.InstanceMethods.InvokeAbstractVoidMethod (__id, this, null); + } finally { + } +} + +", builder.ToString ()); + } + + [Test] + public void WriteStaticVoidMethod () + { + var @class = new TestClass ("java.lang.Object", "com.mypackage.foo"); + var method = new TestMethod (@class, "bar").SetStatic (); + Assert.IsTrue (method.Validate (options, new GenericParameterDefinitionList ()), "method.Validate failed!"); + generator.WriteMethod (method, writer, string.Empty, options, @class, true); + + Assert.AreEqual (@"// Metadata.xml XPath method reference: path=""/api/package[@name='com.mypackage']/class[@name='foo']/method[@name='bar' and count(parameter)=0]"" +[Register (""bar"", ""()V"", """")] +public static unsafe void bar () +{ + const string __id = ""bar.()V""; + try { + _members.StaticMethods.InvokeVoidMethod (__id, null); + } finally { + } +} + +", builder.ToString ()); + } + + [Test] + public void WriteAsyncifiedVoidMethod () + { + var @class = new TestClass ("java.lang.Object", "com.mypackage.foo"); + var method = new TestMethod (@class, "bar").SetAsyncify (); + Assert.IsTrue (method.Validate (options, new GenericParameterDefinitionList ()), "method.Validate failed!"); + generator.WriteMethod (method, writer, string.Empty, options, @class, true); + + StringAssert.Contains (@"public global::System.Threading.Tasks.Task barAsync () +{ + return global::System.Threading.Tasks.Task.Run (() => bar ()); +}", builder.ToString ()); + } + + [Test] + public void WriteAsyncifiedIntMethod () + { + var @class = new TestClass ("java.lang.Object", "com.mypackage.foo"); + var method = new TestMethod (@class, "bar", @return: "int").SetAsyncify (); + Assert.IsTrue (method.Validate (options, new GenericParameterDefinitionList ()), "method.Validate failed!"); + generator.WriteMethod (method, writer, string.Empty, options, @class, true); + + StringAssert.Contains (@"public global::System.Threading.Tasks.Task barAsync () +{ + return global::System.Threading.Tasks.Task.Run (() => bar ()); +}", builder.ToString ()); + } + + [Test] + public void WriteProtectedMethod () + { + var @class = new TestClass ("java.lang.Object", "com.mypackage.foo"); + var method = new TestMethod (@class, "bar").SetVisibility ("protected"); + Assert.IsTrue (method.Validate (options, new GenericParameterDefinitionList ()), "method.Validate failed!"); + generator.WriteMethod (method, writer, string.Empty, options, @class, true); + + StringAssert.Contains (@"protected virtual unsafe void bar ()", builder.ToString ()); + } } } diff --git a/tools/generator/Tests/Unit-Tests/SupportTypes.cs b/tools/generator/Tests/Unit-Tests/SupportTypes.cs index 14fc79521..8b34523f7 100644 --- a/tools/generator/Tests/Unit-Tests/SupportTypes.cs +++ b/tools/generator/Tests/Unit-Tests/SupportTypes.cs @@ -137,4 +137,114 @@ protected override Parameter SetterParameter { } } } + + class TestMethod : Method + { + int apiLevel = 27; + string @return, managedReturn, visibility = "public", deprecated; + bool isAbstract, isFinal, isStatic, asyncify, isReturnEnumified; + + public TestMethod (TestClass @class, string name, string @return = "void") : base (@class) + { + Name = name; + this.@return = @return; + FillReturnType (); + } + + public TestMethod SetApiLevel (int apiLevel) + { + this.apiLevel = apiLevel; + return this; + } + + public TestMethod SetManagedReturn (string managedReturn) + { + this.managedReturn = managedReturn; + FillReturnType (); + return this; + } + + public TestMethod SetFinal () + { + isFinal = true; + IsVirtual = false; + return this; + } + + public TestMethod SetAbstract () + { + isAbstract = true; + return this; + } + + public TestMethod SetStatic () + { + isFinal = + isStatic = true; + IsVirtual = false; + return this; + } + + public TestMethod SetAsyncify () + { + asyncify = true; + return this; + } + + public TestMethod SetVisibility (string visibility) + { + this.visibility = visibility; + return this; + } + + public TestMethod SetDeprecated (string deprecated) + { + this.deprecated = deprecated; + return this; + } + + public TestMethod SetReturnEnumified () + { + this.isReturnEnumified = true; + return this; + } + + public override string ArgsType => null; + + public override string EventName => null; + + public override bool IsAbstract => isAbstract; + + public override bool IsFinal => isFinal; + + public override bool IsInterfaceDefaultMethod => false; + + public override string JavaName => Name; + + public override bool IsStatic => isStatic; + + public override bool IsVirtual { get; set; } = true; + + public override string Return => @return; + + public override bool IsReturnEnumified => isReturnEnumified; + + public override string ManagedReturn => managedReturn; + + public override int SourceApiLevel => apiLevel; + + public override bool Asyncify => asyncify; + + public override string CustomAttributes => null; + + public override string Name { get; set; } + + protected override string PropertyNameOverride => null; + + public override string AssemblyName => null; + + public override string Deprecated => deprecated; + + public override string Visibility => visibility; + } } diff --git a/tools/generator/Tests/Unit-Tests/XamarinAndroidCodeGeneratorTests.cs b/tools/generator/Tests/Unit-Tests/XamarinAndroidCodeGeneratorTests.cs index 0936214d7..a70dccb2d 100644 --- a/tools/generator/Tests/Unit-Tests/XamarinAndroidCodeGeneratorTests.cs +++ b/tools/generator/Tests/Unit-Tests/XamarinAndroidCodeGeneratorTests.cs @@ -1,34 +1,13 @@ using MonoDroid.Generation; using NUnit.Framework; -using System.IO; -using System.Text; +using Xamarin.Android.Binder; namespace generatortests { [TestFixture] - public class XamarinAndroidCodeGeneratorTests + class XamarinAndroidCodeGeneratorTests : CodeGeneratorTests { - CodeGenerator generator; - StringBuilder builder; - StringWriter writer; - CodeGenerationOptions options; - - [SetUp] - public void SetUp () - { - builder = new StringBuilder (); - writer = new StringWriter (builder); - options = new CodeGenerationOptions { - CodeGenerationTarget = Xamarin.Android.Binder.CodeGenerationTarget.XamarinAndroid, - }; - generator = options.CodeGenerator; - } - - [TearDown] - public void TearDown () - { - writer.Dispose (); - } + protected override CodeGenerationTarget Target => CodeGenerationTarget.XamarinAndroid; [Test] public void WriteClassHandle() @@ -169,40 +148,6 @@ public int bar { ", builder.ToString ()); } - [Test] - public void WriteEnumifiedField () - { - var @class = new TestClass ("java.lang.Object", "com.mypackage.foo"); - var field = new TestField ("int", "bar").SetEnumified (); - Assert.IsTrue (field.Validate (options, new GenericParameterDefinitionList ()), "field.Validate failed!"); - generator.WriteField (field, writer, string.Empty, options, @class); - - StringAssert.Contains ("[global::Android.Runtime.GeneratedEnum]", builder.ToString (), "Should contain GeneratedEnumAttribute!"); - } - - [Test] - public void WriteDeprecatedField () - { - var comment = "Don't use this!"; - var @class = new TestClass ("java.lang.Object", "com.mypackage.foo"); - var field = new TestField ("int", "bar").SetConstant ("1234").SetDeprecated (comment); - Assert.IsTrue (field.Validate (options, new GenericParameterDefinitionList ()), "field.Validate failed!"); - generator.WriteField (field, writer, string.Empty, options, @class); - - StringAssert.Contains ($"[Obsolete (\"{comment}\")]", builder.ToString (), "Should contain ObsoleteAttribute!"); - } - - [Test] - public void WriteProtectedField () - { - var @class = new TestClass ("java.lang.Object", "com.mypackage.foo"); - var field = new TestField ("int", "bar").SetVisibility ("protected"); - Assert.IsTrue (field.Validate (options, new GenericParameterDefinitionList ()), "field.Validate failed!"); - generator.WriteField (field, writer, string.Empty, options, @class); - - StringAssert.Contains ("protected int bar {", builder.ToString (), "Property should be protected!"); - } - [Test] public void WriteConstantField () { @@ -253,5 +198,296 @@ public void WriteConstantFieldWithIntValue () public const int bar = (int) 1234; ", builder.ToString ()); } + + [Test] + public void WriteMethodIdField () + { + var @class = new TestClass ("java.lang.Object", "com.mypackage.foo"); + var method = new TestMethod (@class, "bar"); + generator.WriteMethodIdField (method, writer, string.Empty, options); + + Assert.AreEqual (@"static IntPtr id_bar; +", builder.ToString ()); + } + + [Test] + public void WriteMethodBody () + { + var @class = new TestClass ("java.lang.Object", "com.mypackage.foo"); + var method = new TestMethod (@class, "bar"); + Assert.IsTrue (method.Validate (options, new GenericParameterDefinitionList ()), "method.Validate failed!"); + generator.WriteMethodBody (method, writer, string.Empty, options); + + Assert.AreEqual (@"if (id_bar == IntPtr.Zero) + id_bar = JNIEnv.GetMethodID (class_ref, ""bar"", ""()V""); +try { + + if (((object) this).GetType () == ThresholdType) + JNIEnv.CallVoidMethod (((global::Java.Lang.Object) this).Handle, id_bar); + else + JNIEnv.CallNonvirtualVoidMethod (((global::Java.Lang.Object) this).Handle, ThresholdClass, JNIEnv.GetMethodID (ThresholdClass, ""bar"", ""()V"")); +} finally { +} +", builder.ToString ()); + } + + [Test] + public void WriteVoidMethod () + { + var @class = new TestClass ("java.lang.Object", "com.mypackage.foo"); + var method = new TestMethod (@class, "bar"); + Assert.IsTrue (method.Validate (options, new GenericParameterDefinitionList ()), "method.Validate failed!"); + generator.WriteMethod (method, writer, string.Empty, options, @class, true); + + Assert.AreEqual (@"static Delegate cb_bar; +#pragma warning disable 0169 +static Delegate GetbarHandler () +{ + if (cb_bar == null) + cb_bar = JNINativeWrapper.CreateDelegate ((Action) n_bar); + return cb_bar; +} + +static void n_bar (IntPtr jnienv, IntPtr native__this) +{ + com.mypackage.foo __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); + __this.bar (); +} +#pragma warning restore 0169 + +static IntPtr id_bar; +// Metadata.xml XPath method reference: path=""/api/package[@name='com.mypackage']/class[@name='foo']/method[@name='bar' and count(parameter)=0]"" +[Register (""bar"", ""()V"", ""GetbarHandler"")] +public virtual unsafe void bar () +{ + if (id_bar == IntPtr.Zero) + id_bar = JNIEnv.GetMethodID (class_ref, ""bar"", ""()V""); + try { + + if (((object) this).GetType () == ThresholdType) + JNIEnv.CallVoidMethod (((global::Java.Lang.Object) this).Handle, id_bar); + else + JNIEnv.CallNonvirtualVoidMethod (((global::Java.Lang.Object) this).Handle, ThresholdClass, JNIEnv.GetMethodID (ThresholdClass, ""bar"", ""()V"")); + } finally { + } +} + +", builder.ToString ()); + } + + [Test] + public void WriteIntMethod () + { + var @class = new TestClass ("java.lang.Object", "com.mypackage.foo"); + var method = new TestMethod (@class, "bar", @return: "int"); + Assert.IsTrue (method.Validate (options, new GenericParameterDefinitionList ()), "method.Validate failed!"); + generator.WriteMethod (method, writer, string.Empty, options, @class, true); + + Assert.AreEqual (@"static Delegate cb_bar; +#pragma warning disable 0169 +static Delegate GetbarHandler () +{ + if (cb_bar == null) + cb_bar = JNINativeWrapper.CreateDelegate ((Func) n_bar); + return cb_bar; +} + +static int n_bar (IntPtr jnienv, IntPtr native__this) +{ + com.mypackage.foo __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); + return __this.bar (); +} +#pragma warning restore 0169 + +static IntPtr id_bar; +// Metadata.xml XPath method reference: path=""/api/package[@name='com.mypackage']/class[@name='foo']/method[@name='bar' and count(parameter)=0]"" +[Register (""bar"", ""()I"", ""GetbarHandler"")] +public virtual unsafe int bar () +{ + if (id_bar == IntPtr.Zero) + id_bar = JNIEnv.GetMethodID (class_ref, ""bar"", ""()I""); + try { + + if (((object) this).GetType () == ThresholdType) + return JNIEnv.CallIntMethod (((global::Java.Lang.Object) this).Handle, id_bar); + else + return JNIEnv.CallNonvirtualIntMethod (((global::Java.Lang.Object) this).Handle, ThresholdClass, JNIEnv.GetMethodID (ThresholdClass, ""bar"", ""()I"")); + } finally { + } +} + +", builder.ToString ()); + } + + [Test] + public void WriteStringMethod () + { + var @class = new TestClass ("java.lang.Object", "com.mypackage.foo"); + var method = new TestMethod (@class, "bar", @return: "java.lang.String"); + Assert.IsTrue (method.Validate (options, new GenericParameterDefinitionList ()), "method.Validate failed!"); + generator.WriteMethod (method, writer, string.Empty, options, @class, true); + + Assert.AreEqual (@"static Delegate cb_bar; +#pragma warning disable 0169 +static Delegate GetbarHandler () +{ + if (cb_bar == null) + cb_bar = JNINativeWrapper.CreateDelegate ((Func) n_bar); + return cb_bar; +} + +static IntPtr n_bar (IntPtr jnienv, IntPtr native__this) +{ + com.mypackage.foo __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); + return JNIEnv.NewString (__this.bar ()); +} +#pragma warning restore 0169 + +static IntPtr id_bar; +// Metadata.xml XPath method reference: path=""/api/package[@name='com.mypackage']/class[@name='foo']/method[@name='bar' and count(parameter)=0]"" +[Register (""bar"", ""()Ljava/lang/String;"", ""GetbarHandler"")] +public virtual unsafe string bar () +{ + if (id_bar == IntPtr.Zero) + id_bar = JNIEnv.GetMethodID (class_ref, ""bar"", ""()Ljava/lang/String;""); + try { + + if (((object) this).GetType () == ThresholdType) + return JNIEnv.GetString (JNIEnv.CallObjectMethod (((global::Java.Lang.Object) this).Handle, id_bar), JniHandleOwnership.TransferLocalRef); + else + return JNIEnv.GetString (JNIEnv.CallNonvirtualObjectMethod (((global::Java.Lang.Object) this).Handle, ThresholdClass, JNIEnv.GetMethodID (ThresholdClass, ""bar"", ""()Ljava/lang/String;"")), JniHandleOwnership.TransferLocalRef); + } finally { + } +} + +", builder.ToString ()); + } + + [Test] + public void WriteFinalVoidMethod () + { + var @class = new TestClass ("java.lang.Object", "com.mypackage.foo"); + var method = new TestMethod (@class, "bar").SetFinal (); + Assert.IsTrue (method.Validate (options, new GenericParameterDefinitionList ()), "method.Validate failed!"); + generator.WriteMethod (method, writer, string.Empty, options, @class, true); + + Assert.AreEqual (@"static IntPtr id_bar; +// Metadata.xml XPath method reference: path=""/api/package[@name='com.mypackage']/class[@name='foo']/method[@name='bar' and count(parameter)=0]"" +[Register (""bar"", ""()V"", """")] +public unsafe void bar () +{ + if (id_bar == IntPtr.Zero) + id_bar = JNIEnv.GetMethodID (class_ref, ""bar"", ""()V""); + try { + JNIEnv.CallVoidMethod (((global::Java.Lang.Object) this).Handle, id_bar); + } finally { + } +} + +", builder.ToString ()); + } + + [Test] + public void WriteAbstractVoidMethod () + { + var @class = new TestClass ("java.lang.Object", "com.mypackage.foo"); + var method = new TestMethod (@class, "bar").SetAbstract (); + Assert.IsTrue (method.Validate (options, new GenericParameterDefinitionList ()), "method.Validate failed!"); + generator.WriteMethod (method, writer, string.Empty, options, @class, true); + + Assert.AreEqual (@"static Delegate cb_bar; +#pragma warning disable 0169 +static Delegate GetbarHandler () +{ + if (cb_bar == null) + cb_bar = JNINativeWrapper.CreateDelegate ((Action) n_bar); + return cb_bar; +} + +static void n_bar (IntPtr jnienv, IntPtr native__this) +{ + com.mypackage.foo __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); + __this.bar (); +} +#pragma warning restore 0169 + +static IntPtr id_bar; +// Metadata.xml XPath method reference: path=""/api/package[@name='com.mypackage']/class[@name='foo']/method[@name='bar' and count(parameter)=0]"" +[Register (""bar"", ""()V"", ""GetbarHandler"")] +public virtual unsafe void bar () +{ + if (id_bar == IntPtr.Zero) + id_bar = JNIEnv.GetMethodID (class_ref, ""bar"", ""()V""); + try { + JNIEnv.CallVoidMethod (((global::Java.Lang.Object) this).Handle, id_bar); + } finally { + } +} + +", builder.ToString ()); + } + + [Test] + public void WriteStaticVoidMethod () + { + var @class = new TestClass ("java.lang.Object", "com.mypackage.foo"); + var method = new TestMethod (@class, "bar").SetStatic (); + Assert.IsTrue (method.Validate (options, new GenericParameterDefinitionList ()), "method.Validate failed!"); + generator.WriteMethod (method, writer, string.Empty, options, @class, true); + + Assert.AreEqual (@"static IntPtr id_bar; +// Metadata.xml XPath method reference: path=""/api/package[@name='com.mypackage']/class[@name='foo']/method[@name='bar' and count(parameter)=0]"" +[Register (""bar"", ""()V"", """")] +public static unsafe void bar () +{ + if (id_bar == IntPtr.Zero) + id_bar = JNIEnv.GetStaticMethodID (class_ref, ""bar"", ""()V""); + try { + JNIEnv.CallStaticVoidMethod (class_ref, id_bar); + } finally { + } +} + +", builder.ToString ()); + } + + [Test] + public void WriteAsyncifiedVoidMethod () + { + var @class = new TestClass ("java.lang.Object", "com.mypackage.foo"); + var method = new TestMethod (@class, "bar").SetAsyncify (); + Assert.IsTrue (method.Validate (options, new GenericParameterDefinitionList ()), "method.Validate failed!"); + generator.WriteMethod (method, writer, string.Empty, options, @class, true); + + StringAssert.Contains (@"public global::System.Threading.Tasks.Task barAsync () +{ + return global::System.Threading.Tasks.Task.Run (() => bar ()); +}", builder.ToString ()); + } + + [Test] + public void WriteAsyncifiedIntMethod () + { + var @class = new TestClass ("java.lang.Object", "com.mypackage.foo"); + var method = new TestMethod (@class, "bar", @return: "int").SetAsyncify (); + Assert.IsTrue (method.Validate (options, new GenericParameterDefinitionList ()), "method.Validate failed!"); + generator.WriteMethod (method, writer, string.Empty, options, @class, true); + + StringAssert.Contains (@"public global::System.Threading.Tasks.Task barAsync () +{ + return global::System.Threading.Tasks.Task.Run (() => bar ()); +}", builder.ToString ()); + } + + [Test] + public void WriteProtectedMethod () + { + var @class = new TestClass ("java.lang.Object", "com.mypackage.foo"); + var method = new TestMethod (@class, "bar").SetVisibility ("protected"); + Assert.IsTrue (method.Validate (options, new GenericParameterDefinitionList ()), "method.Validate failed!"); + generator.WriteMethod (method, writer, string.Empty, options, @class, true); + + StringAssert.Contains (@"protected virtual unsafe void bar ()", builder.ToString ()); + } } } diff --git a/tools/generator/Tests/generator-Tests.csproj b/tools/generator/Tests/generator-Tests.csproj index 7f31254f3..6d0fe2ed9 100644 --- a/tools/generator/Tests/generator-Tests.csproj +++ b/tools/generator/Tests/generator-Tests.csproj @@ -68,6 +68,7 @@ + diff --git a/tools/generator/XamarinAndroidCodeGenerator.cs b/tools/generator/XamarinAndroidCodeGenerator.cs index ca2ff1a65..afa47aedc 100644 --- a/tools/generator/XamarinAndroidCodeGenerator.cs +++ b/tools/generator/XamarinAndroidCodeGenerator.cs @@ -138,13 +138,13 @@ internal override void WriteMethodBody (Method method, TextWriter writer, string writer.WriteLine ("{0}if (((object) this).GetType () == ThresholdType)", indent); GenerateJNICall (method, writer, indent + "\t", opt, "JNIEnv.Call" + method.RetVal.CallMethodPrefix + "Method (" + - opt.ContextType.GetObjectHandleProperty ("this") + + method.DeclaringType.GetObjectHandleProperty ("this") + ", " + method.EscapedIdName + method.Parameters.GetCallArgs (opt, invoker:false) + ")", declare_ret: false); writer.WriteLine ("{0}else", indent); GenerateJNICall (method, writer, indent + "\t", opt, "JNIEnv.CallNonvirtual" + method.RetVal.CallMethodPrefix + "Method (" + - opt.ContextType.GetObjectHandleProperty ("this") + + method.DeclaringType.GetObjectHandleProperty ("this") + ", ThresholdClass, " + string.Format ("JNIEnv.GetMethodID (ThresholdClass, \"{0}\", \"{1}\")", method.JavaName, method.JniSignature) + method.Parameters.GetCallArgs (opt, invoker:false) + ")", @@ -156,7 +156,7 @@ internal override void WriteMethodBody (Method method, TextWriter writer, string indent, opt, "JNIEnv.Call" + method.RetVal.CallMethodPrefix + "Method (" + - opt.ContextType.GetObjectHandleProperty ("this") + + method.DeclaringType.GetObjectHandleProperty ("this") + ", " + method.EscapedIdName + method.Parameters.GetCallArgs (opt, invoker:false) + ")", declare_ret: true); diff --git a/tools/generator/XmlExtensions.cs b/tools/generator/XmlExtensions.cs new file mode 100644 index 000000000..cd567a9c7 --- /dev/null +++ b/tools/generator/XmlExtensions.cs @@ -0,0 +1,22 @@ +using System.Xml.Linq; +using Xamarin.Android.Tools; + +namespace MonoDroid.Generation +{ + internal static class XmlExtensions + { + public static string Deprecated (this XElement elem) + { + var deprecated = elem.XGetAttribute ("deprecated"); + return deprecated != "not deprecated" ? deprecated : null; + } + + public static string Visibility (this XElement elem) => elem.XGetAttribute ("visibility"); + + public static GenericParameterDefinitionList GenericArguments (this XElement elem) + { + var tps = elem.Element ("typeParameters"); + return tps != null ? GenericParameterDefinitionList.FromXml (tps) : null; + } + } +} diff --git a/tools/generator/generator.csproj b/tools/generator/generator.csproj index cc2eae052..144d39c9d 100644 --- a/tools/generator/generator.csproj +++ b/tools/generator/generator.csproj @@ -86,6 +86,7 @@ + @@ -103,6 +104,7 @@ +