Skip to content

Commit

Permalink
8286470: Support searching for sections in class/package javadoc
Browse files Browse the repository at this point in the history
Reviewed-by: jjg
  • Loading branch information
hns committed May 26, 2023
1 parent 55d297f commit a923634
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@
import jdk.javadoc.internal.doclets.toolkit.util.DocLink;
import jdk.javadoc.internal.doclets.toolkit.util.DocPath;
import jdk.javadoc.internal.doclets.toolkit.util.DocPaths;
import jdk.javadoc.internal.doclets.toolkit.util.IndexItem;
import jdk.javadoc.internal.doclets.toolkit.util.Utils;
import jdk.javadoc.internal.doclets.toolkit.util.Utils.DeclarationPreviewLanguageFeatures;
import jdk.javadoc.internal.doclets.toolkit.util.Utils.ElementFlag;
Expand Down Expand Up @@ -1400,8 +1401,8 @@ public Boolean visitLiteral(LiteralTree node, Content content) {
@Override
public Boolean visitStartElement(StartElementTree node, Content content) {
Content attrs = new ContentBuilder();
if (node.getName().toString().matches("(?i)h[1-6]") && !hasIdAttribute(node)) {
generateHeadingId(node, trees, attrs);
if (node.getName().toString().matches("(?i)h[1-6]")) {
createSectionIdAndIndex(node, trees, attrs, element, context);
}
for (DocTree dt : node.getAttributes()) {
dt.accept(this, attrs);
Expand Down Expand Up @@ -1476,14 +1477,20 @@ private boolean equalsIgnoreCase(Name name, String s) {
return name != null && name.toString().equalsIgnoreCase(s);
}

private boolean hasIdAttribute(StartElementTree node) {
return node.getAttributes().stream().anyMatch(
dt -> dt instanceof AttributeTree at && equalsIgnoreCase(at.getName(), "id"));
private Optional<String> getIdAttributeValue(StartElementTree node) {
return node.getAttributes().stream()
.filter(dt -> dt instanceof AttributeTree at && equalsIgnoreCase(at.getName(), "id"))
.map(dt -> ((AttributeTree)dt).getValue().toString())
.findFirst();
}

private void generateHeadingId(StartElementTree node, List<? extends DocTree> trees, Content content) {
private void createSectionIdAndIndex(StartElementTree node, List<? extends DocTree> trees, Content attrs,
Element element, TagletWriterImpl.Context context) {
// Use existing id attribute if available
String id = getIdAttributeValue(node).orElse(null);
StringBuilder sb = new StringBuilder();
String tagName = node.getName().toString().toLowerCase(Locale.ROOT);
// Go through heading content to collect content and look for existing id
for (DocTree docTree : trees.subList(trees.indexOf(node) + 1, trees.size())) {
if (docTree instanceof TextTree text) {
sb.append(text.getBody());
Expand All @@ -1492,17 +1499,31 @@ private void generateHeadingId(StartElementTree node, List<? extends DocTree> tr
} else if (docTree instanceof LinkTree link) {
var label = link.getLabel();
sb.append(label.isEmpty() ? link.getReference().getSignature() : label.toString());
} else if (id == null && docTree instanceof StartElementTree nested
&& equalsIgnoreCase(nested.getName(), "a")) {
// Use id of embedded anchor element if present
id = getIdAttributeValue(nested).orElse(null);
} else if (docTree instanceof EndElementTree endElement
&& equalsIgnoreCase(endElement.getName(), tagName)) {
break;
} else if (docTree instanceof StartElementTree nested
&& equalsIgnoreCase(nested.getName(), "a")
&& hasIdAttribute(nested)) {
return; // Avoid generating id if embedded <a id=...> is present
}
}
HtmlId htmlId = htmlIds.forHeading(sb, headingIds);
content.add("id=\"").add(htmlId.name()).add("\"");
String headingContent = sb.toString().trim();
if (id == null) {
// Generate id attribute
HtmlId htmlId = htmlIds.forHeading(headingContent, headingIds);
id = htmlId.name();
attrs.add("id=\"").add(htmlId.name()).add("\"");
}
// Generate index item
if (!headingContent.isEmpty() && configuration.mainIndex != null) {
String tagText = headingContent.replaceAll("\\s+", " ");
IndexItem item = IndexItem.of(element, node, tagText,
getTagletWriterInstance(context).getHolderName(element),
resources.getText("doclet.Section"),
new DocLink(path, id));
configuration.mainIndex.add(item);
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -908,62 +908,66 @@ private Content createAnchorAndSearchIndex(Element element, String tagText, Cont
HtmlId id = HtmlIds.forText(tagText, htmlWriter.indexAnchorTable);
result = HtmlTree.SPAN(id, HtmlStyle.searchTagResult, tagContent);
if (options.createIndex() && !tagText.isEmpty()) {
String holder = new SimpleElementVisitor14<String, Void>() {
String holder = getHolderName(element);
IndexItem item = IndexItem.of(element, tree, tagText, holder, desc,
new DocLink(htmlWriter.path, id.name()));
configuration.mainIndex.add(item);
}
}
return result;
}

@Override
public String visitModule(ModuleElement e, Void p) {
return resources.getText("doclet.module")
+ " " + utils.getFullyQualifiedName(e);
}
String getHolderName(Element element) {
return new SimpleElementVisitor14<String, Void>() {

@Override
public String visitPackage(PackageElement e, Void p) {
return resources.getText("doclet.package")
+ " " + utils.getFullyQualifiedName(e);
}
@Override
public String visitModule(ModuleElement e, Void p) {
return resources.getText("doclet.module")
+ " " + utils.getFullyQualifiedName(e);
}

@Override
public String visitType(TypeElement e, Void p) {
return utils.getTypeElementKindName(e, true)
+ " " + utils.getFullyQualifiedName(e);
}
@Override
public String visitPackage(PackageElement e, Void p) {
return resources.getText("doclet.package")
+ " " + utils.getFullyQualifiedName(e);
}

@Override
public String visitExecutable(ExecutableElement e, Void p) {
return utils.getFullyQualifiedName(utils.getEnclosingTypeElement(e))
+ "." + utils.getSimpleName(e)
+ utils.flatSignature(e, htmlWriter.getCurrentPageElement());
}
@Override
public String visitType(TypeElement e, Void p) {
return utils.getTypeElementKindName(e, true)
+ " " + utils.getFullyQualifiedName(e);
}

@Override
public String visitVariable(VariableElement e, Void p) {
return utils.getFullyQualifiedName(utils.getEnclosingTypeElement(e))
+ "." + utils.getSimpleName(e);
}
@Override
public String visitExecutable(ExecutableElement e, Void p) {
return utils.getFullyQualifiedName(utils.getEnclosingTypeElement(e))
+ "." + utils.getSimpleName(e)
+ utils.flatSignature(e, htmlWriter.getCurrentPageElement());
}

@Override
public String visitUnknown(Element e, Void p) {
if (e instanceof DocletElement de) {
return switch (de.getSubKind()) {
case OVERVIEW -> resources.getText("doclet.Overview");
case DOCFILE -> getHolderName(de);
};
} else {
return super.visitUnknown(e, p);
}
}
@Override
public String visitVariable(VariableElement e, Void p) {
return utils.getFullyQualifiedName(utils.getEnclosingTypeElement(e))
+ "." + utils.getSimpleName(e);
}

@Override
protected String defaultAction(Element e, Void p) {
return utils.getFullyQualifiedName(e);
}
}.visit(element);
IndexItem item = IndexItem.of(element, tree, tagText, holder, desc,
new DocLink(htmlWriter.path, id.name()));
configuration.mainIndex.add(item);
@Override
public String visitUnknown(Element e, Void p) {
if (e instanceof DocletElement de) {
return switch (de.getSubKind()) {
case OVERVIEW -> resources.getText("doclet.Overview");
case DOCFILE -> getHolderName(de);
};
} else {
return super.visitUnknown(e, p);
}
}
}
return result;

@Override
protected String defaultAction(Element e, Void p) {
return utils.getFullyQualifiedName(e);
}
}.visit(element);
}

private String getHolderName(DocletElement de) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ doclet.Enclosing_Class=Enclosing class:
doclet.Enclosing_Interface=Enclosing interface:
doclet.Inheritance_Tree=Inheritance Tree
doclet.ReferencedIn=Referenced In
doclet.Section=Section
doclet.External_Specification=External Specification
doclet.External_Specifications=External Specifications
doclet.External_Specifications.All_Specifications=All Specifications
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ public static IndexItem of(Element element, DocTree docTree, String label,
Objects.requireNonNull(link);

switch (docTree.getKind()) {
case INDEX, SPEC, SYSTEM_PROPERTY -> { }
case INDEX, SPEC, SYSTEM_PROPERTY, START_ELEMENT -> { }
default -> throw new IllegalArgumentException(docTree.getKind().toString());
}

Expand Down Expand Up @@ -340,7 +340,7 @@ public Category getCategory() {

protected Category getCategory(DocTree docTree) {
return switch (docTree.getKind()) {
case INDEX, SPEC, SYSTEM_PROPERTY -> Category.TAGS;
case INDEX, SPEC, SYSTEM_PROPERTY, START_ELEMENT -> Category.TAGS;
default -> throw new IllegalArgumentException(docTree.getKind().toString());
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2022, 2023, 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 All @@ -23,7 +23,7 @@

/*
* @test
* @bug 8289332
* @bug 8289332 8286470
* @summary Auto-generate ids for user-defined headings
* @library /tools/lib ../../lib
* @modules jdk.javadoc/jdk.javadoc.internal.tool
Expand Down Expand Up @@ -79,6 +79,10 @@ public void testAutoHeaderId(Path base) throws Exception {
*
* <h4></h4>
*
* <h2> Multi-line
* heading with extra
* whitespace</h2>
*
* Last sentence.
*/
public class C {
Expand Down Expand Up @@ -121,6 +125,32 @@ public class C {
""",
"""
<h4 id="-heading"></h4>
""");
""",
"""
<h2 id="multi-line-heading-with-extra-whitespace-heading"> Multi-line
heading with extra
whitespace</h2>""");
checkOutput("tag-search-index.js", true,
"""
{"l":"Duplicate Text","h":"class p.C","d":"Section","u":"p/C.html#duplicate-text-heading"}""",
"""
{"l":"Duplicate Text","h":"class p.C","d":"Section","u":"p/C.html#duplicate-text-heading1"}""",
"""
{"l":"Embedded A-Tag with ID","h":"class p.C","d":"Section","u":"p/C.html#fixed-id-2"}""",
"""
{"l":"Embedded Code Tag","h":"class p.C","d":"Section","u":"p/C.html#embedded-code-tag-heading"}""",
"""
{"l":"Embedded Link Tag","h":"class p.C","d":"Section","u":"p/C.html#embedded-link-tag-heading"}""",
"""
{"l":"Extra (#*!. chars","h":"class p.C","d":"Section","u":"p/C.html#extra-chars-heading"}""",
"""
{"l":"First Header","h":"class p.C","d":"Section","u":"p/C.html#first-header-heading"}""",
"""
{"l":"Header with ID","h":"class p.C","d":"Section","u":"p/C.html#fixed-id-1"}""",
"""
{"l":"Multi-line heading with extra whitespace","h":"class p.C","d":"Section","u":"p/C.html\
#multi-line-heading-with-extra-whitespace-heading"}""",
"""
{"l":"Other attributes","h":"class p.C","d":"Section","u":"p/C.html#other-attributes-heading"}""");
}
}

1 comment on commit a923634

@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.