Skip to content

Commit 5622b09

Browse files
committed
8200337: Generalize see and link tags for user-defined anchors
Reviewed-by: jjg
1 parent 22347e4 commit 5622b09

File tree

17 files changed

+571
-65
lines changed

17 files changed

+571
-65
lines changed

src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTrees.java

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -106,16 +106,10 @@
106106
import com.sun.tools.javac.resources.CompilerProperties.Notes;
107107
import com.sun.tools.javac.resources.CompilerProperties.Warnings;
108108
import com.sun.tools.javac.tree.DCTree;
109-
import com.sun.tools.javac.tree.DCTree.DCBlockTag;
110-
import com.sun.tools.javac.tree.DCTree.DCComment;
111109
import com.sun.tools.javac.tree.DCTree.DCDocComment;
112-
import com.sun.tools.javac.tree.DCTree.DCEndPosTree;
113-
import com.sun.tools.javac.tree.DCTree.DCEntity;
114-
import com.sun.tools.javac.tree.DCTree.DCErroneous;
115110
import com.sun.tools.javac.tree.DCTree.DCIdentifier;
116111
import com.sun.tools.javac.tree.DCTree.DCParam;
117112
import com.sun.tools.javac.tree.DCTree.DCReference;
118-
import com.sun.tools.javac.tree.DCTree.DCText;
119113
import com.sun.tools.javac.tree.DocCommentTable;
120114
import com.sun.tools.javac.tree.DocTreeMaker;
121115
import com.sun.tools.javac.tree.EndPosTable;

