Skip to content

Commit

Permalink
[generator] Fix parameter string in ref doc links (#935)
Browse files Browse the repository at this point in the history
Fixes: #931
Fixes: #932

We have a handful of issues in the https://developer.android.com/
links that are generated for various type members with complex
parameters.  Collections, varargs, and generics are some examples of
items that are not translated correctly when generating the parameter
portion of the reference documentation URL.

The XML generated by `java-source-utils` when processing
`android-stubs-src.jar` contains `<parameter/>` elements under all
methods.  The `<parameter/>` elements contain attributes with
additional type data; for instance:

	<method jni-return="Landroid/animation/ObjectAnimator;" jni-signature="(Ljava/lang/Object;Landroid/util/Property;Landroid/util/Property;Landroid/graphics/Path;)Landroid/animation/ObjectAnimator;" name="ofFloat" return="android.animation.ObjectAnimator">
	  <parameter jni-type="Ljava/lang/Object;" name="target" type="T"/>
	  <parameter jni-type="Landroid/util/Property;" name="xProperty" type="android.util.Property&lt;T, java.lang.Float&gt;"/>
	  <parameter jni-type="Landroid/util/Property;" name="yProperty" type="android.util.Property&lt;T, java.lang.Float&gt;"/>
	  <parameter jni-type="Landroid/graphics/Path;" name="path" type="android.graphics.Path"/>
	  <javadoc>
	    <![CDATA[Constructs and returns an ObjectAnimator that animates coordinates along a <code>Path</code>
	using two properties. A <code>Path</code></> animation moves in two dimensions, animating
	coordinates <code>(x, y)</code> together to follow the line. In this variation, the
	coordinates are floats that are set to separate properties, <code>xProperty</code> and
	<code>yProperty</code>.

	@param target The object whose properties are to be animated.
	@param xProperty The property for the x coordinate being animated.
	@param yProperty The property for the y coordinate being animated.
	@param path The <code>Path</code> to animate values along.
	@return An ObjectAnimator object that is set up to animate along <code>path</code>.]]>
	  </javadoc>
	</method>

Rather than processing the `//method/@jni-signature` attribute of the
method, use the `//method/parameter/@type` attribute values to create
more reliable type information for our reference documentation links.

With these changes in place, instead of e.g.

	https://developer.android.com/reference/android/accounts/AccountManager#addAccount(java.lang.String,%20java.lang.String,%20java.lang.String[],%20android.os.Bundle,%20android.app.Activity,%20android.accounts.AccountManagerCallback,%20android.os.Handler)

which doesn't anchor to the intended method documentation, we now emit

	https://developer.android.com/reference/android/accounts/AccountManager#addAccount(java.lang.String,%20java.lang.String,%20java.lang.String[],%20android.os.Bundle,%20android.app.Activity,%20android.accounts.AccountManagerCallback%3Candroid.os.Bundle%3E,%20android.os.Handler)

which *does* anchor to the intended method documentation.
  • Loading branch information
pjcollins authored Dec 15, 2021
1 parent d64087c commit d3f0c5c
Showing 1 changed file with 27 additions and 99 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ public static JavadocInfo CreateInfo (XElement element, XmldocStyle style, bool
var desc = GetMemberDescription (element);
string declaringJniType = desc.DeclaringJniType;
string declaringMemberName = desc.DeclaringMemberName;
var declaringMemberJniSignature = desc.DeclaringMemberJniSignature;
var declaringMemberParamString = desc.DeclaringMemberParameterString;

var extras = GetExtra (element, style, declaringJniType, declaringMemberName, declaringMemberJniSignature, appendCopyrightExtra);
var extras = GetExtra (element, style, declaringJniType, declaringMemberName, declaringMemberParamString, appendCopyrightExtra);
XElement[] extra = extras.Extras;
XElement[] copyright = extras.Copyright;

Expand All @@ -54,13 +54,13 @@ public static JavadocInfo CreateInfo (XElement element, XmldocStyle style, bool
Javadoc = javadoc,
MemberDescription = declaringMemberName == null
? declaringJniType
: $"{declaringJniType}.{declaringMemberName}.{declaringMemberJniSignature}",
: $"{declaringJniType}.{declaringMemberName}{declaringMemberParamString}",
XmldocStyle = style,
};
return info;
}

static (string DeclaringJniType, string DeclaringMemberName, string DeclaringMemberJniSignature) GetMemberDescription (XElement element)
static (string DeclaringJniType, string DeclaringMemberName, string DeclaringMemberParameterString) GetMemberDescription (XElement element)
{
bool isType = element.Name.LocalName == "class" ||
element.Name.LocalName == "interface";
Expand All @@ -76,14 +76,26 @@ public static JavadocInfo CreateInfo (XElement element, XmldocStyle style, bool
string declaringMemberName = isType
? null
: (string) element.Attribute ("name") ?? declaringJniType.Substring (declaringJniType.LastIndexOf ('/')+1);

string declaringMemberJniSignature = isType
? null
: (string) element.Attribute ("jni-signature");

return (declaringJniType, declaringMemberName, declaringMemberJniSignature);

string declaringMemberParameterString = null;
if (!isType && (declaringMemberJniSignature?.StartsWith ("(", StringComparison.Ordinal) ?? false)) {
var parameterTypes = element.Elements ("parameter")?.Select (e => e.Attribute ("type")?.Value)?.ToList ();
if (parameterTypes?.Any () ?? false) {
declaringMemberParameterString = $"({string.Join (", ", parameterTypes)})";
} else {
declaringMemberParameterString = "()";
}
}

return (declaringJniType, declaringMemberName, declaringMemberParameterString);
}

static (XElement[] Extras, XElement[] Copyright) GetExtra (XElement element, XmldocStyle style, string declaringJniType, string declaringMemberName, string declaringMemberJniSignature, bool appendCopyrightExtra)
static (XElement[] Extras, XElement[] Copyright) GetExtra (XElement element, XmldocStyle style, string declaringJniType, string declaringMemberName, string declaringMemberParameterString, bool appendCopyrightExtra)
{
if (!style.HasFlag (XmldocStyle.IntelliSenseAndExtraRemarks))
return (null, null);
Expand All @@ -107,7 +119,7 @@ public static JavadocInfo CreateInfo (XElement element, XmldocStyle style, bool

XElement docLink = null;
if (!string.IsNullOrEmpty (urlPrefix)) {
docLink = CreateDocLinkUrl (kind, urlPrefix, declaringJniType, declaringMemberName, declaringMemberJniSignature);
docLink = CreateDocLinkUrl (kind, urlPrefix, declaringJniType, declaringMemberName, declaringMemberParameterString);
}
extra = new List<XElement> ();
extra.Add (docLink);
Expand Down Expand Up @@ -217,26 +229,26 @@ static List<string> GetLines (string text)
[ApiLinkStyle.DeveloperAndroidComReference_2020Nov] = CreateAndroidDocLinkUri,
};

static XElement CreateDocLinkUrl (ApiLinkStyle style, string prefix, string declaringJniType, string declaringMemberName, string declaringMemberJniSignature)
static XElement CreateDocLinkUrl (ApiLinkStyle style, string prefix, string declaringJniType, string declaringMemberName, string declaringMemberParameterString)
{
;
if (style == ApiLinkStyle.None || prefix == null || declaringJniType == null)
return null;
if (UrlCreators.TryGetValue (style, out var creator)) {
return creator (prefix, declaringJniType, declaringMemberName, declaringMemberJniSignature);
return creator (prefix, declaringJniType, declaringMemberName, declaringMemberParameterString);
}
return null;
}

static XElement CreateAndroidDocLinkUri (string prefix, string declaringJniType, string declaringMemberName, string declaringMemberJniSignature)
static XElement CreateAndroidDocLinkUri (string prefix, string declaringJniType, string declaringMemberName, string declaringMemberParameterString)
{
// URL is:
// * {prefix}
// * declaring type in JNI format
// * when `declaringJniMemberName` != null, `#{declaringJniMemberName}`
// * for methods & constructors, a `(`, the arguments in *Java* syntax -- separated by `, ` -- and `)`
//
// Example: https://developer.android.com/reference/android/app/Application#registerOnProvideAssistDataListener(android.app.Application.OnProvideAssistDataListener)
// Example: "https://developer.android.com/reference/android/app/Application#registerOnProvideAssistDataListener(android.app.Application.OnProvideAssistDataListener)"
// Example: "https://developer.android.com/reference/android/animation/ObjectAnimator#ofFloat(T,%20android.util.Property%3CT,%20java.lang.Float%3E,%20float...)"

var java = new StringBuilder (declaringJniType)
.Replace ("/", ".")
Expand All @@ -250,13 +262,9 @@ static XElement CreateAndroidDocLinkUri (string prefix, string declaringJniType,
if (declaringMemberName != null) {
java.Append (".").Append (declaringMemberName);
url.Append ("#").Append (declaringMemberName);
if (declaringMemberJniSignature?.StartsWith ("(", StringComparison.Ordinal) ?? false) {
java.Append ("(");
url.Append ("(");
AppendJavaParameterTypes (java, declaringMemberJniSignature);
AppendJavaParameterTypes (url, declaringMemberJniSignature);
java.Append (")");
url.Append (")");
if (declaringMemberParameterString != null) {
java.Append (declaringMemberParameterString);
url.Append (declaringMemberParameterString);
}
}
var format = new XElement ("format",
Expand All @@ -270,85 +278,5 @@ static XElement CreateAndroidDocLinkUri (string prefix, string declaringJniType,
return new XElement ("para", format);
}

static StringBuilder AppendJavaParameterTypes (StringBuilder builder, string declaringMemberJniSignature)
{
if (string.IsNullOrEmpty (declaringMemberJniSignature) || declaringMemberJniSignature [0] != '(')
return builder;

int startLen = builder.Length;

for (int i = 1; i < declaringMemberJniSignature.Length; ++i) {
if (declaringMemberJniSignature [i] == ')')
break;
AppendComma ();
AppendJavaParameterType (builder, declaringMemberJniSignature, ref i);
}

return builder;

void AppendComma ()
{
if (startLen == builder.Length)
return;
builder.Append (", ");
}
}

static void AppendJavaParameterType (StringBuilder builder, string declaringMemberJniSignature, ref int i)
{
switch (declaringMemberJniSignature [i]) {
case '[': {
++i;
AppendJavaParameterType (builder, declaringMemberJniSignature, ref i);
builder.Append ("[]");
break;
}
case 'B': {
builder.Append ("byte");
break;
}
case 'C': {
builder.Append ("char");
break;
}
case 'D': {
builder.Append ("double");
break;
}
case 'F': {
builder.Append ("float");
break;
}
case 'I': {
builder.Append ("int");
break;
}
case 'J': {
builder.Append ("long");
break;
}
case 'L': {
int end = declaringMemberJniSignature.IndexOf (';', i);
if (end < 0)
throw new InvalidOperationException ($"INTERNAL ERROR: Invalid JNI signature '{declaringMemberJniSignature}': no ';' to end 'L' at index {i}!");
var type = declaringMemberJniSignature.Substring (i+1, end - i - 1)
.Replace ('/', '.')
.Replace ('$', '.');
builder.Append (type);
i = end;
break;
}
case 'S': {
builder.Append ("short");
break;
}
case 'Z': {
builder.Append ("boolean");
break;
}
default:
throw new NotSupportedException ($"INTERNAL ERROR: Don't know what to do with '{declaringMemberJniSignature [i]}' in '{declaringMemberJniSignature}'!");
}
}
}
}

0 comments on commit d3f0c5c

Please sign in to comment.