diff --git a/src/Xamarin.Android.Tools.Bytecode/ClassPath.cs b/src/Xamarin.Android.Tools.Bytecode/ClassPath.cs index e8e8ccd9a..26e6611d1 100644 --- a/src/Xamarin.Android.Tools.Bytecode/ClassPath.cs +++ b/src/Xamarin.Android.Tools.Bytecode/ClassPath.cs @@ -304,14 +304,23 @@ void FixupParametersFromDocs (XElement api, string path) if (!parameterElements.Select (x => x.Attribute ("name")?.Value).Any (p => p != null && IsGeneratedName (p))) continue; - var parameters = parameterElements - .Select (p => p.Attribute ("type")?.Value!) - .Where (p => p != null); + var parameterTypes = parameterElements + .Select (p => new JavaMethodParameterTypeInfo ((string) p.Attribute ("jni-type"), (string) p.Attribute ("type"))) + .ToArray (); - if (!parameters.Any ()) + if (!parameterTypes.Any ()) continue; - var pnames = jdoc.GetParameterNames (currentpackage, className, currentMethod, parameters.ToArray (), isVarArgs: false); + var nameInfo = new JavaMethodParameterNameInfo ( + currentpackage, + className, + currentMethod, + (string) method.Attribute ("jni-signature"), + parameterTypes, + isVarArgs: false + ); + + var pnames = jdoc.GetParameterNames (nameInfo); if (pnames == null || pnames.Length != parameterElements.Count) continue; for (int i = 0; i < parameterElements.Count; i++) { diff --git a/src/Xamarin.Android.Tools.Bytecode/JavaDocumentScraper.cs b/src/Xamarin.Android.Tools.Bytecode/JavaDocumentScraper.cs index da57d29fe..a86d0bb6e 100644 --- a/src/Xamarin.Android.Tools.Bytecode/JavaDocumentScraper.cs +++ b/src/Xamarin.Android.Tools.Bytecode/JavaDocumentScraper.cs @@ -193,9 +193,14 @@ protected virtual string StripTagsFromParameters (string value) { return value; } - - public virtual String[]? GetParameterNames (string package, string type, string method, string[] ptypes, bool isVarArgs) + + public virtual string[]? GetParameterNames (JavaMethodParameterNameInfo info) { + var package = info.PackageName; + var type = info.TypeName; + var method = info.MethodName; + var ptypes = info.ParameterTypes.Select (p => p.JavaType).ToArray (); + string path = package.Replace ('.', '/') + '/' + type.Replace ('$', '.') + ".html"; string file = Path.Combine (root, path); if (!File.Exists (file)) { @@ -301,9 +306,39 @@ public static void LoadXml (String filename) } } + public readonly struct JavaMethodParameterTypeInfo { + public string JniType {get;} + public string JavaType {get;} + + public JavaMethodParameterTypeInfo (string jniType, string javaType) + { + JniType = jniType; + JavaType = javaType; + } + } + + public readonly struct JavaMethodParameterNameInfo { + public string PackageName {get;} + public string TypeName {get;} + public string MethodName {get;} + public string? MethodSignature {get;} + public bool IsVarArgs {get;} + public JavaMethodParameterTypeInfo[] ParameterTypes {get;} + + public JavaMethodParameterNameInfo (string packageName, string typeName, string methodName, string? methodSignature, JavaMethodParameterTypeInfo[] parameterTypes, bool isVarArgs) + { + PackageName = packageName; + TypeName = typeName; + MethodName = methodName; + MethodSignature = methodSignature; + ParameterTypes = parameterTypes; + IsVarArgs = isVarArgs; + } + } + public interface IJavaMethodParameterNameProvider { - String[]? GetParameterNames (string package, string type, string method, string[] ptypes, bool isVarArgs); + string[]? GetParameterNames (JavaMethodParameterNameInfo info); } public static class JavaMethodParameterNameProvider { @@ -360,39 +395,59 @@ public ApiXmlDocScraper (string apiXmlFile) XDocument xdoc; - public string[]? GetParameterNames (string package, string type, string method, string[] ptypes, bool isVarArgs) + public string[]? GetParameterNames (JavaMethodParameterNameInfo info) { - var methodOrCtor = method == "constructor" ? - "constructor[" : $"method[@name='{method}'"; - - var pcount = ptypes.Length; - - var xpath = new StringBuilder (); - - xpath.Append ($"/api/package[@name='{package}']/*[self::class or self::interface]/"); - - if (method == "constructor") - xpath.Append ("constructor["); - else - xpath.Append ($"method[@name='{method}'"); - - xpath.Append ($" and count(parameter)={pcount}"); - - if (pcount > 0) { - xpath.Append (" and "); - xpath.Append (string.Join (" and ", ptypes.Select ((pt, pindex) => $"parameter[{pindex + 1}][@type='{pt}']"))); + var xtype = xdoc + .Elements ("api") + .Elements ("package") + .Where (p => ((string) p.Attribute ("name")) == info.PackageName) + .Elements () + .Where (t => ((string) t.Attribute ("name")) == info.TypeName) + .FirstOrDefault (); + if (xtype == null) { + return null; } - xpath.Append ("]"); - - var methodElem = xdoc.XPathSelectElement (xpath.ToString ()); - - if (methodElem != null) - return methodElem.Elements ("parameter") - .Select (pe => pe.Attribute ("name")?.Value ?? "") - .ToArray (); - - return null; + var members = info.MethodName == "constructor" + ? xtype.Elements ("constructor") + : xtype.Elements ("method").Where (m => ((string) m.Attribute ("name")) == info.MethodName); + var pcount = info.ParameterTypes.Length; + members = members + .Where (m => m.Elements ("parameter").Count () == pcount); + + XElement? member = + members.FirstOrDefault (m => info.MethodSignature == (string) m.Attribute ("jni-signature")); + if (member == null) { + foreach (var m in members) { + var found = true; + int i = 0; + foreach (var p in m.Elements ("parameter")) { + if (!ParameterTypesMatch (p, info.ParameterTypes [i++].JavaType)) { + found = false; + break; + } + } + if (found) { + member = m; + break; + } + } + } + if (member == null) + return null; + return member.Elements ("parameter") + .Select (p => (string) p.Attribute ("name")) + .ToArray (); + + bool ParameterTypesMatch (XElement parameter, string ptype) + { + var jtype = (string) parameter.Attribute ("type"); + if (!jtype.StartsWith (".*", StringComparison.Ordinal)) { + return jtype == ptype; + } + jtype = "." + jtype.Substring (".*".Length); + return ptype.EndsWith (jtype, StringComparison.Ordinal); + } } } } diff --git a/src/Xamarin.Android.Tools.Bytecode/JavaParameterNamesLoader.cs b/src/Xamarin.Android.Tools.Bytecode/JavaParameterNamesLoader.cs index 16ead1973..63136d1fe 100644 --- a/src/Xamarin.Android.Tools.Bytecode/JavaParameterNamesLoader.cs +++ b/src/Xamarin.Android.Tools.Bytecode/JavaParameterNamesLoader.cs @@ -114,8 +114,13 @@ List LoadParameterFixupDescription (string path) return fixup; } - public string[]? GetParameterNames (string package, string type, string method, string[] ptypes, bool isVarArgs) + public string[]? GetParameterNames (JavaMethodParameterNameInfo info) { + var package = info.PackageName; + var type = info.TypeName; + var method = info.MethodName; + var ptypes = info.ParameterTypes.Select (p => p.JavaType).ToArray (); + var methods = this.packages .Where(p => p.Name == package && p.Types != null) .SelectMany(p => p.Types!) diff --git a/tests/Xamarin.Android.Tools.Bytecode-Tests/ParameterFixupTests.cs b/tests/Xamarin.Android.Tools.Bytecode-Tests/ParameterFixupTests.cs index 2c2191d72..2fe450e55 100644 --- a/tests/Xamarin.Android.Tools.Bytecode-Tests/ParameterFixupTests.cs +++ b/tests/Xamarin.Android.Tools.Bytecode-Tests/ParameterFixupTests.cs @@ -45,6 +45,21 @@ public void XmlDeclaration_FixedUpFromApiXmlDocumentation () } } + [Test] + public void XmlDeclaration_FixedUpFromUnresolvedApiXmlDocumentation () + { + string docsPath = null; + + try { + docsPath = LoadToTempFile ("ParameterFixup_JavaInterfaceNoParameters_JavaSourceUtils.xml"); + + AssertXmlDeclaration ("JavaInterfaceNoParameters.class", "ParameterFixup_JavaInterfaceNoParameters.xml", docsPath); + } finally { + if (File.Exists (docsPath)) + File.Delete (docsPath); + } + } + [Test] public void XmlDeclaration_DoesNotThrowAnExceptionIfDocumentationNotFound () { diff --git a/tests/Xamarin.Android.Tools.Bytecode-Tests/Resources/ParameterFixup_JavaInterfaceNoParameters.xml b/tests/Xamarin.Android.Tools.Bytecode-Tests/Resources/ParameterFixup_JavaInterfaceNoParameters.xml new file mode 100644 index 000000000..27d8d50bf --- /dev/null +++ b/tests/Xamarin.Android.Tools.Bytecode-Tests/Resources/ParameterFixup_JavaInterfaceNoParameters.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + diff --git a/tests/Xamarin.Android.Tools.Bytecode-Tests/Resources/ParameterFixup_JavaInterfaceNoParameters_JavaSourceUtils.xml b/tests/Xamarin.Android.Tools.Bytecode-Tests/Resources/ParameterFixup_JavaInterfaceNoParameters_JavaSourceUtils.xml new file mode 100644 index 000000000..fd041241f --- /dev/null +++ b/tests/Xamarin.Android.Tools.Bytecode-Tests/Resources/ParameterFixup_JavaInterfaceNoParameters_JavaSourceUtils.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/tests/Xamarin.Android.Tools.Bytecode-Tests/Xamarin.Android.Tools.Bytecode-Tests.csproj b/tests/Xamarin.Android.Tools.Bytecode-Tests/Xamarin.Android.Tools.Bytecode-Tests.csproj index 9e43b9fa8..cb07bdc4b 100644 --- a/tests/Xamarin.Android.Tools.Bytecode-Tests/Xamarin.Android.Tools.Bytecode-Tests.csproj +++ b/tests/Xamarin.Android.Tools.Bytecode-Tests/Xamarin.Android.Tools.Bytecode-Tests.csproj @@ -32,6 +32,7 @@ + diff --git a/tests/Xamarin.Android.Tools.Bytecode-Tests/java/com/xamarin/JavaInterfaceNoParameters.java b/tests/Xamarin.Android.Tools.Bytecode-Tests/java/com/xamarin/JavaInterfaceNoParameters.java new file mode 100644 index 000000000..56fe849ba --- /dev/null +++ b/tests/Xamarin.Android.Tools.Bytecode-Tests/java/com/xamarin/JavaInterfaceNoParameters.java @@ -0,0 +1,19 @@ +package com.xamarin; + +public interface JavaInterfaceNoParameters { + /** + * JNI sig: ([Ljava/lang/Object;)Ljava/util/List; + */ + java.util.List asList(T... a); + + /** + * JNI sig: ([Ljava/lang/Object;IILjava/lang/Object;)I + * + * @param a [Ljava/lang/Object; + * @param fromIndex int + * @param toIndex int + * @param key Ljava/lang/Object + * @return int + */ + int binarySearch(Object[] a, int fromIndex, int toIndex, Object key); +} diff --git a/tools/java-source-utils/src/main/java/com/microsoft/android/JniPackagesInfoFactory.java b/tools/java-source-utils/src/main/java/com/microsoft/android/JniPackagesInfoFactory.java index 3886ec8bb..93c9b3fb6 100644 --- a/tools/java-source-utils/src/main/java/com/microsoft/android/JniPackagesInfoFactory.java +++ b/tools/java-source-utils/src/main/java/com/microsoft/android/JniPackagesInfoFactory.java @@ -159,7 +159,7 @@ static JniTypeInfo createTypeInfo(final JniPackageInfo packageInfo, final ClassO for (TypeParameter typeParameter : typeDecl.getTypeParameters()) { typeInfo.addTypeParameter( typeParameter.getNameAsString(), - getJniType(typeInfo, null, getTypeParameterBound(typeParameter))); + getJniType(typeInfo, null, getTypeParameterBound(typeParameter), false)); } return typeInfo; } @@ -284,8 +284,8 @@ private final void parseAnnotationMemberDecl(final JniTypeInfo typeInfo, final A typeInfo.add(methodInfo); methodInfo.setReturnType( - getJavaType(typeInfo, methodInfo, memberDecl.getType()), - getJniType(typeInfo, methodInfo, memberDecl.getType())); + getJavaType(typeInfo, methodInfo, memberDecl.getType(), false), + getJniType(typeInfo, methodInfo, memberDecl.getType(), false)); fillJavadoc(methodInfo, memberDecl); } @@ -293,7 +293,7 @@ private final void parseAnnotationMemberDecl(final JniTypeInfo typeInfo, final A private final void parseFieldDecl(final JniTypeInfo typeInfo, final FieldDeclaration fieldDecl) { for (VariableDeclarator f : fieldDecl.getVariables()) { final JniFieldInfo fieldInfo = new JniFieldInfo(typeInfo, f.getNameAsString()); - fieldInfo.setJniType(getJniType(typeInfo, null, f.getType())); + fieldInfo.setJniType(getJniType(typeInfo, null, f.getType(), false)); typeInfo.add(fieldInfo); @@ -316,11 +316,11 @@ private final void parseMethodDecl(final JniTypeInfo typeInfo, final MethodDecla for (TypeParameter typeParameter : methodDecl.getTypeParameters()) { methodInfo.addTypeParameter( typeParameter.getNameAsString(), - getJniType(typeInfo, methodInfo, getTypeParameterBound(typeParameter))); + getJniType(typeInfo, methodInfo, getTypeParameterBound(typeParameter), false)); } methodInfo.setReturnType( - getJavaType(typeInfo, methodInfo, methodDecl.getType()), - getJniType(typeInfo, methodInfo, methodDecl.getType())); + getJavaType(typeInfo, methodInfo, methodDecl.getType(), false), + getJniType(typeInfo, methodInfo, methodDecl.getType(), false)); fillMethodBase(methodInfo, methodDecl); fillJavadoc(methodInfo, methodDecl); @@ -345,43 +345,43 @@ private final void fillMethodBase(final JniMethodBaseInfo methodBaseInfo, final NodeWithParameters params = callableDecl; for (final Parameter p : params.getParameters()) { String name = p.getNameAsString(); - String javaType = getJavaType(methodBaseInfo.getDeclaringType(), methodInfo, p.getType()); - String jniType = getJniType(methodBaseInfo.getDeclaringType(), methodInfo, p.getType()); + String javaType = getJavaType(methodBaseInfo.getDeclaringType(), methodInfo, p.getType(), p.isVarArgs()); + String jniType = getJniType(methodBaseInfo.getDeclaringType(), methodInfo, p.getType(), p.isVarArgs()); methodBaseInfo.addParameter(new JniParameterInfo(name, javaType, jniType)); } } - static String getJavaType(JniTypeInfo typeInfo, JniMethodInfo methodInfo, Type type) { + static String getJavaType(JniTypeInfo typeInfo, JniMethodInfo methodInfo, Type type, boolean isVarArgs) { String typeName = type.asString(); if (methodInfo != null && methodInfo.getTypeParameters().contains(typeName)) - return typeName; + return typeName + (isVarArgs ? "..." : ""); if (typeInfo.getTypeParameters().contains(typeName)) - return typeName; + return typeName + (isVarArgs ? "..." : ""); try { final ResolvedType rt = type.resolve(); - return rt.describe(); + return rt.describe() + (isVarArgs ? "..." : ""); } catch (final Throwable thr) { - return getUnresolvedJavaType(type); + return getUnresolvedJavaType(type) + (isVarArgs ? "..." : ""); } } - static String getJniType(JniTypeInfo typeInfo, JniMethodInfo methodInfo, Type type) { + static String getJniType(JniTypeInfo typeInfo, JniMethodInfo methodInfo, Type type, boolean isVarArgs) { if (type == null) { return "Ljava/lang/Object;"; } if (type.isArrayType()) { - return getJniType(typeInfo, methodInfo, type.asArrayType()); + return (isVarArgs ? "[" : "") + getJniType(typeInfo, methodInfo, type.asArrayType()); } if (type.isPrimitiveType()) { - return getPrimitiveJniType(type.asString()); + return (isVarArgs ? "[" : "") + getPrimitiveJniType(type.asString()); } if (methodInfo != null && methodInfo.getTypeParameters().contains(type.asString())) { - return methodInfo.getTypeParameterJniType(type.asString()); + return (isVarArgs ? "[" : "") + methodInfo.getTypeParameterJniType(type.asString()); } if (typeInfo.getTypeParameters().contains(type.asString())) { - return typeInfo.getTypeParameterJniType(type.asString()); + return (isVarArgs ? "[" : "") + typeInfo.getTypeParameterJniType(type.asString()); } try { @@ -390,7 +390,7 @@ static String getJniType(JniTypeInfo typeInfo, JniMethodInfo methodInfo, Type ty catch (final Exception thr) { } - return getUnresolvedJniType(type); + return (isVarArgs ? "[" : "") + getUnresolvedJniType(type); } static String getJniType(JniTypeInfo typeInfo, JniMethodInfo methodInfo, ArrayType type) { @@ -398,7 +398,7 @@ static String getJniType(JniTypeInfo typeInfo, JniMethodInfo methodInfo, ArrayTy final StringBuilder depth = new StringBuilder(); for (int i = 0; i < level; ++i) depth.append("["); - return depth.toString() + getJniType(typeInfo, methodInfo, type.getElementType()); + return depth.toString() + getJniType(typeInfo, methodInfo, type.getElementType(), false); } static String getPrimitiveJniType(String javaType) { diff --git a/tools/java-source-utils/src/test/resources/UnresolvedTypes.txt b/tools/java-source-utils/src/test/resources/UnresolvedTypes.txt index 14965966e..89ce7c48d 100644 --- a/tools/java-source-utils/src/test/resources/UnresolvedTypes.txt +++ b/tools/java-source-utils/src/test/resources/UnresolvedTypes.txt @@ -6,6 +6,6 @@ public class UnresolvedTypes { * * JNI Sig: method.(L.*example.name.UnresolvedParameterType;)L.*UnresolvedReturnType; */ - public static UnresolvedReturnType method(example.name.UnresolvedParameterType parameter) { + public static UnresolvedReturnType method(example.name.UnresolvedParameterType... parameter) { } } \ No newline at end of file diff --git a/tools/java-source-utils/src/test/resources/UnresolvedTypes.xml b/tools/java-source-utils/src/test/resources/UnresolvedTypes.xml index b80c679a2..3fac8937a 100644 --- a/tools/java-source-utils/src/test/resources/UnresolvedTypes.xml +++ b/tools/java-source-utils/src/test/resources/UnresolvedTypes.xml @@ -3,7 +3,7 @@ - +