Skip to content

Commit

Permalink
8200337: Generalize see and link tags for user-defined anchors
Browse files Browse the repository at this point in the history
Reviewed-by: jjg
  • Loading branch information
hns committed Nov 4, 2022
1 parent 22347e4 commit 5622b09
Show file tree
Hide file tree
Showing 17 changed files with 571 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -106,16 +106,10 @@
import com.sun.tools.javac.resources.CompilerProperties.Notes;
import com.sun.tools.javac.resources.CompilerProperties.Warnings;
import com.sun.tools.javac.tree.DCTree;
import com.sun.tools.javac.tree.DCTree.DCBlockTag;
import com.sun.tools.javac.tree.DCTree.DCComment;
import com.sun.tools.javac.tree.DCTree.DCDocComment;
import com.sun.tools.javac.tree.DCTree.DCEndPosTree;
import com.sun.tools.javac.tree.DCTree.DCEntity;
import com.sun.tools.javac.tree.DCTree.DCErroneous;
import com.sun.tools.javac.tree.DCTree.DCIdentifier;
import com.sun.tools.javac.tree.DCTree.DCParam;
import com.sun.tools.javac.tree.DCTree.DCReference;
import com.sun.tools.javac.tree.DCTree.DCText;
import com.sun.tools.javac.tree.DocCommentTable;
import com.sun.tools.javac.tree.DocTreeMaker;
import com.sun.tools.javac.tree.EndPosTable;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -410,11 +410,9 @@ private DCText inlineText(WhitespaceRetentionPolicy whitespacePolicy) throws Par
* Matching pairs of {@literal < >} are skipped. The text is terminated by the first
* unmatched }. It is an error if the beginning of the next tag is detected.
*/
// TODO: allowMember is currently ignored
// TODO: boolean allowMember should be enum FORBID, ALLOW, REQUIRE
// TODO: improve quality of parse to forbid bad constructions.
@SuppressWarnings("fallthrough")
protected DCReference reference(boolean allowMember) throws ParseException {
protected DCReference reference(ReferenceParser.Mode mode) throws ParseException {
int pos = bp;
int depth = 0;

Expand Down Expand Up @@ -468,13 +466,9 @@ protected DCReference reference(boolean allowMember) throws ParseException {

String sig = newString(pos, bp);


try {
ReferenceParser.Reference ref = new ReferenceParser(fac).parse(sig);
return m.at(pos).newReferenceTree(sig,
ref.moduleName, ref.qualExpr,
ref.member, ref.paramTypes)
.setEndPos(bp);
ReferenceParser.Reference ref = new ReferenceParser(fac).parse(sig, mode);
return m.at(pos).newReferenceTree(sig, ref).setEndPos(bp);
} catch (ReferenceParser.ParseException pe) {
throw new ParseException(pos + pe.pos, pe.getMessage());
}
Expand Down Expand Up @@ -1237,7 +1231,7 @@ public DCTree parse(int pos) throws ParseException {
@Override
public DCTree parse(int pos) throws ParseException {
skipWhitespace();
DCReference ref = reference(false);
DCReference ref = reference(ReferenceParser.Mode.MEMBER_DISALLOWED);
List<DCTree> description = blockContent();
return m.at(pos).newExceptionTree(ref, description);
}
Expand Down Expand Up @@ -1294,7 +1288,7 @@ public DCTree parse(int pos) throws ParseException {
new TagParser(TagParser.Kind.INLINE, DCTree.Kind.LINK) {
@Override
public DCTree parse(int pos) throws ParseException {
DCReference ref = reference(true);
DCReference ref = reference(ReferenceParser.Mode.MEMBER_OPTIONAL);
List<DCTree> label = inlineContent();
return m.at(pos).newLinkTree(ref, label);
}
Expand All @@ -1304,7 +1298,7 @@ public DCTree parse(int pos) throws ParseException {
new TagParser(TagParser.Kind.INLINE, DCTree.Kind.LINK_PLAIN) {
@Override
public DCTree parse(int pos) throws ParseException {
DCReference ref = reference(true);
DCReference ref = reference(ReferenceParser.Mode.MEMBER_OPTIONAL);
List<DCTree> label = inlineContent();
return m.at(pos).newLinkPlainTree(ref, label);
}
Expand Down Expand Up @@ -1351,7 +1345,7 @@ public DCTree parse(int pos) throws ParseException {
@Override
public DCTree parse(int pos) throws ParseException {
skipWhitespace();
DCReference ref = reference(true);
DCReference ref = reference(ReferenceParser.Mode.MEMBER_DISALLOWED);
List<DCTree> description = blockContent();
return m.at(pos).newProvidesTree(ref, description);
}
Expand Down Expand Up @@ -1411,7 +1405,7 @@ public DCTree parse(int pos) throws ParseException {

default:
if (isJavaIdentifierStart(ch) || ch == '#') {
DCReference ref = reference(true);
DCReference ref = reference(ReferenceParser.Mode.MEMBER_OPTIONAL);
List<DCTree> description = blockContent();
return m.at(pos).newSeeTree(description.prepend(ref));
}
Expand All @@ -1436,7 +1430,7 @@ public DCTree parse(int pos) throws ParseException {
skipWhitespace();
DCIdentifier name = identifier();
skipWhitespace();
DCReference type = reference(false);
DCReference type = reference(ReferenceParser.Mode.MEMBER_DISALLOWED);
List<DCTree> description = null;
if (isWhitespace(ch)) {
skipWhitespace();
Expand Down Expand Up @@ -1606,7 +1600,7 @@ public DCTree parse(int pos) throws ParseException {
@Override
public DCTree parse(int pos) throws ParseException {
skipWhitespace();
DCReference ref = reference(false);
DCReference ref = reference(ReferenceParser.Mode.MEMBER_DISALLOWED);
List<DCTree> description = blockContent();
return m.at(pos).newThrowsTree(ref, description);
}
Expand All @@ -1617,7 +1611,7 @@ public DCTree parse(int pos) throws ParseException {
@Override
public DCTree parse(int pos) throws ParseException {
skipWhitespace();
DCReference ref = reference(true);
DCReference ref = reference(ReferenceParser.Mode.MEMBER_DISALLOWED);
List<DCTree> description = blockContent();
return m.at(pos).newUsesTree(ref, description);
}
Expand All @@ -1642,7 +1636,7 @@ public DCTree parse(int pos) throws ParseException {
format = null;
}
}
DCReference ref = reference(true);
DCReference ref = reference(ReferenceParser.Mode.MEMBER_REQUIRED);
skipWhitespace();
if (ch == '}') {
nextChar();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -30,16 +30,13 @@
import com.sun.source.util.TreeScanner;
import com.sun.tools.javac.parser.Tokens.TokenKind;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.util.DiagnosticSource;
import com.sun.tools.javac.util.JCDiagnostic;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.ListBuffer;
import com.sun.tools.javac.util.Log;
import com.sun.tools.javac.util.Name;

import javax.tools.JavaFileObject;
import java.util.Locale;
import java.util.Queue;

/**
* A utility class to parse a string in a doc comment containing a
Expand All @@ -51,6 +48,18 @@
* deletion without notice.</b>
*/
public class ReferenceParser {

/**
* Context dependent parsing mode which either disallows, allows or requires
* a member reference. The <code>MEMBER_OPTIONAL</code> value also allows
* arbitrary URI fragments using a double hash mark.
*/
public enum Mode {
MEMBER_DISALLOWED,
MEMBER_OPTIONAL,
MEMBER_REQUIRED
}

/**
* An object to contain the result of parsing a reference to an API element.
* Any, but not all, of the member fields may be null.
Expand Down Expand Up @@ -98,10 +107,11 @@ public ReferenceParser(ParserFactory fac) {
/**
* Parse a reference to an API element as may be found in doc comment.
* @param sig the signature to be parsed
* @param mode the parsing mode
* @return a {@code Reference} object containing the result of parsing the signature
* @throws ParseException if there is an error while parsing the signature
*/
public Reference parse(String sig) throws ParseException {
public Reference parse(String sig, Mode mode) throws ParseException {

// Break sig apart into moduleName qualifiedExpr member paramTypes.
JCTree.JCExpression moduleName;
Expand Down Expand Up @@ -129,16 +139,28 @@ public Reference parse(String sig) throws ParseException {
qualExpr = null;
member = null;
} else if (hash == -1) {
if (lparen == -1) {
if (lparen == -1 && mode != Mode.MEMBER_REQUIRED) {
qualExpr = parseType(sig, afterSlash, sig.length(), dh);
member = null;
} else {
if (mode == Mode.MEMBER_DISALLOWED) {
throw new ParseException(hash, "dc.ref.unexpected.input");
}
qualExpr = null;
member = parseMember(sig, afterSlash, lparen, dh);
member = parseMember(sig, afterSlash, lparen > -1 ? lparen : sig.length(), dh);
}
} else {
if (mode == Mode.MEMBER_DISALLOWED) {
throw new ParseException(hash, "dc.ref.unexpected.input");
}
qualExpr = (hash == afterSlash) ? null : parseType(sig, afterSlash, hash, dh);
if (lparen == -1) {
if (sig.indexOf("#", afterHash) == afterHash) {
// A hash symbol followed by another hash indicates a literal URL fragment.
if (mode != Mode.MEMBER_OPTIONAL) {
throw new ParseException(afterHash, "dc.ref.unexpected.input");
}
member = null;
} else if (lparen == -1) {
member = parseMember(sig, afterHash, sig.length(), dh);
} else {
member = parseMember(sig, afterHash, lparen, dh);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -872,8 +872,8 @@ public static class DCReference extends DCEndPosTree<DCReference> implements Ref
DCReference(String signature, JCTree.JCExpression moduleName, JCTree qualExpr, Name member, List<JCTree> paramTypes) {
this.signature = signature;
this.moduleName = moduleName;
qualifierExpression = qualExpr;
memberName = member;
this.qualifierExpression = qualExpr;
this.memberName = member;
this.paramTypes = paramTypes;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -350,17 +350,17 @@ public DCProvides newProvidesTree(ReferenceTree name, List<? extends DocTree> de
@Override @DefinedBy(Api.COMPILER_TREE)
public DCReference newReferenceTree(String signature) {
try {
ReferenceParser.Reference ref = referenceParser.parse(signature);
DCReference tree = new DCReference(signature, ref.moduleName, ref.qualExpr, ref.member, ref.paramTypes);
ReferenceParser.Reference ref = referenceParser.parse(signature, ReferenceParser.Mode.MEMBER_OPTIONAL);
DCReference tree = newReferenceTree(signature, ref);
tree.pos = pos;
return tree;
} catch (ReferenceParser.ParseException e) {
throw new IllegalArgumentException("invalid signature", e);
}
}

public DCReference newReferenceTree(String signature, JCTree.JCExpression moduleName, JCTree qualExpr, Name member, List<JCTree> paramTypes) {
DCReference tree = new DCReference(signature, moduleName, qualExpr, member, paramTypes);
public DCReference newReferenceTree(String signature, ReferenceParser.Reference ref) {
DCReference tree = new DCReference(signature, ref.moduleName, ref.qualExpr, ref.member, ref.paramTypes);
tree.pos = pos;
return tree;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -598,10 +598,21 @@ protected DocPath pathString(PackageElement packageElement, DocPath name) {
/**
* {@return the link to the given package}
*
* @param packageElement the package to link to.
* @param label the label for the link.
* @param packageElement the package to link to
* @param label the label for the link
*/
public Content getPackageLink(PackageElement packageElement, Content label) {
return getPackageLink(packageElement, label, null);
}

/**
* {@return the link to the given package}
*
* @param packageElement the package to link to
* @param label the label for the link
* @param fragment the link fragment
*/
public Content getPackageLink(PackageElement packageElement, Content label, String fragment) {
boolean included = packageElement != null && utils.isIncluded(packageElement);
if (!included) {
for (PackageElement p : configuration.packages) {
Expand All @@ -619,7 +630,7 @@ public Content getPackageLink(PackageElement packageElement, Content label) {
}
DocLink targetLink;
if (included || packageElement == null) {
targetLink = new DocLink(pathString(packageElement, DocPaths.PACKAGE_SUMMARY));
targetLink = new DocLink(pathString(packageElement, DocPaths.PACKAGE_SUMMARY), fragment);
} else {
targetLink = getCrossPackageLink(packageElement);
}
Expand Down Expand Up @@ -650,11 +661,23 @@ public Content getPackageLink(PackageElement packageElement, Content label) {
* @param label tag for the link
*/
public Content getModuleLink(ModuleElement mdle, Content label) {
return getModuleLink(mdle, label, null);
}

/**
* {@return a link to module}
*
* @param mdle the module being documented
* @param label tag for the link
* @param fragment the link fragment
*/
public Content getModuleLink(ModuleElement mdle, Content label, String fragment) {
Set<ElementFlag> flags = mdle != null ? utils.elementFlags(mdle)
: EnumSet.noneOf(ElementFlag.class);
boolean included = utils.isIncluded(mdle);
if (included) {
DocLink targetLink = new DocLink(pathToRoot.resolve(docPaths.moduleSummary(mdle)));
DocLink targetLink;
targetLink = new DocLink(pathToRoot.resolve(docPaths.moduleSummary(mdle)), fragment);
Content link = links.createLink(targetLink, label, "");
if (flags.contains(ElementFlag.PREVIEW) && label != contents.moduleLabel) {
link = new ContentBuilder(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -501,24 +501,34 @@ private Content linkSeeReferenceOutput(Element holder,
CommentHelper ch = utils.getCommentHelper(holder);
TypeElement refClass = ch.getReferencedClass(ref);
Element refMem = ch.getReferencedMember(ref);
String refMemName = ch.getReferencedMemberName(refSignature);

if (refMemName == null && refMem != null) {
refMemName = refMem.toString();
String refFragment = ch.getReferencedFragment(refSignature);

if (refFragment == null && refMem != null) {
refFragment = refMem.toString();
} else if (refFragment != null && refFragment.startsWith("#")) {
if (labelContent.isEmpty()) {
// A non-empty label is required for fragment links as the
// reference target does not provide a useful default label.
reportWarning.accept("doclet.link.see.no_label", null);
return invalidTagOutput(resources.getText("doclet.link.see.no_label"),
Optional.of(refSignature));
}
refFragment = refFragment.substring(1);
}
if (refClass == null) {
ModuleElement refModule = ch.getReferencedModule(ref);
if (refModule != null && utils.isIncluded(refModule)) {
return htmlWriter.getModuleLink(refModule, labelContent.isEmpty() ? text : labelContent);
return htmlWriter.getModuleLink(refModule, labelContent.isEmpty() ? text : labelContent, refFragment);
}
//@see is not referencing an included class
PackageElement refPackage = ch.getReferencedPackage(ref);
if (refPackage != null && utils.isIncluded(refPackage)) {
//@see is referencing an included package
if (labelContent.isEmpty())
if (labelContent.isEmpty()) {
labelContent = plainOrCode(isLinkPlain,
Text.of(refPackage.getQualifiedName()));
return htmlWriter.getPackageLink(refPackage, labelContent);
}
return htmlWriter.getPackageLink(refPackage, labelContent, refFragment);
} else {
// @see is not referencing an included class, module or package. Check for cross links.
String refModuleName = ch.getReferencedModuleName(refSignature);
Expand All @@ -541,8 +551,8 @@ private Content linkSeeReferenceOutput(Element holder,
Optional.of(labelContent.isEmpty() ? text: labelContent));
}
}
} else if (refMemName == null) {
// Must be a class reference since refClass is not null and refMemName is null.
} else if (refFragment == null) {
// Must be a class reference since refClass is not null and refFragment is null.
if (labelContent.isEmpty() && refTree != null) {
TypeMirror referencedType = ch.getReferencedType(refTree);
if (utils.isGenericType(referencedType)) {
Expand All @@ -555,9 +565,11 @@ private Content linkSeeReferenceOutput(Element holder,
return htmlWriter.getLink(new HtmlLinkInfo(configuration, HtmlLinkInfo.Kind.DEFAULT, refClass)
.label(labelContent));
} else if (refMem == null) {
// Must be a member reference since refClass is not null and refMemName is not null.
// However, refMem is null, so this referenced member does not exist.
return (labelContent.isEmpty() ? text: labelContent);
// This is a fragment reference since refClass and refFragment are not null but refMem is null.
return htmlWriter.getLink(new HtmlLinkInfo(configuration, HtmlLinkInfo.Kind.SEE_TAG, refClass)
.label(labelContent)
.where(refFragment)
.style(null));
} else {
// Must be a member reference since refClass is not null and refMemName is not null.
// refMem is not null, so this @see tag must be referencing a valid member.
Expand Down Expand Up @@ -591,6 +603,7 @@ private Content linkSeeReferenceOutput(Element holder,
}
}
}
String refMemName = refFragment;
if (configuration.currentTypeElement != containing) {
refMemName = (utils.isConstructor(refMem))
? refMemName
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ doclet.File_error=Error reading file: {0}
doclet.URL_error=Error fetching URL: {0}
doclet.Resource_error=Error reading resource: {0}
doclet.link.no_reference=no reference given
doclet.link.see.no_label=missing reference label
doclet.see.class_or_package_not_found=Tag {0}: reference not found: {1}
doclet.see.class_or_package_not_accessible=Tag {0}: reference not accessible: {1}
doclet.see.nested_link=Tag {0}: nested link
Expand Down
Loading

1 comment on commit 5622b09

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.