diff --git a/src/Java.Interop.Tools.Diagnostics/Java.Interop.Tools.Diagnostics/Diagnostic.cs b/src/Java.Interop.Tools.Diagnostics/Java.Interop.Tools.Diagnostics/Diagnostic.cs index 4f9419799..a260ba98f 100644 --- a/src/Java.Interop.Tools.Diagnostics/Java.Interop.Tools.Diagnostics/Diagnostic.cs +++ b/src/Java.Interop.Tools.Diagnostics/Java.Interop.Tools.Diagnostics/Diagnostic.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; - +using System.Diagnostics.CodeAnalysis; using Mono.Cecil.Cil; namespace Java.Interop.Tools.Diagnostics { @@ -148,6 +148,7 @@ namespace Java.Interop.Tools.Diagnostics { // public static class Diagnostic { + [DoesNotReturn] public static void Error (int code, SequencePoint? location, string message, params object[] args) { throw new XamarinAndroidException (code, message, args) { @@ -156,11 +157,13 @@ public static void Error (int code, SequencePoint? location, string message, par } + [DoesNotReturn] public static void Error (int code, string message, params object[] args) { throw new XamarinAndroidException (code, message, args); } + [DoesNotReturn] public static void Error (int code, Exception innerException, string message, params object[] args) { throw new XamarinAndroidException (code, innerException, message, args); diff --git a/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers.Adapters/CecilImporter.cs b/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers.Adapters/CecilImporter.cs new file mode 100644 index 000000000..51cb90707 --- /dev/null +++ b/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers.Adapters/CecilImporter.cs @@ -0,0 +1,568 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Android.Runtime; +using Java.Interop.Tools.Cecil; +using Java.Interop.Tools.Diagnostics; +using Java.Interop.Tools.JavaCallableWrappers.CallableWrapperMembers; +using Java.Interop.Tools.JavaCallableWrappers.Utilities; +using Java.Interop.Tools.TypeNameMappings; +using Mono.Cecil; + +namespace Java.Interop.Tools.JavaCallableWrappers.Adapters; + +public class CecilImporter +{ + // Don't expose internal "outerType" parameter to the public API + public static CallableWrapperType CreateType (TypeDefinition type, IMetadataResolver resolver, CallableWrapperReaderOptions? options = null) + => CreateType (type, resolver, options, null); + + static CallableWrapperType CreateType (TypeDefinition type, IMetadataResolver resolver, CallableWrapperReaderOptions? options = null, string? outerType = null) + { + if (type.IsEnum || type.IsInterface || type.IsValueType) + Diagnostic.Error (4200, CecilExtensions.LookupSource (type), Localization.Resources.JavaCallableWrappers_XA4200, type.FullName); + + var jniName = JavaNativeTypeManager.ToJniName (type, resolver); + + if (jniName is null) + Diagnostic.Error (4201, CecilExtensions.LookupSource (type), Localization.Resources.JavaCallableWrappers_XA4201, type.FullName); + + if (outerType != null && !string.IsNullOrEmpty (outerType)) { + jniName = jniName.Substring (outerType.Length + 1); + ExtractJavaNames (outerType, out var p, out outerType); + } + + ExtractJavaNames (jniName, out var package, out var name); + + options ??= new CallableWrapperReaderOptions (); + + if (string.IsNullOrEmpty (package) && + (type.IsSubclassOf ("Android.App.Activity", resolver) || + type.IsSubclassOf ("Android.App.Application", resolver) || + type.IsSubclassOf ("Android.App.Service", resolver) || + type.IsSubclassOf ("Android.Content.BroadcastReceiver", resolver) || + type.IsSubclassOf ("Android.Content.ContentProvider", resolver))) + Diagnostic.Error (4203, CecilExtensions.LookupSource (type), Localization.Resources.JavaCallableWrappers_XA4203, jniName); + + var cwt = new CallableWrapperType (name, package, type.GetPartialAssemblyQualifiedName (resolver)) { + IsApplication = JavaNativeTypeManager.IsApplication (type, resolver), + IsInstrumentation = JavaNativeTypeManager.IsInstrumentation (type, resolver), + IsAbstract = type.IsAbstract, + ApplicationJavaClass = options.DefaultApplicationJavaClass, + GenerateOnCreateOverrides = options.DefaultGenerateOnCreateOverrides, + MonoRuntimeInitialization = options.DefaultMonoRuntimeInitialization, + }; + + // Type annotations + cwt.Annotations.AddRange (CreateAnnotations (type, resolver)); + + // Extends + cwt.ExtendsType = GetJavaTypeName (type.BaseType, resolver); + + // Implemented interfaces + foreach (var ifaceInfo in type.Interfaces) { + var iface = resolver.Resolve (ifaceInfo.InterfaceType); + + if (!CecilExtensions.GetTypeRegistrationAttributes (iface).Any ()) + continue; + + cwt.ImplementedInterfaces.Add (GetJavaTypeName (iface, resolver)); + } + + // Application constructor + if (CreateApplicationConstructor (cwt.Name, type, resolver) is CallableWrapperApplicationConstructor app_ctor) + cwt.ApplicationConstructor = app_ctor; + + // Methods + foreach (var minfo in type.Methods.Where (m => !m.IsConstructor)) { + var baseRegisteredMethod = CecilExtensions.GetBaseRegisteredMethod (minfo, resolver); + + if (baseRegisteredMethod is not null) + AddMethod (cwt, type, baseRegisteredMethod, minfo, options.MethodClassifier, resolver); + else if (minfo.AnyCustomAttributes ("Java.Interop.JavaCallableAttribute")) { + AddMethod (cwt, type, null, minfo, options.MethodClassifier, resolver); + cwt.HasExport = true; + } else if (minfo.AnyCustomAttributes ("Java.Interop.JavaCallableConstructorAttribute")) { + AddMethod (cwt, type, null, minfo, options.MethodClassifier, resolver); + cwt.HasExport = true; + } else if (minfo.AnyCustomAttributes (typeof (ExportFieldAttribute))) { + AddMethod (cwt, type, null, minfo, options.MethodClassifier, resolver); + cwt.HasExport = true; + } else if (minfo.AnyCustomAttributes (typeof (ExportAttribute))) { + AddMethod (cwt, type, null, minfo, options.MethodClassifier, resolver); + cwt.HasExport = true; + } + } + + // Methods from interfaces + foreach (InterfaceImplementation ifaceInfo in type.Interfaces) { + var typeReference = ifaceInfo.InterfaceType; + var typeDefinition = resolver.Resolve (typeReference); + + if (typeDefinition is null) { + Diagnostic.Error (4204, + CecilExtensions.LookupSource (type), + Localization.Resources.JavaCallableWrappers_XA4204, + typeReference.FullName); + continue; + } + + if (!CecilExtensions.GetTypeRegistrationAttributes (typeDefinition).Any ()) + continue; + + foreach (MethodDefinition imethod in typeDefinition.Methods) { + if (imethod.IsStatic) + continue; + + AddMethod (cwt, type, imethod, imethod, options.MethodClassifier, resolver); + } + } + + // Constructors + var ctorTypes = new List () { + type, + }; + + foreach (var bt in type.GetBaseTypes (resolver)) { + ctorTypes.Add (bt); + var rattr = CecilExtensions.GetMethodRegistrationAttributes (bt).FirstOrDefault (); + + if (rattr != null && rattr.DoNotGenerateAcw) + break; + } + + ctorTypes.Reverse (); + + var curCtors = new List (); + + foreach (var minfo in type.Methods) { + if (minfo.IsConstructor && minfo.AnyCustomAttributes (typeof (ExportAttribute))) { + if (minfo.IsStatic) { + // Diagnostic.Warning (log, "ExportAttribute does not work on static constructor"); + } else { + if (CreateConstructor (cwt, minfo, ctorTypes [0], type, outerType, null, curCtors, false, true, resolver) is CallableWrapperConstructor c) + cwt.Constructors.Add (c); + + cwt.HasExport = true; + } + } + } + + AddConstructors (cwt, ctorTypes [0], type, outerType, null, curCtors, true, resolver); + + for (var i = 1; i < ctorTypes.Count; ++i) { + var baseCtors = curCtors; + curCtors = new List (); + AddConstructors (cwt, ctorTypes [i], type, outerType, baseCtors, curCtors, false, resolver); + } + + AddNestedTypes (cwt, type, resolver, options); + + return cwt; + } + + static CallableWrapperField CreateField (MethodDefinition method, string fieldName, IMetadataResolver resolver) + { + var visibility = GetJavaAccess (method.Attributes & MethodAttributes.MemberAccessMask); + var type_name = JavaNativeTypeManager.ReturnTypeFromSignature (JavaNativeTypeManager.GetJniSignature (method, resolver))?.Type + ?? throw new ArgumentException ($"Could not get JNI signature for method `{method.Name}`", nameof (method)); + + var field = new CallableWrapperField (fieldName, type_name, visibility, method.Name) { + IsStatic = method.IsStatic, + }; + + field.Annotations.AddRange (CreateAnnotations (method, resolver)); + + return field; + } + + // Constructor with [Register] attribute + static CallableWrapperConstructor CreateConstructor (MethodDefinition methodDefinition, CallableWrapperType type, RegisterAttribute register, string? managedParameters, string? outerType, IMetadataResolver cache, bool shouldBeDynamicallyRegistered = true) + { + var method = CreateConstructor (methodDefinition.Name, type, register.Signature, register.Connector, managedParameters, outerType, null); + + method.Annotations.AddRange (CreateAnnotations (methodDefinition, cache)); + method.IsDynamicallyRegistered = shouldBeDynamicallyRegistered; + + return method; + } + + // Constructor with [Export] attribute + static CallableWrapperConstructor CreateConstructor (MethodDefinition methodDefinition, CallableWrapperType type, ExportAttribute export, string? managedParameters, IMetadataResolver resolver) + { + var method = CreateConstructor (methodDefinition.Name, type, JavaNativeTypeManager.GetJniSignature (methodDefinition, resolver), "__export__", null, null, export.SuperArgumentsString); + + method.IsExport = true; + method.IsStatic = methodDefinition.IsStatic; + method.JavaAccess = GetJavaAccess (methodDefinition.Attributes & MethodAttributes.MemberAccessMask); + method.ThrownTypeNames = export.ThrownNames; + method.JavaNameOverride = export.Name; + method.ManagedParameters = managedParameters; + method.Annotations.AddRange (CreateAnnotations (methodDefinition, resolver)); + + return method; + } + + // Common constructor creation code + static CallableWrapperConstructor CreateConstructor (string name, CallableWrapperType type, string? signature, string? connector, string? managedParameters, string? outerType, string? superCall) + { + signature = signature ?? throw new ArgumentNullException ("`connector` cannot be null.", nameof (connector)); + var method_name = "n_" + name + ":" + signature + ":" + connector; + + var method = new CallableWrapperConstructor (type, name, method_name, signature); + + PopulateMethod (method, signature, managedParameters, outerType, superCall); + + method.Name = type.Name; + + return method; + } + + // Method with a [Register] attribute + static CallableWrapperMethod CreateMethod (MethodDefinition methodDefinition, CallableWrapperType declaringType, RegisterAttribute register, string? managedParameters, string? outerType, IMetadataResolver resolver, bool shouldBeDynamicallyRegistered = true) + { + var method = CreateMethod (register.Name, declaringType, register.Signature, register.Connector, managedParameters, outerType, null); + + method.Annotations.AddRange (CreateAnnotations (methodDefinition, resolver)); + method.IsDynamicallyRegistered = shouldBeDynamicallyRegistered; + + return method; + } + + // Method with an [Export] attribute + static CallableWrapperMethod CreateMethod (MethodDefinition methodDefinition, CallableWrapperType declaringType, ExportAttribute export, string? managedParameters, IMetadataResolver resolver) + { + var method = CreateMethod (methodDefinition.Name, declaringType, JavaNativeTypeManager.GetJniSignature (methodDefinition, resolver), "__export__", null, null, export.SuperArgumentsString); + + method.IsExport = true; + method.IsStatic = methodDefinition.IsStatic; + method.JavaAccess = GetJavaAccess (methodDefinition.Attributes & MethodAttributes.MemberAccessMask); + method.ThrownTypeNames = export.ThrownNames; + method.JavaNameOverride = export.Name; + method.ManagedParameters = managedParameters; + method.Annotations.AddRange (CreateAnnotations (methodDefinition, resolver)); + + return method; + } + + // Method with an [ExportField] attribute + static CallableWrapperMethod CreateMethod (MethodDefinition methodDefinition, CallableWrapperType declaringType, IMetadataResolver resolver) + { + var method = CreateMethod (methodDefinition.Name, declaringType, JavaNativeTypeManager.GetJniSignature (methodDefinition, resolver), "__export__", null, null, null); + + if (methodDefinition.HasParameters) + Diagnostic.Error (4205, CecilExtensions.LookupSource (methodDefinition), Localization.Resources.JavaCallableWrappers_XA4205); + if (methodDefinition.ReturnType.MetadataType == MetadataType.Void) + Diagnostic.Error (4208, CecilExtensions.LookupSource (methodDefinition), Localization.Resources.JavaCallableWrappers_XA4208); + + method.IsExport = true; + method.IsStatic = methodDefinition.IsStatic; + method.JavaAccess = GetJavaAccess (methodDefinition.Attributes & MethodAttributes.MemberAccessMask); + + // Annotations are processed within CallableWrapperField, not the initializer method. So we don't generate them here. + + return method; + } + + // Common method creation code + static CallableWrapperMethod CreateMethod (string name, CallableWrapperType declaringType, string? signature, string? connector, string? managedParameters, string? outerType, string? superCall) + { + signature = signature ?? throw new ArgumentNullException ("`connector` cannot be null.", nameof (connector)); + var method_name = "n_" + name + ":" + signature + ":" + connector; + + var method = new CallableWrapperMethod (declaringType, name, method_name, signature); + + PopulateMethod (method, signature, managedParameters, outerType, superCall); + + return method; + } + + // This is done this way to allow sharing between CallableWrapperMethod and CallableWrapperConstructor + static void PopulateMethod (CallableWrapperMethod method, string signature, string? managedParameters, string? outerType, string? superCall) + { + method.ManagedParameters = managedParameters; + + var jnisig = signature; + var closer = jnisig.IndexOf (')'); + var ret = jnisig.Substring (closer + 1); + + method.Retval = JavaNativeTypeManager.Parse (ret)?.Type; + + var jniparms = jnisig.Substring (1, closer - 1); + + if (string.IsNullOrEmpty (jniparms) && string.IsNullOrEmpty (superCall)) + return; + + var parms = new StringBuilder (); + var scall = new StringBuilder (); + var acall = new StringBuilder (); + var first = true; + var i = 0; + + foreach (var jti in JavaNativeTypeManager.FromSignature (jniparms)) { + if (outerType != null) { + acall.Append (outerType).Append (".this"); + outerType = null; + continue; + } + + var parmType = jti.Type; + + if (!first) { + parms.Append (", "); + scall.Append (", "); + acall.Append (", "); + } + + first = false; + parms.Append (parmType).Append (" p").Append (i); + scall.Append ("p").Append (i); + acall.Append ("p").Append (i); + ++i; + } + + method.Params = parms.ToString (); + method.SuperCall = superCall ?? scall.ToString (); + method.ActivateCall = acall.ToString (); + } + + static void AddConstructors (CallableWrapperType declaringType, TypeDefinition type, TypeDefinition rootType, string? outerType, List? baseCtors, List curCtors, bool onlyRegisteredOrExportedCtors, IMetadataResolver cache) + { + foreach (var ctor in type.Methods) + if (ctor.IsConstructor && !ctor.IsStatic && !ctor.AnyCustomAttributes (typeof (ExportAttribute))) + if (CreateConstructor (declaringType, ctor, type, rootType, outerType, baseCtors, curCtors, onlyRegisteredOrExportedCtors, false, cache) is CallableWrapperConstructor c) + declaringType.Constructors.Add (c); + } + + static CallableWrapperConstructor? CreateConstructor (CallableWrapperType declaringType, MethodDefinition ctor, TypeDefinition type, TypeDefinition rootType, string? outerType, List? baseCtors, List curCtors, bool onlyRegisteredOrExportedCtors, bool skipParameterCheck, IMetadataResolver cache) + { + // We create a parameter-less constructor for the application class, so don't use the imported one + if (!ctor.HasParameters && JavaNativeTypeManager.IsApplication (rootType, cache)) + return null; + + var managedParameters = GetManagedParameters (ctor, outerType, type, cache); + + if (!skipParameterCheck && (managedParameters == null || declaringType.Constructors.Any (c => c.ManagedParameters == managedParameters))) + return null; + + // Constructor with [Export] attribute + var eattr = CecilExtensions.GetExportAttributes (ctor, cache).FirstOrDefault (); + + if (eattr != null) { + if (!string.IsNullOrEmpty (eattr.Name)) { + // Diagnostic.Warning (log, "Use of ExportAttribute.Name property is invalid on constructors"); + } + + curCtors.Add (ctor); + return CreateConstructor (ctor, declaringType, eattr, managedParameters, cache); + } + + // Constructor with [Register] attribute + var rattr = CecilExtensions.GetMethodRegistrationAttributes (ctor).FirstOrDefault (); + + if (rattr != null) { + if (declaringType.Constructors.Any (c => c.JniSignature == rattr.Signature)) + return null; + + curCtors.Add (ctor); + return CreateConstructor (ctor, declaringType, rattr, managedParameters, outerType, cache); + } + + if (onlyRegisteredOrExportedCtors) + return null; + + // Constructors without [Export] or [Register] attributes + var jniSignature = JavaNativeTypeManager.GetJniSignature (ctor, cache); + + if (jniSignature is null) + return null; + + if (declaringType.Constructors.Any (c => c.JniSignature == jniSignature)) + return null; + + if (baseCtors is null) + throw new InvalidOperationException ("`baseCtors` should not be null!"); + + if (baseCtors.Any (m => m.Parameters.AreParametersCompatibleWith (ctor.Parameters, cache))) { + curCtors.Add (ctor); + return CreateConstructor (".ctor", declaringType, jniSignature, "", managedParameters, outerType, null); + } + + if (baseCtors.Any (m => !m.HasParameters)) { + curCtors.Add (ctor); + return CreateConstructor (".ctor", declaringType, jniSignature, "", managedParameters, outerType, ""); + } + + return null; + } + + static string GetManagedParameters (MethodDefinition ctor, string? outerType, TypeDefinition type, IMetadataResolver cache) + { + var sb = new StringBuilder (); + + foreach (var pdef in ctor.Parameters) { + if (sb.Length > 0) + sb.Append (':'); + if (outerType != null && sb.Length == 0) + sb.Append (type.DeclaringType.GetPartialAssemblyQualifiedName (cache)); + else + sb.Append (pdef.ParameterType.GetPartialAssemblyQualifiedName (cache)); + } + + return sb.ToString (); + } + + static CallableWrapperApplicationConstructor? CreateApplicationConstructor (string name, TypeDefinition type, IMetadataResolver resolver) + { + if (!JavaNativeTypeManager.IsApplication (type, resolver)) + return null; + + return new CallableWrapperApplicationConstructor (name); + } + + static void AddNestedTypes (CallableWrapperType declaringType, TypeDefinition type, IMetadataResolver cache, CallableWrapperReaderOptions? options) + { + if (!type.HasNestedTypes) + return; + + foreach (var nt in type.NestedTypes) { + if (!nt.HasJavaPeer (cache)) + continue; + if (!JavaNativeTypeManager.IsNonStaticInnerClass (nt, cache)) + continue; + + declaringType.NestedTypes.Add (CreateType (nt, cache, options, JavaNativeTypeManager.ToJniName (type, cache))); + AddNestedTypes (declaringType, nt, cache, options); + } + + declaringType.HasExport |= declaringType.NestedTypes.Any (t => t.HasExport); + } + + static void AddMethod (CallableWrapperType declaringType, TypeDefinition type, MethodDefinition? registeredMethod, MethodDefinition implementedMethod, JavaCallableMethodClassifier? methodClassifier, IMetadataResolver cache) + { + if (registeredMethod != null) + foreach (RegisterAttribute attr in CecilExtensions.GetMethodRegistrationAttributes (registeredMethod)) { + // Check for Kotlin-mangled methods that cannot be overridden + if (attr.Name.Contains ("-impl") || (attr.Name.Length > 7 && attr.Name [attr.Name.Length - 8] == '-')) + Diagnostic.Error (4217, CecilExtensions.LookupSource (implementedMethod), Localization.Resources.JavaCallableWrappers_XA4217, attr.Name); + + var shouldBeDynamicallyRegistered = methodClassifier?.ShouldBeDynamicallyRegistered (type, registeredMethod, implementedMethod, attr.OriginAttribute) ?? true; + var method = CreateMethod (implementedMethod, declaringType, attr, null, null, cache, shouldBeDynamicallyRegistered); + + if (!registeredMethod.IsConstructor && !declaringType.Methods.Any (m => m.Name == method.Name && m.Params == method.Params)) + declaringType.Methods.Add (method); + } + foreach (ExportAttribute attr in CecilExtensions.GetExportAttributes (implementedMethod, cache)) { + if (type.HasGenericParameters) + Diagnostic.Error (4206, CecilExtensions.LookupSource (implementedMethod), Localization.Resources.JavaCallableWrappers_XA4206); + + var method = CreateMethod (implementedMethod, declaringType, attr, null, cache); + + if (!string.IsNullOrEmpty (attr.SuperArgumentsString)) { + // Diagnostic.Warning (log, "Use of ExportAttribute.SuperArgumentsString property is invalid on methods"); + } + + if (!implementedMethod.IsConstructor && !declaringType.Methods.Any (m => m.Name == method.Name && m.Params == method.Params)) + declaringType.Methods.Add (method); + } + foreach (ExportFieldAttribute attr in CecilExtensions.GetExportFieldAttributes (implementedMethod)) { + if (type.HasGenericParameters) + Diagnostic.Error (4207, CecilExtensions.LookupSource (implementedMethod), Localization.Resources.JavaCallableWrappers_XA4207); + + var method = CreateMethod (implementedMethod, declaringType, cache); + + if (!implementedMethod.IsConstructor && !declaringType.Methods.Any (m => m.Name == method.Name && m.Params == method.Params)) { + declaringType.Methods.Add (method); + declaringType.Fields.Add (CreateField (implementedMethod, attr.Name, cache)); + } + } + } + + static void ExtractJavaNames (string jniName, out string package, out string type) + { + var i = jniName.LastIndexOf ('/'); + + if (i < 0) { + type = jniName; + package = string.Empty; + } else { + type = jniName.Substring (i + 1); + package = jniName.Substring (0, i).Replace ('/', '.'); + } + } + + static string GetJavaTypeName (TypeReference r, IMetadataResolver cache) + { + var d = cache.Resolve (r); + var jniName = JavaNativeTypeManager.ToJniName (d, cache); + + if (jniName is null) { + Diagnostic.Error (4201, Localization.Resources.JavaCallableWrappers_XA4201, r.FullName); + throw new InvalidOperationException ("--nrt:jniName-- Should not be reached"); + } + + return jniName.Replace ('/', '.').Replace ('$', '.'); + } + + static IEnumerable CreateAnnotations (ICustomAttributeProvider type, IMetadataResolver resolver) + { + foreach (var ca in type.CustomAttributes) { + var annotation = CreateAnnotation (ca, resolver); + + if (annotation is not null) + yield return annotation; + } + } + + static CallableWrapperTypeAnnotation? CreateAnnotation (CustomAttribute ca, IMetadataResolver resolver) + { + var catype = resolver.Resolve (ca.AttributeType); + var tca = catype.CustomAttributes.FirstOrDefault (a => a.AttributeType.FullName == "Android.Runtime.AnnotationAttribute"); + + if (tca is null) + return null; + + var name_object = tca.ConstructorArguments [0].Value; + + // Should never be hit + if (name_object is not string name) + throw new ArgumentException ($"Expected a string for the first argument of the {nameof (RegisterAttribute)} constructor.", nameof (ca)); + + var annotation = new CallableWrapperTypeAnnotation (name); + + foreach (var p in ca.Properties) { + var pd = catype.Properties.FirstOrDefault (pp => pp.Name == p.Name); + var reg = pd?.CustomAttributes.FirstOrDefault (pdca => pdca.AttributeType.FullName == "Android.Runtime.RegisterAttribute"); + var key = reg != null ? (string) reg.ConstructorArguments [0].Value : p.Name; + var value = ManagedValueToJavaSource (p.Argument.Value); + + annotation.Properties.Add (new KeyValuePair (key, value)); + } + + return annotation; + } + + // FIXME: this is hacky. Is there any existing code for value to source conversion? + static string ManagedValueToJavaSource (object value) + { + if (value is string) + return "\"" + value.ToString ().Replace ("\"", "\"\"") + '"'; + else if (value.GetType ().FullName == "Java.Lang.Class") + return value.ToString () + ".class"; + else if (value is bool v) + return v ? "true" : "false"; + else + return value.ToString (); + } + + static string GetJavaAccess (MethodAttributes access) + { + return access switch { + MethodAttributes.Public => "public", + MethodAttributes.FamORAssem => "protected", + MethodAttributes.Family => "protected", + _ => "private", + }; + } +} diff --git a/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers.CallableWrapperMembers/CallableWrapperApplicationConstructor.cs b/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers.CallableWrapperMembers/CallableWrapperApplicationConstructor.cs new file mode 100644 index 000000000..2d8c40938 --- /dev/null +++ b/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers.CallableWrapperMembers/CallableWrapperApplicationConstructor.cs @@ -0,0 +1,26 @@ +using System.IO; + +namespace Java.Interop.Tools.JavaCallableWrappers.CallableWrapperMembers; + +public class CallableWrapperApplicationConstructor +{ + public string Name { get; set; } + + public CallableWrapperApplicationConstructor (string name) + { + Name = name; + } + + public void Generate (TextWriter sw, CallableWrapperWriterOptions options) + { + sw.WriteLine (); + + sw.Write ("\tpublic "); + sw.Write (Name); + sw.WriteLine (" ()"); + + sw.WriteLine ("\t{"); + sw.WriteLine ("\t\tmono.MonoPackageManager.setContext (this);"); + sw.WriteLine ("\t}"); + } +} diff --git a/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers.CallableWrapperMembers/CallableWrapperConstructor.cs b/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers.CallableWrapperMembers/CallableWrapperConstructor.cs new file mode 100644 index 000000000..59ef048ae --- /dev/null +++ b/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers.CallableWrapperMembers/CallableWrapperConstructor.cs @@ -0,0 +1,72 @@ +using System.IO; + +namespace Java.Interop.Tools.JavaCallableWrappers.CallableWrapperMembers; + +public class CallableWrapperConstructor : CallableWrapperMethod +{ + public CallableWrapperConstructor (CallableWrapperType declaringType, string name, string method, string jniSignature) : base (declaringType, name, method, jniSignature) + { + } + + public override void Generate (TextWriter sw, CallableWrapperWriterOptions options) + { + // TODO: we only generate constructors so that Android types w/ no + // default constructor can be subclasses by our generated code. + // + // This does NOT currently allow creating managed types from Java. + sw.WriteLine (); + + foreach (var annotation in Annotations) + annotation.Generate (sw, "", options); + + sw.Write ("\tpublic "); + sw.Write (Name); + + sw.Write (" ("); + sw.Write (Params); + sw.Write (')'); + + sw.WriteLine (ThrowsDeclaration); + + sw.WriteLine ("\t{"); + sw.Write ("\t\tsuper ("); + sw.Write (SuperCall); + sw.WriteLine (");"); + +#if MONODROID_TIMING + sw.WriteLine ("\t\tandroid.util.Log.i(\"MonoDroid-Timing\", \"{0}..ctor({1}): time: \"+java.lang.System.currentTimeMillis());", Name, Params); +#endif + + if (!DeclaringType.CannotRegisterInStaticConstructor) { + + sw.Write ("\t\tif (getClass () == "); + sw.Write (Name); + sw.WriteLine (".class) {"); + + sw.Write ("\t\t\t"); + + switch (options.CodeGenerationTarget) { + case JavaPeerStyle.JavaInterop1: + sw.Write ("net.dot.jni.ManagedPeer.construct (this, \""); + sw.Write (JniSignature); + sw.Write ("\", new java.lang.Object[] { "); + sw.Write (ActivateCall); + sw.WriteLine (" });"); + break; + default: + sw.Write ("mono.android.TypeManager.Activate (\""); + sw.Write (DeclaringType.PartialAssemblyQualifiedName); + sw.Write ("\", \""); + sw.Write (ManagedParameters); + sw.Write ("\", this, new java.lang.Object[] { "); + sw.Write (ActivateCall); + sw.WriteLine (" });"); + break; + } + + sw.WriteLine ("\t\t}"); + } + + sw.WriteLine ("\t}"); + } +} diff --git a/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers.CallableWrapperMembers/CallableWrapperField.cs b/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers.CallableWrapperMembers/CallableWrapperField.cs new file mode 100644 index 000000000..043dedc9e --- /dev/null +++ b/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers.CallableWrapperMembers/CallableWrapperField.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; +using System.IO; + +namespace Java.Interop.Tools.JavaCallableWrappers.CallableWrapperMembers; + +public class CallableWrapperField +{ + public string FieldName { get; set; } + public string TypeName { get; set; } + public string Visibility { get; set; } + public bool IsStatic { get; set; } + public string InitializerName { get; set; } + public List Annotations { get; } = new List (); + + public CallableWrapperField (string fieldName, string typeName, string visibility, string initializerName) + { + FieldName = fieldName; + TypeName = typeName; + Visibility = visibility; + InitializerName = initializerName; + } + + public void Generate (TextWriter sw, CallableWrapperWriterOptions options) + { + sw.WriteLine (); + + foreach (var annotation in Annotations) + annotation.Generate (sw, "", options); + + sw.Write ("\t"); + sw.Write (Visibility); + sw.Write (' '); + + if (IsStatic) + sw.Write ("static "); + + sw.Write (TypeName); + sw.Write (' '); + + sw.Write (FieldName); + sw.Write (" = "); + + sw.Write (InitializerName); + sw.WriteLine (" ();"); + } +} diff --git a/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers.CallableWrapperMembers/CallableWrapperMethod.cs b/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers.CallableWrapperMembers/CallableWrapperMethod.cs new file mode 100644 index 000000000..9b9e692d5 --- /dev/null +++ b/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers.CallableWrapperMembers/CallableWrapperMethod.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace Java.Interop.Tools.JavaCallableWrappers.CallableWrapperMembers; + +public class CallableWrapperMethod +{ + public string Name { get; set; } + public string Method { get; set; } + public string JniSignature { get; set; } + public string? ManagedParameters { get; set; } + public string? JavaNameOverride { get; set; } + public string? Params { get; set; } + public string? Retval { get; set; } + public string? JavaAccess { get; set; } + public bool IsExport { get; set; } + public bool IsStatic { get; set; } + public bool IsDynamicallyRegistered { get; set; } = true; + public string []? ThrownTypeNames { get; set; } + public string? SuperCall { get; set; } + public string? ActivateCall { get; set; } + public string JavaName => JavaNameOverride ?? Name; + public List Annotations { get; } = new List (); + public CallableWrapperType DeclaringType { get; } + + public string? ThrowsDeclaration => ThrownTypeNames?.Length > 0 ? $" throws {string.Join (", ", ThrownTypeNames)}" : null; + + public CallableWrapperMethod (CallableWrapperType declaringType, string name, string method, string jniSignature) + { + DeclaringType = declaringType; + Name = name; + Method = method; + JniSignature = jniSignature; + } + + public virtual void Generate (TextWriter sw, CallableWrapperWriterOptions options) + { + sw.WriteLine (); + + foreach (var annotation in Annotations) + annotation.Generate (sw, "", options); + + sw.Write ("\t"); + + sw.Write (IsExport ? JavaAccess : "public"); + sw.Write (' '); + + if (IsStatic) + sw.Write ("static "); + + sw.Write (Retval); + sw.Write (' '); + + sw.Write (JavaName); + + sw.Write (" ("); + sw.Write (Params); + sw.Write (')'); + + sw.WriteLine (ThrowsDeclaration); + sw.WriteLine ("\t{"); + +#if MONODROID_TIMING + sw.WriteLine ("\t\tandroid.util.Log.i(\"MonoDroid-Timing\", \"{0}.{1}: time: \"+java.lang.System.currentTimeMillis());", Name, Method); +#endif + + sw.Write ("\t\t"); + sw.Write (Retval == "void" ? string.Empty : "return "); + + sw.Write ("n_"); + sw.Write (Name); + + sw.Write (" ("); + sw.Write (ActivateCall); + sw.WriteLine (");"); + + sw.WriteLine ("\t}"); + sw.WriteLine (); + + sw.Write ("\tprivate "); + + if (IsStatic) + sw.Write ("static "); + + sw.Write ("native "); + + sw.Write (Retval); + + sw.Write (" n_"); + sw.Write (Name); + + sw.Write (" ("); + sw.Write (Params); + sw.WriteLine (");"); + } +} diff --git a/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers.CallableWrapperMembers/CallableWrapperType.cs b/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers.CallableWrapperMembers/CallableWrapperType.cs new file mode 100644 index 000000000..2fd5d082e --- /dev/null +++ b/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers.CallableWrapperMembers/CallableWrapperType.cs @@ -0,0 +1,322 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Java.Interop.Tools.JavaCallableWrappers.CallableWrapperMembers; + +public class CallableWrapperType +{ + public string Name { get; set; } + public string Package { get; set; } + public bool IsAbstract { get; set; } + public string? ApplicationJavaClass { get; set; } + public bool GenerateOnCreateOverrides { get; set; } + /// + /// The Java source code to be included in Instrumentation.onCreate + /// + /// Originally came from MonoRuntimeProvider.java delimited by: + /// // Mono Runtime Initialization {{{ + /// // }}} + /// + public string? MonoRuntimeInitialization { get; set; } + public string? ExtendsType { get; set; } + public CallableWrapperApplicationConstructor? ApplicationConstructor { get; set; } + public bool IsApplication { get; set; } + public bool IsInstrumentation { get; set; } + public string PartialAssemblyQualifiedName { get; set; } + public bool HasExport { get; set; } + + public List Annotations { get; } = new List (); + public List ImplementedInterfaces { get; } = new List (); + public List Constructors { get; } = new List (); + public List Fields { get; } = new List (); + public List Methods { get; } = new List (); + public List NestedTypes { get; } = new List (); + + public bool CannotRegisterInStaticConstructor => IsApplication || IsInstrumentation; + + public CallableWrapperType (string name, string package, string partialAssemblyQualifiedName) + { + Name = name; + Package = package; + PartialAssemblyQualifiedName = partialAssemblyQualifiedName; + } + + // example of java target to generate for a type + // + // package mono.droid; + // + // import android.app.Activity; + // import android.os.Bundle; + // + // public class MonoActivity extends android.app.Activity + // { + // static final String __md_methods; + // static { + // __md_methods = + // "n_OnCreate:(Landroid/os/Bundle;)V:GetOnCreate_Landroid_os_Bundle_Handler\n" + + // ""; + // mono.android.Runtime.register ("Mono.Droid.MonoActivity, AssemblyName", MonoActivity.class, __md_methods); + // } + // + // public void onCreate(android.os.Bundle savedInstanceState) + // { + // n_onCreate (savedInstanceState); + // } + // + // private native void n_onCreate (android.os.Bundle bundle); + // } + public void Generate (TextWriter writer, CallableWrapperWriterOptions options, bool isNested = false) + { + if (!isNested && !string.IsNullOrEmpty (Package)) { + writer.WriteLine ("package " + Package + ";"); + writer.WriteLine (); + } + + GenerateHeader (writer, options); + + if (!isNested) + GenerateInfrastructure (writer, options); + + GenerateBody (writer, options); + + foreach (var nested in NestedTypes) + nested.Generate (writer, options, true); + + GenerateFooter (writer, options); + } + + void GenerateHeader (TextWriter sw, CallableWrapperWriterOptions options) + { + sw.WriteLine (); + + // Type annotations + foreach (var annotation in Annotations) + annotation.Generate (sw, "", options); + + sw.WriteLine ("public " + (IsAbstract ? "abstract " : "") + "class " + Name); + + var extends = ExtendsType; + + // Do this check here rather than the constructor because it can be set after the constructor is called + if (extends == "android.app.Application" && ApplicationJavaClass != null && !string.IsNullOrEmpty (ApplicationJavaClass)) + extends = ApplicationJavaClass; + + sw.WriteLine ("\textends " + extends); + + sw.WriteLine ("\timplements"); + sw.Write ("\t\t"); + + switch (options.CodeGenerationTarget) { + case JavaPeerStyle.JavaInterop1: + sw.Write ("net.dot.jni.GCUserPeerable"); + break; + default: + sw.Write ("mono.android.IGCUserPeer"); + break; + } + + foreach (var iface in ImplementedInterfaces) { + sw.WriteLine (","); + sw.Write ("\t\t"); + sw.Write (iface); + } + + sw.WriteLine (); + sw.WriteLine ("{"); + } + + void GenerateInfrastructure (TextWriter writer, CallableWrapperWriterOptions options) + { + var needCtor = false; + + if (HasDynamicallyRegisteredMethods) { + needCtor = true; + writer.WriteLine ("/** @hide */"); + writer.WriteLine ("\tpublic static final String __md_methods;"); + } + + for (var i = 0; i < NestedTypes.Count; i++) { + if (!NestedTypes [i].HasDynamicallyRegisteredMethods) + continue; + + needCtor = true; + writer.Write ("\tstatic final String __md_"); + writer.Write (i + 1); + writer.WriteLine ("_methods;"); + } + + if (needCtor) { + writer.WriteLine ("\tstatic {"); + + if (HasDynamicallyRegisteredMethods) + GenerateRegisterType (writer, this, "__md_methods", options); + + for (var i = 0; i < NestedTypes.Count; ++i) + GenerateRegisterType (writer, NestedTypes [i], $"__md_{i + 1}_methods", options); + + writer.WriteLine ("\t}"); + } + } + + void GenerateBody (TextWriter sw, CallableWrapperWriterOptions options) + { + foreach (var ctor in Constructors) + ctor.Generate (sw, options); + + ApplicationConstructor?.Generate (sw, options); + + foreach (var field in Fields) + field.Generate (sw, options); + + foreach (var method in Methods) + method.Generate (sw, options); + + if (GenerateOnCreateOverrides && IsApplication && !Methods.Any (m => m.Name == "onCreate")) + WriteApplicationOnCreate (sw, options); + + if (GenerateOnCreateOverrides && IsInstrumentation && !Methods.Any (m => m.Name == "onCreate")) + WriteInstrumentationOnCreate (sw, options); + + var addRef = options.CodeGenerationTarget == JavaPeerStyle.JavaInterop1 ? "jiAddManagedReference" : "monodroidAddReference"; + var clearRefs = options.CodeGenerationTarget == JavaPeerStyle.JavaInterop1 ? "jiClearManagedReferences" : "monodroidClearReferences"; + + sw.WriteLine (); + sw.WriteLine ("\tprivate java.util.ArrayList refList;"); + + sw.WriteLine ($"\tpublic void {addRef} (java.lang.Object obj)"); + sw.WriteLine ("\t{"); + sw.WriteLine ("\t\tif (refList == null)"); + sw.WriteLine ("\t\t\trefList = new java.util.ArrayList ();"); + sw.WriteLine ("\t\trefList.add (obj);"); + sw.WriteLine ("\t}"); + sw.WriteLine (); + + sw.WriteLine ($"\tpublic void {clearRefs} ()"); + sw.WriteLine ("\t{"); + sw.WriteLine ("\t\tif (refList != null)"); + sw.WriteLine ("\t\t\trefList.clear ();"); + sw.WriteLine ("\t}"); + } + + void GenerateFooter (TextWriter sw, CallableWrapperWriterOptions options) + { + sw.WriteLine ("}"); + } + + void WriteApplicationOnCreate (TextWriter sw, CallableWrapperWriterOptions options) + { + sw.WriteLine (); + + sw.WriteLine ("\tpublic void onCreate ()"); + sw.WriteLine ("\t{"); + + sw.Write ("\t\tmono.android.Runtime.register (\""); + sw.Write (PartialAssemblyQualifiedName); + sw.Write ("\", "); + sw.Write (Name); + sw.WriteLine (".class, __md_methods);"); + + sw.WriteLine ("\t\tsuper.onCreate ();"); + sw.WriteLine ("\t}"); + } + + void WriteInstrumentationOnCreate (TextWriter sw, CallableWrapperWriterOptions options) + { + sw.WriteLine (); + sw.WriteLine ("\tpublic void onCreate (android.os.Bundle arguments)"); + sw.WriteLine ("\t{"); + +#if MONODROID_TIMING + sw.WriteLine ("\t\tandroid.util.Log.i(\"MonoDroid-Timing\", \"{0}.onCreate(Bundle): time: \"+java.lang.System.currentTimeMillis());", Name); + sw.WriteLine (); +#endif + + sw.WriteLine ("\t\tandroid.content.Context context = getContext ();"); + sw.WriteLine (); + + if (!string.IsNullOrEmpty (MonoRuntimeInitialization)) { + sw.WriteLine (MonoRuntimeInitialization); + sw.WriteLine (); + } + + sw.Write ("\t\tmono.android.Runtime.register (\""); + sw.Write (PartialAssemblyQualifiedName); + sw.Write ("\", "); + sw.Write (Name); + sw.WriteLine (".class, __md_methods);"); + + sw.WriteLine ("\t\tsuper.onCreate (arguments);"); + sw.WriteLine ("\t}"); + } + + void GenerateRegisterType (TextWriter sw, CallableWrapperType self, string field, CallableWrapperWriterOptions options) + { + if (!self.HasDynamicallyRegisteredMethods) + return; + + sw.Write ("\t\t"); + sw.Write (field); + sw.WriteLine (" = "); + + foreach (var method in self.Methods) { + if (method.IsDynamicallyRegistered) { + sw.Write ("\t\t\t\"", method.Method); + sw.Write (method.Method); + sw.WriteLine ("\\n\" +"); + } + } + + sw.WriteLine ("\t\t\t\"\";"); + + if (CannotRegisterInStaticConstructor) + return; + + sw.Write ("\t\t"); + + switch (options.CodeGenerationTarget) { + case JavaPeerStyle.JavaInterop1: + sw.Write ("net.dot.jni.ManagedPeer.registerNativeMembers ("); + sw.Write (self.Name); + sw.Write (".class, "); + sw.Write (field); + sw.WriteLine (");"); + break; + default: + sw.Write ("mono.android.Runtime.register (\""); + sw.Write (self.PartialAssemblyQualifiedName); + sw.Write ("\", "); + sw.Write (self.Name); + sw.Write (".class, "); + sw.Write (field); + sw.WriteLine (");"); + break; + } + } + + // If there are no methods, we need to generate "empty" registration because of backward compatibility + public bool HasDynamicallyRegisteredMethods => Methods.Count == 0 || Methods.Any (sig => sig.IsDynamicallyRegistered); + + /// + /// Returns a destination file path based on the package name of this Java type + /// + public string GetDestinationPath (string outputPath) + { + var dir = Package.Replace ('.', Path.DirectorySeparatorChar); + return Path.Combine (outputPath, dir, Name + ".java"); + } + + public void Generate (string outputPath, CallableWrapperWriterOptions options) + { + using (StreamWriter sw = OpenStream (outputPath)) + Generate (sw, options, false); + } + + StreamWriter OpenStream (string outputPath) + { + var destination = GetDestinationPath (outputPath); + Directory.CreateDirectory (Path.GetDirectoryName (destination)); + + return new StreamWriter (new FileStream (destination, FileMode.Create, FileAccess.Write)); + } +} diff --git a/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers.CallableWrapperMembers/CallableWrapperTypeAnnotation.cs b/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers.CallableWrapperMembers/CallableWrapperTypeAnnotation.cs new file mode 100644 index 000000000..e605eee62 --- /dev/null +++ b/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers.CallableWrapperMembers/CallableWrapperTypeAnnotation.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Java.Interop.Tools.JavaCallableWrappers.CallableWrapperMembers; + +public class CallableWrapperTypeAnnotation +{ + public string Name { get; set; } + public List> Properties { get; } = new (); + + public CallableWrapperTypeAnnotation (string name) + { + Name = name; + } + + public void Generate (TextWriter sw, string indent, CallableWrapperWriterOptions options) + { + sw.Write (indent); + sw.Write ('@'); + sw.Write (Name); + + var properties = string.Join (", ", Properties.Select (p => $"{p.Key} = {p.Value}")); + + if (!string.IsNullOrEmpty (properties)) { + sw.Write (" ("); + sw.Write (properties); + sw.Write (")"); + } + + sw.WriteLine (); + } +} diff --git a/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers.csproj b/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers.csproj index 7e4704293..05f576b4d 100644 --- a/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers.csproj +++ b/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers.csproj @@ -3,7 +3,7 @@ netstandard2.0 false - 8.0 + 11.0 enable true true diff --git a/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/CallableWrapperReaderOptions.cs b/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/CallableWrapperReaderOptions.cs new file mode 100644 index 000000000..acd5a9633 --- /dev/null +++ b/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/CallableWrapperReaderOptions.cs @@ -0,0 +1,9 @@ +namespace Java.Interop.Tools.JavaCallableWrappers; + +public class CallableWrapperReaderOptions +{ + public string? DefaultApplicationJavaClass { get; set; } + public bool DefaultGenerateOnCreateOverrides { get; set; } + public string? DefaultMonoRuntimeInitialization { get; set; } + public JavaCallableMethodClassifier? MethodClassifier { get; set; } +} diff --git a/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/CallableWrapperWriterOptions.cs b/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/CallableWrapperWriterOptions.cs new file mode 100644 index 000000000..e0bd76c08 --- /dev/null +++ b/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/CallableWrapperWriterOptions.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Java.Interop.Tools.JavaCallableWrappers; + +public class CallableWrapperWriterOptions +{ + public JavaPeerStyle CodeGenerationTarget { get; set; } +} diff --git a/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/CecilExtensions.cs b/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/CecilExtensions.cs new file mode 100644 index 000000000..c73b829b2 --- /dev/null +++ b/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/CecilExtensions.cs @@ -0,0 +1,237 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using Android.Runtime; +using Java.Interop.Tools.Cecil; +using Java.Interop.Tools.TypeNameMappings; +using Mono.Cecil; +using Mono.Cecil.Cil; +using static Java.Interop.Tools.TypeNameMappings.JavaNativeTypeManager; + +namespace Java.Interop.Tools.JavaCallableWrappers.Utilities; + +static class CecilExtensions +{ + public static IEnumerable GetExportFieldAttributes (Mono.Cecil.ICustomAttributeProvider p) + { + return GetAttributes (p, a => ToExportFieldAttribute (a)); + } + + public static IEnumerable GetAttributes (Mono.Cecil.ICustomAttributeProvider p, Func selector) + where TAttribute : class + { + return GetAttributes (p, typeof (TAttribute).FullName, selector); + } + + public static IEnumerable GetAttributes (Mono.Cecil.ICustomAttributeProvider p, string attributeName, Func selector) + where TAttribute : class + { + return p.GetCustomAttributes (attributeName) + .Select (selector) + .Where (v => v != null) + .Select (v => v!); + } + + internal static ExportFieldAttribute ToExportFieldAttribute (CustomAttribute attr) + { + return new ExportFieldAttribute ((string) attr.ConstructorArguments [0].Value); + } + + public static MethodDefinition? GetBaseRegisteredMethod (MethodDefinition method, IMetadataResolver cache) + { + MethodDefinition bmethod; + while ((bmethod = method.GetBaseDefinition (cache)) != method) { + method = bmethod; + + if (method.AnyCustomAttributes (typeof (RegisterAttribute))) { + return method; + } + } + return null; + } + + internal static RegisterAttribute? ToRegisterAttribute (CustomAttribute attr) + { + // attr.Resolve (); + RegisterAttribute? r = null; + if (attr.ConstructorArguments.Count == 1) + r = new RegisterAttribute ((string) attr.ConstructorArguments [0].Value, attr); + else if (attr.ConstructorArguments.Count == 3) + r = new RegisterAttribute ( + (string) attr.ConstructorArguments [0].Value, + (string) attr.ConstructorArguments [1].Value, + (string) attr.ConstructorArguments [2].Value, + attr); + if (r != null) { + var v = attr.Properties.FirstOrDefault (p => p.Name == "DoNotGenerateAcw"); + r.DoNotGenerateAcw = v.Name == null ? false : (bool) v.Argument.Value; + } + return r; + } + + + internal static RegisterAttribute? RegisterFromJniTypeSignatureAttribute (CustomAttribute attr) + { + // attr.Resolve (); + RegisterAttribute? r = null; + if (attr.ConstructorArguments.Count == 1) + r = new RegisterAttribute ((string) attr.ConstructorArguments [0].Value, attr); + if (r != null) { + var v = attr.Properties.FirstOrDefault (p => p.Name == "GenerateJavaPeer"); + if (v.Name == null) { + r.DoNotGenerateAcw = false; + } else if (v.Name == "GenerateJavaPeer") { + r.DoNotGenerateAcw = !(bool) v.Argument.Value; + } + var isKeyProp = attr.Properties.FirstOrDefault (p => p.Name == "IsKeyword"); + var isKeyword = isKeyProp.Name != null && ((bool) isKeyProp.Argument.Value) == true; + var arrRankProp = attr.Properties.FirstOrDefault (p => p.Name == "ArrayRank"); + if (arrRankProp.Name != null && arrRankProp.Argument.Value is int rank) { + r.Name = new string ('[', rank) + (isKeyword ? r.Name : "L" + r.Name + ";"); + } + } + return r; + } + + internal static RegisterAttribute? RegisterFromJniConstructorSignatureAttribute (CustomAttribute attr) + { + // attr.Resolve (); + RegisterAttribute? r = null; + if (attr.ConstructorArguments.Count == 1) + r = new RegisterAttribute ( + name: ".ctor", + signature: (string) attr.ConstructorArguments [0].Value, + connector: "", + originAttribute: attr); + return r; + } + + internal static RegisterAttribute? RegisterFromJniMethodSignatureAttribute (CustomAttribute attr) + { + // attr.Resolve (); + RegisterAttribute? r = null; + if (attr.ConstructorArguments.Count == 2) + r = new RegisterAttribute ((string) attr.ConstructorArguments [0].Value, + (string) attr.ConstructorArguments [1].Value, + "", + attr); + return r; + } + + static ExportAttribute ToExportAttribute (CustomAttribute attr, IMemberDefinition declaringMember, IMetadataResolver cache) + { + var name = attr.ConstructorArguments.Count > 0 ? (string) attr.ConstructorArguments [0].Value : declaringMember.Name; + if (attr.Properties.Count == 0) + return new ExportAttribute (name); + var typeArgs = (CustomAttributeArgument []) attr.Properties.FirstOrDefault (p => p.Name == "Throws").Argument.Value; + var thrown = typeArgs != null && typeArgs.Any () + ? (from caa in typeArgs select JavaNativeTypeManager.Parse (GetJniTypeName ((TypeReference) caa.Value, cache))?.Type) + .Where (v => v != null) + .ToArray () + : null; + var superArgs = (string) attr.Properties.FirstOrDefault (p => p.Name == "SuperArgumentsString").Argument.Value; + return new ExportAttribute (name) { ThrownNames = thrown, SuperArgumentsString = superArgs }; + } + + static ExportAttribute ToExportAttributeFromJavaCallableAttribute (CustomAttribute attr, IMemberDefinition declaringMember) + { + var name = attr.ConstructorArguments.Count > 0 + ? (string) attr.ConstructorArguments [0].Value + : declaringMember.Name; + return new ExportAttribute (name); + } + + static ExportAttribute ToExportAttributeFromJavaCallableConstructorAttribute (CustomAttribute attr, IMemberDefinition declaringMember) + { + var superArgs = (string) attr.Properties + .FirstOrDefault (p => p.Name == "SuperConstructorExpression") + .Argument + .Value; + return new ExportAttribute (".ctor") { + SuperArgumentsString = superArgs, + }; + } + + internal static IEnumerable GetTypeRegistrationAttributes (Mono.Cecil.ICustomAttributeProvider p) + { + foreach (var a in CecilExtensions.GetAttributes (p, a => CecilExtensions.ToRegisterAttribute (a))) { + yield return a; + } + foreach (var c in p.GetCustomAttributes ("Java.Interop.JniTypeSignatureAttribute")) { + var r = RegisterFromJniTypeSignatureAttribute (c); + if (r == null) { + continue; + } + yield return r; + } + } + + public static IEnumerable GetMethodRegistrationAttributes (Mono.Cecil.ICustomAttributeProvider p) + { + foreach (var a in CecilExtensions.GetAttributes (p, a => CecilExtensions.ToRegisterAttribute (a))) { + yield return a; + } + foreach (var c in p.GetCustomAttributes ("Java.Interop.JniConstructorSignatureAttribute")) { + var r = RegisterFromJniConstructorSignatureAttribute (c); + if (r == null) { + continue; + } + yield return r; + } + foreach (var c in p.GetCustomAttributes ("Java.Interop.JniMethodSignatureAttribute")) { + var r = RegisterFromJniMethodSignatureAttribute (c); + if (r == null) { + continue; + } + yield return r; + } + } + + public static IEnumerable GetExportAttributes (IMemberDefinition p, IMetadataResolver cache) + { + return CecilExtensions.GetAttributes (p, a => CecilExtensions.ToExportAttribute (a, p, cache)) + .Concat (CecilExtensions.GetAttributes (p, "Java.Interop.JavaCallableAttribute", + a => CecilExtensions.ToExportAttributeFromJavaCallableAttribute (a, p))) + .Concat (CecilExtensions.GetAttributes (p, "Java.Interop.JavaCallableConstructorAttribute", + a => CecilExtensions.ToExportAttributeFromJavaCallableConstructorAttribute (a, p))); + } + + public static SequencePoint? LookupSource (MethodDefinition method) + { + if (!method.HasBody) + return null; + + foreach (var ins in method.Body.Instructions) { + var seqPoint = method.DebugInformation.GetSequencePoint (ins); + if (seqPoint != null) + return seqPoint; + } + + return null; + } + + public static SequencePoint? LookupSource (TypeDefinition type) + { + SequencePoint? candidate = null; + foreach (var method in type.Methods) { + if (!method.HasBody) + continue; + + foreach (var ins in method.Body.Instructions) { + var seq = method.DebugInformation.GetSequencePoint (ins); + if (seq == null) + continue; + + if (Regex.IsMatch (seq.Document.Url, ".+\\.(g|designer)\\..+")) + break; + if (candidate == null || seq.StartLine < candidate.StartLine) + candidate = seq; + break; + } + } + + return candidate; + } +} diff --git a/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/JavaCallableMethodClassifier.cs b/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/JavaCallableMethodClassifier.cs new file mode 100644 index 000000000..75e8061d6 --- /dev/null +++ b/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/JavaCallableMethodClassifier.cs @@ -0,0 +1,9 @@ +using Mono.Cecil; + +namespace Java.Interop.Tools.JavaCallableWrappers +{ + public abstract class JavaCallableMethodClassifier + { + public abstract bool ShouldBeDynamicallyRegistered (TypeDefinition topType, MethodDefinition registeredMethod, MethodDefinition implementedMethod, CustomAttribute? registerAttribute); + } +} diff --git a/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/JavaCallableWrapperGenerator.cs b/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/JavaCallableWrapperGenerator.cs deleted file mode 100644 index 699b3597e..000000000 --- a/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/JavaCallableWrapperGenerator.cs +++ /dev/null @@ -1,1176 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Text.RegularExpressions; - -using Mono.Cecil; -using Mono.Cecil.Cil; - -using Android.Runtime; - -using Java.Interop.Tools.Cecil; -using Java.Interop.Tools.Diagnostics; -using Java.Interop.Tools.TypeNameMappings; - -using MethodAttributes = Mono.Cecil.MethodAttributes; -using static Java.Interop.Tools.TypeNameMappings.JavaNativeTypeManager; - -namespace Java.Interop.Tools.JavaCallableWrappers { - - public enum JavaPeerStyle { - XAJavaInterop1, - JavaInterop1, - } - - public abstract class JavaCallableMethodClassifier - { - public abstract bool ShouldBeDynamicallyRegistered (TypeDefinition topType, MethodDefinition registeredMethod, MethodDefinition implementedMethod, CustomAttribute? registerAttribute); - } - - public class JavaCallableWrapperGenerator { - - class JavaFieldInfo { - public JavaFieldInfo (MethodDefinition method, string fieldName, IMetadataResolver resolver) - { - this.FieldName = fieldName; - InitializerName = method.Name; - TypeName = JavaNativeTypeManager.ReturnTypeFromSignature (GetJniSignature (method, resolver))?.Type - ?? throw new ArgumentException ($"Could not get JNI signature for method `{method.Name}`", nameof (method)); - IsStatic = method.IsStatic; - Access = method.Attributes & MethodAttributes.MemberAccessMask; - Annotations = GetAnnotationsString ("\t", method.CustomAttributes, resolver); - } - - public MethodAttributes Access { get; private set; } - public bool IsStatic { get; private set; } - public string TypeName { get; private set; } - public string FieldName { get; private set; } - public string InitializerName { get; private set; } - public string Annotations { get; private set; } - - public string GetJavaAccess () - { - return JavaCallableWrapperGenerator.GetJavaAccess (Access); - } - } - - Action log; - string name; - string package; - TypeDefinition type; - List exported_fields = new List (); - List methods = new List (); - List ctors = new List (); - List? children; - - readonly IMetadataResolver cache; - readonly JavaCallableMethodClassifier? methodClassifier; - - [Obsolete ("Use the TypeDefinitionCache overload for better performance.", error: true)] - public JavaCallableWrapperGenerator (TypeDefinition type, Action log) => throw new NotSupportedException (); - - public JavaCallableWrapperGenerator (TypeDefinition type, Action log, TypeDefinitionCache cache) - : this (type, log, (IMetadataResolver) cache, methodClassifier: null) - { } - - public JavaCallableWrapperGenerator (TypeDefinition type, Action log, TypeDefinitionCache cache, JavaCallableMethodClassifier? methodClassifier) - : this (type, log, (IMetadataResolver) cache, methodClassifier) - { - } - - public JavaCallableWrapperGenerator (TypeDefinition type, Action log, IMetadataResolver resolver) - : this (type, log, resolver, methodClassifier: null) - { } - - public JavaCallableWrapperGenerator (TypeDefinition type, Action log, IMetadataResolver resolver, JavaCallableMethodClassifier? methodClassifier) - : this (type, null, log, resolver, methodClassifier) - { - AddNestedTypes (type); - } - - public string? ApplicationJavaClass { get; set; } - public JavaPeerStyle CodeGenerationTarget { get; set; } - - public bool GenerateOnCreateOverrides { get; set; } - - public bool HasExport { get; private set; } - - // If there are no methods, we need to generate "empty" registration because of backward compatibility - public bool HasDynamicallyRegisteredMethods => methods.Count == 0 || methods.Any ((Signature sig) => sig.IsDynamicallyRegistered); - - /// - /// The Java source code to be included in Instrumentation.onCreate - /// - /// Originally came from MonoRuntimeProvider.java delimited by: - /// // Mono Runtime Initialization {{{ - /// // }}} - /// - public string? MonoRuntimeInitialization { get; set; } - - public string Name { - get { return name; } - } - - void AddNestedTypes (TypeDefinition type) - { - if (!type.HasNestedTypes) { - return; - } - children = children ?? new List (); - foreach (TypeDefinition nt in type.NestedTypes) { - if (!nt.HasJavaPeer (cache)) - continue; - if (!JavaNativeTypeManager.IsNonStaticInnerClass (nt, cache)) - continue; - children.Add (new JavaCallableWrapperGenerator (nt, JavaNativeTypeManager.ToJniName (type, cache), log, cache)); - AddNestedTypes (nt); - } - HasExport |= children.Any (t => t.HasExport); - } - - JavaCallableWrapperGenerator (TypeDefinition type, string? outerType, Action log, IMetadataResolver resolver, JavaCallableMethodClassifier? methodClassifier = null) - { - this.methodClassifier = methodClassifier; - this.type = type; - this.log = log; - this.cache = resolver ?? new TypeDefinitionCache (); - - if (type.IsEnum || type.IsInterface || type.IsValueType) - Diagnostic.Error (4200, LookupSource (type), Localization.Resources.JavaCallableWrappers_XA4200, type.FullName); - - string jniName = JavaNativeTypeManager.ToJniName (type, this.cache); - if (jniName == null) { - Diagnostic.Error (4201, LookupSource (type), Localization.Resources.JavaCallableWrappers_XA4201, type.FullName); - throw new InvalidOperationException ("--nrt:jniName-- Should not be reached"); - } - if (outerType != null && !string.IsNullOrEmpty (outerType)) { - string p; - jniName = jniName.Substring (outerType.Length + 1); - ExtractJavaNames (outerType, out p, out outerType); - } - ExtractJavaNames (jniName, out package, out name); - if (string.IsNullOrEmpty (package) && - (type.IsSubclassOf ("Android.App.Activity", cache) || - type.IsSubclassOf ("Android.App.Application", cache) || - type.IsSubclassOf ("Android.App.Service", cache) || - type.IsSubclassOf ("Android.Content.BroadcastReceiver", cache) || - type.IsSubclassOf ("Android.Content.ContentProvider", cache))) - Diagnostic.Error (4203, LookupSource (type), Localization.Resources.JavaCallableWrappers_XA4203, jniName); - - foreach (MethodDefinition minfo in type.Methods.Where (m => !m.IsConstructor)) { - var baseRegisteredMethod = GetBaseRegisteredMethod (minfo); - if (baseRegisteredMethod != null) - AddMethod (baseRegisteredMethod, minfo); - else if (minfo.AnyCustomAttributes ("Java.Interop.JavaCallableAttribute")) { - AddMethod (null, minfo); - HasExport = true; - } else if (minfo.AnyCustomAttributes ("Java.Interop.JavaCallableConstructorAttribute")) { - AddMethod (null, minfo); - HasExport = true; - } else if (minfo.AnyCustomAttributes (typeof(ExportFieldAttribute))) { - AddMethod (null, minfo); - HasExport = true; - } else if (minfo.AnyCustomAttributes (typeof (ExportAttribute))) { - AddMethod (null, minfo); - HasExport = true; - } - } - - foreach (InterfaceImplementation ifaceInfo in type.Interfaces) { - var typeReference = ifaceInfo.InterfaceType; - var typeDefinition = cache.Resolve (typeReference); - if (typeDefinition == null) { - Diagnostic.Error (4204, - LookupSource (type), - Localization.Resources.JavaCallableWrappers_XA4204, - typeReference.FullName); - continue; - } - if (!GetTypeRegistrationAttributes (typeDefinition).Any ()) - continue; - foreach (MethodDefinition imethod in typeDefinition.Methods) { - if (imethod.IsStatic) - continue; - AddMethod (imethod, imethod); - } - } - - var ctorTypes = new List () { - type, - }; - foreach (var bt in type.GetBaseTypes (cache)) { - ctorTypes.Add (bt); - RegisterAttribute rattr = GetMethodRegistrationAttributes (bt).FirstOrDefault (); - if (rattr != null && rattr.DoNotGenerateAcw) - break; - } - ctorTypes.Reverse (); - - var curCtors = new List (); - - foreach (MethodDefinition minfo in type.Methods) { - if (minfo.IsConstructor && minfo.AnyCustomAttributes (typeof (ExportAttribute))) { - if (minfo.IsStatic) { - // Diagnostic.Warning (log, "ExportAttribute does not work on static constructor"); - } - else { - AddConstructor (minfo, ctorTypes [0], outerType, null, curCtors, false, true); - HasExport = true; - } - } - } - - AddConstructors (ctorTypes [0], outerType, null, curCtors, true); - - for (int i = 1; i < ctorTypes.Count; ++i) { - var baseCtors = curCtors; - curCtors = new List (); - AddConstructors (ctorTypes [i], outerType, baseCtors, curCtors, false); - } - } - - static void ExtractJavaNames (string jniName, out string package, out string type) - { - int i = jniName.LastIndexOf ('/'); - if (i < 0) { - type = jniName; - package = string.Empty; - } - else { - type = jniName.Substring (i+1); - package = jniName.Substring (0, i).Replace ('/', '.'); - } - } - - static SequencePoint? LookupSource (MethodDefinition method) - { - if (!method.HasBody) - return null; - - foreach (var ins in method.Body.Instructions) { - var seqPoint = method.DebugInformation.GetSequencePoint (ins); - if (seqPoint != null) - return seqPoint; - } - - return null; - } - - static SequencePoint? LookupSource (TypeDefinition type) - { - SequencePoint? candidate = null; - foreach (var method in type.Methods) { - if (!method.HasBody) - continue; - - foreach (var ins in method.Body.Instructions) { - var seq = method.DebugInformation.GetSequencePoint (ins); - if (seq == null) - continue; - - if (Regex.IsMatch (seq.Document.Url, ".+\\.(g|designer)\\..+")) - break; - if (candidate == null || seq.StartLine < candidate.StartLine) - candidate = seq; - break; - } - } - - return candidate; - } - - void AddConstructors (TypeDefinition type, string? outerType, List? baseCtors, List curCtors, bool onlyRegisteredOrExportedCtors) - { - foreach (MethodDefinition ctor in type.Methods) - if (ctor.IsConstructor && !ctor.IsStatic && !ctor.AnyCustomAttributes (typeof (ExportAttribute))) - AddConstructor (ctor, type, outerType, baseCtors, curCtors, onlyRegisteredOrExportedCtors, false); - } - - void AddConstructor (MethodDefinition ctor, TypeDefinition type, string? outerType, List? baseCtors, List curCtors, bool onlyRegisteredOrExportedCtors, bool skipParameterCheck) - { - string managedParameters = GetManagedParameters (ctor, outerType); - if (!skipParameterCheck && (managedParameters == null || ctors.Any (c => c.ManagedParameters == managedParameters))) { - return; - } - - ExportAttribute eattr = GetExportAttributes (ctor).FirstOrDefault (); - if (eattr != null) { - if (!string.IsNullOrEmpty (eattr.Name)) { - // Diagnostic.Warning (log, "Use of ExportAttribute.Name property is invalid on constructors"); - } - ctors.Add (new Signature (ctor, eattr, managedParameters, cache)); - curCtors.Add (ctor); - return; - } - - RegisterAttribute rattr = GetMethodRegistrationAttributes (ctor).FirstOrDefault (); - if (rattr != null) { - if (ctors.Any (c => c.JniSignature == rattr.Signature)) - return; - ctors.Add (new Signature (ctor, rattr, managedParameters, outerType, cache)); - curCtors.Add (ctor); - return; - } - - if (onlyRegisteredOrExportedCtors) - return; - - string? jniSignature = GetJniSignature (ctor, cache); - - if (jniSignature == null) - return; - - if (ctors.Any (c => c.JniSignature == jniSignature)) - return; - - if (baseCtors == null) { - throw new InvalidOperationException ("`baseCtors` should not be null!"); - } - - if (baseCtors.Any (m => m.Parameters.AreParametersCompatibleWith (ctor.Parameters, cache))) { - ctors.Add (new Signature (".ctor", jniSignature, "", managedParameters, outerType, null)); - curCtors.Add (ctor); - return; - } - if (baseCtors.Any (m => !m.HasParameters)) { - ctors.Add (new Signature (".ctor", jniSignature, "", managedParameters, outerType, "")); - curCtors.Add (ctor); - return; - } - } - - MethodDefinition? GetBaseRegisteredMethod (MethodDefinition method) - { - MethodDefinition bmethod; - while ((bmethod = method.GetBaseDefinition (cache)) != method) { - method = bmethod; - - if (method.AnyCustomAttributes (typeof (RegisterAttribute))) { - return method; - } - } - return null; - } - - internal static RegisterAttribute? ToRegisterAttribute (CustomAttribute attr) - { - // attr.Resolve (); - RegisterAttribute? r = null; - if (attr.ConstructorArguments.Count == 1) - r = new RegisterAttribute ((string) attr.ConstructorArguments [0].Value, attr); - else if (attr.ConstructorArguments.Count == 3) - r = new RegisterAttribute ( - (string) attr.ConstructorArguments [0].Value, - (string) attr.ConstructorArguments [1].Value, - (string) attr.ConstructorArguments [2].Value, - attr); - if (r != null) { - var v = attr.Properties.FirstOrDefault (p => p.Name == "DoNotGenerateAcw"); - r.DoNotGenerateAcw = v.Name == null ? false : (bool) v.Argument.Value; - } - return r; - } - - internal static RegisterAttribute? RegisterFromJniTypeSignatureAttribute (CustomAttribute attr) - { - // attr.Resolve (); - RegisterAttribute? r = null; - if (attr.ConstructorArguments.Count == 1) - r = new RegisterAttribute ((string) attr.ConstructorArguments [0].Value, attr); - if (r != null) { - var v = attr.Properties.FirstOrDefault (p => p.Name == "GenerateJavaPeer"); - if (v.Name == null) { - r.DoNotGenerateAcw = false; - } else if (v.Name == "GenerateJavaPeer") { - r.DoNotGenerateAcw = ! (bool) v.Argument.Value; - } - var isKeyProp = attr.Properties.FirstOrDefault (p => p.Name == "IsKeyword"); - var isKeyword = isKeyProp.Name != null && ((bool) isKeyProp.Argument.Value) == true; - var arrRankProp = attr.Properties.FirstOrDefault (p => p.Name == "ArrayRank"); - if (arrRankProp.Name != null && arrRankProp.Argument.Value is int rank) { - r.Name = new string ('[', rank) + (isKeyword ? r.Name : "L" + r.Name + ";"); - } - } - return r; - } - - internal static RegisterAttribute? RegisterFromJniConstructorSignatureAttribute (CustomAttribute attr) - { - // attr.Resolve (); - RegisterAttribute? r = null; - if (attr.ConstructorArguments.Count == 1) - r = new RegisterAttribute ( - name: ".ctor", - signature: (string) attr.ConstructorArguments [0].Value, - connector: "", - originAttribute: attr); - return r; - } - - internal static RegisterAttribute? RegisterFromJniMethodSignatureAttribute (CustomAttribute attr) - { - // attr.Resolve (); - RegisterAttribute? r = null; - if (attr.ConstructorArguments.Count == 2) - r = new RegisterAttribute ((string) attr.ConstructorArguments [0].Value, - (string) attr.ConstructorArguments [1].Value, - "", - attr); - return r; - } - - ExportAttribute ToExportAttribute (CustomAttribute attr, IMemberDefinition declaringMember) - { - var name = attr.ConstructorArguments.Count > 0 ? (string) attr.ConstructorArguments [0].Value : declaringMember.Name; - if (attr.Properties.Count == 0) - return new ExportAttribute (name); - var typeArgs = (CustomAttributeArgument []) attr.Properties.FirstOrDefault (p => p.Name == "Throws").Argument.Value; - var thrown = typeArgs != null && typeArgs.Any () - ? (from caa in typeArgs select JavaNativeTypeManager.Parse (GetJniTypeName ((TypeReference)caa.Value, cache))?.Type) - .Where (v => v != null) - .ToArray () - : null; - var superArgs = (string) attr.Properties.FirstOrDefault (p => p.Name == "SuperArgumentsString").Argument.Value; - return new ExportAttribute (name) {ThrownNames = thrown, SuperArgumentsString = superArgs}; - } - - ExportAttribute ToExportAttributeFromJavaCallableAttribute (CustomAttribute attr, IMemberDefinition declaringMember) - { - var name = attr.ConstructorArguments.Count > 0 - ? (string) attr.ConstructorArguments [0].Value - : declaringMember.Name; - return new ExportAttribute (name); - } - - ExportAttribute ToExportAttributeFromJavaCallableConstructorAttribute (CustomAttribute attr, IMemberDefinition declaringMember) - { - var superArgs = (string) attr.Properties - .FirstOrDefault (p => p.Name == "SuperConstructorExpression") - .Argument - .Value; - return new ExportAttribute (".ctor") { - SuperArgumentsString = superArgs, - }; - } - - internal static ExportFieldAttribute ToExportFieldAttribute (CustomAttribute attr) - { - return new ExportFieldAttribute ((string) attr.ConstructorArguments [0].Value); - } - - internal static IEnumerable GetTypeRegistrationAttributes (Mono.Cecil.ICustomAttributeProvider p) - { - foreach (var a in GetAttributes (p, a => ToRegisterAttribute (a))) { - yield return a; - } - foreach (var c in p.GetCustomAttributes ("Java.Interop.JniTypeSignatureAttribute")) { - var r = RegisterFromJniTypeSignatureAttribute (c); - if (r == null) { - continue; - } - yield return r; - } - } - - static IEnumerable GetMethodRegistrationAttributes (Mono.Cecil.ICustomAttributeProvider p) - { - foreach (var a in GetAttributes (p, a => ToRegisterAttribute (a))) { - yield return a; - } - foreach (var c in p.GetCustomAttributes ("Java.Interop.JniConstructorSignatureAttribute")) { - var r = RegisterFromJniConstructorSignatureAttribute (c); - if (r == null) { - continue; - } - yield return r; - } - foreach (var c in p.GetCustomAttributes ("Java.Interop.JniMethodSignatureAttribute")) { - var r = RegisterFromJniMethodSignatureAttribute (c); - if (r == null) { - continue; - } - yield return r; - } - } - - IEnumerable GetExportAttributes (IMemberDefinition p) - { - return GetAttributes (p, a => ToExportAttribute (a, p)) - .Concat (GetAttributes (p, "Java.Interop.JavaCallableAttribute", - a => ToExportAttributeFromJavaCallableAttribute (a, p))) - .Concat (GetAttributes (p, "Java.Interop.JavaCallableConstructorAttribute", - a => ToExportAttributeFromJavaCallableConstructorAttribute (a, p))); - } - - static IEnumerable GetExportFieldAttributes (Mono.Cecil.ICustomAttributeProvider p) - { - return GetAttributes (p, a => ToExportFieldAttribute (a)); - } - - static IEnumerable GetAttributes (Mono.Cecil.ICustomAttributeProvider p, Func selector) - where TAttribute : class - { - return GetAttributes (p, typeof (TAttribute).FullName, selector); - } - - static IEnumerable GetAttributes (Mono.Cecil.ICustomAttributeProvider p, string attributeName, Func selector) - where TAttribute : class - { - return p.GetCustomAttributes (attributeName) - .Select (selector) - .Where (v => v != null) - .Select (v => v!); - } - - void AddMethod (MethodDefinition? registeredMethod, MethodDefinition implementedMethod) - { - if (registeredMethod != null) - foreach (RegisterAttribute attr in GetMethodRegistrationAttributes (registeredMethod)) { - // Check for Kotlin-mangled methods that cannot be overridden - if (attr.Name.Contains ("-impl") || (attr.Name.Length > 7 && attr.Name[attr.Name.Length - 8] == '-')) - Diagnostic.Error (4217, LookupSource (implementedMethod), Localization.Resources.JavaCallableWrappers_XA4217, attr.Name); - - bool shouldBeDynamicallyRegistered = methodClassifier?.ShouldBeDynamicallyRegistered (type, registeredMethod, implementedMethod, attr.OriginAttribute) ?? true; - var msig = new Signature (implementedMethod, attr, cache, shouldBeDynamicallyRegistered); - if (!registeredMethod.IsConstructor && !methods.Any (m => m.Name == msig.Name && m.Params == msig.Params)) - methods.Add (msig); - } - foreach (ExportAttribute attr in GetExportAttributes (implementedMethod)) { - if (type.HasGenericParameters) - Diagnostic.Error (4206, LookupSource (implementedMethod), Localization.Resources.JavaCallableWrappers_XA4206); - - var msig = new Signature (implementedMethod, attr, managedParameters: null, cache: cache); - if (!string.IsNullOrEmpty (attr.SuperArgumentsString)) { - // Diagnostic.Warning (log, "Use of ExportAttribute.SuperArgumentsString property is invalid on methods"); - } - if (!implementedMethod.IsConstructor && !methods.Any (m => m.Name == msig.Name && m.Params == msig.Params)) - methods.Add (msig); - } - foreach (ExportFieldAttribute attr in GetExportFieldAttributes (implementedMethod)) { - if (type.HasGenericParameters) - Diagnostic.Error (4207, LookupSource (implementedMethod), Localization.Resources.JavaCallableWrappers_XA4207); - - var msig = new Signature (implementedMethod, attr, cache); - if (!implementedMethod.IsConstructor && !methods.Any (m => m.Name == msig.Name && m.Params == msig.Params)) { - methods.Add (msig); - exported_fields.Add (new JavaFieldInfo (implementedMethod, attr.Name, cache)); - } - } - } - - string GetManagedParameters (MethodDefinition ctor, string? outerType) - { - StringBuilder sb = new StringBuilder (); - foreach (ParameterDefinition pdef in ctor.Parameters) { - if (sb.Length > 0) - sb.Append (':'); - if (outerType != null && sb.Length == 0) - sb.Append (type.DeclaringType.GetPartialAssemblyQualifiedName (cache)); - else - sb.Append (pdef.ParameterType.GetPartialAssemblyQualifiedName (cache)); - } - return sb.ToString (); - } - - // example of java target to generate for a type - // - // package mono.droid; - // - // import android.app.Activity; - // import android.os.Bundle; - // - // public class MonoActivity extends android.app.Activity - // { - // static final String __md_methods; - // static { - // __md_methods = - // "n_OnCreate:(Landroid/os/Bundle;)V:GetOnCreate_Landroid_os_Bundle_Handler\n" + - // ""; - // mono.android.Runtime.register ("Mono.Droid.MonoActivity, AssemblyName", MonoActivity.class, __md_methods); - // } - // - // public void onCreate(android.os.Bundle savedInstanceState) - // { - // n_onCreate (savedInstanceState); - // } - // - // private native void n_onCreate (android.os.Bundle bundle); - // } - - public void Generate (TextWriter writer) - { - if (!string.IsNullOrEmpty (package)) { - writer.WriteLine ("package " + package + ";"); - writer.WriteLine (); - } - - GenerateHeader (writer); - - bool needCtor = false; - if (HasDynamicallyRegisteredMethods) { - needCtor = true; - writer.WriteLine ("/** @hide */"); - writer.WriteLine ("\tpublic static final String __md_methods;"); - } - - if (children != null) { - for (int i = 0; i < children.Count; i++) { - if (!children[i].HasDynamicallyRegisteredMethods) { - continue; - } - needCtor = true; - writer.Write ("\tstatic final String __md_"); - writer.Write (i + 1); - writer.WriteLine ("_methods;"); - } - } - - if (needCtor) { - writer.WriteLine ("\tstatic {"); - - if (HasDynamicallyRegisteredMethods) { - GenerateRegisterType (writer, this, "__md_methods"); - } - - if (children != null) { - for (int i = 0; i < children.Count; ++i) { - GenerateRegisterType (writer, children [i], $"__md_{i + 1}_methods"); - } - } - writer.WriteLine ("\t}"); - } - - GenerateBody (writer); - - if (children != null) - foreach (JavaCallableWrapperGenerator child in children) { - child.GenerateHeader (writer); - child.GenerateBody (writer); - child.GenerateFooter (writer); - } - - GenerateFooter (writer); - } - - public void Generate (string outputPath) - { - using (StreamWriter sw = OpenStream (outputPath)) { - Generate (sw); - } - } - - static string GetAnnotationsString (string indent, IEnumerable atts, IMetadataResolver resolver) - { - var sw = new StringWriter (); - WriteAnnotations (indent, sw, atts, resolver); - return sw.ToString (); - } - - static void WriteAnnotations (string indent, TextWriter sw, IEnumerable atts, IMetadataResolver resolver) - { - foreach (var ca in atts) { - var catype = resolver.Resolve (ca.AttributeType); - var tca = catype.CustomAttributes.FirstOrDefault (a => a.AttributeType.FullName == "Android.Runtime.AnnotationAttribute"); - if (tca != null) { - sw.Write (indent); - sw.Write ('@'); - sw.Write (tca.ConstructorArguments [0].Value); - if (ca.Properties.Count > 0) { - sw.WriteLine ("("); - bool wrote = false; - foreach (var p in ca.Properties) { - if (wrote) - sw.WriteLine (','); - var pd = catype.Properties.FirstOrDefault (pp => pp.Name == p.Name); - var reg = pd != null ? pd.CustomAttributes.FirstOrDefault (pdca => pdca.AttributeType.FullName == "Android.Runtime.RegisterAttribute") : null; - sw.Write (reg != null ? reg.ConstructorArguments [0].Value : p.Name); - sw.Write (" = "); - sw.Write (ManagedValueToJavaSource (p.Argument.Value)); - wrote = true; - } - sw.Write (")"); - } - sw.WriteLine (); - } - } - } - - // FIXME: this is hacky. Is there any existing code for value to source conversion? - static string ManagedValueToJavaSource (object value) - { - if (value is string) - return "\"" + value.ToString ().Replace ("\"", "\"\"") + '"'; - else if (value.GetType ().FullName == "Java.Lang.Class") - return value.ToString () + ".class"; - else if (value is bool) - return ((bool) value) ? "true" : "false"; - else - return value.ToString (); - } - - void GenerateHeader (TextWriter sw) - { - sw.WriteLine (); - - // class annotations. - WriteAnnotations ("", sw, type.CustomAttributes, cache); - - sw.WriteLine ("public " + (type.IsAbstract ? "abstract " : "") + "class " + name); - - string extendsType = GetJavaTypeName (type.BaseType, cache); - if (extendsType == "android.app.Application" && ApplicationJavaClass != null && !string.IsNullOrEmpty (ApplicationJavaClass)) - extendsType = ApplicationJavaClass; - sw.WriteLine ("\textends " + extendsType); - sw.WriteLine ("\timplements"); - sw.Write ("\t\t"); - switch (CodeGenerationTarget) { - case JavaPeerStyle.JavaInterop1: - sw.Write ("net.dot.jni.GCUserPeerable"); - break; - default: - sw.Write ("mono.android.IGCUserPeer"); - break; - } - foreach (var ifaceInfo in type.Interfaces) { - var iface = cache.Resolve(ifaceInfo.InterfaceType); - if (!GetTypeRegistrationAttributes (iface).Any ()) - continue; - sw.WriteLine (","); - sw.Write ("\t\t"); - sw.Write (GetJavaTypeName (iface, cache)); - } - sw.WriteLine (); - sw.WriteLine ("{"); - } - - void GenerateBody (TextWriter sw) - { - foreach (Signature ctor in ctors) { - if (string.IsNullOrEmpty (ctor.Params) && JavaNativeTypeManager.IsApplication (type, cache)) - continue; - GenerateConstructor (ctor, sw); - } - - GenerateApplicationConstructor (sw); - - foreach (JavaFieldInfo field in exported_fields) - GenerateExportedField (field, sw); - - foreach (Signature method in methods) - GenerateMethod (method, sw); - - if (GenerateOnCreateOverrides && JavaNativeTypeManager.IsApplication (type, cache) && !methods.Any (m => m.Name == "onCreate")) - WriteApplicationOnCreate (sw, w => { - w.Write ("\t\tmono.android.Runtime.register (\""); - w.Write (type.GetPartialAssemblyQualifiedName (cache)); - w.Write ("\", "); - w.Write (name); - w.WriteLine (".class, __md_methods);"); - w.WriteLine ("\t\tsuper.onCreate ();"); - }); - if (GenerateOnCreateOverrides && JavaNativeTypeManager.IsInstrumentation (type, cache) && !methods.Any (m => m.Name == "onCreate")) - WriteInstrumentationOnCreate (sw, w => { - w.Write ("\t\tmono.android.Runtime.register (\""); - w.Write (type.GetPartialAssemblyQualifiedName (cache)); - w.Write ("\", "); - w.Write (name); - w.WriteLine (".class, __md_methods);"); - w.WriteLine ("\t\tsuper.onCreate (arguments);"); - }); - - string addRef = "monodroidAddReference"; - string clearRefs = "monodroidClearReferences"; - if (CodeGenerationTarget == JavaPeerStyle.JavaInterop1) { - addRef = "jiAddManagedReference"; - clearRefs = "jiClearManagedReferences"; - } - - sw.WriteLine (); - sw.WriteLine ("\tprivate java.util.ArrayList refList;"); - sw.WriteLine ($"\tpublic void {addRef} (java.lang.Object obj)"); - sw.WriteLine ("\t{"); - sw.WriteLine ("\t\tif (refList == null)"); - sw.WriteLine ("\t\t\trefList = new java.util.ArrayList ();"); - sw.WriteLine ("\t\trefList.add (obj);"); - sw.WriteLine ("\t}"); - sw.WriteLine (); - sw.WriteLine ($"\tpublic void {clearRefs} ()"); - sw.WriteLine ("\t{"); - sw.WriteLine ("\t\tif (refList != null)"); - sw.WriteLine ("\t\t\trefList.clear ();"); - sw.WriteLine ("\t}"); - } - - void GenerateRegisterType (TextWriter sw, JavaCallableWrapperGenerator self, string field) - { - if (!self.HasDynamicallyRegisteredMethods) { - return; - } - - sw.Write ("\t\t"); - sw.Write (field); - sw.WriteLine (" = "); - string managedTypeName = self.type.GetPartialAssemblyQualifiedName (cache); - string javaTypeName = $"{package}.{name}"; - - foreach (Signature method in self.methods) { - if (method.IsDynamicallyRegistered) { - sw.Write ("\t\t\t\"", method.Method); - sw.Write (method.Method); - sw.WriteLine ("\\n\" +"); - } - } - sw.WriteLine ("\t\t\t\"\";"); - if (CannotRegisterInStaticConstructor (self.type)) - return; - sw.Write ("\t\t"); - switch (CodeGenerationTarget) { - case JavaPeerStyle.JavaInterop1: - sw.Write ("net.dot.jni.ManagedPeer.registerNativeMembers ("); - sw.Write (self.name); - sw.Write (".class, "); - sw.Write (field); - sw.WriteLine (");"); - break; - default: - sw.Write ("mono.android.Runtime.register (\""); - sw.Write (managedTypeName); - sw.Write ("\", "); - sw.Write (self.name); - sw.Write (".class, "); - sw.Write (field); - sw.WriteLine (");"); - break; - } - } - - void GenerateFooter (TextWriter sw) - { - sw.WriteLine ("}"); - } - - static string GetJavaAccess (MethodAttributes access) - { - switch (access) { - case MethodAttributes.Public: - return "public"; - case MethodAttributes.FamORAssem: - return "protected"; - case MethodAttributes.Family: - return "protected"; - default: - return "private"; - } - } - - static string GetJavaTypeName (TypeReference r, IMetadataResolver cache) - { - TypeDefinition d = cache.Resolve (r); - string? jniName = JavaNativeTypeManager.ToJniName (d, cache); - if (jniName == null) { - Diagnostic.Error (4201, Localization.Resources.JavaCallableWrappers_XA4201, r.FullName); - throw new InvalidOperationException ("--nrt:jniName-- Should not be reached"); - } - return jniName.Replace ('/', '.').Replace ('$', '.'); - } - - bool CannotRegisterInStaticConstructor (TypeDefinition type) - { - return JavaNativeTypeManager.IsApplication (type, cache) || JavaNativeTypeManager.IsInstrumentation (type, cache); - } - - class Signature { - - public Signature (MethodDefinition method, RegisterAttribute register, IMetadataResolver cache, bool shouldBeDynamicallyRegistered = true) : this (method, register, null, null, cache, shouldBeDynamicallyRegistered) {} - - public Signature (MethodDefinition method, RegisterAttribute register, string? managedParameters, string? outerType, IMetadataResolver cache, bool shouldBeDynamicallyRegistered = true) - : this (register.Name, register.Signature, register.Connector, managedParameters, outerType, null) - { - Annotations = JavaCallableWrapperGenerator.GetAnnotationsString ("\t", method.CustomAttributes, cache); - IsDynamicallyRegistered = shouldBeDynamicallyRegistered; - } - - public Signature (MethodDefinition method, ExportAttribute export, string? managedParameters, IMetadataResolver cache) - : this (method.Name, GetJniSignature (method, cache), "__export__", null, null, export.SuperArgumentsString) - { - IsExport = true; - IsStatic = method.IsStatic; - JavaAccess = JavaCallableWrapperGenerator.GetJavaAccess (method.Attributes & MethodAttributes.MemberAccessMask); - ThrownTypeNames = export.ThrownNames; - JavaNameOverride = export.Name; - ManagedParameters = managedParameters; - Annotations = JavaCallableWrapperGenerator.GetAnnotationsString ("\t", method.CustomAttributes, cache); - } - - public Signature (MethodDefinition method, ExportFieldAttribute exportField, IMetadataResolver cache) - : this (method.Name, GetJniSignature (method, cache), "__export__", null, null, null) - { - if (method.HasParameters) - Diagnostic.Error (4205, JavaCallableWrapperGenerator.LookupSource (method), Localization.Resources.JavaCallableWrappers_XA4205); - if (method.ReturnType.MetadataType == MetadataType.Void) - Diagnostic.Error (4208, JavaCallableWrapperGenerator.LookupSource (method), Localization.Resources.JavaCallableWrappers_XA4208); - IsExport = true; - IsStatic = method.IsStatic; - JavaAccess = JavaCallableWrapperGenerator.GetJavaAccess (method.Attributes & MethodAttributes.MemberAccessMask); - - // annotations are processed within JavaFieldInfo, not the initializer method. So we don't generate them here. - } - - public Signature (string name, string? signature, string? connector, string? managedParameters, string? outerType, string? superCall) - { - ManagedParameters = managedParameters; - JniSignature = signature ?? throw new ArgumentNullException ("`connector` cannot be null.", nameof (connector)); - Method = "n_" + name + ":" + JniSignature + ":" + connector; - Name = name; - - var jnisig = JniSignature; - int closer = jnisig.IndexOf (')'); - string ret = jnisig.Substring (closer + 1); - retval = JavaNativeTypeManager.Parse (ret)?.Type; - string jniparms = jnisig.Substring (1, closer - 1); - if (string.IsNullOrEmpty (jniparms) && string.IsNullOrEmpty (superCall)) - return; - var parms = new StringBuilder (); - var scall = new StringBuilder (); - var acall = new StringBuilder (); - bool first = true; - int i = 0; - foreach (JniTypeName jti in JavaNativeTypeManager.FromSignature (jniparms)) { - if (outerType != null) { - acall.Append (outerType).Append (".this"); - outerType = null; - continue; - } - string? parmType = jti.Type; - if (!first) { - parms.Append (", "); - scall.Append (", "); - acall.Append (", "); - } - first = false; - parms.Append (parmType).Append (" p").Append (i); - scall.Append ("p").Append (i); - acall.Append ("p").Append (i); - ++i; - } - this.parms = parms.ToString (); - this.call = superCall != null ? superCall : scall.ToString (); - this.ActivateCall = acall.ToString (); - } - - string? call; - public string? SuperCall { - get { return call; } - } - - public string? ActivateCall {get; private set;} - - public readonly string Name; - public readonly string? JavaNameOverride; - public string JavaName { - get { return JavaNameOverride ?? Name; } - } - - string? parms; - public string? Params { - get { return parms; } - } - - string? retval; - public string? Retval { - get { return retval; } - } - - public string? ThrowsDeclaration { - get { return ThrownTypeNames?.Length > 0 ? " throws " + String.Join (", ", ThrownTypeNames) : null; } - } - - public readonly string? JavaAccess; - public readonly string? ManagedParameters; - public readonly string JniSignature; - public readonly string Method; - public readonly bool IsExport; - public readonly bool IsStatic; - public readonly bool IsDynamicallyRegistered = true; - public readonly string []? ThrownTypeNames; - public readonly string? Annotations; - } - - void GenerateConstructor (Signature ctor, TextWriter sw) - { - // TODO: we only generate constructors so that Android types w/ no - // default constructor can be subclasses by our generated code. - // - // This does NOT currently allow creating managed types from Java. - sw.WriteLine (); - if (ctor.Annotations != null) - sw.WriteLine (ctor.Annotations); - sw.Write ("\tpublic "); - sw.Write (name); - sw.Write (" ("); - sw.Write (ctor.Params); - sw.Write (')'); - sw.WriteLine (ctor.ThrowsDeclaration); - sw.WriteLine ("\t{"); - sw.Write ("\t\tsuper ("); - sw.Write (ctor.SuperCall); - sw.WriteLine (");"); -#if MONODROID_TIMING - sw.WriteLine ("\t\tandroid.util.Log.i(\"MonoDroid-Timing\", \"{0}..ctor({1}): time: \"+java.lang.System.currentTimeMillis());", name, ctor.Params); -#endif - if (!CannotRegisterInStaticConstructor (type)) { - sw.Write ("\t\tif (getClass () == "); - sw.Write (name); - sw.WriteLine (".class) {"); - sw.Write ("\t\t\t"); - switch (CodeGenerationTarget) { - case JavaPeerStyle.JavaInterop1: - sw.Write ("net.dot.jni.ManagedPeer.construct (this, \""); - sw.Write (ctor.JniSignature); - sw.Write ("\", new java.lang.Object[] { "); - sw.Write (ctor.ActivateCall); - sw.WriteLine (" });"); - break; - default: - sw.Write ("mono.android.TypeManager.Activate (\""); - sw.Write (type.GetPartialAssemblyQualifiedName (cache)); - sw.Write ("\", \""); - sw.Write (ctor.ManagedParameters); - sw.Write ("\", this, new java.lang.Object[] { "); - sw.Write (ctor.ActivateCall); - sw.WriteLine (" });"); - break; - } - sw.WriteLine ("\t\t}"); - } - sw.WriteLine ("\t}"); - } - - void GenerateApplicationConstructor (TextWriter sw) - { - if (!JavaNativeTypeManager.IsApplication (type, cache)) { - return; - } - - sw.WriteLine (); - sw.Write ("\tpublic "); - sw.Write (name); - sw.WriteLine (" ()"); - sw.WriteLine ("\t{"); - sw.WriteLine ("\t\tmono.MonoPackageManager.setContext (this);"); - sw.WriteLine ("\t}"); - } - - void GenerateExportedField (JavaFieldInfo field, TextWriter sw) - { - sw.WriteLine (); - if (field.Annotations != null) - sw.WriteLine (field.Annotations); - sw.Write ("\t"); - sw.Write (field.GetJavaAccess ()); - sw.Write (' '); - if (field.IsStatic) - sw.Write ("static "); - sw.Write (field.TypeName); - sw.Write (' '); - sw.Write (field.FieldName); - sw.Write (" = "); - sw.Write (field.InitializerName); - sw.WriteLine (" ();"); - } - - void GenerateMethod (Signature method, TextWriter sw) - { - sw.WriteLine (); - if (method.Annotations != null) - sw.WriteLine (method.Annotations); - sw.Write ("\t"); - sw.Write (method.IsExport ? method.JavaAccess : "public"); - sw.Write (' '); - if (method.IsStatic) - sw.Write ("static "); - sw.Write (method.Retval); - sw.Write (' '); - sw.Write (method.JavaName); - sw.Write (" ("); - sw.Write (method.Params); - sw.Write (')'); - sw.WriteLine (method.ThrowsDeclaration); - sw.WriteLine ("\t{"); -#if MONODROID_TIMING - sw.WriteLine ("\t\tandroid.util.Log.i(\"MonoDroid-Timing\", \"{0}.{1}: time: \"+java.lang.System.currentTimeMillis());", name, method.Name); -#endif - sw.Write ("\t\t"); - sw.Write (method.Retval == "void" ? String.Empty : "return "); - sw.Write ("n_"); - sw.Write (method.Name); - sw.Write (" ("); - sw.Write (method.ActivateCall); - sw.WriteLine (");"); - - sw.WriteLine ("\t}"); - sw.WriteLine (); - sw.Write ("\tprivate "); - if (method.IsStatic) - sw.Write ("static "); - sw.Write ("native "); - sw.Write (method.Retval); - sw.Write (" n_"); - sw.Write (method.Name); - sw.Write (" ("); - sw.Write (method.Params); - sw.WriteLine (");"); - } - - void WriteApplicationOnCreate (TextWriter sw, Action extra) - { - sw.WriteLine (); - sw.WriteLine ("\tpublic void onCreate ()"); - sw.WriteLine ("\t{"); - extra (sw); - sw.WriteLine ("\t}"); - } - - void WriteInstrumentationOnCreate (TextWriter sw, Action extra) - { - sw.WriteLine (); - sw.WriteLine ("\tpublic void onCreate (android.os.Bundle arguments)"); - sw.WriteLine ("\t{"); - -#if MONODROID_TIMING - sw.WriteLine ("\t\tandroid.util.Log.i(\"MonoDroid-Timing\", \"{0}.onCreate(Bundle): time: \"+java.lang.System.currentTimeMillis());", name); - sw.WriteLine (); -#endif - - sw.WriteLine ("\t\tandroid.content.Context context = getContext ();"); - sw.WriteLine (); - - if (!string.IsNullOrEmpty (MonoRuntimeInitialization)) { - sw.WriteLine (MonoRuntimeInitialization); - sw.WriteLine (); - } - - extra (sw); - sw.WriteLine ("\t}"); - } - - StreamWriter OpenStream (string outputPath) - { - string destination = GetDestinationPath (outputPath); - Directory.CreateDirectory (Path.GetDirectoryName (destination)); - return new StreamWriter (new FileStream (destination, FileMode.Create, FileAccess.Write)); - } - - /// - /// Returns a destination file path based on the package name of this Java type - /// - public string GetDestinationPath (string outputPath) - { - var dir = package.Replace ('.', Path.DirectorySeparatorChar); - return Path.Combine (outputPath, dir, name + ".java"); - } - } -} diff --git a/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/JavaPeerStyle.cs b/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/JavaPeerStyle.cs new file mode 100644 index 000000000..917196a16 --- /dev/null +++ b/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/JavaPeerStyle.cs @@ -0,0 +1,7 @@ +namespace Java.Interop.Tools.JavaCallableWrappers +{ + public enum JavaPeerStyle { + XAJavaInterop1, + JavaInterop1, + } +} diff --git a/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/JavaTypeScanner.cs b/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/JavaTypeScanner.cs index 9d74487a9..638464335 100644 --- a/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/JavaTypeScanner.cs +++ b/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/JavaTypeScanner.cs @@ -8,6 +8,7 @@ using Java.Interop.Tools.Cecil; using Java.Interop.Tools.Diagnostics; using Java.Interop.Tools.TypeNameMappings; +using Java.Interop.Tools.JavaCallableWrappers.Utilities; namespace Java.Interop.Tools.JavaCallableWrappers { @@ -95,7 +96,7 @@ public static bool ShouldSkipJavaCallableWrapperGeneration (TypeDefinition type, if (JavaNativeTypeManager.IsNonStaticInnerClass (type, resolver)) return true; - foreach (var c in JavaCallableWrapperGenerator.GetTypeRegistrationAttributes (type)) { + foreach (var c in CecilExtensions.GetTypeRegistrationAttributes (type)) { if (c.DoNotGenerateAcw) { return true; } diff --git a/tests/Java.Interop.Tools.JavaCallableWrappers-Tests/Java.Interop.Tools.JavaCallableWrappers/JavaCallableWrapperGeneratorTests.cs b/tests/Java.Interop.Tools.JavaCallableWrappers-Tests/Java.Interop.Tools.JavaCallableWrappers/JavaCallableWrapperGeneratorTests.cs index e64033543..04e1f6049 100644 --- a/tests/Java.Interop.Tools.JavaCallableWrappers-Tests/Java.Interop.Tools.JavaCallableWrappers/JavaCallableWrapperGeneratorTests.cs +++ b/tests/Java.Interop.Tools.JavaCallableWrappers-Tests/Java.Interop.Tools.JavaCallableWrappers/JavaCallableWrapperGeneratorTests.cs @@ -13,6 +13,8 @@ using Java.Interop.Tools.TypeNameMappings; using Xamarin.Android.ToolsTests; +using Java.Interop.Tools.JavaCallableWrappers.Adapters; +using Java.Interop.Tools.Cecil; namespace Java.Interop.Tools.JavaCallableWrappersTests { @@ -26,7 +28,7 @@ public void ConstructorExceptions () // structs aren't supported var td = SupportDeclarations.GetTypeDefinition (typeof (int)); - var e = Assert.Throws (() => new JavaCallableWrapperGenerator (td, logger, cache: null)); + var e = Assert.Throws (() => CecilImporter.CreateType (td, new TypeDefinitionCache ())); Assert.AreEqual (4200, e.Code); } @@ -37,7 +39,7 @@ public void KotlinInvalidImplRegisterName () // Contains invalid [Register] name of "foo-impl" var td = SupportDeclarations.GetTypeDefinition (typeof (KotlinInvalidImplRegisterName)); - var e = Assert.Throws (() => new JavaCallableWrapperGenerator (td, logger, cache: null)); + var e = Assert.Throws (() => CecilImporter.CreateType (td, new TypeDefinitionCache ())); Assert.AreEqual (4217, e.Code); } @@ -48,7 +50,7 @@ public void KotlinInvalidHashRegisterName () // Contains invalid [Register] name of "foo-f8k2a13" var td = SupportDeclarations.GetTypeDefinition (typeof (KotlinInvalidHashRegisterName)); - var e = Assert.Throws (() => new JavaCallableWrapperGenerator (td, logger, cache: null)); + var e = Assert.Throws (() => CecilImporter.CreateType (td, new TypeDefinitionCache ())); Assert.AreEqual (4217, e.Code); } @@ -104,17 +106,24 @@ public void monodroidClearReferences () static string Generate (Type type, string applicationJavaClass = null, string monoRuntimeInit = null, JavaPeerStyle style = JavaPeerStyle.XAJavaInterop1) { - var td = SupportDeclarations.GetTypeDefinition (type); - var g = new JavaCallableWrapperGenerator (td, log: null, cache: null) { - ApplicationJavaClass = applicationJavaClass, - GenerateOnCreateOverrides = true, - MonoRuntimeInitialization = monoRuntimeInit, - CodeGenerationTarget = style, + var reader_options = new CallableWrapperReaderOptions { + DefaultApplicationJavaClass = applicationJavaClass, + DefaultGenerateOnCreateOverrides = true, + DefaultMonoRuntimeInitialization = monoRuntimeInit, }; + + var td = SupportDeclarations.GetTypeDefinition (type); + var g = CecilImporter.CreateType (td, new TypeDefinitionCache (), reader_options); + var o = new StringWriter (); var dir = Path.GetDirectoryName (typeof (JavaCallableWrapperGeneratorTests).Assembly.Location); - g.Generate (Path.Combine (dir, "__o")); - g.Generate (o); + var options = new CallableWrapperWriterOptions { + CodeGenerationTarget = style, + }; + + g.Generate (Path.Combine (dir, "__o"), options); + g.Generate (o, options); + return o.ToString (); } @@ -146,7 +155,6 @@ public IndirectApplication () mono.MonoPackageManager.setContext (this); } - public void onCreate () { n_onCreate (); @@ -199,10 +207,8 @@ extends java.lang.Object mono.android.Runtime.register (""Xamarin.Android.ToolsTests.ExportsMembers, Java.Interop.Tools.JavaCallableWrappers-Tests"", ExportsMembers.class, __md_methods); } - public static crc64197ae30a36756915.ExportsMembers STATIC_INSTANCE = GetInstance (); - public java.lang.String VALUE = GetValue (); public static crc64197ae30a36756915.ExportsMembers GetInstance () @@ -219,7 +225,6 @@ public java.lang.String GetValue () private native java.lang.String n_GetValue (); - public static void staticMethodNotMangled () { n_staticMethodNotMangled (); @@ -227,7 +232,6 @@ public static void staticMethodNotMangled () private static native void n_staticMethodNotMangled (); - public void methodNamesNotMangled () { n_methodNamesNotMangled (); @@ -235,7 +239,6 @@ public void methodNamesNotMangled () private native void n_methodNamesNotMangled (); - public java.lang.String attributeOverridesNames (java.lang.String p0, int p1) { return n_CompletelyDifferentName (p0, p1); @@ -243,7 +246,6 @@ public java.lang.String attributeOverridesNames (java.lang.String p0, int p1) private native java.lang.String n_CompletelyDifferentName (java.lang.String p0, int p1); - public void methodThatThrows () throws java.lang.Throwable { n_methodThatThrows (); @@ -251,7 +253,6 @@ public void methodThatThrows () throws java.lang.Throwable private native void n_methodThatThrows (); - public void methodThatThrowsEmptyArray () { n_methodThatThrowsEmptyArray (); @@ -436,7 +437,6 @@ extends java.lang.Object mono.android.Runtime.register (""Xamarin.Android.ToolsTests.ExportsConstructors, Java.Interop.Tools.JavaCallableWrappers-Tests"", ExportsConstructors.class, __md_methods); } - public ExportsConstructors () { super (); @@ -445,7 +445,6 @@ public ExportsConstructors () } } - public ExportsConstructors (int p0) { super (p0); @@ -492,7 +491,6 @@ extends java.lang.Object mono.android.Runtime.register (""Xamarin.Android.ToolsTests.ExportsThrowsConstructors, Java.Interop.Tools.JavaCallableWrappers-Tests"", ExportsThrowsConstructors.class, __md_methods); } - public ExportsThrowsConstructors () throws java.lang.Throwable { super (); @@ -501,7 +499,6 @@ public ExportsThrowsConstructors () throws java.lang.Throwable } } - public ExportsThrowsConstructors (int p0) throws java.lang.Throwable { super (p0); @@ -510,7 +507,6 @@ public ExportsThrowsConstructors (int p0) throws java.lang.Throwable } } - public ExportsThrowsConstructors (java.lang.String p0) { super (p0); @@ -644,7 +640,6 @@ extends java.lang.Object net.dot.jni.ManagedPeer.registerNativeMembers (JavaInteropExample.class, __md_methods); } - public JavaInteropExample (int p0, int p1) { super (); @@ -653,7 +648,6 @@ public JavaInteropExample (int p0, int p1) } } - public void example () { n_Example (); diff --git a/tools/jcw-gen/App.cs b/tools/jcw-gen/App.cs index 02788d55d..0eb606902 100644 --- a/tools/jcw-gen/App.cs +++ b/tools/jcw-gen/App.cs @@ -9,6 +9,7 @@ using Java.Interop.Tools.JavaCallableWrappers; using Mono.Cecil; using Mono.Options; +using Java.Interop.Tools.JavaCallableWrappers.Adapters; namespace Java.Interop.Tools { @@ -91,10 +92,13 @@ static void GenerateJavaCallableWrapper (TypeDefinition type, string outputPath, return; } - var generator = new JavaCallableWrapperGenerator (type, log: Console.WriteLine, cache) { - CodeGenerationTarget = style, + var t = CecilImporter.CreateType (type, cache); + + var options = new CallableWrapperWriterOptions { + CodeGenerationTarget = style, }; - generator.Generate (outputPath); + + t.Generate (outputPath, options); } } } diff --git a/tools/jcw-gen/jcw-gen.csproj b/tools/jcw-gen/jcw-gen.csproj index e798b5e19..f99367616 100644 --- a/tools/jcw-gen/jcw-gen.csproj +++ b/tools/jcw-gen/jcw-gen.csproj @@ -11,6 +11,10 @@ $(UtilityOutputFullPath) + + + + diff --git a/tools/jcw-gen/jcw-gen.slnf b/tools/jcw-gen/jcw-gen.slnf new file mode 100644 index 000000000..b8e3a83f9 --- /dev/null +++ b/tools/jcw-gen/jcw-gen.slnf @@ -0,0 +1,13 @@ +{ + "solution": { + "path": "..\\..\\Java.Interop.sln", + "projects": [ + "src\\Java.Interop.Localization\\Java.Interop.Localization.csproj", + "src\\Java.Interop.Tools.Cecil\\Java.Interop.Tools.Cecil.csproj", + "src\\Java.Interop.Tools.Diagnostics\\Java.Interop.Tools.Diagnostics.csproj", + "src\\Java.Interop.Tools.JavaCallableWrappers\\Java.Interop.Tools.JavaCallableWrappers.csproj", + "tests\\Java.Interop.Tools.JavaCallableWrappers-Tests\\Java.Interop.Tools.JavaCallableWrappers-Tests.csproj", + "tools\\jcw-gen\\jcw-gen.csproj" + ] + } +} \ No newline at end of file