src/jdk.compiler/share/classes/com/sun/tools/javac/parser/DocCommentParser.java

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -410,11 +410,9 @@ private DCText inlineText(WhitespaceRetentionPolicy whitespacePolicy) throws Par
410410
* Matching pairs of {@literal < >} are skipped. The text is terminated by the first
411411
* unmatched }. It is an error if the beginning of the next tag is detected.
412412
*/
413-
// TODO: allowMember is currently ignored
414-
// TODO: boolean allowMember should be enum FORBID, ALLOW, REQUIRE
415413
// TODO: improve quality of parse to forbid bad constructions.
416414
@SuppressWarnings("fallthrough")
417-
protected DCReference reference(boolean allowMember) throws ParseException {
415+
protected DCReference reference(ReferenceParser.Mode mode) throws ParseException {
418416
int pos = bp;
419417
int depth = 0;
420418

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

469467
String sig = newString(pos, bp);
470468

471-
472469
try {
473-
ReferenceParser.Reference ref = new ReferenceParser(fac).parse(sig);
474-
return m.at(pos).newReferenceTree(sig,
475-
ref.moduleName, ref.qualExpr,
476-
ref.member, ref.paramTypes)
477-
.setEndPos(bp);
470+
ReferenceParser.Reference ref = new ReferenceParser(fac).parse(sig, mode);
471+
return m.at(pos).newReferenceTree(sig, ref).setEndPos(bp);
478472
} catch (ReferenceParser.ParseException pe) {
479473
throw new ParseException(pos + pe.pos, pe.getMessage());
480474
}
@@ -1237,7 +1231,7 @@ public DCTree parse(int pos) throws ParseException {
12371231
@Override
12381232
public DCTree parse(int pos) throws ParseException {
12391233
skipWhitespace();
1240-
DCReference ref = reference(false);
1234+
DCReference ref = reference(ReferenceParser.Mode.MEMBER_DISALLOWED);
12411235
List<DCTree> description = blockContent();
12421236
return m.at(pos).newExceptionTree(ref, description);
12431237
}
@@ -1294,7 +1288,7 @@ public DCTree parse(int pos) throws ParseException {
12941288
new TagParser(TagParser.Kind.INLINE, DCTree.Kind.LINK) {
12951289
@Override
12961290
public DCTree parse(int pos) throws ParseException {
1297-
DCReference ref = reference(true);
1291+
DCReference ref = reference(ReferenceParser.Mode.MEMBER_OPTIONAL);
12981292
List<DCTree> label = inlineContent();
12991293
return m.at(pos).newLinkTree(ref, label);
13001294
}
@@ -1304,7 +1298,7 @@ public DCTree parse(int pos) throws ParseException {
13041298
new TagParser(TagParser.Kind.INLINE, DCTree.Kind.LINK_PLAIN) {
13051299
@Override
13061300
public DCTree parse(int pos) throws ParseException {
1307-
DCReference ref = reference(true);
1301+
DCReference ref = reference(ReferenceParser.Mode.MEMBER_OPTIONAL);
13081302
List<DCTree> label = inlineContent();
13091303
return m.at(pos).newLinkPlainTree(ref, label);
13101304
}
@@ -1351,7 +1345,7 @@ public DCTree parse(int pos) throws ParseException {
13511345
@Override
13521346
public DCTree parse(int pos) throws ParseException {
13531347
skipWhitespace();
1354-
DCReference ref = reference(true);
1348+
DCReference ref = reference(ReferenceParser.Mode.MEMBER_DISALLOWED);
13551349
List<DCTree> description = blockContent();
13561350
return m.at(pos).newProvidesTree(ref, description);
13571351
}
@@ -1411,7 +1405,7 @@ public DCTree parse(int pos) throws ParseException {
14111405

14121406
default:
14131407
if (isJavaIdentifierStart(ch) || ch == '#') {
1414-
DCReference ref = reference(true);
1408+
DCReference ref = reference(ReferenceParser.Mode.MEMBER_OPTIONAL);
14151409
List<DCTree> description = blockContent();
14161410
return m.at(pos).newSeeTree(description.prepend(ref));
14171411
}
@@ -1436,7 +1430,7 @@ public DCTree parse(int pos) throws ParseException {
14361430
skipWhitespace();
14371431
DCIdentifier name = identifier();
14381432
skipWhitespace();
1439-
DCReference type = reference(false);
1433+
DCReference type = reference(ReferenceParser.Mode.MEMBER_DISALLOWED);
14401434
List<DCTree> description = null;
14411435
if (isWhitespace(ch)) {
14421436
skipWhitespace();
@@ -1606,7 +1600,7 @@ public DCTree parse(int pos) throws ParseException {
16061600
@Override
16071601
public DCTree parse(int pos) throws ParseException {
16081602
skipWhitespace();
1609-
DCReference ref = reference(false);
1603+
DCReference ref = reference(ReferenceParser.Mode.MEMBER_DISALLOWED);
16101604
List<DCTree> description = blockContent();
16111605
return m.at(pos).newThrowsTree(ref, description);
16121606
}
@@ -1617,7 +1611,7 @@ public DCTree parse(int pos) throws ParseException {
16171611
@Override
16181612
public DCTree parse(int pos) throws ParseException {
16191613
skipWhitespace();
1620-
DCReference ref = reference(true);
1614+
DCReference ref = reference(ReferenceParser.Mode.MEMBER_DISALLOWED);
16211615
List<DCTree> description = blockContent();
16221616
return m.at(pos).newUsesTree(ref, description);
16231617
}
@@ -1642,7 +1636,7 @@ public DCTree parse(int pos) throws ParseException {
16421636
format = null;
16431637
}
16441638
}
1645-
DCReference ref = reference(true);
1639+
DCReference ref = reference(ReferenceParser.Mode.MEMBER_REQUIRED);
16461640
skipWhitespace();
16471641
if (ch == '}') {
16481642
nextChar();

src/jdk.compiler/share/classes/com/sun/tools/javac/parser/ReferenceParser.java

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -30,16 +30,13 @@
3030
import com.sun.source.util.TreeScanner;
3131
import com.sun.tools.javac.parser.Tokens.TokenKind;
3232
import com.sun.tools.javac.tree.JCTree;
33-
import com.sun.tools.javac.util.DiagnosticSource;
3433
import com.sun.tools.javac.util.JCDiagnostic;
3534
import com.sun.tools.javac.util.List;
3635
import com.sun.tools.javac.util.ListBuffer;
3736
import com.sun.tools.javac.util.Log;
3837
import com.sun.tools.javac.util.Name;
3938

4039
import javax.tools.JavaFileObject;
41-
import java.util.Locale;
42-
import java.util.Queue;
4340

4441
/**
4542
* A utility class to parse a string in a doc comment containing a
@@ -51,6 +48,18 @@
5148
* deletion without notice.</b>
5249
*/
5350
public class ReferenceParser {
51+
52+
/**
53+
* Context dependent parsing mode which either disallows, allows or requires
54+
* a member reference. The <code>MEMBER_OPTIONAL</code> value also allows
55+
* arbitrary URI fragments using a double hash mark.
56+
*/
57+
public enum Mode {
58+
MEMBER_DISALLOWED,
59+
MEMBER_OPTIONAL,
60+
MEMBER_REQUIRED
61+
}
62+
5463
/**
5564
* An object to contain the result of parsing a reference to an API element.
5665
* Any, but not all, of the member fields may be null.
@@ -98,10 +107,11 @@ public ReferenceParser(ParserFactory fac) {
98107
/**
99108
* Parse a reference to an API element as may be found in doc comment.
100109
* @param sig the signature to be parsed
110+
* @param mode the parsing mode
101111
* @return a {@code Reference} object containing the result of parsing the signature
102112
* @throws ParseException if there is an error while parsing the signature
103113
*/
104-
public Reference parse(String sig) throws ParseException {
114+
public Reference parse(String sig, Mode mode) throws ParseException {
105115

106116
// Break sig apart into moduleName qualifiedExpr member paramTypes.
107117
JCTree.JCExpression moduleName;
@@ -129,16 +139,28 @@ public Reference parse(String sig) throws ParseException {
129139
qualExpr = null;
130140
member = null;
131141
} else if (hash == -1) {
132-
if (lparen == -1) {
142+
if (lparen == -1 && mode != Mode.MEMBER_REQUIRED) {
133143
qualExpr = parseType(sig, afterSlash, sig.length(), dh);
134144
member = null;
135145
} else {
146+
if (mode == Mode.MEMBER_DISALLOWED) {
147+
throw new ParseException(hash, "dc.ref.unexpected.input");
148+
}
136149
qualExpr = null;
137-
member = parseMember(sig, afterSlash, lparen, dh);
150+
member = parseMember(sig, afterSlash, lparen > -1 ? lparen : sig.length(), dh);
138151
}
139152
} else {
153+
if (mode == Mode.MEMBER_DISALLOWED) {
154+
throw new ParseException(hash, "dc.ref.unexpected.input");
155+
}
140156
qualExpr = (hash == afterSlash) ? null : parseType(sig, afterSlash, hash, dh);
141-
if (lparen == -1) {
157+
if (sig.indexOf("#", afterHash) == afterHash) {
158+
// A hash symbol followed by another hash indicates a literal URL fragment.
159+
if (mode != Mode.MEMBER_OPTIONAL) {
160+
throw new ParseException(afterHash, "dc.ref.unexpected.input");
161+
}
162+
member = null;
163+
} else if (lparen == -1) {
142164
member = parseMember(sig, afterHash, sig.length(), dh);
143165
} else {
144166
member = parseMember(sig, afterHash, lparen, dh);

src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DCTree.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -872,8 +872,8 @@ public static class DCReference extends DCEndPosTree<DCReference> implements Ref
872872
DCReference(String signature, JCTree.JCExpression moduleName, JCTree qualExpr, Name member, List<JCTree> paramTypes) {
873873
this.signature = signature;
874874
this.moduleName = moduleName;
875-
qualifierExpression = qualExpr;
876-
memberName = member;
875+
this.qualifierExpression = qualExpr;
876+
this.memberName = member;
877877
this.paramTypes = paramTypes;
878878
}
879879

src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocTreeMaker.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -350,17 +350,17 @@ public DCProvides newProvidesTree(ReferenceTree name, List<? extends DocTree> de
350350
@Override @DefinedBy(Api.COMPILER_TREE)
351351
public DCReference newReferenceTree(String signature) {
352352
try {
353-
ReferenceParser.Reference ref = referenceParser.parse(signature);
354-
DCReference tree = new DCReference(signature, ref.moduleName, ref.qualExpr, ref.member, ref.paramTypes);
353+
ReferenceParser.Reference ref = referenceParser.parse(signature, ReferenceParser.Mode.MEMBER_OPTIONAL);
354+
DCReference tree = newReferenceTree(signature, ref);
355355
tree.pos = pos;
356356
return tree;
357357
} catch (ReferenceParser.ParseException e) {
358358
throw new IllegalArgumentException("invalid signature", e);
359359
}
360360
}
361361

362-
public DCReference newReferenceTree(String signature, JCTree.JCExpression moduleName, JCTree qualExpr, Name member, List<JCTree> paramTypes) {
363-
DCReference tree = new DCReference(signature, moduleName, qualExpr, member, paramTypes);
362+
public DCReference newReferenceTree(String signature, ReferenceParser.Reference ref) {
363+
DCReference tree = new DCReference(signature, ref.moduleName, ref.qualExpr, ref.member, ref.paramTypes);
364364
tree.pos = pos;
365365
return tree;
366366
}

src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDocletWriter.java

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -598,10 +598,21 @@ protected DocPath pathString(PackageElement packageElement, DocPath name) {
598598
/**
599599
* {@return the link to the given package}
600600
*
601-
* @param packageElement the package to link to.
602-
* @param label the label for the link.
601+
* @param packageElement the package to link to
602+
* @param label the label for the link
603603
*/
604604
public Content getPackageLink(PackageElement packageElement, Content label) {
605+
return getPackageLink(packageElement, label, null);
606+
}
607+
608+
/**
609+
* {@return the link to the given package}
610+
*
611+
* @param packageElement the package to link to
612+
* @param label the label for the link
613+
* @param fragment the link fragment
614+
*/
615+
public Content getPackageLink(PackageElement packageElement, Content label, String fragment) {
605616
boolean included = packageElement != null && utils.isIncluded(packageElement);
606617
if (!included) {
607618
for (PackageElement p : configuration.packages) {
@@ -619,7 +630,7 @@ public Content getPackageLink(PackageElement packageElement, Content label) {
619630
}
620631
DocLink targetLink;
621632
if (included || packageElement == null) {
622-
targetLink = new DocLink(pathString(packageElement, DocPaths.PACKAGE_SUMMARY));
633+
targetLink = new DocLink(pathString(packageElement, DocPaths.PACKAGE_SUMMARY), fragment);
623634
} else {
624635
targetLink = getCrossPackageLink(packageElement);
625636
}
@@ -650,11 +661,23 @@ public Content getPackageLink(PackageElement packageElement, Content label) {
650661
* @param label tag for the link
651662
*/
652663
public Content getModuleLink(ModuleElement mdle, Content label) {
664+
return getModuleLink(mdle, label, null);
665+
}
666+
667+
/**
668+
* {@return a link to module}
669+
*
670+
* @param mdle the module being documented
671+
* @param label tag for the link
672+
* @param fragment the link fragment
673+
*/
674+
public Content getModuleLink(ModuleElement mdle, Content label, String fragment) {
653675
Set<ElementFlag> flags = mdle != null ? utils.elementFlags(mdle)
654676
: EnumSet.noneOf(ElementFlag.class);
655677
boolean included = utils.isIncluded(mdle);
656678
if (included) {
657-
DocLink targetLink = new DocLink(pathToRoot.resolve(docPaths.moduleSummary(mdle)));
679+
DocLink targetLink;
680+
targetLink = new DocLink(pathToRoot.resolve(docPaths.moduleSummary(mdle)), fragment);
658681
Content link = links.createLink(targetLink, label, "");
659682
if (flags.contains(ElementFlag.PREVIEW) && label != contents.moduleLabel) {
660683
link = new ContentBuilder(

src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/TagletWriterImpl.java

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -501,24 +501,34 @@ private Content linkSeeReferenceOutput(Element holder,
501501
CommentHelper ch = utils.getCommentHelper(holder);
502502
TypeElement refClass = ch.getReferencedClass(ref);
503503
Element refMem = ch.getReferencedMember(ref);
504-
String refMemName = ch.getReferencedMemberName(refSignature);
505-
506-
if (refMemName == null && refMem != null) {
507-
refMemName = refMem.toString();
504+
String refFragment = ch.getReferencedFragment(refSignature);
505+
506+
if (refFragment == null && refMem != null) {
507+
refFragment = refMem.toString();
508+
} else if (refFragment != null && refFragment.startsWith("#")) {
509+
if (labelContent.isEmpty()) {
510+
// A non-empty label is required for fragment links as the
511+
// reference target does not provide a useful default label.
512+
reportWarning.accept("doclet.link.see.no_label", null);
513+
return invalidTagOutput(resources.getText("doclet.link.see.no_label"),
514+
Optional.of(refSignature));
515+
}
516+
refFragment = refFragment.substring(1);
508517
}
509518
if (refClass == null) {
510519
ModuleElement refModule = ch.getReferencedModule(ref);
511520
if (refModule != null && utils.isIncluded(refModule)) {
512-
return htmlWriter.getModuleLink(refModule, labelContent.isEmpty() ? text : labelContent);
521+
return htmlWriter.getModuleLink(refModule, labelContent.isEmpty() ? text : labelContent, refFragment);
513522
}
514523
//@see is not referencing an included class
515524
PackageElement refPackage = ch.getReferencedPackage(ref);
516525
if (refPackage != null && utils.isIncluded(refPackage)) {
517526
//@see is referencing an included package
518-
if (labelContent.isEmpty())
527+
if (labelContent.isEmpty()) {
519528
labelContent = plainOrCode(isLinkPlain,
520529
Text.of(refPackage.getQualifiedName()));
521-
return htmlWriter.getPackageLink(refPackage, labelContent);
530+
}
531+
return htmlWriter.getPackageLink(refPackage, labelContent, refFragment);
522532
} else {
523533
// @see is not referencing an included class, module or package. Check for cross links.
524534
String refModuleName = ch.getReferencedModuleName(refSignature);
@@ -541,8 +551,8 @@ private Content linkSeeReferenceOutput(Element holder,
541551
Optional.of(labelContent.isEmpty() ? text: labelContent));
542552
}
543553
}
544-
} else if (refMemName == null) {
545-
// Must be a class reference since refClass is not null and refMemName is null.
554+
} else if (refFragment == null) {
555+
// Must be a class reference since refClass is not null and refFragment is null.
546556
if (labelContent.isEmpty() && refTree != null) {
547557
TypeMirror referencedType = ch.getReferencedType(refTree);
548558
if (utils.isGenericType(referencedType)) {
@@ -555,9 +565,11 @@ private Content linkSeeReferenceOutput(Element holder,
555565
return htmlWriter.getLink(new HtmlLinkInfo(configuration, HtmlLinkInfo.Kind.DEFAULT, refClass)
556566
.label(labelContent));
557567
} else if (refMem == null) {
558-
// Must be a member reference since refClass is not null and refMemName is not null.
559-
// However, refMem is null, so this referenced member does not exist.
560-
return (labelContent.isEmpty() ? text: labelContent);
568+
// This is a fragment reference since refClass and refFragment are not null but refMem is null.
569+
return htmlWriter.getLink(new HtmlLinkInfo(configuration, HtmlLinkInfo.Kind.SEE_TAG, refClass)
570+
.label(labelContent)
571+
.where(refFragment)
572+
.style(null));
561573
} else {
562574
// Must be a member reference since refClass is not null and refMemName is not null.
563575
// refMem is not null, so this @see tag must be referencing a valid member.
@@ -591,6 +603,7 @@ private Content linkSeeReferenceOutput(Element holder,
591603
}
592604
}
593605
}
606+
String refMemName = refFragment;
594607
if (configuration.currentTypeElement != containing) {
595608
refMemName = (utils.isConstructor(refMem))
596609
? refMemName

src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ doclet.File_error=Error reading file: {0}
103103
doclet.URL_error=Error fetching URL: {0}
104104
doclet.Resource_error=Error reading resource: {0}
105105
doclet.link.no_reference=no reference given
106+
doclet.link.see.no_label=missing reference label
106107
doclet.see.class_or_package_not_found=Tag {0}: reference not found: {1}
107108
doclet.see.class_or_package_not_accessible=Tag {0}: reference not accessible: {1}
108109
doclet.see.nested_link=Tag {0}: nested link

0 commit comments

Comments
 (0)