From a92363461dbe67d8736a6b0c3cbe1c3ad7aa28ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Walln=C3=B6fer?= Date: Fri, 26 May 2023 18:36:45 +0000 Subject: [PATCH] 8286470: Support searching for sections in class/package javadoc Reviewed-by: jjg --- .../formats/html/HtmlDocletWriter.java | 45 +++++--- .../formats/html/TagletWriterImpl.java | 100 +++++++++--------- .../html/resources/standard.properties | 1 + .../doclets/toolkit/util/IndexItem.java | 4 +- .../testAutoHeaderId/TestAutoHeaderId.java | 36 ++++++- 5 files changed, 121 insertions(+), 65 deletions(-) diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDocletWriter.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDocletWriter.java index 27532f84697f0..79cfae7ef4790 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDocletWriter.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDocletWriter.java @@ -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; @@ -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); @@ -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 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 trees, Content content) { + private void createSectionIdAndIndex(StartElementTree node, List 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()); @@ -1492,17 +1499,31 @@ private void generateHeadingId(StartElementTree node, List 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 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); + } } /** diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/TagletWriterImpl.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/TagletWriterImpl.java index 57c2bdb76f346..fead3a828441c 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/TagletWriterImpl.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/TagletWriterImpl.java @@ -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 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() { - @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) { diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard.properties b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard.properties index 0e003b73c84ec..1e517e4e0f0f0 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard.properties +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard.properties @@ -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 diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/IndexItem.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/IndexItem.java index 19c4d00922b69..8a3ba61793f9d 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/IndexItem.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/IndexItem.java @@ -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()); } @@ -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()); }; } diff --git a/test/langtools/jdk/javadoc/doclet/testAutoHeaderId/TestAutoHeaderId.java b/test/langtools/jdk/javadoc/doclet/testAutoHeaderId/TestAutoHeaderId.java index eab52d836d0ea..9a5f9b4c6de16 100644 --- a/test/langtools/jdk/javadoc/doclet/testAutoHeaderId/TestAutoHeaderId.java +++ b/test/langtools/jdk/javadoc/doclet/testAutoHeaderId/TestAutoHeaderId.java @@ -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 @@ -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 @@ -79,6 +79,10 @@ public void testAutoHeaderId(Path base) throws Exception { * *

* + *

Multi-line + * heading with extra + * whitespace

+ * * Last sentence. */ public class C { @@ -121,6 +125,32 @@ public class C { """, """

- """); + """, + """ +

Multi-line + heading with extra + whitespace

"""); + 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"}"""); } }