diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/AbstractMemberWriter.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/AbstractMemberWriter.java index 5a015980d70..c00a00c008a 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/AbstractMemberWriter.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/AbstractMemberWriter.java @@ -46,7 +46,6 @@ import jdk.javadoc.internal.doclets.toolkit.Content; import jdk.javadoc.internal.doclets.toolkit.MemberSummaryWriter; import jdk.javadoc.internal.doclets.toolkit.MemberWriter; import jdk.javadoc.internal.doclets.toolkit.Resources; -import jdk.javadoc.internal.doclets.toolkit.taglets.DeprecatedTaglet; import jdk.javadoc.internal.doclets.toolkit.util.Utils; /** @@ -257,8 +256,8 @@ public abstract class AbstractMemberWriter implements MemberSummaryWriter, Membe * @param target the content to which the deprecated information will be added. */ protected void addDeprecatedInfo(Element member, Content target) { - Content output = (new DeprecatedTaglet()).getAllBlockTagOutput(member, - writer.getTagletWriterInstance(false)); + var t = configuration.tagletManager.getTaglet(DocTree.Kind.DEPRECATED); + Content output = t.getAllBlockTagOutput(member, writer.getTagletWriterInstance(false)); if (!output.isEmpty()) { target.add(HtmlTree.DIV(HtmlStyle.deprecationBlock, output)); } diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/ClassWriterImpl.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/ClassWriterImpl.java index 640102dfabf..8884cc3d6dd 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/ClassWriterImpl.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/ClassWriterImpl.java @@ -31,6 +31,7 @@ import java.util.List; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; + import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ModuleElement; @@ -41,6 +42,7 @@ import javax.lang.model.util.SimpleElementVisitor8; import com.sun.source.doctree.DeprecatedTree; import com.sun.source.doctree.DocTree; + import jdk.javadoc.internal.doclets.formats.html.Navigation.PageMode; import jdk.javadoc.internal.doclets.formats.html.markup.ContentBuilder; import jdk.javadoc.internal.doclets.formats.html.markup.Entity; @@ -51,7 +53,6 @@ import jdk.javadoc.internal.doclets.formats.html.markup.TagName; import jdk.javadoc.internal.doclets.formats.html.markup.Text; import jdk.javadoc.internal.doclets.toolkit.ClassWriter; import jdk.javadoc.internal.doclets.toolkit.Content; -import jdk.javadoc.internal.doclets.toolkit.taglets.ParamTaglet; import jdk.javadoc.internal.doclets.toolkit.util.ClassTree; import jdk.javadoc.internal.doclets.toolkit.util.CommentHelper; import jdk.javadoc.internal.doclets.toolkit.util.DocFileIOException; @@ -92,6 +93,11 @@ public class ClassWriterImpl extends SubWriterHolderWriter implements ClassWrite this.classTree = classTree; } + @Override + public Content getOutputInstance() { + return new ContentBuilder(); + } + @Override public Content getHeader(String header) { HtmlTree body = getBody(getWindowTitle(utils.getSimpleName(typeElement))); @@ -173,7 +179,7 @@ public class ClassWriterImpl extends SubWriterHolderWriter implements ClassWrite } @Override - protected TypeElement getCurrentPageElement() { + public TypeElement getCurrentPageElement() { return typeElement; } @@ -268,8 +274,8 @@ public class ClassWriterImpl extends SubWriterHolderWriter implements ClassWrite @Override public void addParamInfo(Content target) { if (utils.hasBlockTag(typeElement, DocTree.Kind.PARAM)) { - Content paramInfo = (new ParamTaglet()).getAllBlockTagOutput(typeElement, - getTagletWriterInstance(false)); + var t = configuration.tagletManager.getTaglet(DocTree.Kind.PARAM); + Content paramInfo = t.getAllBlockTagOutput(typeElement, getTagletWriterInstance(false)); if (!paramInfo.isEmpty()) { target.add(HtmlTree.DL(HtmlStyle.notes, paramInfo)); } diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlConfiguration.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlConfiguration.java index 6bff863e178..6709dbbdb3e 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlConfiguration.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlConfiguration.java @@ -25,8 +25,13 @@ package jdk.javadoc.internal.doclets.formats.html; +import java.io.File; +import java.io.IOException; +import java.nio.file.InvalidPathException; +import java.nio.file.Path; import java.time.ZonedDateTime; import java.util.ArrayList; +import java.util.Arrays; import java.util.EnumSet; import java.util.HashMap; import java.util.List; @@ -39,6 +44,7 @@ import java.util.stream.Collectors; import javax.lang.model.element.Element; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; +import javax.tools.DocumentationTool; import javax.tools.JavaFileManager; import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; @@ -49,6 +55,7 @@ import jdk.javadoc.doclet.Reporter; import jdk.javadoc.doclet.StandardDoclet; import jdk.javadoc.doclet.Taglet; import jdk.javadoc.internal.Versions; +import jdk.javadoc.internal.doclets.formats.html.taglets.TagletManager; import jdk.javadoc.internal.doclets.toolkit.BaseConfiguration; import jdk.javadoc.internal.doclets.toolkit.BaseOptions; import jdk.javadoc.internal.doclets.toolkit.DocletException; @@ -61,6 +68,7 @@ import jdk.javadoc.internal.doclets.toolkit.util.DocPath; import jdk.javadoc.internal.doclets.toolkit.util.DocPaths; import jdk.javadoc.internal.doclets.toolkit.util.NewAPIBuilder; import jdk.javadoc.internal.doclets.toolkit.util.PreviewAPIListBuilder; +import jdk.javadoc.internal.doclets.toolkit.util.SimpleDocletException; /** * Configure the output based on the command-line options. @@ -104,7 +112,7 @@ public class HtmlConfiguration extends BaseConfiguration { * 2. items for elements are added in bulk before generating the index files * 3. additional items are added as needed */ - protected HtmlIndexBuilder mainIndex; + public HtmlIndexBuilder mainIndex; /** * The collection of deprecated items, if any, to be displayed on the deprecated-list page, @@ -133,7 +141,7 @@ public class HtmlConfiguration extends BaseConfiguration { public Contents contents; - protected final Messages messages; + public final Messages messages; public DocPaths docPaths; @@ -143,6 +151,11 @@ public class HtmlConfiguration extends BaseConfiguration { private final HtmlOptions options; + /** + * The taglet manager. + */ + public TagletManager tagletManager; + /** * Kinds of conditional pages. */ @@ -424,6 +437,125 @@ public class HtmlConfiguration extends BaseConfiguration { return false; } } + + String snippetPath = options.snippetPath(); + if (snippetPath != null) { + Messages messages = getMessages(); + JavaFileManager fm = getFileManager(); + if (fm instanceof StandardJavaFileManager) { + try { + List sp = Arrays.stream(snippetPath.split(File.pathSeparator)) + .map(Path::of) + .toList(); + StandardJavaFileManager sfm = (StandardJavaFileManager) fm; + sfm.setLocationFromPaths(DocumentationTool.Location.SNIPPET_PATH, sp); + } catch (IOException | InvalidPathException e) { + throw new SimpleDocletException(messages.getResources().getText( + "doclet.error_setting_snippet_path", snippetPath, e), e); + } + } else { + throw new SimpleDocletException(messages.getResources().getText( + "doclet.cannot_use_snippet_path", snippetPath)); + } + } + + initTagletManager(options.customTagStrs()); + return super.finishOptionSettings0(); } + + /** + * Initialize the taglet manager. The strings to initialize the simple custom tags should + * be in the following format: "[tag name]:[location str]:[heading]". + * + * @param customTagStrs the set two-dimensional arrays of strings. These arrays contain + * either -tag or -taglet arguments. + */ + private void initTagletManager(Set> customTagStrs) { + tagletManager = tagletManager != null ? tagletManager : new TagletManager(this); + JavaFileManager fileManager = getFileManager(); + Messages messages = getMessages(); + try { + tagletManager.initTagletPath(fileManager); + tagletManager.loadTaglets(fileManager); + + for (List args : customTagStrs) { + if (args.get(0).equals("-taglet")) { + tagletManager.addCustomTag(args.get(1), fileManager); + continue; + } + /* Since there are few constraints on the characters in a tag name, + * and real world examples with ':' in the tag name, we cannot simply use + * String.split(regex); instead, we tokenize the string, allowing + * special characters to be escaped with '\'. */ + List tokens = tokenize(args.get(1), 3); + switch (tokens.size()) { + case 1 -> { + String tagName = args.get(1); + if (tagletManager.isKnownCustomTag(tagName)) { + //reorder a standard tag + tagletManager.addNewSimpleCustomTag(tagName, null, ""); + } else { + //Create a simple tag with the heading that has the same name as the tag. + StringBuilder heading = new StringBuilder(tagName + ":"); + heading.setCharAt(0, Character.toUpperCase(tagName.charAt(0))); + tagletManager.addNewSimpleCustomTag(tagName, heading.toString(), "a"); + } + } + + case 2 -> + //Add simple taglet without heading, probably to excluding it in the output. + tagletManager.addNewSimpleCustomTag(tokens.get(0), tokens.get(1), ""); + + case 3 -> + tagletManager.addNewSimpleCustomTag(tokens.get(0), tokens.get(2), tokens.get(1)); + + default -> + messages.error("doclet.Error_invalid_custom_tag_argument", args.get(1)); + } + } + } catch (IOException e) { + messages.error("doclet.taglet_could_not_set_location", e.toString()); + } + } + + /** + * Given a string, return an array of tokens, separated by ':'. + * The separator character can be escaped with the '\' character. + * The '\' character may also be escaped with the '\' character. + * + * @param s the string to tokenize + * @param maxTokens the maximum number of tokens returned. If the + * max is reached, the remaining part of s is appended + * to the end of the last token. + * @return an array of tokens + */ + private List tokenize(String s, int maxTokens) { + List tokens = new ArrayList<>(); + StringBuilder token = new StringBuilder(); + boolean prevIsEscapeChar = false; + for (int i = 0; i < s.length(); i += Character.charCount(i)) { + int currentChar = s.codePointAt(i); + if (prevIsEscapeChar) { + // Case 1: escaped character + token.appendCodePoint(currentChar); + prevIsEscapeChar = false; + } else if (currentChar == ':' && tokens.size() < maxTokens - 1) { + // Case 2: separator + tokens.add(token.toString()); + token = new StringBuilder(); + } else if (currentChar == '\\') { + // Case 3: escape character + prevIsEscapeChar = true; + } else { + // Case 4: regular character + token.appendCodePoint(currentChar); + } + } + if (token.length() > 0) { + tokens.add(token.toString()); + } + return tokens; + } + } diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDoclet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDoclet.java index 2eceda94be5..ca88ef26e7c 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDoclet.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDoclet.java @@ -319,6 +319,16 @@ public class HtmlDoclet extends AbstractDoclet { copyLegalFiles(options.createIndex()); } + @Override + protected void generateFiles() throws DocletException { + super.generateFiles(); + + if (configuration.tagletManager != null) { // may be null, if no files generated, perhaps because of errros + configuration.tagletManager.printReport(); + } + + } + private void copyJqueryFiles() throws DocletException { List files = Arrays.asList( DocPaths.JQUERY_JS.getPath(), 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 79cfae7ef47..fde4734f049 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 @@ -41,6 +41,7 @@ import java.util.Optional; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; + import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; @@ -68,13 +69,11 @@ import com.sun.source.doctree.EndElementTree; import com.sun.source.doctree.EntityTree; import com.sun.source.doctree.ErroneousTree; import com.sun.source.doctree.EscapeTree; -import com.sun.source.doctree.IndexTree; import com.sun.source.doctree.InheritDocTree; +import com.sun.source.doctree.InlineTagTree; import com.sun.source.doctree.LinkTree; import com.sun.source.doctree.LiteralTree; import com.sun.source.doctree.StartElementTree; -import com.sun.source.doctree.SummaryTree; -import com.sun.source.doctree.SystemPropertyTree; import com.sun.source.doctree.TextTree; import com.sun.source.util.DocTreePath; import com.sun.source.util.SimpleDocTreeVisitor; @@ -91,12 +90,11 @@ import jdk.javadoc.internal.doclets.formats.html.markup.RawHtml; import jdk.javadoc.internal.doclets.formats.html.markup.Script; import jdk.javadoc.internal.doclets.formats.html.markup.TagName; import jdk.javadoc.internal.doclets.formats.html.markup.Text; +import jdk.javadoc.internal.doclets.formats.html.taglets.Taglet; +import jdk.javadoc.internal.doclets.formats.html.taglets.TagletWriter; import jdk.javadoc.internal.doclets.toolkit.Content; import jdk.javadoc.internal.doclets.toolkit.Messages; import jdk.javadoc.internal.doclets.toolkit.Resources; -import jdk.javadoc.internal.doclets.toolkit.taglets.DocRootTaglet; -import jdk.javadoc.internal.doclets.toolkit.taglets.Taglet; -import jdk.javadoc.internal.doclets.toolkit.taglets.TagletWriter; import jdk.javadoc.internal.doclets.toolkit.util.CommentHelper; import jdk.javadoc.internal.doclets.toolkit.util.Comparators; import jdk.javadoc.internal.doclets.toolkit.util.DocFile; @@ -109,14 +107,9 @@ 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; import jdk.javadoc.internal.doclets.toolkit.util.Utils.PreviewSummary; -import jdk.javadoc.internal.doclets.toolkit.util.VisibleMemberTable; import jdk.javadoc.internal.doclint.HtmlTag; -import static com.sun.source.doctree.DocTree.Kind.CODE; import static com.sun.source.doctree.DocTree.Kind.COMMENT; -import static com.sun.source.doctree.DocTree.Kind.LINK; -import static com.sun.source.doctree.DocTree.Kind.LINK_PLAIN; -import static com.sun.source.doctree.DocTree.Kind.SEE; import static com.sun.source.doctree.DocTree.Kind.TEXT; @@ -160,11 +153,11 @@ public class HtmlDocletWriter { protected final Contents contents; - protected final Messages messages; + public final Messages messages; protected final Resources resources; - protected final Links links; + public final Links links; protected final DocPaths docPaths; @@ -197,7 +190,7 @@ public class HtmlDocletWriter { * (Ideally, javadoc should be tracking all id's generated in a file * to avoid generating duplicates.) */ - Map indexAnchorTable = new HashMap<>(); + public final Map indexAnchorTable = new HashMap<>(); /** * Creates an {@code HtmlDocletWriter}. @@ -278,7 +271,7 @@ public class HtmlDocletWriter { return buf.toString(); } //where: - // Note: {@docRoot} is not case sensitive when passed in with a command-line option: + // Note: {@docRoot} is not case-sensitive when passed in with a command-line option: private static final Pattern docrootPattern = Pattern.compile(Pattern.quote("{@docroot}"), Pattern.CASE_INSENSITIVE); @@ -374,9 +367,8 @@ public class HtmlDocletWriter { return !output.isEmpty(); } - private Content getInlineTagOutput(Element element, DocTree tree, TagletWriterImpl.Context context) { - return getTagletWriterInstance(context) - .getInlineTagOutput(element, configuration.tagletManager, tree); + private Content getInlineTagOutput(Element element, InlineTagTree tree, TagletWriter.Context context) { + return getTagletWriterInstance(context).getInlineTagOutput(element, tree); } /** @@ -386,7 +378,7 @@ public class HtmlDocletWriter { * @return a TagletWriter that knows how to write HTML. */ public TagletWriter getTagletWriterInstance(boolean isFirstSentence) { - return new TagletWriterImpl(this, isFirstSentence); + return new TagletWriter(this, isFirstSentence); } /** @@ -395,8 +387,8 @@ public class HtmlDocletWriter { * @param context the enclosing context * @return a TagletWriter */ - public TagletWriterImpl getTagletWriterInstance(TagletWriterImpl.Context context) { - return new TagletWriterImpl(this, context); + public TagletWriter getTagletWriterInstance(TagletWriter.Context context) { + return new TagletWriter(this, context); } /** @@ -756,7 +748,7 @@ public class HtmlDocletWriter { } /************************************************************* - * Return a class cross link to external class documentation. + * Return a class cross-link to external class documentation. * The -link option does not allow users to * link to external classes in the "default" package. * @@ -886,7 +878,7 @@ public class HtmlDocletWriter { * * @return the type element of the current page. */ - protected TypeElement getCurrentPageElement() { + public TypeElement getCurrentPageElement() { return null; } @@ -1177,7 +1169,7 @@ public class HtmlDocletWriter { boolean isFirstSentence, boolean inSummary) { return commentTagsToContent(element, trees, - new TagletWriterImpl.Context(isFirstSentence, inSummary)); + new TagletWriter.Context(isFirstSentence, inSummary)); } /** @@ -1194,7 +1186,7 @@ public class HtmlDocletWriter { */ public Content commentTagsToContent(Element element, List trees, - TagletWriterImpl.Context context) + TagletWriter.Context context) { final Content result = new ContentBuilder() { @Override @@ -1307,12 +1299,6 @@ public class HtmlDocletWriter { return false; } - @Override - public Boolean visitDocRoot(DocRootTree node, Content content) { - content.add(getInlineTagOutput(element, node, context)); - return false; - } - @Override public Boolean visitEndElement(EndElementTree node, Content content) { content.add(RawHtml.endElement(node.getName())); @@ -1361,43 +1347,6 @@ public class HtmlDocletWriter { return (context.isFirstSentence && !output.isEmpty()); } - @Override - public Boolean visitIndex(IndexTree node, Content content) { - Content output = getInlineTagOutput(element, node, context); - if (output != null) { - content.add(output); - } - return false; - } - - @Override - public Boolean visitLink(LinkTree node, Content content) { - var inTags = context.inTags; - if (inTags.contains(LINK) || inTags.contains(LINK_PLAIN) || inTags.contains(SEE)) { - DocTreePath dtp = ch.getDocTreePath(node); - if (dtp != null) { - messages.warning(dtp, "doclet.see.nested_link", "{@" + node.getTagName() + "}"); - } - Content label = commentTagsToContent(element, node.getLabel(), context); - if (label.isEmpty()) { - label = Text.of(node.getReference().getSignature()); - } - content.add(label); - } else { - TagletWriterImpl t = getTagletWriterInstance(context.within(node)); - content.add(t.linkTagOutput(element, node)); - } - return false; - } - - @Override - public Boolean visitLiteral(LiteralTree node, Content content) { - String s = node.getBody().getBody(); - Content t = Text.of(Text.normalizeNewlines(s)); - content.add(node.getKind() == CODE ? HtmlTree.CODE(t) : t); - return false; - } - @Override public Boolean visitStartElement(StartElementTree node, Content content) { Content attrs = new ContentBuilder(); @@ -1411,22 +1360,6 @@ public class HtmlDocletWriter { return false; } - @Override - public Boolean visitSummary(SummaryTree node, Content content) { - Content output = getInlineTagOutput(element, node, context); - content.add(output); - return false; - } - - @Override - public Boolean visitSystemProperty(SystemPropertyTree node, Content content) { - Content output = getInlineTagOutput(element, node, context); - if (output != null) { - content.add(output); - } - return false; - } - private CharSequence textCleanup(String text, boolean isLast) { return textCleanup(text, isLast, false); } @@ -1455,9 +1388,11 @@ public class HtmlDocletWriter { @Override protected Boolean defaultAction(DocTree node, Content content) { - Content output = getInlineTagOutput(element, node, context); - if (output != null) { - content.add(output); + if (node instanceof InlineTagTree itt) { + var output = getInlineTagOutput(element, itt, context); + if (output != null) { + content.add(output); + } } return false; } @@ -1485,7 +1420,7 @@ public class HtmlDocletWriter { } private void createSectionIdAndIndex(StartElementTree node, List trees, Content attrs, - Element element, TagletWriterImpl.Context context) { + Element element, TagletWriter.Context context) { // Use existing id attribute if available String id = getIdAttributeValue(node).orElse(null); StringBuilder sb = new StringBuilder(); @@ -1562,7 +1497,7 @@ public class HtmlDocletWriter { * @param detail the optional detail message which may contain preformatted text * @return the output */ - protected Content invalidTagOutput(String summary, Optional detail) { + public Content invalidTagOutput(String summary, Optional detail) { if (detail.isEmpty() || detail.get().isEmpty()) { return HtmlTree.SPAN(HtmlStyle.invalidTag, Text.of(summary)); } @@ -1665,7 +1600,7 @@ public class HtmlDocletWriter { } }.visit(element); if (redirectPathFromRoot != null) { - text = "{@" + (new DocRootTaglet()).getName() + "}/" + text = "{@" + Kind.DOC_ROOT.tagName + "}/" + redirectPathFromRoot.resolve(text).getPath(); return replaceDocRootDir(text); } @@ -2049,7 +1984,7 @@ public class HtmlDocletWriter { * Returns the path of module/package specific stylesheets for the element. * @param element module/Package element * @return list of path of module/package specific stylesheets - * @throws DocFileIOException + * @throws DocFileIOException if an issue arises while accessing any stylesheets */ List getLocalStylesheets(Element element) throws DocFileIOException { List stylesheets = new ArrayList<>(); diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlIds.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlIds.java index 4511022697c..0491cbdc727 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlIds.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlIds.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 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 @@ -394,7 +394,7 @@ public class HtmlIds { * * @return the id */ - static HtmlId forParam(String paramName) { + public static HtmlId forParam(String paramName) { return HtmlId.of("param-" + paramName); } @@ -407,7 +407,7 @@ public class HtmlIds { * * @return the id */ - static HtmlId forText(String text, Map counts) { + public static HtmlId forText(String text, Map counts) { String base = text.replaceAll("\\s+", ""); int count = counts.compute(base, (k, v) -> v == null ? 0 : v + 1); return HtmlId.of(count == 0 ? base : base + "-" + count); diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlOptions.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlOptions.java index fc3bf519edf..f9d1f6f5dd3 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlOptions.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlOptions.java @@ -28,6 +28,7 @@ package jdk.javadoc.internal.doclets.formats.html; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; +import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.TreeSet; @@ -100,6 +101,11 @@ public class HtmlOptions extends BaseOptions { */ private boolean createTree = true; + /** + * Arguments for command-line option {@code -tag} and {@code -taglet}. + */ + private final LinkedHashSet> customTagStrs = new LinkedHashSet<>(); + /** * Arguments for command-line option {@code -Xdoclint} and friends. * Collected set of doclint options. @@ -175,6 +181,12 @@ public class HtmlOptions extends BaseOptions { */ private String packagesHeader = ""; + /** + * Argument for command-line option {@code --snippet-path}. + * The path for external snippets. + */ + private String snippetPath = null; + /** * Argument for command-line option {@code -splitindex}. * True if command-line option "-splitindex" is used. Default value is @@ -182,11 +194,23 @@ public class HtmlOptions extends BaseOptions { */ private boolean splitIndex = false; + /** + * Argument for command-line option {@code --show-taglets}. + * Show taglets (internal debug switch) + */ + private boolean showTaglets = false; + /** * Argument for command-line option {@code -stylesheetfile}. */ private String stylesheetFile = ""; + /** + * Argument for command-line option {@code -tagletpath}. + * The path to Taglets + */ + private String tagletPath = null; + /** * Argument for command-line option {@code -top}. */ @@ -406,6 +430,44 @@ public class HtmlOptions extends BaseOptions { } }, + new Option(resources, "--snippet-path", 1) { + @Override + public boolean process(String opt, List args) { + snippetPath = args.get(0); + return true; + } + }, + + new Option(resources, "-tag", 1) { + @Override + public boolean process(String opt, List args) { + ArrayList list = new ArrayList<>(); + list.add(opt); + list.add(args.get(0)); + customTagStrs.add(list); + return true; + } + }, + + new Option(resources, "-taglet", 1) { + @Override + public boolean process(String opt, List args) { + ArrayList list = new ArrayList<>(); + list.add(opt); + list.add(args.get(0)); + customTagStrs.add(list); + return true; + } + }, + + new Option(resources, "-tagletpath", 1) { + @Override + public boolean process(String opt, List args) { + tagletPath = args.get(0); + return true; + } + }, + new Option(resources, "-top", 1) { @Override public boolean process(String opt, List args) { @@ -489,6 +551,14 @@ public class HtmlOptions extends BaseOptions { messages.warning("doclet.NoFrames_specified"); return true; } + }, + + new Hidden(resources, "--show-taglets") { + @Override + public boolean process(String opt, List args) { + showTaglets = true; + return true; + } } ); Set allOptions = new TreeSet<>(); @@ -620,6 +690,13 @@ public class HtmlOptions extends BaseOptions { return createTree; } + /** + * Arguments for command-line option {@code -tag} and {@code -taglet}. + */ + LinkedHashSet> customTagStrs() { + return customTagStrs; + } + /** * Arguments for command-line option {@code -Xdoclint} and friends. * Collected set of doclint options. @@ -721,6 +798,22 @@ public class HtmlOptions extends BaseOptions { return packagesHeader; } + /** + * Argument for command-line option {@code --show-taglets}. + * Show taglets (internal debug switch) + */ + public boolean showTaglets() { + return showTaglets; + } + + /** + * Argument for command-line option {@code --snippet-path}. + * The path for external snippets. + */ + public String snippetPath() { + return snippetPath; + } + /** * Argument for command-line option {@code -splitindex}. * True if command-line option "-splitindex" is used. Default value is @@ -737,6 +830,14 @@ public class HtmlOptions extends BaseOptions { return stylesheetFile; } + /** + * Argument for command-line option {@code -tagletpath}. + * The path to Taglets + */ + public String tagletPath() { + return tagletPath; + } + /** * Argument for command-line option {@code -top}. */ diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlSerialFieldWriter.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlSerialFieldWriter.java index e67519de082..2838e549451 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlSerialFieldWriter.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlSerialFieldWriter.java @@ -25,20 +25,22 @@ package jdk.javadoc.internal.doclets.formats.html; -import java.util.*; +import java.util.List; +import java.util.SortedSet; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeMirror; import com.sun.source.doctree.DocTree; - import com.sun.source.doctree.SerialFieldTree; import com.sun.source.doctree.SerialTree; + import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; -import jdk.javadoc.internal.doclets.formats.html.markup.TagName; import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.formats.html.markup.TagName; import jdk.javadoc.internal.doclets.formats.html.markup.Text; +import jdk.javadoc.internal.doclets.formats.html.taglets.TagletWriter; import jdk.javadoc.internal.doclets.toolkit.Content; import jdk.javadoc.internal.doclets.toolkit.SerializedFormWriter; @@ -134,7 +136,7 @@ public class HtmlSerialFieldWriter extends FieldWriterImpl if (!description.isEmpty()) { Content serialFieldContent = writer.commentTagsToContent(field, description, - new TagletWriterImpl.Context(false, false)); + new TagletWriter.Context(false, false)); var div = HtmlTree.DIV(HtmlStyle.block, serialFieldContent); content.add(div); } diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlSerialMethodWriter.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlSerialMethodWriter.java index 8c0c41eaeb5..2f9b7da1b2f 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlSerialMethodWriter.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlSerialMethodWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 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 @@ -34,7 +34,7 @@ import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; import jdk.javadoc.internal.doclets.formats.html.markup.Text; import jdk.javadoc.internal.doclets.toolkit.Content; import jdk.javadoc.internal.doclets.toolkit.SerializedFormWriter; -import jdk.javadoc.internal.doclets.toolkit.taglets.TagletManager; +import jdk.javadoc.internal.doclets.formats.html.taglets.TagletManager; /** 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 deleted file mode 100644 index fa466e723b9..00000000000 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/TagletWriterImpl.java +++ /dev/null @@ -1,985 +0,0 @@ -/* - * Copyright (c) 2003, 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 - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.javadoc.internal.doclets.formats.html; - -import java.net.URI; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.HashSet; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.function.BiConsumer; -import java.util.function.Predicate; -import java.util.stream.Collectors; - -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.ModuleElement; -import javax.lang.model.element.PackageElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.SimpleElementVisitor14; - -import com.sun.source.doctree.DeprecatedTree; -import com.sun.source.doctree.DocTree; -import com.sun.source.doctree.IndexTree; -import com.sun.source.doctree.LinkTree; -import com.sun.source.doctree.LiteralTree; -import com.sun.source.doctree.ParamTree; -import com.sun.source.doctree.ReturnTree; -import com.sun.source.doctree.SeeTree; -import com.sun.source.doctree.SnippetTree; -import com.sun.source.doctree.SpecTree; -import com.sun.source.doctree.SystemPropertyTree; -import com.sun.source.doctree.TextTree; -import com.sun.source.doctree.ThrowsTree; -import com.sun.source.util.DocTreePath; -import jdk.javadoc.internal.doclets.formats.html.markup.ContentBuilder; -import jdk.javadoc.internal.doclets.formats.html.markup.HtmlAttr; -import jdk.javadoc.internal.doclets.formats.html.markup.HtmlId; -import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; -import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; -import jdk.javadoc.internal.doclets.formats.html.markup.RawHtml; -import jdk.javadoc.internal.doclets.formats.html.markup.TagName; -import jdk.javadoc.internal.doclets.formats.html.markup.Text; -import jdk.javadoc.internal.doclets.toolkit.BaseConfiguration; -import jdk.javadoc.internal.doclets.toolkit.Content; -import jdk.javadoc.internal.doclets.toolkit.DocletElement; -import jdk.javadoc.internal.doclets.toolkit.Messages; -import jdk.javadoc.internal.doclets.toolkit.Resources; -import jdk.javadoc.internal.doclets.toolkit.builders.SerializedFormBuilder; -import jdk.javadoc.internal.doclets.toolkit.taglets.ParamTaglet; -import jdk.javadoc.internal.doclets.toolkit.taglets.TagletWriter; -import jdk.javadoc.internal.doclets.toolkit.taglets.snippet.Style; -import jdk.javadoc.internal.doclets.toolkit.taglets.snippet.StyledText; -import jdk.javadoc.internal.doclets.toolkit.util.CommentHelper; -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.PreviewFlagProvider; -import jdk.javadoc.internal.doclets.toolkit.util.VisibleMemberTable; - -import static com.sun.source.doctree.DocTree.Kind.LINK_PLAIN; - -/** - * The taglet writer that writes HTML. - */ -public class TagletWriterImpl extends TagletWriter { - /** - * A class that provides the information about the enclosing context for - * a series of {@code DocTree} nodes. - * This context may be used to determine the content that should be generated from the tree nodes. - */ - static class Context { - /** - * Whether or not the trees are appearing in a context of just the first sentence, - * such as in the summary table of the enclosing element. - */ - final boolean isFirstSentence; - /** - * Whether or not the trees are appearing in the "summary" section of the - * page for a declaration. - */ - final boolean inSummary; - /** - * The set of enclosing kinds of tags. - */ - final Set inTags; - - /** - * Creates an outermost context, with no enclosing tags. - * - * @param isFirstSentence {@code true} if the trees are appearing in a context of just the - * first sentence and {@code false} otherwise - * @param inSummary {@code true} if the trees are appearing in the "summary" section - * of the page for a declaration and {@code false} otherwise - */ - Context(boolean isFirstSentence, boolean inSummary) { - this(isFirstSentence, inSummary, EnumSet.noneOf(DocTree.Kind.class)); - } - - private Context(boolean isFirstSentence, boolean inSummary, Set inTags) { - this.isFirstSentence = isFirstSentence; - this.inSummary = inSummary; - this.inTags = inTags; - } - - /** - * Creates a new {@code Context} that includes an extra tag kind in the set of enclosing - * kinds of tags. - * - * @param tree the enclosing tree - * - * @return the new {@code Context} - */ - Context within(DocTree tree) { - var newInTags = EnumSet.copyOf(inTags); - newInTags.add(tree.getKind()); - return new Context(isFirstSentence, inSummary, newInTags); - } - } - - private final HtmlDocletWriter htmlWriter; - private final HtmlConfiguration configuration; - private final HtmlOptions options; - private final Utils utils; - private final Resources resources; - - private final Messages messages; - - private final Contents contents; - private final Context context; - - // Threshold for length of @see tag label for switching from inline to block layout. - private static final int TAG_LIST_ITEM_MAX_INLINE_LENGTH = 30; - - /** - * Creates a taglet writer. - * - * @param htmlWriter the {@code HtmlDocletWriter} for the page - * @param isFirstSentence {@code true} if this taglet writer is being used for a - * "first sentence" summary - */ - public TagletWriterImpl(HtmlDocletWriter htmlWriter, boolean isFirstSentence) { - this(htmlWriter, isFirstSentence, false); - } - - /** - * Creates a taglet writer. - * - * @param htmlWriter the {@code HtmlDocletWriter} for the page - * @param isFirstSentence {@code true} if this taglet writer is being used for a - * "first sentence" summary, and {@code false} otherwise - * @param inSummary {@code true} if this taglet writer is being used for the content - * of a {@code {@summary ...}} tag, and {@code false} otherwise - */ - public TagletWriterImpl(HtmlDocletWriter htmlWriter, boolean isFirstSentence, boolean inSummary) { - this(htmlWriter, new Context(isFirstSentence, inSummary)); - } - - /** - * Creates a taglet writer. - * - * @param htmlWriter the {@code HtmlDocletWriter} for the page - * @param context the enclosing context for any tags - */ - public TagletWriterImpl(HtmlDocletWriter htmlWriter, Context context) { - super(context.isFirstSentence); - this.htmlWriter = htmlWriter; - this.context = context; - configuration = htmlWriter.configuration; - options = configuration.getOptions(); - utils = configuration.utils; - messages = configuration.messages; - resources = configuration.getDocResources(); - contents = configuration.getContents(); - } - - @Override - public Content getOutputInstance() { - return new ContentBuilder(); - } - - @Override - protected Content codeTagOutput(Element element, LiteralTree tag) { - return HtmlTree.CODE(Text.of(Text.normalizeNewlines(tag.getBody().getBody()))); - } - - @Override - protected Content indexTagOutput(Element element, IndexTree tag) { - CommentHelper ch = utils.getCommentHelper(element); - - DocTree searchTerm = tag.getSearchTerm(); - String tagText = (searchTerm instanceof TextTree tt) ? tt.getBody() : ""; - if (tagText.charAt(0) == '"' && tagText.charAt(tagText.length() - 1) == '"') { - tagText = tagText.substring(1, tagText.length() - 1); - } - tagText = tagText.replaceAll("\\s+", " "); - - Content desc = htmlWriter.commentTagsToContent(element, tag.getDescription(), context.within(tag)); - String descText = extractText(desc); - - return createAnchorAndSearchIndex(element, tagText, descText, tag); - } - - // ugly but simple; - // alternatives would be to walk the Content's tree structure, or to add new functionality to Content - private String extractText(Content c) { - return c.toString().replaceAll("<[^>]+>", ""); - } - - @Override - public Content getDocRootOutput() { - String path; - if (htmlWriter.pathToRoot.isEmpty()) - path = "."; - else - path = htmlWriter.pathToRoot.getPath(); - return Text.of(path); - } - - @Override - public Content deprecatedTagOutput(Element element) { - ContentBuilder result = new ContentBuilder(); - CommentHelper ch = utils.getCommentHelper(element); - List deprs = utils.getDeprecatedTrees(element); - if (utils.isTypeElement(element)) { - if (utils.isDeprecated(element)) { - result.add(HtmlTree.SPAN(HtmlStyle.deprecatedLabel, - htmlWriter.getDeprecatedPhrase(element))); - if (!deprs.isEmpty()) { - List commentTrees = ch.getDescription(deprs.get(0)); - if (!commentTrees.isEmpty()) { - result.add(commentTagsToOutput(element, null, commentTrees, false)); - } - } - } - } else { - if (utils.isDeprecated(element)) { - result.add(HtmlTree.SPAN(HtmlStyle.deprecatedLabel, - htmlWriter.getDeprecatedPhrase(element))); - if (!deprs.isEmpty()) { - List bodyTrees = ch.getBody(deprs.get(0)); - Content body = commentTagsToOutput(element, null, bodyTrees, false); - if (!body.isEmpty()) - result.add(HtmlTree.DIV(HtmlStyle.deprecationComment, body)); - } - } else { - Element ee = utils.getEnclosingTypeElement(element); - if (utils.isDeprecated(ee)) { - result.add(HtmlTree.SPAN(HtmlStyle.deprecatedLabel, - htmlWriter.getDeprecatedPhrase(ee))); - } - } - } - return result; - } - - @Override - public Content linkTagOutput(Element element, LinkTree tag) { - CommentHelper ch = utils.getCommentHelper(element); - - var linkRef = tag.getReference(); - if (linkRef == null) { - messages.warning(ch.getDocTreePath(tag), "doclet.link.no_reference"); - return invalidTagOutput(resources.getText("doclet.tag.invalid_input", tag.toString()), - Optional.empty()); - } - - DocTree.Kind kind = tag.getKind(); - String refSignature = ch.getReferencedSignature(linkRef); - - return linkSeeReferenceOutput(element, - tag, - refSignature, - ch.getReferencedElement(tag), - (kind == LINK_PLAIN), - htmlWriter.commentTagsToContent(element, tag.getLabel(), context), - (key, args) -> messages.warning(ch.getDocTreePath(tag), key, args) - ); - } - - @Override - protected Content literalTagOutput(Element element, LiteralTree tag) { - return Text.of(Text.normalizeNewlines(tag.getBody().getBody())); - } - - @Override - public Content getParamHeader(ParamTaglet.ParamKind kind) { - Content header = switch (kind) { - case PARAMETER -> contents.parameters; - case TYPE_PARAMETER -> contents.typeParameters; - case RECORD_COMPONENT -> contents.recordComponents; - default -> throw new IllegalArgumentException(kind.toString()); - }; - return HtmlTree.DT(header); - } - - @Override - public Content paramTagOutput(Element element, ParamTree paramTag, String paramName) { - ContentBuilder body = new ContentBuilder(); - CommentHelper ch = utils.getCommentHelper(element); - // define id attributes for state components so that generated descriptions may refer to them - boolean defineID = (element.getKind() == ElementKind.RECORD) - && !paramTag.isTypeParameter(); - Content nameContent = Text.of(paramName); - body.add(HtmlTree.CODE(defineID ? HtmlTree.SPAN_ID(HtmlIds.forParam(paramName), nameContent) : nameContent)); - body.add(" - "); - List description = ch.getDescription(paramTag); - body.add(htmlWriter.commentTagsToContent(element, description, context.within(paramTag))); - return HtmlTree.DD(body); - } - - @Override - public Content returnTagOutput(Element element, ReturnTree returnTag, boolean inline) { - CommentHelper ch = utils.getCommentHelper(element); - List desc = ch.getDescription(returnTag); - Content content = htmlWriter.commentTagsToContent(element, desc, context.within(returnTag)); - return inline - ? new ContentBuilder(contents.getContent("doclet.Returns_0", content)) - : new ContentBuilder(HtmlTree.DT(contents.returns), HtmlTree.DD(content)); - } - - @Override - public Content seeTagOutput(Element holder, List seeTags) { - List links = new ArrayList<>(); - for (SeeTree dt : seeTags) { - TagletWriterImpl t = new TagletWriterImpl(htmlWriter, context.within(dt)); - links.add(t.seeTagOutput(holder, dt)); - } - if (utils.isVariableElement(holder) && ((VariableElement)holder).getConstantValue() != null && - htmlWriter instanceof ClassWriterImpl writer) { - //Automatically add link to constant values page for constant fields. - DocPath constantsPath = - htmlWriter.pathToRoot.resolve(DocPaths.CONSTANT_VALUES); - String whichConstant = - writer.getTypeElement().getQualifiedName() + "." + - utils.getSimpleName(holder); - DocLink link = constantsPath.fragment(whichConstant); - links.add(htmlWriter.links.createLink(link, - contents.getContent("doclet.Constants_Summary"))); - } - if (utils.isClass(holder) && utils.isSerializable((TypeElement)holder)) { - //Automatically add link to serialized form page for serializable classes. - if (SerializedFormBuilder.serialInclude(utils, holder) && - SerializedFormBuilder.serialInclude(utils, utils.containingPackage(holder))) { - DocPath serialPath = htmlWriter.pathToRoot.resolve(DocPaths.SERIALIZED_FORM); - DocLink link = serialPath.fragment(utils.getFullyQualifiedName(holder)); - links.add(htmlWriter.links.createLink(link, - contents.getContent("doclet.Serialized_Form"))); - } - } - if (links.isEmpty()) { - return Text.EMPTY; - } - // Use a different style if any link label is longer than 30 chars or contains commas. - boolean hasLongLabels = links.stream().anyMatch(this::isLongOrHasComma); - var seeList = HtmlTree.UL(hasLongLabels ? HtmlStyle.tagListLong : HtmlStyle.tagList); - links.stream() - .filter(Predicate.not(Content::isEmpty)) - .forEach(item -> seeList.add(HtmlTree.LI(item))); - - return new ContentBuilder( - HtmlTree.DT(contents.seeAlso), - HtmlTree.DD(seeList)); - } - - private boolean isLongOrHasComma(Content c) { - String s = c.toString() - .replaceAll("<.*?>", "") // ignore HTML - .replaceAll("&#?[A-Za-z0-9]+;", " ") // entities count as a single character - .replaceAll("\\R", "\n"); // normalize newlines - return s.length() > TAG_LIST_ITEM_MAX_INLINE_LENGTH || s.contains(","); - } - - String textOf(List trees) { - return trees.stream() - .filter(dt -> dt instanceof TextTree) - .map(dt -> ((TextTree) dt).getBody().trim()) - .collect(Collectors.joining(" ")); - } - - /** - * {@return the output for a single {@code @see} tag} - * - * @param element the element that has the documentation comment containing this tag - * @param seeTag the tag - */ - private Content seeTagOutput(Element element, SeeTree seeTag) { - List ref = seeTag.getReference(); - assert !ref.isEmpty(); - DocTree ref0 = ref.get(0); - switch (ref0.getKind()) { - case TEXT, START_ELEMENT -> { - // @see "Reference" - // @see ... - return htmlWriter.commentTagsToContent(element, ref, false, false); - } - - case REFERENCE -> { - // @see reference label... - CommentHelper ch = utils.getCommentHelper(element); - String refSignature = ch.getReferencedSignature(ref0); - List label = ref.subList(1, ref.size()); - - return linkSeeReferenceOutput(element, - seeTag, - refSignature, - ch.getReferencedElement(seeTag), - false, - htmlWriter.commentTagsToContent(element, label, context), - (key, args) -> messages.warning(ch.getDocTreePath(seeTag), key, args) - ); - } - - case ERRONEOUS -> { - return invalidTagOutput(resources.getText("doclet.tag.invalid_input", - ref0.toString()), - Optional.empty()); - } - - default -> throw new IllegalStateException(ref0.getKind().toString()); - } - - } - - /** - * Worker method to generate a link from the information in different kinds of tags, - * such as {@code {@link ...}} tags, {@code @see ...} tags and the {@code link} markup tag - * in a {@code {@snippet ...}} tag. - * - * @param holder the element that has the documentation comment containing the information - * @param refTree the tree node containing the information, or {@code null} if not available - * @param refSignature the normalized signature of the target of the reference - * @param ref the target of the reference - * @param isLinkPlain {@code true} if the link should be presented in "plain" font, - * or {@code false} for "code" font - * @param label the label for the link, - * or an empty item to use a default label derived from the signature - * @param reportWarning a function to report warnings about issues found in the reference - * - * @return the output containing the generated link, or content indicating an error - */ - private Content linkSeeReferenceOutput(Element holder, - DocTree refTree, - String refSignature, - Element ref, - boolean isLinkPlain, - Content label, - BiConsumer reportWarning) { - Content labelContent = plainOrCode(isLinkPlain, label); - - // The signature from the @see tag. We will output this text when a label is not specified. - Content text = plainOrCode(isLinkPlain, - Text.of(Objects.requireNonNullElse(refSignature, ""))); - - CommentHelper ch = utils.getCommentHelper(holder); - TypeElement refClass = ch.getReferencedClass(ref); - Element refMem = ch.getReferencedMember(ref); - 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. - htmlWriter.messages.error(ch.getDocTreePath(refTree), "doclet.link.see.no_label"); - 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, 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()) { - labelContent = plainOrCode(isLinkPlain, - Text.of(refPackage.getQualifiedName())); - } - 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); - DocLink elementCrossLink = (refPackage != null) ? htmlWriter.getCrossPackageLink(refPackage) : - (configuration.extern.isModule(refModuleName)) - ? htmlWriter.getCrossModuleLink(utils.elementUtils.getModuleElement(refModuleName)) - : null; - if (elementCrossLink != null) { - // Element cross link found - return htmlWriter.links.createExternalLink(elementCrossLink, - (labelContent.isEmpty() ? text : labelContent)); - } else { - // No cross link found so print warning - if (!configuration.isDocLintReferenceGroupEnabled()) { - reportWarning.accept( - "doclet.link.see.reference_not_found", - new Object[] { refSignature}); - } - return htmlWriter.invalidTagOutput(resources.getText("doclet.link.see.reference_invalid"), - Optional.of(labelContent.isEmpty() ? text: labelContent)); - } - } - } 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)) { - // This is a generic type link, use the TypeMirror representation. - return plainOrCode(isLinkPlain, htmlWriter.getLink( - new HtmlLinkInfo(configuration, HtmlLinkInfo.Kind.LINK_TYPE_PARAMS_AND_BOUNDS, referencedType))); - } - labelContent = plainOrCode(isLinkPlain, Text.of(utils.getSimpleName(refClass))); - } - return htmlWriter.getLink(new HtmlLinkInfo(configuration, HtmlLinkInfo.Kind.PLAIN, refClass) - .label(labelContent)); - } else if (refMem == null) { - // This is a fragment reference since refClass and refFragment are not null but refMem is null. - return htmlWriter.getLink(new HtmlLinkInfo(configuration, HtmlLinkInfo.Kind.PLAIN, refClass) - .label(labelContent) - .fragment(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. - TypeElement containing = utils.getEnclosingTypeElement(refMem); - - // Find the enclosing type where the method is actually visible - // in the inheritance hierarchy. - ExecutableElement overriddenMethod = null; - if (refMem.getKind() == ElementKind.METHOD) { - VisibleMemberTable vmt = configuration.getVisibleMemberTable(containing); - overriddenMethod = vmt.getOverriddenMethod((ExecutableElement)refMem); - - if (overriddenMethod != null) { - containing = utils.getEnclosingTypeElement(overriddenMethod); - } - } - if (refSignature.trim().startsWith("#") && - ! (utils.isPublic(containing) || utils.isLinkable(containing))) { - // Since the link is relative and the holder is not even being - // documented, this must be an inherited link. Redirect it. - // The current class either overrides the referenced member or - // inherits it automatically. - if (htmlWriter instanceof ClassWriterImpl writer) { - containing = writer.getTypeElement(); - } else if (!utils.isPublic(containing)) { - reportWarning.accept("doclet.link.see.reference_not_accessible", - new Object[] { utils.getFullyQualifiedName(containing)}); - } else { - if (!configuration.isDocLintReferenceGroupEnabled()) { - reportWarning.accept("doclet.link.see.reference_not_found", - new Object[] { refSignature }); - } - } - } - String refMemName = refFragment; - if (configuration.currentTypeElement != containing) { - refMemName = (utils.isConstructor(refMem)) - ? refMemName - : utils.getSimpleName(containing) + "." + refMemName; - } - if (utils.isExecutableElement(refMem)) { - if (refMemName.indexOf('(') < 0) { - refMemName += utils.makeSignature((ExecutableElement) refMem, null, true); - } - if (overriddenMethod != null) { - // The method to actually link. - refMem = overriddenMethod; - } - } - - return htmlWriter.getDocLink(HtmlLinkInfo.Kind.SHOW_PREVIEW, containing, - refMem, (labelContent.isEmpty() - ? plainOrCode(isLinkPlain, Text.of(refMemName)) - : labelContent), null, false); - } - } - - private Content plainOrCode(boolean plain, Content body) { - return (plain || body.isEmpty()) ? body : HtmlTree.CODE(body); - } - - @Override - public Content simpleBlockTagOutput(Element element, List simpleTags, String header) { - CommentHelper ch = utils.getCommentHelper(element); - ContentBuilder body = new ContentBuilder(); - boolean many = false; - for (DocTree simpleTag : simpleTags) { - if (many) { - body.add(", "); - } - List bodyTags = ch.getBody(simpleTag); - body.add(htmlWriter.commentTagsToContent(element, bodyTags, context.within(simpleTag))); - many = true; - } - return new ContentBuilder( - HtmlTree.DT(RawHtml.of(header)), - HtmlTree.DD(body)); - } - - @Override - protected Content snippetTagOutput(Element element, SnippetTree tag, StyledText content, - String id, String lang) { - var pre = new HtmlTree(TagName.PRE).setStyle(HtmlStyle.snippet); - if (id != null && !id.isBlank()) { - pre.put(HtmlAttr.ID, id); - } - var code = new HtmlTree(TagName.CODE) - .addUnchecked(Text.EMPTY); // Make sure the element is always rendered - if (lang != null && !lang.isBlank()) { - code.addStyle("language-" + lang); - } - - content.consumeBy((styles, sequence) -> { - CharSequence text = Text.normalizeNewlines(sequence); - if (styles.isEmpty()) { - code.add(text); - } else { - Element e = null; - String t = null; - boolean linkEncountered = false; - boolean markupEncountered = false; - Set classes = new HashSet<>(); - for (Style s : styles) { - if (s instanceof Style.Name n) { - classes.add(n.name()); - } else if (s instanceof Style.Link l) { - assert !linkEncountered; // TODO: do not assert; pick the first link report on subsequent - linkEncountered = true; - t = l.target(); - e = getLinkedElement(element, t); - if (e == null) { - // TODO: diagnostic output - } - } else if (s instanceof Style.Markup) { - markupEncountered = true; - break; - } else { - // TODO: transform this if...else into an exhaustive - // switch over the sealed Style hierarchy when "Pattern - // Matching for switch" has been implemented (JEP 406 - // and friends) - throw new AssertionError(styles); - } - } - Content c; - if (markupEncountered) { - return; - } else if (linkEncountered) { - assert e != null; - //disable preview tagging inside the snippets: - PreviewFlagProvider prevPreviewProvider = utils.setPreviewFlagProvider(el -> false); - try { - c = linkSeeReferenceOutput(element, - null, - t, - e, - false, // TODO: for now - Text.of(sequence.toString()), - (key, args) -> { /* TODO: report diagnostic */ }); - } finally { - utils.setPreviewFlagProvider(prevPreviewProvider); - } - } else { - c = HtmlTree.SPAN(Text.of(text)); - classes.forEach(((HtmlTree) c)::addStyle); - } - code.add(c); - } - }); - String copyText = resources.getText("doclet.Copy_to_clipboard"); - String copiedText = resources.getText("doclet.Copied_to_clipboard"); - String copySnippetText = resources.getText("doclet.Copy_snippet_to_clipboard"); - var snippetContainer = HtmlTree.DIV(HtmlStyle.snippetContainer, - new HtmlTree(TagName.BUTTON) - .add(HtmlTree.SPAN(Text.of(copyText)) - .put(HtmlAttr.DATA_COPIED, copiedText)) - .add(new HtmlTree(TagName.IMG) - .put(HtmlAttr.SRC, htmlWriter.pathToRoot.resolve(DocPaths.CLIPBOARD_SVG).getPath()) - .put(HtmlAttr.ALT, copySnippetText)) - .addStyle(HtmlStyle.copy) - .addStyle(HtmlStyle.snippetCopy) - .put(HtmlAttr.ARIA_LABEL, copySnippetText) - .put(HtmlAttr.ONCLICK, "copySnippet(this)")); - return snippetContainer.add(pre.add(code)); - } - - /* - * Returns the element that is linked from the context of the referrer using - * the provided signature; returns null if such element could not be found. - * - * This method is to be used when it is the target of the link that is - * important, not the container of the link (e.g. was it an @see, - * @link/@linkplain or @snippet tags, etc.) - */ - public Element getLinkedElement(Element referer, String signature) { - var factory = utils.docTrees.getDocTreeFactory(); - var docCommentTree = utils.getDocCommentTree(referer); - var rootPath = new DocTreePath(utils.getTreePath(referer), docCommentTree); - var reference = factory.newReferenceTree(signature); - var fabricatedPath = new DocTreePath(rootPath, reference); - return utils.docTrees.getElement(fabricatedPath); - } - - @Override - public Content specTagOutput(Element holder, List specTags) { - if (specTags.isEmpty()) { - return Text.EMPTY; - } - - List links = specTags.stream() - .map(st -> specTagToContent(holder, st)) - .collect(Collectors.toList()); - - // Use a different style if any link label is longer than 30 chars or contains commas. - boolean hasLongLabels = links.stream().anyMatch(this::isLongOrHasComma); - var specList = HtmlTree.UL(hasLongLabels ? HtmlStyle.tagListLong : HtmlStyle.tagList); - links.stream() - .filter(Predicate.not(Content::isEmpty)) - .forEach(item -> specList.add(HtmlTree.LI(item))); - - return new ContentBuilder( - HtmlTree.DT(contents.externalSpecifications), - HtmlTree.DD(specList)); - } - - private Content specTagToContent(Element holder, SpecTree specTree) { - String specTreeURL = specTree.getURL().getBody(); - List specTreeLabel = specTree.getTitle(); - Content label = htmlWriter.commentTagsToContent(holder, specTreeLabel, isFirstSentence); - return getExternalSpecContent(holder, specTree, specTreeURL, - textOf(specTreeLabel).replaceAll("\\s+", " "), label); - } - - Content getExternalSpecContent(Element holder, DocTree docTree, String url, String searchText, Content title) { - URI specURI; - try { - // Use the canonical title of the spec if one is available - specURI = new URI(url); - } catch (URISyntaxException e) { - CommentHelper ch = utils.getCommentHelper(holder); - DocTreePath dtp = ch.getDocTreePath(docTree); - htmlWriter.messages.error(dtp, "doclet.Invalid_URL", e.getMessage()); - specURI = null; - } - - Content titleWithAnchor = createAnchorAndSearchIndex(holder, - searchText, - title, - resources.getText("doclet.External_Specification"), - docTree); - - if (specURI == null) { - return titleWithAnchor; - } else { - return HtmlTree.A(htmlWriter.resolveExternalSpecURI(specURI), titleWithAnchor); - } - - } - - @Override - protected Content systemPropertyTagOutput(Element element, SystemPropertyTree tag) { - String tagText = tag.getPropertyName().toString(); - return HtmlTree.CODE(createAnchorAndSearchIndex(element, tagText, - resources.getText("doclet.System_Property"), tag)); - } - - @Override - public Content getThrowsHeader() { - return HtmlTree.DT(contents.throws_); - } - - @Deprecated(forRemoval = true) - private Content throwsTagOutput(Element element, ThrowsTree throwsTag, TypeMirror substituteType) { - ContentBuilder body = new ContentBuilder(); - CommentHelper ch = utils.getCommentHelper(element); - Element exception = ch.getException(throwsTag); - Content excName; - if (substituteType != null) { - excName = htmlWriter.getLink(new HtmlLinkInfo(configuration, HtmlLinkInfo.Kind.PLAIN, - substituteType)); - } else if (exception == null) { - excName = Text.of(throwsTag.getExceptionName().toString()); - } else if (exception.asType() == null) { - excName = Text.of(utils.getFullyQualifiedName(exception)); - } else { - HtmlLinkInfo link = new HtmlLinkInfo(configuration, HtmlLinkInfo.Kind.PLAIN, - exception.asType()); - excName = htmlWriter.getLink(link); - } - body.add(HtmlTree.CODE(excName)); - List description = ch.getDescription(throwsTag); - Content desc = htmlWriter.commentTagsToContent(element, description, context.within(throwsTag)); - if (desc != null && !desc.isEmpty()) { - body.add(" - "); - body.add(desc); - } - return HtmlTree.DD(body); - } - - @Override - public Content throwsTagOutput(TypeMirror throwsType, Optional content) { - var linkInfo = new HtmlLinkInfo(configuration, HtmlLinkInfo.Kind.PLAIN, throwsType); - var link = htmlWriter.getLink(linkInfo); - var concat = new ContentBuilder(HtmlTree.CODE(link)); - if (content.isPresent()) { - concat.add(" - "); - concat.add(content.get()); - } - return HtmlTree.DD(concat); - } - - @Override - public Content valueTagOutput(VariableElement field, String constantVal, boolean includeLink) { - return includeLink - ? htmlWriter.getDocLink(HtmlLinkInfo.Kind.LINK_TYPE_PARAMS_AND_BOUNDS, field, constantVal) - : Text.of(constantVal); - } - - @Override - protected Content invalidTagOutput(String summary, Optional detail) { - return htmlWriter.invalidTagOutput(summary, - detail.isEmpty() || detail.get().isEmpty() - ? Optional.empty() - : Optional.of(Text.of(Text.normalizeNewlines(detail.get())))); - } - - @Override - public Content commentTagsToOutput(DocTree holder, List tags) { - return commentTagsToOutput(null, holder, tags, false); - } - - @Override - public Content commentTagsToOutput(Element element, List tags) { - return commentTagsToOutput(element, null, tags, false); - } - - @Override - public Content commentTagsToOutput(Element holder, - DocTree holderTag, - List tags, - boolean isFirstSentence) - { - return htmlWriter.commentTagsToContent(holder, - tags, holderTag == null ? context : context.within(holderTag)); - } - - @Override - public BaseConfiguration configuration() { - return configuration; - } - - @Override - protected TypeElement getCurrentPageElement() { - return htmlWriter.getCurrentPageElement(); - } - - public HtmlDocletWriter getHtmlWriter() { - return htmlWriter; - } - - private Content createAnchorAndSearchIndex(Element element, String tagText, String desc, DocTree tree) { - return createAnchorAndSearchIndex(element, tagText, Text.of(tagText), desc, tree); - } - - @SuppressWarnings("preview") - private Content createAnchorAndSearchIndex(Element element, String tagText, Content tagContent, String desc, DocTree tree) { - Content result = null; - if (context.isFirstSentence && context.inSummary || context.inTags.contains(DocTree.Kind.INDEX)) { - result = tagContent; - } else { - HtmlId id = HtmlIds.forText(tagText, htmlWriter.indexAnchorTable); - result = HtmlTree.SPAN(id, HtmlStyle.searchTagResult, tagContent); - if (options.createIndex() && !tagText.isEmpty()) { - 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; - } - - String getHolderName(Element element) { - return new SimpleElementVisitor14() { - - @Override - public String visitModule(ModuleElement e, Void p) { - return resources.getText("doclet.module") - + " " + utils.getFullyQualifiedName(e); - } - - @Override - public String visitPackage(PackageElement e, Void p) { - return resources.getText("doclet.package") - + " " + utils.getFullyQualifiedName(e); - } - - @Override - public String visitType(TypeElement e, Void p) { - return utils.getTypeElementKindName(e, true) - + " " + 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 visitVariable(VariableElement e, Void p) { - return utils.getFullyQualifiedName(utils.getEnclosingTypeElement(e)) - + "." + utils.getSimpleName(e); - } - - @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 - protected String defaultAction(Element e, Void p) { - return utils.getFullyQualifiedName(e); - } - }.visit(element); - } - - private String getHolderName(DocletElement de) { - PackageElement pe = de.getPackageElement(); - if (pe.isUnnamed()) { - // if package is unnamed use enclosing module only if it is named - Element ee = pe.getEnclosingElement(); - if (ee instanceof ModuleElement && !((ModuleElement)ee).isUnnamed()) { - return resources.getText("doclet.module") + " " + utils.getFullyQualifiedName(ee); - } - return pe.toString(); // "Unnamed package" or similar - } - return resources.getText("doclet.package") + " " + utils.getFullyQualifiedName(pe); - } -} diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/BaseTaglet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/BaseTaglet.java similarity index 58% rename from src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/BaseTaglet.java rename to src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/BaseTaglet.java index 4faf17f51e9..868e12f2700 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/BaseTaglet.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/BaseTaglet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 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,35 +23,56 @@ * questions. */ -package jdk.javadoc.internal.doclets.toolkit.taglets; +package jdk.javadoc.internal.doclets.formats.html.taglets; import java.util.Set; + import javax.lang.model.element.Element; import com.sun.source.doctree.DocTree; -import com.sun.source.doctree.UnknownBlockTagTree; + import jdk.javadoc.doclet.Taglet.Location; +import jdk.javadoc.internal.doclets.formats.html.HtmlConfiguration; import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.Messages; +import jdk.javadoc.internal.doclets.toolkit.Resources; +import jdk.javadoc.internal.doclets.toolkit.util.Utils; /** * A base class that implements the {@link Taglet} interface. */ public class BaseTaglet implements Taglet { + // The following members are global to the lifetime of the doclet + protected final HtmlConfiguration config; + protected final Messages messages; + protected final Resources resources; + protected final Utils utils; + // The following members are specific to the instance of the taglet protected final DocTree.Kind tagKind; protected final String name; private final boolean inline; private final Set sites; - BaseTaglet(DocTree.Kind tagKind, boolean inline, Set sites) { - this(tagKind.tagName, tagKind, inline, sites); + // The following is dynamically set for the duration of the methods + // getInlineTagOutput and getAllBlockTagOutput + // by those taglets that need to refer to it + protected TagletWriter tagletWriter; + + public BaseTaglet(HtmlConfiguration config, DocTree.Kind tagKind, boolean inline, Set sites) { + this(config, tagKind.tagName, tagKind, inline, sites); } - BaseTaglet(String name, boolean inline, Set sites) { - this(name, inline ? DocTree.Kind.UNKNOWN_INLINE_TAG : DocTree.Kind.UNKNOWN_BLOCK_TAG, inline, sites); + protected BaseTaglet(HtmlConfiguration config, String name, boolean inline, Set sites) { + this(config, name, inline ? DocTree.Kind.UNKNOWN_INLINE_TAG : DocTree.Kind.UNKNOWN_BLOCK_TAG, inline, sites); } - private BaseTaglet(String name, DocTree.Kind tagKind, boolean inline, Set sites) { + private BaseTaglet(HtmlConfiguration config, String name, DocTree.Kind tagKind, boolean inline, Set sites) { + this.config = config; + this.messages = config.getMessages(); + this.resources = config.getDocResources(); + this.utils = config.utils; + this.name = name; this.tagKind = tagKind; this.inline = inline; @@ -63,41 +84,6 @@ public class BaseTaglet implements Taglet { return sites; } - @Override - public final boolean inField() { - return sites.contains(Location.FIELD); - } - - @Override - public final boolean inConstructor() { - return sites.contains(Location.CONSTRUCTOR); - } - - @Override - public final boolean inMethod() { - return sites.contains(Location.METHOD); - } - - @Override - public final boolean inOverview() { - return sites.contains(Location.OVERVIEW); - } - - @Override - public final boolean inModule() { - return sites.contains(Location.MODULE); - } - - @Override - public final boolean inPackage() { - return sites.contains(Location.PACKAGE); - } - - @Override - public final boolean inType() { - return sites.contains(Location.TYPE); - } - @Override public final boolean isInlineTag() { return inline; @@ -117,28 +103,13 @@ public class BaseTaglet implements Taglet { return tagKind; } - /** - * Returns whether or not this taglet accepts a {@code DocTree} node. - * The taglet accepts a tree node if it has the same kind, and - * if the kind is {@code UNKNOWN_BLOCK_TAG} the same tag name. - * - * @param tree the tree node - * @return {@code true} if this taglet accepts this tree node - */ - public boolean accepts(DocTree tree) { - return (tree.getKind() == DocTree.Kind.UNKNOWN_BLOCK_TAG - && tagKind == DocTree.Kind.UNKNOWN_BLOCK_TAG) - ? ((UnknownBlockTagTree) tree).getTagName().equals(name) - : tree.getKind() == tagKind; - } - /** * {@inheritDoc} * * @implSpec This implementation throws {@link UnsupportedTagletOperationException}. */ @Override - public Content getInlineTagOutput(Element element, DocTree tag, TagletWriter writer) { + public Content getInlineTagOutput(Element element, DocTree tag, TagletWriter tagletWriter) { throw new UnsupportedTagletOperationException("Method not supported in taglet " + getName() + "."); } @@ -148,7 +119,7 @@ public class BaseTaglet implements Taglet { * @implSpec This implementation throws {@link UnsupportedTagletOperationException} */ @Override - public Content getAllBlockTagOutput(Element holder, TagletWriter writer) { + public Content getAllBlockTagOutput(Element holder, TagletWriter tagletWriter) { throw new UnsupportedTagletOperationException("Method not supported in taglet " + getName() + "."); } } diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/DeprecatedTaglet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/DeprecatedTaglet.java new file mode 100644 index 00000000000..457bc15a018 --- /dev/null +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/DeprecatedTaglet.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2003, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html.taglets; + +import java.util.EnumSet; +import java.util.List; + +import javax.lang.model.element.Element; + +import com.sun.source.doctree.DeprecatedTree; +import com.sun.source.doctree.DocTree; + +import jdk.javadoc.doclet.Taglet; +import jdk.javadoc.internal.doclets.formats.html.HtmlConfiguration; +import jdk.javadoc.internal.doclets.formats.html.markup.ContentBuilder; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.util.CommentHelper; + +/** + * A taglet that represents the {@code @deprecated} tag. + */ +public class DeprecatedTaglet extends BaseTaglet { + + DeprecatedTaglet(HtmlConfiguration config) { + super(config, DocTree.Kind.DEPRECATED, false, + EnumSet.of(Taglet.Location.MODULE, Taglet.Location.TYPE, Taglet.Location.CONSTRUCTOR, Taglet.Location.METHOD, Taglet.Location.FIELD)); + + } + + @Override + public Content getAllBlockTagOutput(Element element, TagletWriter tagletWriter) { + var htmlWriter = tagletWriter.htmlWriter; + + ContentBuilder result = new ContentBuilder(); + CommentHelper ch = utils.getCommentHelper(element); + List deprs = utils.getDeprecatedTrees(element); + if (utils.isTypeElement(element)) { + if (utils.isDeprecated(element)) { + result.add(HtmlTree.SPAN(HtmlStyle.deprecatedLabel, + htmlWriter.getDeprecatedPhrase(element))); + if (!deprs.isEmpty()) { + List commentTrees = ch.getDescription(deprs.get(0)); + if (!commentTrees.isEmpty()) { + result.add(tagletWriter.commentTagsToOutput(element, null, commentTrees, false)); + } + } + } + } else { + if (utils.isDeprecated(element)) { + result.add(HtmlTree.SPAN(HtmlStyle.deprecatedLabel, + htmlWriter.getDeprecatedPhrase(element))); + if (!deprs.isEmpty()) { + List bodyTrees = ch.getBody(deprs.get(0)); + Content body = tagletWriter.commentTagsToOutput(element, null, bodyTrees, false); + if (!body.isEmpty()) + result.add(HtmlTree.DIV(HtmlStyle.deprecationComment, body)); + } + } else { + Element ee = utils.getEnclosingTypeElement(element); + if (utils.isDeprecated(ee)) { + result.add(HtmlTree.SPAN(HtmlStyle.deprecatedLabel, + htmlWriter.getDeprecatedPhrase(ee))); + } + } + } + return result; + + } + +} diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/DocRootTaglet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/DocRootTaglet.java similarity index 71% rename from src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/DocRootTaglet.java rename to src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/DocRootTaglet.java index 4003c6e05dc..520532e261d 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/DocRootTaglet.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/DocRootTaglet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 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,13 +23,17 @@ * questions. */ -package jdk.javadoc.internal.doclets.toolkit.taglets; +package jdk.javadoc.internal.doclets.formats.html.taglets; import java.util.EnumSet; + import javax.lang.model.element.Element; import com.sun.source.doctree.DocTree; -import jdk.javadoc.doclet.Taglet.Location; + +import jdk.javadoc.doclet.Taglet; +import jdk.javadoc.internal.doclets.formats.html.HtmlConfiguration; +import jdk.javadoc.internal.doclets.formats.html.markup.Text; import jdk.javadoc.internal.doclets.toolkit.Content; /** @@ -38,16 +42,14 @@ import jdk.javadoc.internal.doclets.toolkit.Content; * directory. */ public class DocRootTaglet extends BaseTaglet { - - /** - * Construct a new DocRootTaglet. - */ - public DocRootTaglet() { - super(DocTree.Kind.DOC_ROOT, true, EnumSet.allOf(Location.class)); + DocRootTaglet(HtmlConfiguration config) { + super(config, DocTree.Kind.DOC_ROOT, true, EnumSet.allOf(Taglet.Location.class)); } @Override - public Content getInlineTagOutput(Element holder, DocTree tag, TagletWriter writer) { - return writer.getDocRootOutput(); + public Content getInlineTagOutput(Element holder, DocTree tag, TagletWriter tagletWriter) { + var htmlWriter = tagletWriter.htmlWriter; + var pathToRoot = htmlWriter.pathToRoot; + return Text.of(pathToRoot.isEmpty() ? "." : pathToRoot.getPath()); } } diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/IndexTaglet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/IndexTaglet.java new file mode 100644 index 00000000000..ae578f283c3 --- /dev/null +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/IndexTaglet.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2015, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html.taglets; + +import java.util.EnumSet; + +import javax.lang.model.element.Element; + +import com.sun.source.doctree.DocTree; +import com.sun.source.doctree.IndexTree; +import com.sun.source.doctree.TextTree; + +import jdk.javadoc.doclet.Taglet; +import jdk.javadoc.internal.doclets.formats.html.HtmlConfiguration; +import jdk.javadoc.internal.doclets.toolkit.Content; + +/** + * An inline taglet used to index a word or a phrase. + * The enclosed text is interpreted as not containing HTML markup or + * nested javadoc tags. + */ +public class IndexTaglet extends BaseTaglet { + + IndexTaglet(HtmlConfiguration config) { + super(config, DocTree.Kind.INDEX, true, EnumSet.allOf(Taglet.Location.class)); + } + + @Override + public Content getInlineTagOutput(Element element, DocTree tag, TagletWriter tagletWriter) { + var context = tagletWriter.context; + var indexTree = (IndexTree) tag; + + DocTree searchTerm = indexTree.getSearchTerm(); + String tagText = (searchTerm instanceof TextTree tt) ? tt.getBody() : ""; + if (tagText.charAt(0) == '"' && tagText.charAt(tagText.length() - 1) == '"') { + tagText = tagText.substring(1, tagText.length() - 1); + } + tagText = tagText.replaceAll("\\s+", " "); + + Content desc = tagletWriter.htmlWriter.commentTagsToContent(element, indexTree.getDescription(), context.within(indexTree)); + String descText = extractText(desc); + + return tagletWriter.createAnchorAndSearchIndex(element, tagText, descText, tag); + } + + // ugly but simple; + // alternatives would be to walk the Content's tree structure, or to add new functionality to Content + private String extractText(Content c) { + return c.toString().replaceAll("<[^>]+>", ""); + } +} diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/InheritDocTaglet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/InheritDocTaglet.java similarity index 90% rename from src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/InheritDocTaglet.java rename to src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/InheritDocTaglet.java index 149770b177f..180078b9e86 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/InheritDocTaglet.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/InheritDocTaglet.java @@ -23,7 +23,7 @@ * questions. */ -package jdk.javadoc.internal.doclets.toolkit.taglets; +package jdk.javadoc.internal.doclets.formats.html.taglets; import java.util.EnumSet; import java.util.List; @@ -36,10 +36,10 @@ import javax.lang.model.element.TypeElement; import com.sun.source.doctree.DocTree; import com.sun.source.doctree.InheritDocTree; import com.sun.source.util.DocTreePath; + import jdk.javadoc.doclet.Taglet.Location; -import jdk.javadoc.internal.doclets.toolkit.BaseConfiguration; +import jdk.javadoc.internal.doclets.formats.html.HtmlConfiguration; import jdk.javadoc.internal.doclets.toolkit.Content; -import jdk.javadoc.internal.doclets.toolkit.Messages; import jdk.javadoc.internal.doclets.toolkit.util.CommentHelper; import jdk.javadoc.internal.doclets.toolkit.util.DocFinder; import jdk.javadoc.internal.doclets.toolkit.util.DocFinder.Result; @@ -54,8 +54,8 @@ public class InheritDocTaglet extends BaseTaglet { /** * Construct a new InheritDocTaglet. */ - public InheritDocTaglet() { - super(DocTree.Kind.INHERIT_DOC, true, EnumSet.of(Location.METHOD)); + InheritDocTaglet(HtmlConfiguration config) { + super(config, DocTree.Kind.INHERIT_DOC, true, EnumSet.of(Location.METHOD)); } /** @@ -76,9 +76,6 @@ public class InheritDocTaglet extends BaseTaglet { InheritDocTree inheritDoc, boolean isFirstSentence) { Content replacement = writer.getOutputInstance(); - BaseConfiguration configuration = writer.configuration(); - Messages messages = configuration.getMessages(); - Utils utils = configuration.utils; CommentHelper ch = utils.getCommentHelper(method); DocTreePath inheritDocPath = ch.getDocTreePath(inheritDoc); var path = inheritDocPath.getParentPath(); @@ -103,7 +100,7 @@ public class InheritDocTaglet extends BaseTaglet { // // This way we do more work in erroneous case, but less in the typical // case. We don't optimize for the former. - VisibleMemberTable visibleMemberTable = configuration.getVisibleMemberTable(supertype); + VisibleMemberTable visibleMemberTable = config.getVisibleMemberTable(supertype); List methods = visibleMemberTable.getAllVisibleMembers(VisibleMemberTable.Kind.METHODS); for (Element e : methods) { ExecutableElement m = (ExecutableElement) e; @@ -142,7 +139,7 @@ public class InheritDocTaglet extends BaseTaglet { return replacement; } - Taglet taglet = configuration.tagletManager.getTaglet(ch.getTagName(holderTag)); + Taglet taglet = config.tagletManager.getTaglet(ch.getTagName(holderTag)); // taglet is null if holderTag is unknown, which it shouldn't be since we reached here assert taglet != null; if (!(taglet instanceof InheritableTaglet inheritableTaglet)) { @@ -151,7 +148,7 @@ public class InheritDocTaglet extends BaseTaglet { return replacement; } - InheritableTaglet.Output inheritedDoc = inheritableTaglet.inherit(method, src, holderTag, isFirstSentence, configuration); + InheritableTaglet.Output inheritedDoc = inheritableTaglet.inherit(method, src, holderTag, isFirstSentence); if (inheritedDoc.isValidInheritDocTag()) { if (!inheritedDoc.inlineTags().isEmpty()) { @@ -182,6 +179,9 @@ public class InheritDocTaglet extends BaseTaglet { if (e.getKind() != ElementKind.METHOD) { return tagletWriter.getOutputInstance(); } - return retrieveInheritedDocumentation(tagletWriter, (ExecutableElement) e, (InheritDocTree) inheritDoc, tagletWriter.isFirstSentence); + return retrieveInheritedDocumentation(tagletWriter, + (ExecutableElement) e, + (InheritDocTree) inheritDoc, + tagletWriter.context.isFirstSentence); } } diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/InheritableTaglet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/InheritableTaglet.java similarity index 90% rename from src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/InheritableTaglet.java rename to src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/InheritableTaglet.java index 531f5264f87..9c53531656c 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/InheritableTaglet.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/InheritableTaglet.java @@ -23,7 +23,7 @@ * questions. */ -package jdk.javadoc.internal.doclets.toolkit.taglets; +package jdk.javadoc.internal.doclets.formats.html.taglets; import java.util.List; @@ -31,13 +31,12 @@ import java.util.List; import javax.lang.model.element.Element; import com.sun.source.doctree.DocTree; -import jdk.javadoc.internal.doclets.toolkit.BaseConfiguration; /** * A taglet should implement this interface if it supports an {@code {@inheritDoc}} * tag or is automatically inherited if it is missing. */ -public interface InheritableTaglet extends Taglet { +public interface InheritableTaglet { /* * Called by InheritDocTaglet on an inheritable taglet to expand {@inheritDoc S} @@ -52,7 +51,7 @@ public interface InheritableTaglet extends Taglet { * In the future, this could be reworked using some other mechanism, * such as throwing an exception. */ - Output inherit(Element dst, Element src, DocTree tag, boolean isFirstSentence, BaseConfiguration configuration); + Output inherit(Element dst, Element src, DocTree tag, boolean isFirstSentence); record Output(DocTree holderTag, Element holder, diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/LinkTaglet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/LinkTaglet.java new file mode 100644 index 00000000000..459b0b9a407 --- /dev/null +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/LinkTaglet.java @@ -0,0 +1,278 @@ +/* + * Copyright (c) 2003, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html.taglets; + +import java.util.EnumSet; +import java.util.Objects; +import java.util.Optional; +import java.util.function.BiConsumer; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.ModuleElement; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; + +import com.sun.source.doctree.DocTree; +import com.sun.source.doctree.LinkTree; +import com.sun.source.util.DocTreePath; + +import jdk.javadoc.doclet.Taglet; +import jdk.javadoc.internal.doclets.formats.html.ClassWriterImpl; +import jdk.javadoc.internal.doclets.formats.html.HtmlConfiguration; +import jdk.javadoc.internal.doclets.formats.html.HtmlLinkInfo; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.formats.html.markup.Text; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.util.CommentHelper; +import jdk.javadoc.internal.doclets.toolkit.util.DocLink; +import jdk.javadoc.internal.doclets.toolkit.util.VisibleMemberTable; + +import static com.sun.source.doctree.DocTree.Kind.LINK; +import static com.sun.source.doctree.DocTree.Kind.LINK_PLAIN; +import static com.sun.source.doctree.DocTree.Kind.SEE; + +/** + * A taglet that represents the {@code {@link ...}} and {@linkplain ...} tags, + * with support for links to program elements in {@code @see} and + * {@code {@snippet ...}} tags. + */ +public class LinkTaglet extends BaseTaglet { + LinkTaglet(HtmlConfiguration config, DocTree.Kind tagKind) { + super(config, tagKind, true, EnumSet.allOf(Taglet.Location.class)); + } + + @Override + public Content getInlineTagOutput(Element element, DocTree tag, TagletWriter tagletWriter) { + this.tagletWriter = tagletWriter; + var linkTree = (LinkTree) tag; + var ch = utils.getCommentHelper(element); + var context = tagletWriter.context; + var htmlWriter = tagletWriter.htmlWriter; + + var inTags = context.inTags; + if (inTags.contains(LINK) || inTags.contains(LINK_PLAIN) || inTags.contains(SEE)) { + DocTreePath dtp = ch.getDocTreePath(linkTree); + if (dtp != null) { + messages.warning(dtp, "doclet.see.nested_link", "{@" + linkTree.getTagName() + "}"); + } + Content label = htmlWriter.commentTagsToContent(element, linkTree.getLabel(), context.within(linkTree)); + if (label.isEmpty()) { + label = Text.of(linkTree.getReference().getSignature()); + } + return label; + } + + var linkRef = linkTree.getReference(); + if (linkRef == null) { + messages.warning(ch.getDocTreePath(tag), "doclet.link.no_reference"); + return tagletWriter.invalidTagOutput(resources.getText("doclet.tag.invalid_input", tag.toString()), + Optional.empty()); + } + + DocTree.Kind kind = tag.getKind(); + String refSignature = ch.getReferencedSignature(linkRef); + + return linkSeeReferenceOutput(element, + tag, + refSignature, + ch.getReferencedElement(tag), + (kind == LINK_PLAIN), + htmlWriter.commentTagsToContent(element, linkTree.getLabel(), context.within(linkTree)), + (key, args) -> messages.warning(ch.getDocTreePath(tag), key, args), + tagletWriter); + } + + /** + * Worker method to generate a link from the information in different kinds of tags, + * such as {@code {@link ...}} tags, {@code @see ...} tags and the {@code link} markup tag + * in a {@code {@snippet ...}} tag. + * + * @param holder the element that has the documentation comment containing the information + * @param refTree the tree node containing the information, or {@code null} if not available + * @param refSignature the normalized signature of the target of the reference + * @param ref the target of the reference + * @param isLinkPlain {@code true} if the link should be presented in "plain" font, + * or {@code false} for "code" font + * @param label the label for the link, + * or an empty item to use a default label derived from the signature + * @param reportWarning a function to report warnings about issues found in the reference + * @param tagletWriter the writer providing the context for this call + * + * @return the output containing the generated link, or content indicating an error + */ + Content linkSeeReferenceOutput(Element holder, + DocTree refTree, + String refSignature, + Element ref, + boolean isLinkPlain, + Content label, + BiConsumer reportWarning, + TagletWriter tagletWriter) { + var config = tagletWriter.configuration; + var htmlWriter = tagletWriter.htmlWriter; + + Content labelContent = plainOrCode(isLinkPlain, label); + + // The signature from the @see tag. We will output this text when a label is not specified. + Content text = plainOrCode(isLinkPlain, + Text.of(Objects.requireNonNullElse(refSignature, ""))); + + CommentHelper ch = utils.getCommentHelper(holder); + TypeElement refClass = ch.getReferencedClass(ref); + Element refMem = ch.getReferencedMember(ref); + 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. + htmlWriter.messages.error(ch.getDocTreePath(refTree), "doclet.link.see.no_label"); + return tagletWriter.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, 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()) { + labelContent = plainOrCode(isLinkPlain, + Text.of(refPackage.getQualifiedName())); + } + 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); + DocLink elementCrossLink = (refPackage != null) ? htmlWriter.getCrossPackageLink(refPackage) : + (config.extern.isModule(refModuleName)) + ? htmlWriter.getCrossModuleLink(utils.elementUtils.getModuleElement(refModuleName)) + : null; + if (elementCrossLink != null) { + // Element cross-link found + return htmlWriter.links.createExternalLink(elementCrossLink, + (labelContent.isEmpty() ? text : labelContent)); + } else { + // No cross-link found so print warning + if (!config.isDocLintReferenceGroupEnabled()) { + reportWarning.accept( + "doclet.link.see.reference_not_found", + new Object[] { refSignature}); + } + return htmlWriter.invalidTagOutput(resources.getText("doclet.link.see.reference_invalid"), + Optional.of(labelContent.isEmpty() ? text: labelContent)); + } + } + } 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)) { + // This is a generic type link, use the TypeMirror representation. + return plainOrCode(isLinkPlain, htmlWriter.getLink( + new HtmlLinkInfo(config, HtmlLinkInfo.Kind.LINK_TYPE_PARAMS_AND_BOUNDS, referencedType))); + } + labelContent = plainOrCode(isLinkPlain, Text.of(utils.getSimpleName(refClass))); + } + return htmlWriter.getLink(new HtmlLinkInfo(config, HtmlLinkInfo.Kind.PLAIN, refClass) + .label(labelContent)); + } else if (refMem == null) { + // This is a fragment reference since refClass and refFragment are not null but refMem is null. + return htmlWriter.getLink(new HtmlLinkInfo(config, HtmlLinkInfo.Kind.PLAIN, refClass) + .label(labelContent) + .fragment(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. + TypeElement containing = utils.getEnclosingTypeElement(refMem); + + // Find the enclosing type where the method is actually visible + // in the inheritance hierarchy. + ExecutableElement overriddenMethod = null; + if (refMem.getKind() == ElementKind.METHOD) { + VisibleMemberTable vmt = config.getVisibleMemberTable(containing); + overriddenMethod = vmt.getOverriddenMethod((ExecutableElement)refMem); + + if (overriddenMethod != null) { + containing = utils.getEnclosingTypeElement(overriddenMethod); + } + } + if (refSignature.trim().startsWith("#") && + ! (utils.isPublic(containing) || utils.isLinkable(containing))) { + // Since the link is relative and the holder is not even being + // documented, this must be an inherited link. Redirect it. + // The current class either overrides the referenced member or + // inherits it automatically. + if (htmlWriter instanceof ClassWriterImpl cw) { + containing = cw.getTypeElement(); + } else if (!utils.isPublic(containing)) { + reportWarning.accept("doclet.link.see.reference_not_accessible", + new Object[] { utils.getFullyQualifiedName(containing)}); + } else { + if (!config.isDocLintReferenceGroupEnabled()) { + reportWarning.accept("doclet.link.see.reference_not_found", + new Object[] { refSignature }); + } + } + } + String refMemName = refFragment; + if (config.currentTypeElement != containing) { + refMemName = (utils.isConstructor(refMem)) + ? refMemName + : utils.getSimpleName(containing) + "." + refMemName; + } + if (utils.isExecutableElement(refMem)) { + if (refMemName.indexOf('(') < 0) { + refMemName += utils.makeSignature((ExecutableElement) refMem, null, true); + } + if (overriddenMethod != null) { + // The method to actually link. + refMem = overriddenMethod; + } + } + + return htmlWriter.getDocLink(HtmlLinkInfo.Kind.SHOW_PREVIEW, containing, + refMem, (labelContent.isEmpty() + ? plainOrCode(isLinkPlain, Text.of(refMemName)) + : labelContent), null, false); + } + } + + private Content plainOrCode(boolean plain, Content body) { + return (plain || body.isEmpty()) ? body : HtmlTree.CODE(body); + } +} diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/CodeTaglet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/LiteralTaglet.java similarity index 62% rename from src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/CodeTaglet.java rename to src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/LiteralTaglet.java index 5397ba32db3..23e891a21f7 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/CodeTaglet.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/LiteralTaglet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 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 @@ * questions. */ -package jdk.javadoc.internal.doclets.toolkit.taglets; +package jdk.javadoc.internal.doclets.formats.html.taglets; import java.util.EnumSet; @@ -31,29 +31,38 @@ import javax.lang.model.element.Element; import com.sun.source.doctree.DocTree; import com.sun.source.doctree.LiteralTree; -import jdk.javadoc.doclet.Taglet.Location; + +import jdk.javadoc.doclet.Taglet; +import jdk.javadoc.internal.doclets.formats.html.HtmlConfiguration; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.formats.html.markup.Text; import jdk.javadoc.internal.doclets.toolkit.Content; /** - * An inline taglet used to denote literal code fragments. - * The enclosed text is interpreted as not containing HTML markup or - * nested javadoc tags, and is rendered in a font suitable for code. + * An inline taglet used to denote literal text, possibly in monospace font. + * + * For example, the text: + *
{@code {@literal ac}}
+ * displays as: + *
{@literal ac}
* *

The tag {@code {@code ...}} is equivalent to * {@code {@literal ...}}. + * * For example, the text: *

The type {@code {@code List

}}

* displays as: *
The type {@code List

}

*/ -public class CodeTaglet extends BaseTaglet { - - CodeTaglet() { - super(DocTree.Kind.CODE, true, EnumSet.allOf(Location.class)); +public class LiteralTaglet extends BaseTaglet { + LiteralTaglet(HtmlConfiguration config, DocTree.Kind tagKind) { + super(config, tagKind, true, EnumSet.allOf(Taglet.Location.class)); } @Override - public Content getInlineTagOutput(Element element, DocTree tag, TagletWriter writer) { - return writer.codeTagOutput(element, (LiteralTree) tag); + public Content getInlineTagOutput(Element element, DocTree tag, TagletWriter tagletWriter) { + var literalTree = (LiteralTree) tag; + var body = Text.of(Text.normalizeNewlines(literalTree.getBody().getBody())); + return tag.getKind() == DocTree.Kind.CODE ? HtmlTree.CODE(body) : body; } } diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/ParamTaglet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/ParamTaglet.java similarity index 78% rename from src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/ParamTaglet.java rename to src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/ParamTaglet.java index 98800a3bccc..0ef1a41f777 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/ParamTaglet.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/ParamTaglet.java @@ -23,9 +23,13 @@ * questions. */ -package jdk.javadoc.internal.doclets.toolkit.taglets; +package jdk.javadoc.internal.doclets.formats.html.taglets; -import java.util.*; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; @@ -34,19 +38,24 @@ import javax.lang.model.element.TypeElement; import com.sun.source.doctree.DocTree; import com.sun.source.doctree.ParamTree; -import jdk.javadoc.doclet.Taglet.Location; -import jdk.javadoc.internal.doclets.toolkit.BaseConfiguration; + +import jdk.javadoc.doclet.Taglet; +import jdk.javadoc.internal.doclets.formats.html.Contents; +import jdk.javadoc.internal.doclets.formats.html.HtmlConfiguration; +import jdk.javadoc.internal.doclets.formats.html.HtmlIds; +import jdk.javadoc.internal.doclets.formats.html.markup.ContentBuilder; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.formats.html.markup.Text; import jdk.javadoc.internal.doclets.toolkit.Content; -import jdk.javadoc.internal.doclets.toolkit.Messages; import jdk.javadoc.internal.doclets.toolkit.util.CommentHelper; import jdk.javadoc.internal.doclets.toolkit.util.DocFinder; -import jdk.javadoc.internal.doclets.toolkit.util.DocFinder.Result; import jdk.javadoc.internal.doclets.toolkit.util.Utils; /** * A taglet that represents the {@code @param} tag. */ public class ParamTaglet extends BaseTaglet implements InheritableTaglet { + public enum ParamKind { /** Parameter of an executable element. */ PARAMETER, @@ -56,15 +65,15 @@ public class ParamTaglet extends BaseTaglet implements InheritableTaglet { TYPE_PARAMETER } - /** - * Construct a ParamTaglet. - */ - public ParamTaglet() { - super(DocTree.Kind.PARAM, false, EnumSet.of(Location.TYPE, Location.CONSTRUCTOR, Location.METHOD)); + private final Contents contents; + + ParamTaglet(HtmlConfiguration config) { + super(config, DocTree.Kind.PARAM, false, EnumSet.of(Taglet.Location.TYPE, Taglet.Location.CONSTRUCTOR, Taglet.Location.METHOD)); + contents = config.contents; } @Override - public Output inherit(Element dst, Element src, DocTree tag, boolean isFirstSentence, BaseConfiguration configuration) { + public Output inherit(Element dst, Element src, DocTree tag, boolean isFirstSentence) { assert dst.getKind() == ElementKind.METHOD; assert tag.getKind() == DocTree.Kind.PARAM; var method = (ExecutableElement) dst; @@ -76,24 +85,24 @@ public class ParamTaglet extends BaseTaglet implements InheritableTaglet { } else { parameterElements = method.getParameters(); } - Map stringIntegerMap = mapNameToPosition(configuration.utils, parameterElements); - CommentHelper ch = configuration.utils.getCommentHelper(dst); + Map stringIntegerMap = mapNameToPosition(utils, parameterElements); + CommentHelper ch = utils.getCommentHelper(dst); Integer position = stringIntegerMap.get(ch.getParameterName(param)); if (position == null) { return new Output(null, null, List.of(), true); } // try to inherit description of the respective parameter in an overridden method try { - var docFinder = configuration.utils.docFinder(); + var docFinder = utils.docFinder(); Optional r; if (src != null){ r = docFinder.search((ExecutableElement) src, - m -> Result.fromOptional(extract(configuration.utils, m, position, param.isTypeParameter()))) + m -> DocFinder.Result.fromOptional(extract(utils, m, position, param.isTypeParameter()))) .toOptional(); } else { r = docFinder.find((ExecutableElement) dst, - m -> Result.fromOptional(extract(configuration.utils, m, position, param.isTypeParameter()))) + m -> DocFinder.Result.fromOptional(extract(utils, m, position, param.isTypeParameter()))) .toOptional(); } return r.map(result -> new Output(result.paramTree, result.method, result.paramTree.getDescription(), true)) @@ -122,21 +131,21 @@ public class ParamTaglet extends BaseTaglet implements InheritableTaglet { } @Override - public Content getAllBlockTagOutput(Element holder, TagletWriter writer) { - Utils utils = writer.configuration().utils; + public Content getAllBlockTagOutput(Element holder, TagletWriter tagletWriter) { + this.tagletWriter = tagletWriter; if (utils.isExecutableElement(holder)) { ExecutableElement member = (ExecutableElement) holder; Content output = convertParams(member, ParamKind.TYPE_PARAMETER, - utils.getTypeParamTrees(member), member.getTypeParameters(), writer); + utils.getTypeParamTrees(member), member.getTypeParameters(), tagletWriter); output.add(convertParams(member, ParamKind.PARAMETER, - utils.getParamTrees(member), member.getParameters(), writer)); + utils.getParamTrees(member), member.getParameters(), tagletWriter)); return output; } else { TypeElement typeElement = (TypeElement) holder; Content output = convertParams(typeElement, ParamKind.TYPE_PARAMETER, - utils.getTypeParamTrees(typeElement), typeElement.getTypeParameters(), writer); + utils.getTypeParamTrees(typeElement), typeElement.getTypeParameters(), tagletWriter); output.add(convertParams(typeElement, ParamKind.RECORD_COMPONENT, - utils.getParamTrees(typeElement), typeElement.getRecordComponents(), writer)); + utils.getParamTrees(typeElement), typeElement.getRecordComponents(), tagletWriter)); return output; } } @@ -163,10 +172,9 @@ public class ParamTaglet extends BaseTaglet implements InheritableTaglet { List parameters, TagletWriter writer) { Map tagOfPosition = new HashMap<>(); - Messages messages = writer.configuration().getMessages(); - CommentHelper ch = writer.configuration().utils.getCommentHelper(e); + CommentHelper ch = utils.getCommentHelper(e); if (!tags.isEmpty()) { - Map positionOfName = mapNameToPosition(writer.configuration().utils, parameters); + Map positionOfName = mapNameToPosition(utils, parameters); for (ParamTree tag : tags) { String name = ch.getParameterName(tag); String paramName = kind == ParamKind.TYPE_PARAMETER ? "<" + name + ">" : name; @@ -176,7 +184,7 @@ public class ParamTaglet extends BaseTaglet implements InheritableTaglet { case TYPE_PARAMETER -> "doclet.TypeParameters_warn"; case RECORD_COMPONENT -> "doclet.RecordComponents_warn"; }; - if (!writer.configuration().isDocLintReferenceGroupEnabled()) { + if (!config.isDocLintReferenceGroupEnabled()) { messages.warning(ch.getDocTreePath(tag), key, paramName); } } @@ -188,7 +196,7 @@ public class ParamTaglet extends BaseTaglet implements InheritableTaglet { case TYPE_PARAMETER -> "doclet.TypeParameters_dup_warn"; case RECORD_COMPONENT -> "doclet.RecordComponents_dup_warn"; }; - if (!writer.configuration().isDocLintReferenceGroupEnabled()) { + if (!config.isDocLintReferenceGroupEnabled()) { messages.warning(ch.getDocTreePath(tag), key, paramName); } } else { @@ -205,7 +213,7 @@ public class ParamTaglet extends BaseTaglet implements InheritableTaglet { if (tag != null) { result.add(convertParam(e, kind, writer, tag, ch.getParameterName(tag), result.isEmpty())); - } else if (writer.configuration().utils.isMethod(e)) { + } else if (utils.isMethod(e)) { result.add(getInheritedTagletOutput(kind, e, writer, parameters.get(i), i, result.isEmpty())); } @@ -233,10 +241,9 @@ public class ParamTaglet extends BaseTaglet implements InheritableTaglet { Element param, int position, boolean isFirst) { - Utils utils = writer.configuration().utils; Content result = writer.getOutputInstance(); var r = utils.docFinder().search((ExecutableElement) holder, - m -> Result.fromOptional(extract(utils, m, position, kind == ParamKind.TYPE_PARAMETER))) + m -> DocFinder.Result.fromOptional(extract(utils, m, position, kind == ParamTaglet.ParamKind.TYPE_PARAMETER))) .toOptional(); if (r.isPresent()) { String name = kind != ParamKind.TYPE_PARAMETER @@ -249,6 +256,31 @@ public class ParamTaglet extends BaseTaglet implements InheritableTaglet { return result; } + private Content getParamHeader(ParamKind kind) { + var header = switch (kind) { + case PARAMETER -> contents.parameters; + case TYPE_PARAMETER -> contents.typeParameters; + case RECORD_COMPONENT -> contents.recordComponents; + }; + return HtmlTree.DT(header); + } + + private Content paramTagOutput(Element element, ParamTree paramTag, String paramName) { + var context = tagletWriter.context; + var htmlWriter = tagletWriter.htmlWriter; + var body = new ContentBuilder(); + CommentHelper ch = utils.getCommentHelper(element); + // define id attributes for state components so that generated descriptions may refer to them + boolean defineID = (element.getKind() == ElementKind.RECORD) + && !paramTag.isTypeParameter(); + Content nameContent = Text.of(paramName); + body.add(HtmlTree.CODE(defineID ? HtmlTree.SPAN_ID(HtmlIds.forParam(paramName), nameContent) : nameContent)); + body.add(" - "); + List description = ch.getDescription(paramTag); + body.add(htmlWriter.commentTagsToContent(element, description, context.within(paramTag))); + return HtmlTree.DD(body); + } + private record Documentation(ParamTree paramTree, ExecutableElement method) { } private static Optional extract(Utils utils, ExecutableElement method, Integer position, boolean typeParam) { @@ -276,9 +308,9 @@ public class ParamTaglet extends BaseTaglet implements InheritableTaglet { boolean isFirstParam) { Content result = writer.getOutputInstance(); if (isFirstParam) { - result.add(writer.getParamHeader(kind)); + result.add(getParamHeader(kind)); } - result.add(writer.paramTagOutput(e, paramTag, name)); + result.add(paramTagOutput(e, paramTag, name)); return result; } } diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/ReturnTaglet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/ReturnTaglet.java similarity index 67% rename from src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/ReturnTaglet.java rename to src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/ReturnTaglet.java index 9ad94ae7944..290ad2dd15e 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/ReturnTaglet.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/ReturnTaglet.java @@ -23,7 +23,7 @@ * questions. */ -package jdk.javadoc.internal.doclets.toolkit.taglets; +package jdk.javadoc.internal.doclets.formats.html.taglets; import java.util.EnumSet; import java.util.List; @@ -37,21 +37,25 @@ import javax.lang.model.type.TypeMirror; import com.sun.source.doctree.DocTree; import com.sun.source.doctree.ReturnTree; -import jdk.javadoc.doclet.Taglet.Location; -import jdk.javadoc.internal.doclets.toolkit.BaseConfiguration; + +import jdk.javadoc.doclet.Taglet; +import jdk.javadoc.internal.doclets.formats.html.Contents; +import jdk.javadoc.internal.doclets.formats.html.HtmlConfiguration; +import jdk.javadoc.internal.doclets.formats.html.markup.ContentBuilder; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; import jdk.javadoc.internal.doclets.toolkit.Content; -import jdk.javadoc.internal.doclets.toolkit.Messages; import jdk.javadoc.internal.doclets.toolkit.util.DocFinder; -import jdk.javadoc.internal.doclets.toolkit.util.DocFinder.Result; import jdk.javadoc.internal.doclets.toolkit.util.Utils; /** * A taglet that represents the {@code @return} and {@code {@return }} tags. */ public class ReturnTaglet extends BaseTaglet implements InheritableTaglet { + private final Contents contents; - public ReturnTaglet() { - super(DocTree.Kind.RETURN, true, EnumSet.of(Location.METHOD)); + ReturnTaglet(HtmlConfiguration config) { + super(config, DocTree.Kind.RETURN, true, EnumSet.of(Taglet.Location.METHOD)); + contents = config.contents; } @Override @@ -60,14 +64,14 @@ public class ReturnTaglet extends BaseTaglet implements InheritableTaglet { } @Override - public Output inherit(Element dst, Element src, DocTree tag, boolean isFirstSentence, BaseConfiguration configuration) { + public Output inherit(Element dst, Element src, DocTree tag, boolean isFirstSentence) { try { - var docFinder = configuration.utils.docFinder(); + var docFinder = utils.docFinder(); Optional r; if (src == null) { - r = docFinder.find((ExecutableElement) dst, m -> Result.fromOptional(extract(configuration.utils, m))).toOptional(); + r = docFinder.find((ExecutableElement) dst, m -> DocFinder.Result.fromOptional(extract(utils, m))).toOptional(); } else { - r = docFinder.search((ExecutableElement) src, m -> Result.fromOptional(extract(configuration.utils, m))).toOptional(); + r = docFinder.search((ExecutableElement) src, m -> DocFinder.Result.fromOptional(extract(utils, m))).toOptional(); } return r.map(result -> new Output(result.returnTree, result.method, result.returnTree.getDescription(), true)) .orElseGet(() -> new Output(null, null, List.of(), true)); @@ -77,22 +81,22 @@ public class ReturnTaglet extends BaseTaglet implements InheritableTaglet { } @Override - public Content getInlineTagOutput(Element element, DocTree tag, TagletWriter writer) { - return writer.returnTagOutput(element, (ReturnTree) tag, true); + public Content getInlineTagOutput(Element element, DocTree tag, TagletWriter tagletWriter) { + this.tagletWriter = tagletWriter; + return returnTagOutput(element, (ReturnTree) tag, true); } @Override - public Content getAllBlockTagOutput(Element holder, TagletWriter writer) { + public Content getAllBlockTagOutput(Element holder, TagletWriter tagletWriter) { assert holder.getKind() == ElementKind.METHOD : holder.getKind(); var method = (ExecutableElement) holder; - Messages messages = writer.configuration().getMessages(); - Utils utils = writer.configuration().utils; + this.tagletWriter = tagletWriter; List tags = utils.getReturnTrees(holder); // make sure we are not using @return on a method with the void return type - TypeMirror returnType = utils.getReturnType(writer.getCurrentPageElement(), method); + TypeMirror returnType = utils.getReturnType(tagletWriter.getCurrentPageElement(), method); if (returnType != null && utils.isVoid(returnType)) { - if (!tags.isEmpty() && !writer.configuration().isDocLintReferenceGroupEnabled()) { + if (!tags.isEmpty() && !config.isDocLintReferenceGroupEnabled()) { messages.warning(holder, "doclet.Return_tag_on_void_method"); } return null; @@ -103,11 +107,31 @@ public class ReturnTaglet extends BaseTaglet implements InheritableTaglet { // above for a case where @return is used for void var docFinder = utils.docFinder(); - return docFinder.search(method, m -> Result.fromOptional(extract(utils, m))).toOptional() - .map(r -> writer.returnTagOutput(r.method, r.returnTree, false)) + return docFinder.search(method, m -> DocFinder.Result.fromOptional(extract(utils, m))).toOptional() + .map(r -> returnTagOutput(r.method, r.returnTree, false)) .orElse(null); } + /** + * Returns the output for a {@code @return} tag. + * + * @param element the element that owns the doc comment + * @param returnTag the return tag to document + * @param inline whether this should be written as an inline instance or block instance + * + * @return the output + */ + public Content returnTagOutput(Element element, ReturnTree returnTag, boolean inline) { + var context = tagletWriter.context; + var htmlWriter = tagletWriter.htmlWriter; + var ch = utils.getCommentHelper(element); + List desc = ch.getDescription(returnTag); + Content content = htmlWriter.commentTagsToContent(element, desc, context.within(returnTag)); + return inline + ? new ContentBuilder(contents.getContent("doclet.Returns_0", content)) + : new ContentBuilder(HtmlTree.DT(contents.returns), HtmlTree.DD(content)); + } + private record Documentation(ReturnTree returnTree, ExecutableElement method) { } private static Optional extract(Utils utils, ExecutableElement method) { diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SeeTaglet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SeeTaglet.java new file mode 100644 index 00000000000..c3595bd12b8 --- /dev/null +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SeeTaglet.java @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2001, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html.taglets; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.Optional; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; + +import com.sun.source.doctree.DocTree; +import com.sun.source.doctree.SeeTree; + +import jdk.javadoc.doclet.Taglet; +import jdk.javadoc.internal.doclets.formats.html.ClassWriterImpl; +import jdk.javadoc.internal.doclets.formats.html.Contents; +import jdk.javadoc.internal.doclets.formats.html.HtmlConfiguration; +import jdk.javadoc.internal.doclets.formats.html.HtmlDocletWriter; +import jdk.javadoc.internal.doclets.formats.html.markup.ContentBuilder; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.formats.html.markup.Text; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.builders.SerializedFormBuilder; +import jdk.javadoc.internal.doclets.toolkit.util.CommentHelper; +import jdk.javadoc.internal.doclets.toolkit.util.DocFinder; +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.Utils; + +public class SeeTaglet extends BaseTaglet implements InheritableTaglet { + SeeTaglet(HtmlConfiguration config) { + super(config, DocTree.Kind.SEE, false, EnumSet.allOf(Taglet.Location.class)); + contents = config.contents; + } + + private final Contents contents; + private HtmlDocletWriter htmlWriter; + + + @Override + public Output inherit(Element dst, Element src, DocTree tag, boolean isFirstSentence) { + CommentHelper ch = utils.getCommentHelper(dst); + var path = ch.getDocTreePath(tag); + messages.warning(path, "doclet.inheritDocWithinInappropriateTag"); + return new Output(null, null, List.of(), true /* true, otherwise there will be an exception up the stack */); + } + + @Override + public Content getAllBlockTagOutput(Element holder, TagletWriter tagletWriter) { + this.tagletWriter = tagletWriter; + List tags = utils.getSeeTrees(holder); + Element e = holder; + if (utils.isMethod(holder)) { + var docFinder = utils.docFinder(); + Optional result = docFinder.search((ExecutableElement) holder, + m -> DocFinder.Result.fromOptional(extract(utils, m))).toOptional(); + if (result.isPresent()) { + ExecutableElement m = result.get().method(); + tags = utils.getSeeTrees(m); + e = m; + } + } + return seeTagOutput(e, tags); + } + + /** + * Returns the output for {@code @see} tags. + * + * @param holder The element that owns the doc comment + * @param seeTags the list of tags + * + * @return the output + */ + public Content seeTagOutput(Element holder, List seeTags) { + htmlWriter = tagletWriter.htmlWriter; + + List links = new ArrayList<>(); + for (SeeTree dt : seeTags) { + links.add(seeTagOutput(holder, dt)); + } + if (utils.isVariableElement(holder) && ((VariableElement)holder).getConstantValue() != null && + htmlWriter instanceof ClassWriterImpl classWriter) { + //Automatically add link to constant values page for constant fields. + DocPath constantsPath = + htmlWriter.pathToRoot.resolve(DocPaths.CONSTANT_VALUES); + String whichConstant = + classWriter.getTypeElement().getQualifiedName() + "." + + utils.getSimpleName(holder); + DocLink link = constantsPath.fragment(whichConstant); + links.add(htmlWriter.links.createLink(link, + contents.getContent("doclet.Constants_Summary"))); + } + if (utils.isClass(holder) && utils.isSerializable((TypeElement)holder)) { + //Automatically add link to serialized form page for serializable classes. + if (SerializedFormBuilder.serialInclude(utils, holder) && + SerializedFormBuilder.serialInclude(utils, utils.containingPackage(holder))) { + DocPath serialPath = htmlWriter.pathToRoot.resolve(DocPaths.SERIALIZED_FORM); + DocLink link = serialPath.fragment(utils.getFullyQualifiedName(holder)); + links.add(htmlWriter.links.createLink(link, + contents.getContent("doclet.Serialized_Form"))); + } + } + if (links.isEmpty()) { + return Text.EMPTY; + } + + var seeList = tagletWriter.tagList(links); + return new ContentBuilder( + HtmlTree.DT(contents.seeAlso), + HtmlTree.DD(seeList)); + } + + private record Documentation(List seeTrees, ExecutableElement method) { } + + private static Optional extract(Utils utils, ExecutableElement method) { + List tags = utils.getSeeTrees(method); + return tags.isEmpty() ? Optional.empty() : Optional.of(new Documentation(tags, method)); + } + + /** + * {@return the output for a single {@code @see} tag} + * + * @param element the element that has the documentation comment containing this tag + * @param seeTag the tag + */ + private Content seeTagOutput(Element element, SeeTree seeTag) { + + List ref = seeTag.getReference(); + assert !ref.isEmpty(); + DocTree ref0 = ref.get(0); + switch (ref0.getKind()) { + case TEXT, START_ELEMENT -> { + // @see "Reference" + // @see ... + return htmlWriter.commentTagsToContent(element, ref, false, false); + } + + case REFERENCE -> { + // @see reference label... + CommentHelper ch = utils.getCommentHelper(element); + String refSignature = ch.getReferencedSignature(ref0); + List label = ref.subList(1, ref.size()); + + var lt = (LinkTaglet) config.tagletManager.getTaglet(DocTree.Kind.LINK); + return lt.linkSeeReferenceOutput(element, + seeTag, + refSignature, + ch.getReferencedElement(seeTag), + false, + htmlWriter.commentTagsToContent(element, label, tagletWriter.getContext().within(seeTag)), + (key, args) -> messages.warning(ch.getDocTreePath(seeTag), key, args), + tagletWriter + ); + } + + case ERRONEOUS -> { + return tagletWriter.invalidTagOutput(resources.getText("doclet.tag.invalid_input", + ref0.toString()), + Optional.empty()); + } + + default -> throw new IllegalStateException(ref0.getKind().toString()); + } + + } + +} diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/SimpleTaglet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SimpleTaglet.java similarity index 57% rename from src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/SimpleTaglet.java rename to src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SimpleTaglet.java index cef474f05c0..bf9fd1d60f3 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/SimpleTaglet.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SimpleTaglet.java @@ -23,7 +23,7 @@ * questions. */ -package jdk.javadoc.internal.doclets.toolkit.taglets; +package jdk.javadoc.internal.doclets.formats.html.taglets; import java.util.EnumSet; import java.util.List; @@ -34,14 +34,17 @@ import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; +import com.sun.source.doctree.BlockTagTree; import com.sun.source.doctree.DocTree; -import jdk.javadoc.doclet.Taglet.Location; +import com.sun.source.doctree.UnknownBlockTagTree; -import jdk.javadoc.internal.doclets.toolkit.BaseConfiguration; +import jdk.javadoc.doclet.Taglet; +import jdk.javadoc.internal.doclets.formats.html.HtmlConfiguration; +import jdk.javadoc.internal.doclets.formats.html.markup.ContentBuilder; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.formats.html.markup.RawHtml; import jdk.javadoc.internal.doclets.toolkit.Content; import jdk.javadoc.internal.doclets.toolkit.util.DocFinder; -import jdk.javadoc.internal.doclets.toolkit.util.DocFinder.Result; -import jdk.javadoc.internal.doclets.toolkit.util.Utils; /** * A custom single-argument block tag. @@ -51,15 +54,9 @@ public class SimpleTaglet extends BaseTaglet implements InheritableTaglet { /** * The header to output. */ - protected String header; + private final String header; - /** - * Whether or not the taglet should generate output. - * Standard tags like {@code @author}, {@code @since}, {@code @version} can - * be disabled by command-line options; custom tags created with -tag can be - * disabled with an X in the defining string. - */ - protected final boolean enabled; + private final boolean enabled; /** * Constructs a {@code SimpleTaglet}. @@ -72,8 +69,8 @@ public class SimpleTaglet extends BaseTaglet implements InheritableTaglet { * See {@link #getLocations(String) getLocations} for the * complete list. */ - public SimpleTaglet(String tagName, String header, String locations) { - this(tagName, header, getLocations(locations), isEnabled(locations)); + SimpleTaglet(HtmlConfiguration config, String tagName, String header, String locations) { + this(config, tagName, header, getLocations(locations), isEnabled(locations)); } /** @@ -83,8 +80,8 @@ public class SimpleTaglet extends BaseTaglet implements InheritableTaglet { * @param header the header to output * @param locations the possible locations that this tag can appear in */ - public SimpleTaglet(DocTree.Kind tagKind, String header, Set locations) { - this(tagKind, header, locations, true); + SimpleTaglet(HtmlConfiguration config, DocTree.Kind tagKind, String header, Set locations) { + this(config, tagKind, header, locations, true); } /** @@ -94,8 +91,8 @@ public class SimpleTaglet extends BaseTaglet implements InheritableTaglet { * @param header the header to output * @param locations the possible locations that this tag can appear in */ - public SimpleTaglet(String tagName, String header, Set locations) { - this(tagName, header, locations, true); + SimpleTaglet(HtmlConfiguration config, String tagName, String header, Set locations) { + this(config, tagName, header, locations, true); } /** @@ -105,8 +102,8 @@ public class SimpleTaglet extends BaseTaglet implements InheritableTaglet { * @param header the header to output * @param locations the possible locations that this tag can appear in */ - public SimpleTaglet(String tagName, String header, Set locations, boolean enabled) { - super(tagName, false, locations); + private SimpleTaglet(HtmlConfiguration config, String tagName, String header, Set locations, boolean enabled) { + super(config, tagName, false, locations); this.header = header; this.enabled = enabled; } @@ -118,38 +115,136 @@ public class SimpleTaglet extends BaseTaglet implements InheritableTaglet { * @param header the header to output * @param locations the possible locations that this tag can appear in */ - public SimpleTaglet(DocTree.Kind tagKind, String header, Set locations, boolean enabled) { - super(tagKind, false, locations); + protected SimpleTaglet(HtmlConfiguration config, DocTree.Kind tagKind, String header, Set locations, boolean enabled) { + super(config, tagKind, false, locations); this.header = header; this.enabled = enabled; } - private static Set getLocations(String locations) { - Set set = EnumSet.noneOf(Location.class); + @Override + public Output inherit(Element dst, Element src, DocTree tag, boolean isFirstSentence) { + assert dst.getKind() == ElementKind.METHOD; + assert !isFirstSentence; + try { + var docFinder = utils.docFinder(); + Optional r; + if (src == null) { + r = docFinder.find((ExecutableElement) dst, + m -> DocFinder.Result.fromOptional(extractFirst(m))).toOptional(); + } else { + r = docFinder.search((ExecutableElement) src, + m -> DocFinder.Result.fromOptional(extractFirst(m))).toOptional(); + } + return r.map(result -> new Output(result.tag, result.method, result.description, true)) + .orElseGet(()->new Output(null, null, List.of(), true)); + } catch (DocFinder.NoOverriddenMethodFound e) { + return new Output(null, null, List.of(), false); + } + } + + /** + * Whether the taglet should generate output. + * Standard tags like {@code @author}, {@code @since}, {@code @version} can + * be disabled by command-line options; custom tags created with -tag can be + * disabled with an X in the defining string. + */ + boolean isEnabled() { + return enabled; + } + + /** + * Returns whether this taglet accepts a {@code BlockTagTree} node. + * The taglet accepts a tree node if it has the same kind, and + * if the kind is {@code UNKNOWN_BLOCK_TAG} the same tag name. + * + * @param tree the tree node + * @return {@code true} if this taglet accepts this tree node + */ + private boolean accepts(BlockTagTree tree) { + return (tree.getKind() == DocTree.Kind.UNKNOWN_BLOCK_TAG && tagKind == DocTree.Kind.UNKNOWN_BLOCK_TAG) + ? tree.getTagName().equals(name) + : tree.getKind() == tagKind; + } + + record Documentation(DocTree tag, List description, ExecutableElement method) { } + + private Optional extractFirst(ExecutableElement m) { + List tags = utils.getBlockTags(m, this::accepts); + if (tags.isEmpty()) { + return Optional.empty(); + } + DocTree t = tags.get(0); + return Optional.of(new Documentation(t, utils.getCommentHelper(m).getDescription(t), m)); + } + + @Override + public Content getAllBlockTagOutput(Element holder, TagletWriter tagletWriter) { + this.tagletWriter = tagletWriter; + List tags = utils.getBlockTags(holder, this::accepts); + if (header == null || tags.isEmpty()) { + return null; + } + return simpleBlockTagOutput(holder, tags, header, tagletWriter); + } + + /** + * Returns the output for a series of simple tags. + * + * @param element The element that owns the doc comment + * @param simpleTags the list of simple tags + * @param header the header for the series of tags + * + * @return the output + */ + private Content simpleBlockTagOutput(Element element, + List simpleTags, + String header, + TagletWriter writer) { + var ch = utils.getCommentHelper(element); + var context = tagletWriter.context; + var htmlWriter = tagletWriter.htmlWriter; + + ContentBuilder body = new ContentBuilder(); + boolean many = false; + for (DocTree simpleTag : simpleTags) { + if (many) { + body.add(", "); + } + List bodyTags = ch.getBody(simpleTag); + body.add(htmlWriter.commentTagsToContent(element, bodyTags, context.within(simpleTag))); + many = true; + } + return new ContentBuilder( + HtmlTree.DT(RawHtml.of(header)), + HtmlTree.DD(body)); + } + + private static Set getLocations(String locations) { + Set set = EnumSet.noneOf(Taglet.Location.class); for (int i = 0; i < locations.length(); i++) { switch (locations.charAt(i)) { case 'a': case 'A': - return EnumSet.allOf(Location.class); + return EnumSet.allOf(Taglet.Location.class); case 'c': case 'C': - set.add(Location.CONSTRUCTOR); + set.add(Taglet.Location.CONSTRUCTOR); break; case 'f': case 'F': - set.add(Location.FIELD); + set.add(Taglet.Location.FIELD); break; case 'm': case 'M': - set.add(Location.METHOD); + set.add(Taglet.Location.METHOD); break; case 'o': case 'O': - set.add(Location.OVERVIEW); + set.add(Taglet.Location.OVERVIEW); break; case 'p': case 'P': - set.add(Location.PACKAGE); + set.add(Taglet.Location.PACKAGE); break; case 's': case 'S': // super-packages, anyone? - set.add(Location.MODULE); + set.add(Taglet.Location.MODULE); break; case 't': case 'T': - set.add(Location.TYPE); + set.add(Taglet.Location.TYPE); break; case 'x': case 'X': break; @@ -161,46 +256,4 @@ public class SimpleTaglet extends BaseTaglet implements InheritableTaglet { private static boolean isEnabled(String locations) { return locations.matches("[^Xx]*"); } - - @Override - public Output inherit(Element dst, Element src, DocTree tag, boolean isFirstSentence, BaseConfiguration configuration) { - assert dst.getKind() == ElementKind.METHOD; - assert !isFirstSentence; - try { - var docFinder = configuration.utils.docFinder(); - Optional r; - if (src == null) { - r = docFinder.find((ExecutableElement) dst, - m -> Result.fromOptional(extractFirst(m, configuration.utils))).toOptional(); - } else { - r = docFinder.search((ExecutableElement) src, - m -> Result.fromOptional(extractFirst(m, configuration.utils))).toOptional(); - } - return r.map(result -> new Output(result.tag, result.method, result.description, true)) - .orElseGet(()->new Output(null, null, List.of(), true)); - } catch (DocFinder.NoOverriddenMethodFound e) { - return new Output(null, null, List.of(), false); - } - } - - record Documentation(DocTree tag, List description, ExecutableElement method) { } - - private Optional extractFirst(ExecutableElement m, Utils utils) { - List tags = utils.getBlockTags(m, this); - if (tags.isEmpty()) { - return Optional.empty(); - } - DocTree t = tags.get(0); - return Optional.of(new Documentation(t, utils.getCommentHelper(m).getDescription(t), m)); - } - - @Override - public Content getAllBlockTagOutput(Element holder, TagletWriter writer) { - Utils utils = writer.configuration().utils; - List tags = utils.getBlockTags(holder, this); - if (header == null || tags.isEmpty()) { - return null; - } - return writer.simpleBlockTagOutput(holder, tags, header); - } } diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/SnippetTaglet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SnippetTaglet.java similarity index 65% rename from src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/SnippetTaglet.java rename to src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SnippetTaglet.java index 9ebe5f281d4..ce8cdf06415 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/SnippetTaglet.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SnippetTaglet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 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,34 +23,46 @@ * questions. */ -package jdk.javadoc.internal.doclets.toolkit.taglets; +package jdk.javadoc.internal.doclets.formats.html.taglets; import java.io.IOException; import java.util.EnumSet; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; import javax.lang.model.element.Element; import javax.lang.model.element.PackageElement; import javax.tools.Diagnostic; -import javax.tools.DocumentationTool.Location; +import javax.tools.DocumentationTool; import javax.tools.FileObject; import com.sun.source.doctree.AttributeTree; import com.sun.source.doctree.DocTree; import com.sun.source.doctree.SnippetTree; import com.sun.source.doctree.TextTree; +import com.sun.source.util.DocTreePath; + import jdk.javadoc.doclet.Taglet; +import jdk.javadoc.internal.doclets.formats.html.HtmlConfiguration; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlAttr; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.formats.html.markup.TagName; +import jdk.javadoc.internal.doclets.formats.html.markup.Text; +import jdk.javadoc.internal.doclets.formats.html.taglets.snippet.Action; +import jdk.javadoc.internal.doclets.formats.html.taglets.snippet.ParseException; +import jdk.javadoc.internal.doclets.formats.html.taglets.snippet.Parser; +import jdk.javadoc.internal.doclets.formats.html.taglets.snippet.Style; +import jdk.javadoc.internal.doclets.formats.html.taglets.snippet.StyledText; import jdk.javadoc.internal.doclets.toolkit.Content; import jdk.javadoc.internal.doclets.toolkit.DocletElement; import jdk.javadoc.internal.doclets.toolkit.Resources; -import jdk.javadoc.internal.doclets.toolkit.taglets.snippet.Action; -import jdk.javadoc.internal.doclets.toolkit.taglets.snippet.ParseException; -import jdk.javadoc.internal.doclets.toolkit.taglets.snippet.Parser; -import jdk.javadoc.internal.doclets.toolkit.taglets.snippet.StyledText; +import jdk.javadoc.internal.doclets.toolkit.util.DocPaths; import jdk.javadoc.internal.doclets.toolkit.util.Utils; /** @@ -90,8 +102,8 @@ public class SnippetTaglet extends BaseTaglet { public String getIdentifier() {return identifier;} } - public SnippetTaglet() { - super(DocTree.Kind.SNIPPET, true, EnumSet.allOf(Taglet.Location.class)); + SnippetTaglet(HtmlConfiguration config) { + super(config, DocTree.Kind.SNIPPET, true, EnumSet.allOf(Taglet.Location.class)); } /* @@ -109,16 +121,116 @@ public class SnippetTaglet extends BaseTaglet { * one of the named regions in the snippets content. */ @Override - public Content getInlineTagOutput(Element holder, DocTree tag, TagletWriter writer) { + public Content getInlineTagOutput(Element holder, DocTree tag, TagletWriter tagletWriter) { + this.tagletWriter = tagletWriter; try { - return generateContent(holder, tag, writer); + return generateContent(holder, tag); } catch (BadSnippetException e) { - error(writer, holder, e.tag(), e.key(), e.args()); - String details = writer.configuration().getDocResources().getText(e.key(), e.args()); - return badSnippet(writer, Optional.of(details)); + error(tagletWriter, holder, e.tag(), e.key(), e.args()); + String details = config.getDocResources().getText(e.key(), e.args()); + return badSnippet(tagletWriter, Optional.of(details)); } } + /** + * Returns the output for a {@code {@snippet ...}} tag. + * + * @param element The element that owns the doc comment + * @param tag the snippet tag + * @param id the value of the id attribute, or null if not defined + * @param lang the value of the lang attribute, or null if not defined + * + * @return the output + */ + private Content snippetTagOutput(Element element, SnippetTree tag, StyledText content, + String id, String lang) { + var pathToRoot = tagletWriter.htmlWriter.pathToRoot; + var pre = new HtmlTree(TagName.PRE).setStyle(HtmlStyle.snippet); + if (id != null && !id.isBlank()) { + pre.put(HtmlAttr.ID, id); + } + var code = new HtmlTree(TagName.CODE) + .addUnchecked(Text.EMPTY); // Make sure the element is always rendered + if (lang != null && !lang.isBlank()) { + code.addStyle("language-" + lang); + } + + content.consumeBy((styles, sequence) -> { + CharSequence text = Text.normalizeNewlines(sequence); + if (styles.isEmpty()) { + code.add(text); + } else { + Element e = null; + String t = null; + boolean linkEncountered = false; + boolean markupEncountered = false; + Set classes = new HashSet<>(); + for (Style s : styles) { + if (s instanceof Style.Name n) { + classes.add(n.name()); + } else if (s instanceof Style.Link l) { + assert !linkEncountered; // TODO: do not assert; pick the first link report on subsequent + linkEncountered = true; + t = l.target(); + e = getLinkedElement(element, t); + if (e == null) { + // TODO: diagnostic output + } + } else if (s instanceof Style.Markup) { + markupEncountered = true; + break; + } else { + // TODO: transform this if...else into an exhaustive + // switch over the sealed Style hierarchy when "Pattern + // Matching for switch" has been implemented (JEP 406 + // and friends) + throw new AssertionError(styles); + } + } + Content c; + if (markupEncountered) { + return; + } else if (linkEncountered) { + assert e != null; + //disable preview tagging inside the snippets: + Utils.PreviewFlagProvider prevPreviewProvider = utils.setPreviewFlagProvider(el -> false); + try { + var lt = (LinkTaglet) config.tagletManager.getTaglet(DocTree.Kind.LINK); + c = lt.linkSeeReferenceOutput(element, + null, + t, + e, + false, // TODO: for now + Text.of(sequence.toString()), + (key, args) -> { /* TODO: report diagnostic */ }, + tagletWriter); + } finally { + utils.setPreviewFlagProvider(prevPreviewProvider); + } + } else { + c = HtmlTree.SPAN(Text.of(text)); + classes.forEach(((HtmlTree) c)::addStyle); + } + code.add(c); + } + }); + String copyText = resources.getText("doclet.Copy_to_clipboard"); + String copiedText = resources.getText("doclet.Copied_to_clipboard"); + String copySnippetText = resources.getText("doclet.Copy_snippet_to_clipboard"); + var snippetContainer = HtmlTree.DIV(HtmlStyle.snippetContainer, + new HtmlTree(TagName.BUTTON) + .add(HtmlTree.SPAN(Text.of(copyText)) + .put(HtmlAttr.DATA_COPIED, copiedText)) + .add(new HtmlTree(TagName.IMG) + .put(HtmlAttr.SRC, pathToRoot.resolve(DocPaths.CLIPBOARD_SVG).getPath()) + .put(HtmlAttr.ALT, copySnippetText)) + .addStyle(HtmlStyle.copy) + .addStyle(HtmlStyle.snippetCopy) + .put(HtmlAttr.ARIA_LABEL, copySnippetText) + .put(HtmlAttr.ONCLICK, "copySnippet(this)")); + return snippetContainer.add(pre.add(code)); + } + private static final class BadSnippetException extends Exception { @java.io.Serial @@ -147,7 +259,7 @@ public class SnippetTaglet extends BaseTaglet { } } - private Content generateContent(Element holder, DocTree tag, TagletWriter writer) + private Content generateContent(Element holder, DocTree tag) throws BadSnippetException { SnippetTree snippetTag = (SnippetTree) tag; @@ -212,11 +324,10 @@ public class SnippetTaglet extends BaseTaglet { } // we didn't create JavaFileManager, so we won't close it; even if an error occurs - var fileManager = writer.configuration().getFileManager(); + var fileManager = config.getFileManager(); try { // first, look in local snippet-files subdirectory - var utils = writer.configuration().utils; var pkg = getPackageElement(holder, utils); var pkgLocation = utils.getLocationForPackage(pkg); var pkgName = pkg.getQualifiedName().toString(); // note: empty string for unnamed package @@ -224,8 +335,8 @@ public class SnippetTaglet extends BaseTaglet { fileObject = fileManager.getFileForInput(pkgLocation, pkgName, relativeName); // if not found in local snippet-files directory, look on snippet path - if (fileObject == null && fileManager.hasLocation(Location.SNIPPET_PATH)) { - fileObject = fileManager.getFileForInput(Location.SNIPPET_PATH, "", v); + if (fileObject == null && fileManager.hasLocation(DocumentationTool.Location.SNIPPET_PATH)) { + fileObject = fileManager.getFileForInput(DocumentationTool.Location.SNIPPET_PATH, "", v); } } catch (IOException | IllegalArgumentException e) { // TODO: test this when JDK-8276892 is integrated // JavaFileManager.getFileForInput can throw IllegalArgumentException in certain cases @@ -265,36 +376,35 @@ public class SnippetTaglet extends BaseTaglet { try { Diags d = (text, pos) -> { - var path = writer.configuration().utils.getCommentHelper(holder) + var path = utils.getCommentHelper(holder) .getDocTreePath(snippetTag.getBody()); - writer.configuration().getReporter().print(Diagnostic.Kind.WARNING, + config.getReporter().print(Diagnostic.Kind.WARNING, path, pos, pos, pos, text); }; if (inlineContent != null) { - inlineSnippet = parse(writer.configuration().getDocResources(), d, language, inlineContent); + inlineSnippet = parse(resources, d, language, inlineContent); } } catch (ParseException e) { - var path = writer.configuration().utils.getCommentHelper(holder) + var path = utils.getCommentHelper(holder) .getDocTreePath(snippetTag.getBody()); // TODO: there should be a method in Messages; that method should mirror Reporter's; use that method instead accessing Reporter. - String msg = writer.configuration().getDocResources() - .getText("doclet.snippet.markup", e.getMessage()); - writer.configuration().getReporter().print(Diagnostic.Kind.ERROR, + String msg = resources.getText("doclet.snippet.markup", e.getMessage()); + config.getReporter().print(Diagnostic.Kind.ERROR, path, e.getPosition(), e.getPosition(), e.getPosition(), msg); - return badSnippet(writer, Optional.of(e.getMessage())); + return badSnippet(tagletWriter, Optional.of(e.getMessage())); } try { var finalFileObject = fileObject; - Diags d = (text, pos) -> writer.configuration().getMessages().warning(finalFileObject, pos, pos, pos, text); + Diags d = (text, pos) -> messages.warning(finalFileObject, pos, pos, pos, text); if (externalContent != null) { - externalSnippet = parse(writer.configuration().getDocResources(), d, language, externalContent); + externalSnippet = parse(resources, d, language, externalContent); } } catch (ParseException e) { assert fileObject != null; - writer.configuration().getMessages().error(fileObject, e.getPosition(), + messages.error(fileObject, e.getPosition(), e.getPosition(), e.getPosition(), "doclet.snippet.markup", e.getMessage()); - return badSnippet(writer, Optional.of(e.getMessage())); + return badSnippet(tagletWriter, Optional.of(e.getMessage())); } // the region must be matched at least in one content: it can be matched @@ -343,7 +453,7 @@ public class SnippetTaglet extends BaseTaglet { ? null : stringValueOf(idAttr); - return writer.snippetTagOutput(holder, snippetTag, text, id, lang); + return snippetTagOutput(holder, snippetTag, text, id, lang); } /* @@ -382,10 +492,10 @@ public class SnippetTaglet extends BaseTaglet { at.getName().toString()); } return at.getValue().stream() - // value consists of TextTree or ErroneousTree nodes; - // ErroneousTree is a subtype of TextTree - .map(t -> ((TextTree) t).getBody()) - .collect(Collectors.joining()); + // value consists of TextTree or ErroneousTree nodes; + // ErroneousTree is a subtype of TextTree + .map(t -> ((TextTree) t).getBody()) + .collect(Collectors.joining()); } private String languageFromFileName(String fileName) { @@ -399,12 +509,11 @@ public class SnippetTaglet extends BaseTaglet { } private void error(TagletWriter writer, Element holder, DocTree tag, String key, Object... args) { - writer.configuration().getMessages().error( - writer.configuration().utils.getCommentHelper(holder).getDocTreePath(tag), key, args); + messages.error(utils.getCommentHelper(holder).getDocTreePath(tag), key, args); } private Content badSnippet(TagletWriter writer, Optional details) { - Resources resources = writer.configuration().getDocResources(); + var resources = config.getDocResources(); return writer.invalidTagOutput(resources.getText("doclet.tag.invalid", "snippet"), details); } @@ -461,4 +570,21 @@ public class SnippetTaglet extends BaseTaglet { // The most trivial example of such a string is " ". In fact, any string // with a trailing non-empty blank line would do. } + + /* + * Returns the element that is linked from the context of the referrer using + * the provided signature; returns null if such element could not be found. + * + * This method is to be used when it is the target of the link that is + * important, not the container of the link (e.g. was it an @see, + * @link/@linkplain or @snippet tags, etc.) + */ + private Element getLinkedElement(Element referer, String signature) { + var factory = utils.docTrees.getDocTreeFactory(); + var docCommentTree = utils.getDocCommentTree(referer); + var rootPath = new DocTreePath(utils.getTreePath(referer), docCommentTree); + var reference = factory.newReferenceTree(signature); + var fabricatedPath = new DocTreePath(rootPath, reference); + return utils.docTrees.getElement(fabricatedPath); + } } diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SpecTaglet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SpecTaglet.java new file mode 100644 index 00000000000..1548ab3fcd2 --- /dev/null +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SpecTaglet.java @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2019, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html.taglets; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.EnumSet; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; + +import com.sun.source.doctree.DocTree; +import com.sun.source.doctree.SpecTree; +import com.sun.source.doctree.TextTree; +import com.sun.source.util.DocTreePath; + +import jdk.javadoc.doclet.Taglet; +import jdk.javadoc.internal.doclets.formats.html.Contents; +import jdk.javadoc.internal.doclets.formats.html.HtmlConfiguration; +import jdk.javadoc.internal.doclets.formats.html.markup.ContentBuilder; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.formats.html.markup.Text; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.util.CommentHelper; +import jdk.javadoc.internal.doclets.toolkit.util.DocFinder; +import jdk.javadoc.internal.doclets.toolkit.util.Utils; + +/** + * A taglet that represents the {@code @spec} tag. + */ +public class SpecTaglet extends BaseTaglet implements InheritableTaglet { + private final Contents contents; + + SpecTaglet(HtmlConfiguration config) { + super(config, DocTree.Kind.SPEC, false, EnumSet.allOf(Taglet.Location.class)); + this.contents = config.contents; + } + + @Override + public Output inherit(Element dst, Element src, DocTree tag, boolean isFirstSentence) { + CommentHelper ch = utils.getCommentHelper(dst); + var path = ch.getDocTreePath(tag); + messages.warning(path, "doclet.inheritDocWithinInappropriateTag"); + return new Output(null, null, List.of(), true /* true, otherwise there will be an exception up the stack */); + } + + @Override + public Content getAllBlockTagOutput(Element holder, TagletWriter tagletWriter) { + this.tagletWriter = tagletWriter; + List tags = utils.getSpecTrees(holder); + Element e = holder; + if (utils.isMethod(holder)) { + var docFinder = utils.docFinder(); + Optional result = docFinder.search((ExecutableElement) holder, + m -> DocFinder.Result.fromOptional(extract(utils, m))).toOptional(); + if (result.isPresent()) { + e = result.get().method(); + tags = result.get().specTrees(); + } + } + return specTagOutput(e, tags); + } + + /** + * Returns the output for one or more {@code @spec} tags. + * + * @param holder the element that owns the doc comment + * @param specTags the array of @spec tags. + * + * @return the output + */ + public Content specTagOutput(Element holder, List specTags) { + if (specTags.isEmpty()) { + return Text.EMPTY; + } + + var links = specTags.stream() + .map(st -> specTagToContent(holder, st)).toList(); + + var specList = tagletWriter.tagList(links); + return new ContentBuilder( + HtmlTree.DT(contents.externalSpecifications), + HtmlTree.DD(specList)); + } + + private record Documentation(List specTrees, ExecutableElement method) { } + + private static Optional extract(Utils utils, ExecutableElement method) { + List tags = utils.getSpecTrees(method); + return tags.isEmpty() ? Optional.empty() : Optional.of(new Documentation(tags, method)); + } + + private Content specTagToContent(Element holder, SpecTree specTree) { + var htmlWriter = tagletWriter.htmlWriter; + String specTreeURL = specTree.getURL().getBody(); + List specTreeLabel = specTree.getTitle(); + Content label = htmlWriter.commentTagsToContent(holder, specTreeLabel, tagletWriter.context.isFirstSentence); + return getExternalSpecContent(holder, specTree, specTreeURL, + textOf(specTreeLabel).replaceAll("\\s+", " "), label); + } + + private String textOf(List trees) { + return trees.stream() + .filter(dt -> dt instanceof TextTree) + .map(dt -> ((TextTree) dt).getBody().trim()) + .collect(Collectors.joining(" ")); + } + + Content getExternalSpecContent(Element holder, + DocTree docTree, + String url, + String searchText, + Content title) { + URI specURI; + try { + // Use the canonical title of the spec if one is available + specURI = new URI(url); + } catch (URISyntaxException e) { + CommentHelper ch = utils.getCommentHelper(holder); + DocTreePath dtp = ch.getDocTreePath(docTree); + tagletWriter.htmlWriter.messages.error(dtp, "doclet.Invalid_URL", e.getMessage()); + specURI = null; + } + + Content titleWithAnchor = tagletWriter.createAnchorAndSearchIndex(holder, + searchText, + title, + resources.getText("doclet.External_Specification"), + docTree); + + if (specURI == null) { + return titleWithAnchor; + } else { + var htmlWriter = tagletWriter.htmlWriter; + return HtmlTree.A(htmlWriter.resolveExternalSpecURI(specURI), titleWithAnchor); + } + + } +} diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/SummaryTaglet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SummaryTaglet.java similarity index 76% rename from src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/SummaryTaglet.java rename to src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SummaryTaglet.java index 07aab93422b..c1df4588ec3 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/SummaryTaglet.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SummaryTaglet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 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,14 +23,17 @@ * questions. */ -package jdk.javadoc.internal.doclets.toolkit.taglets; +package jdk.javadoc.internal.doclets.formats.html.taglets; import java.util.EnumSet; + import javax.lang.model.element.Element; import com.sun.source.doctree.DocTree; -import jdk.javadoc.doclet.Taglet.Location; import com.sun.source.doctree.SummaryTree; + +import jdk.javadoc.doclet.Taglet.Location; +import jdk.javadoc.internal.doclets.formats.html.HtmlConfiguration; import jdk.javadoc.internal.doclets.toolkit.Content; /** @@ -38,13 +41,13 @@ import jdk.javadoc.internal.doclets.toolkit.Content; */ public class SummaryTaglet extends BaseTaglet { - public SummaryTaglet() { - super(DocTree.Kind.SUMMARY, true, EnumSet.allOf(Location.class)); + SummaryTaglet(HtmlConfiguration config) { + super(config, DocTree.Kind.SUMMARY, true, EnumSet.allOf(Location.class)); } @Override - public Content getInlineTagOutput(Element holder, DocTree tag, TagletWriter writer) { - return writer.commentTagsToOutput(holder, tag, ((SummaryTree)tag).getSummary(), - writer.isFirstSentence); + public Content getInlineTagOutput(Element holder, DocTree tag, TagletWriter tagletWriter) { + return tagletWriter.commentTagsToOutput(holder, tag, ((SummaryTree)tag).getSummary(), + tagletWriter.context.isFirstSentence); } } diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/SystemPropertyTaglet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SystemPropertyTaglet.java similarity index 58% rename from src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/SystemPropertyTaglet.java rename to src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SystemPropertyTaglet.java index 9aa8ca3acf3..0f7e8f9a620 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/SystemPropertyTaglet.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SystemPropertyTaglet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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,27 +23,46 @@ * questions. */ -package jdk.javadoc.internal.doclets.toolkit.taglets; +package jdk.javadoc.internal.doclets.formats.html.taglets; + +import java.util.EnumSet; + +import javax.lang.model.element.Element; import com.sun.source.doctree.DocTree; import com.sun.source.doctree.SystemPropertyTree; -import jdk.javadoc.doclet.Taglet.Location; -import jdk.javadoc.internal.doclets.toolkit.Content; -import javax.lang.model.element.Element; -import java.util.EnumSet; +import jdk.javadoc.doclet.Taglet; +import jdk.javadoc.internal.doclets.formats.html.HtmlConfiguration; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.toolkit.Content; /** * A taglet that represents the {@code @systemProperty} tag. */ public class SystemPropertyTaglet extends BaseTaglet { - SystemPropertyTaglet() { - super(DocTree.Kind.SYSTEM_PROPERTY, true, EnumSet.allOf(Location.class)); + SystemPropertyTaglet(HtmlConfiguration config) { + super(config, DocTree.Kind.SYSTEM_PROPERTY, true, EnumSet.allOf(Taglet.Location.class)); } @Override - public Content getInlineTagOutput(Element element, DocTree tag, TagletWriter writer) { - return writer.systemPropertyTagOutput(element, (SystemPropertyTree) tag); + public Content getInlineTagOutput(Element element, DocTree tag, TagletWriter tagletWriter) { + this.tagletWriter = tagletWriter; + return systemPropertyTagOutput(element, (SystemPropertyTree) tag); + } + + /** + * Returns the output for a {@code {@systemProperty...}} tag. + * + * @param element the element that owns the doc comment + * @param tag the system property tag + * + * @return the output + */ + private Content systemPropertyTagOutput(Element element, SystemPropertyTree tag) { + String tagText = tag.getPropertyName().toString(); + return HtmlTree.CODE(tagletWriter.createAnchorAndSearchIndex(element, tagText, + resources.getText("doclet.System_Property"), tag)); } } diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/Taglet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/Taglet.java similarity index 78% rename from src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/Taglet.java rename to src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/Taglet.java index e26651e2f36..cfaa63a8551 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/Taglet.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/Taglet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 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,17 +23,25 @@ * questions. */ -package jdk.javadoc.internal.doclets.toolkit.taglets; +package jdk.javadoc.internal.doclets.formats.html.taglets; import java.util.Set; import javax.lang.model.element.Element; import com.sun.source.doctree.DocTree; import jdk.javadoc.doclet.Taglet.Location; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; import jdk.javadoc.internal.doclets.toolkit.Content; /** * This is the taglet interface used internally within the doclet. + * + * The public {@link jdk.javadoc.doclet.Taglet} interface only supports + * output to strings. This interface supports structured output, + * to {@link Content} objects, such as {@link HtmlTree}. + * + * User-provided taglets are supported using the {@link UserTaglet} + * wrapper class. */ public interface Taglet { /** @@ -49,7 +57,9 @@ public interface Taglet { * @return {@code true} if this {@code Taglet} can be used in field documentation * and {@code false} otherwise */ - boolean inField(); + default boolean inField() { + return getAllowedLocations().contains(Location.FIELD); + } /** * Indicates whether this {@code Taglet} can be used in constructor documentation. @@ -57,7 +67,9 @@ public interface Taglet { * @return {@code true} if this {@code Taglet} can be used in constructor documentation * and {@code false} otherwise */ - boolean inConstructor(); + default boolean inConstructor() { + return getAllowedLocations().contains(Location.CONSTRUCTOR); + } /** * Indicates whether this {@code Taglet} can be used in method documentation. @@ -65,7 +77,9 @@ public interface Taglet { * @return {@code true} if this {@code Taglet} can be used in method documentation * and {@code false} otherwise */ - boolean inMethod(); + default boolean inMethod() { + return getAllowedLocations().contains(Location.METHOD); + } /** * Indicates whether this {@code Taglet} can be used in overview documentation. @@ -73,7 +87,9 @@ public interface Taglet { * @return {@code true} if this {@code Taglet} can be used in overview documentation * and {@code false} otherwise */ - boolean inOverview(); + default boolean inOverview() { + return getAllowedLocations().contains(Location.OVERVIEW); + } /** * Indicates whether this {@code Taglet} can be used in module documentation. @@ -81,7 +97,9 @@ public interface Taglet { * @return {@code true} if this {@code Taglet} can be used in module documentation * and {@code false} otherwise */ - boolean inModule(); + default boolean inModule() { + return getAllowedLocations().contains(Location.MODULE); + } /** * Indicates whether this {@code Taglet} can be used in package documentation. @@ -89,7 +107,9 @@ public interface Taglet { * @return {@code true} if this {@code Taglet} can be used in package documentation * and {@code false} otherwise */ - boolean inPackage(); + default boolean inPackage() { + return getAllowedLocations().contains(Location.PACKAGE); + } /** * Indicates whether this {@code Taglet} can be used in type documentation (classes or interfaces). @@ -97,7 +117,9 @@ public interface Taglet { * @return {@code true} if this {@code Taglet} can be used in type documentation * and {@code false} otherwise */ - boolean inType(); + default boolean inType() { + return getAllowedLocations().contains(Location.TYPE); + } /** * Indicates whether this {@code Taglet} represents an inline tag. @@ -130,12 +152,12 @@ public interface Taglet { * * @param owner the element for the enclosing doc comment * @param tag the tag - * @param writer the taglet-writer used in this doclet + * @param tagletWriter the taglet-writer used in this doclet * * @return the output for this tag * @throws UnsupportedTagletOperationException if the method is not supported by the taglet */ - Content getInlineTagOutput(Element owner, DocTree tag, TagletWriter writer) throws + Content getInlineTagOutput(Element owner, DocTree tag, TagletWriter tagletWriter) throws UnsupportedTagletOperationException; /** @@ -143,12 +165,12 @@ public interface Taglet { * all instances of block tags handled by this taglet. * * @param owner the element for the enclosing doc comment - * @param writer the taglet-writer used in this doclet + * @param tagletWriter the taglet-writer used in this doclet * * @return the output for this tag * @throws UnsupportedTagletOperationException if the method is not supported by the taglet */ - Content getAllBlockTagOutput(Element owner, TagletWriter writer) throws + Content getAllBlockTagOutput(Element owner, TagletWriter tagletWriter) throws UnsupportedTagletOperationException; class UnsupportedTagletOperationException extends UnsupportedOperationException { diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/TagletManager.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/TagletManager.java similarity index 86% rename from src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/TagletManager.java rename to src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/TagletManager.java index f09979eeac8..a4724f03e9d 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/TagletManager.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/TagletManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 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 @@ * questions. */ -package jdk.javadoc.internal.doclets.toolkit.taglets; +package jdk.javadoc.internal.doclets.formats.html.taglets; import java.io.File; import java.io.IOException; @@ -55,8 +55,8 @@ import com.sun.source.doctree.DocTree; import jdk.javadoc.doclet.Doclet; import jdk.javadoc.doclet.DocletEnvironment; import jdk.javadoc.doclet.Taglet.Location; -import jdk.javadoc.internal.doclets.toolkit.BaseConfiguration; -import jdk.javadoc.internal.doclets.toolkit.BaseOptions; +import jdk.javadoc.internal.doclets.formats.html.HtmlConfiguration; +import jdk.javadoc.internal.doclets.formats.html.HtmlOptions; import jdk.javadoc.internal.doclets.toolkit.DocletElement; import jdk.javadoc.internal.doclets.toolkit.Messages; import jdk.javadoc.internal.doclets.toolkit.Resources; @@ -66,8 +66,6 @@ import jdk.javadoc.internal.doclets.toolkit.util.Utils; import static com.sun.source.doctree.DocTree.Kind.AUTHOR; import static com.sun.source.doctree.DocTree.Kind.EXCEPTION; import static com.sun.source.doctree.DocTree.Kind.HIDDEN; -import static com.sun.source.doctree.DocTree.Kind.LINK; -import static com.sun.source.doctree.DocTree.Kind.LINK_PLAIN; import static com.sun.source.doctree.DocTree.Kind.PARAM; import static com.sun.source.doctree.DocTree.Kind.PROVIDES; import static com.sun.source.doctree.DocTree.Kind.SEE; @@ -78,10 +76,11 @@ import static com.sun.source.doctree.DocTree.Kind.SINCE; import static com.sun.source.doctree.DocTree.Kind.THROWS; import static com.sun.source.doctree.DocTree.Kind.USES; import static com.sun.source.doctree.DocTree.Kind.VERSION; + import static javax.tools.DocumentationTool.Location.TAGLET_PATH; /** - * Manages the {@code Taglet}s used by doclets. + * Manages the {@code Taglet}s used by the standard doclet. */ public class TagletManager { @@ -173,32 +172,32 @@ public class TagletManager { private final String tagletPath; - private final BaseConfiguration configuration; + private final HtmlConfiguration config; /** * Constructs a new {@code TagletManager}. * - * @param configuration the configuration for this taglet manager + * @param config the configuration for this taglet manager */ - public TagletManager(BaseConfiguration configuration) { + public TagletManager(HtmlConfiguration config) { overriddenStandardTags = new HashSet<>(); potentiallyConflictingTags = new HashSet<>(); standardTags = new HashSet<>(); standardTagsLowercase = new HashSet<>(); unseenCustomTags = new HashSet<>(); allTaglets = new LinkedHashMap<>(); - this.configuration = configuration; - BaseOptions options = configuration.getOptions(); + this.config = config; + HtmlOptions options = config.getOptions(); this.nosince = options.noSince(); this.showversion = options.showVersion(); this.showauthor = options.showAuthor(); this.javafx = options.javafx(); - this.docEnv = configuration.docEnv; - this.doclet = configuration.doclet; - this.messages = configuration.getMessages(); - this.resources = configuration.getDocResources(); + this.docEnv = config.docEnv; + this.doclet = config.doclet; + this.messages = config.getMessages(); + this.resources = config.getDocResources(); this.showTaglets = options.showTaglets(); - this.utils = configuration.utils; + this.utils = config.utils; this.tagletPath = options.tagletPath(); initStandardTaglets(); } @@ -239,7 +238,7 @@ public class TagletManager { */ public void addCustomTag(String classname, JavaFileManager fileManager) { ClassLoader tagClassLoader = fileManager.getClassLoader(TAGLET_PATH); - if (configuration.workArounds.accessInternalAPI()) { + if (config.workArounds.accessInternalAPI()) { Module thisModule = getClass().getModule(); Module tagletLoaderUnnamedModule = tagClassLoader.getUnnamedModule(); List pkgs = List.of( @@ -309,7 +308,7 @@ public class TagletManager { // remove + put in both branches below move the tag to the back of the map's ordering Taglet tag = allTaglets.remove(tagName); if (tag == null || header != null) { - allTaglets.put(tagName, new SimpleTaglet(tagName, header, locations)); + allTaglets.put(tagName, new SimpleTaglet(config, tagName, header, locations)); if (Utils.toLowerCase(locations).indexOf('x') == -1) { checkTagName(tagName); } @@ -371,7 +370,7 @@ public class TagletManager { final Taglet taglet = allTaglets.get(name); // Check and verify tag usage if (taglet != null) { - if (taglet instanceof SimpleTaglet st && !st.enabled) { + if (taglet instanceof SimpleTaglet st && !st.isEnabled()) { // taglet has been disabled return; } @@ -591,69 +590,69 @@ public class TagletManager { initJavaFXTaglets(); } - addStandardTaglet(new ParamTaglet()); - addStandardTaglet(new ReturnTaglet()); - addStandardTaglet(new ThrowsTaglet(configuration), EXCEPTION); + addStandardTaglet(new ParamTaglet(config)); + addStandardTaglet(new ReturnTaglet(config)); + addStandardTaglet(new ThrowsTaglet(config), EXCEPTION); addStandardTaglet( - new SimpleTaglet(SINCE, resources.getText("doclet.Since"), + new SimpleTaglet(config, SINCE, resources.getText("doclet.Since"), EnumSet.allOf(Location.class), !nosince)); addStandardTaglet( - new SimpleTaglet(VERSION, resources.getText("doclet.Version"), + new SimpleTaglet(config, VERSION, resources.getText("doclet.Version"), EnumSet.of(Location.OVERVIEW, Location.MODULE, Location.PACKAGE, Location.TYPE), showversion)); addStandardTaglet( - new SimpleTaglet(AUTHOR, resources.getText("doclet.Author"), + new SimpleTaglet(config, AUTHOR, resources.getText("doclet.Author"), EnumSet.of(Location.OVERVIEW, Location.MODULE, Location.PACKAGE, Location.TYPE), showauthor)); addStandardTaglet( - new SimpleTaglet(SERIAL_DATA, resources.getText("doclet.SerialData"), + new SimpleTaglet(config, SERIAL_DATA, resources.getText("doclet.SerialData"), EnumSet.noneOf(Location.class))); addStandardTaglet( - new SimpleTaglet(HIDDEN, null, + new SimpleTaglet(config, HIDDEN, null, EnumSet.of(Location.TYPE, Location.METHOD, Location.FIELD))); // This appears to be a default custom (non-standard) taglet - Taglet factoryTaglet = new SimpleTaglet("factory", resources.getText("doclet.Factory"), + Taglet factoryTaglet = new SimpleTaglet(config, "factory", resources.getText("doclet.Factory"), EnumSet.of(Location.METHOD)); allTaglets.put(factoryTaglet.getName(), factoryTaglet); - addStandardTaglet(new SeeTaglet()); - addStandardTaglet(new SpecTaglet()); + addStandardTaglet(new SeeTaglet(config)); + addStandardTaglet(new SpecTaglet(config)); // Standard inline tags - addStandardTaglet(new DocRootTaglet()); - addStandardTaglet(new InheritDocTaglet()); - addStandardTaglet(new ValueTaglet()); - addStandardTaglet(new LiteralTaglet()); - addStandardTaglet(new CodeTaglet()); - addStandardTaglet(new SnippetTaglet()); - addStandardTaglet(new IndexTaglet()); - addStandardTaglet(new SummaryTaglet()); - addStandardTaglet(new SystemPropertyTaglet()); + addStandardTaglet(new DocRootTaglet(config)); + addStandardTaglet(new InheritDocTaglet(config)); + addStandardTaglet(new ValueTaglet(config)); + addStandardTaglet(new LinkTaglet(config, DocTree.Kind.LINK)); + addStandardTaglet(new LinkTaglet(config, DocTree.Kind.LINK_PLAIN)); + addStandardTaglet(new LiteralTaglet(config, DocTree.Kind.CODE)); + addStandardTaglet(new LiteralTaglet(config, DocTree.Kind.LITERAL)); + addStandardTaglet(new SnippetTaglet(config)); + addStandardTaglet(new IndexTaglet(config)); + addStandardTaglet(new SummaryTaglet(config)); + addStandardTaglet(new SystemPropertyTaglet(config)); // Keep track of the names of standard tags for error checking purposes. // The following are not handled above. - addStandardTaglet(new DeprecatedTaglet()); - addStandardTaglet(new BaseTaglet(LINK, true, EnumSet.allOf(Location.class))); - addStandardTaglet(new BaseTaglet(LINK_PLAIN, true, EnumSet.allOf(Location.class))); - addStandardTaglet(new BaseTaglet(USES, false, EnumSet.of(Location.MODULE))); - addStandardTaglet(new BaseTaglet(PROVIDES, false, EnumSet.of(Location.MODULE))); + addStandardTaglet(new DeprecatedTaglet(config)); + addStandardTaglet(new BaseTaglet(config, USES, false, EnumSet.of(jdk.javadoc.doclet.Taglet.Location.MODULE))); + addStandardTaglet(new BaseTaglet(config, PROVIDES, false, EnumSet.of(jdk.javadoc.doclet.Taglet.Location.MODULE))); addStandardTaglet( - new SimpleTaglet(SERIAL, null, - EnumSet.of(Location.PACKAGE, Location.TYPE, Location.FIELD))); + new SimpleTaglet(config, SERIAL, null, + EnumSet.of(jdk.javadoc.doclet.Taglet.Location.PACKAGE, jdk.javadoc.doclet.Taglet.Location.TYPE, jdk.javadoc.doclet.Taglet.Location.FIELD))); addStandardTaglet( - new SimpleTaglet(SERIAL_FIELD, null, EnumSet.of(Location.FIELD))); + new SimpleTaglet(config, SERIAL_FIELD, null, EnumSet.of(jdk.javadoc.doclet.Taglet.Location.FIELD))); } /** * Initialize JavaFX-related tags. */ private void initJavaFXTaglets() { - addStandardTaglet(new SimpleTaglet("propertyDescription", + addStandardTaglet(new SimpleTaglet(config, "propertyDescription", resources.getText("doclet.PropertyDescription"), - EnumSet.of(Location.METHOD, Location.FIELD))); - addStandardTaglet(new SimpleTaglet("defaultValue", resources.getText("doclet.DefaultValue"), - EnumSet.of(Location.METHOD, Location.FIELD))); - addStandardTaglet(new SimpleTaglet("treatAsPrivate", null, - EnumSet.of(Location.TYPE, Location.METHOD, Location.FIELD))); + EnumSet.of(jdk.javadoc.doclet.Taglet.Location.METHOD, jdk.javadoc.doclet.Taglet.Location.FIELD))); + addStandardTaglet(new SimpleTaglet(config, "defaultValue", resources.getText("doclet.DefaultValue"), + EnumSet.of(jdk.javadoc.doclet.Taglet.Location.METHOD, jdk.javadoc.doclet.Taglet.Location.FIELD))); + addStandardTaglet(new SimpleTaglet(config, "treatAsPrivate", null, + EnumSet.of(jdk.javadoc.doclet.Taglet.Location.TYPE, jdk.javadoc.doclet.Taglet.Location.METHOD, jdk.javadoc.doclet.Taglet.Location.FIELD))); } private void addStandardTaglet(Taglet taglet) { @@ -711,6 +710,14 @@ public class TagletManager { } } + public Taglet getTaglet(DocTree.Kind kind) { + return switch (kind) { + case DEPRECATED, LINK, LINK_PLAIN, PARAM, RETURN, THROWS -> getTaglet(kind.tagName); + default -> + throw new IllegalArgumentException(kind.toString()); + }; + } + /* * The output of this method is the basis for a table at the end of the * doc comment specification, so any changes in the output may indicate @@ -732,7 +739,7 @@ public class TagletManager { + format(t.inMethod(), "method") + " " + format(t.inField(), "field") + " " + format(t.isInlineTag(), "inline")+ " " - + format((t instanceof SimpleTaglet st) && !st.enabled, "disabled")); + + format((t instanceof SimpleTaglet st) && !st.isEnabled(), "disabled")); }); } diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/TagletWriter.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/TagletWriter.java new file mode 100644 index 00000000000..f6cef9af500 --- /dev/null +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/TagletWriter.java @@ -0,0 +1,474 @@ +/* + * Copyright (c) 2003, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html.taglets; + +import java.util.EnumSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.Predicate; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.ModuleElement; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.util.SimpleElementVisitor14; + +import com.sun.source.doctree.DocTree; + +import com.sun.source.doctree.InlineTagTree; +import jdk.javadoc.internal.doclets.formats.html.HtmlConfiguration; +import jdk.javadoc.internal.doclets.formats.html.HtmlDocletWriter; +import jdk.javadoc.internal.doclets.formats.html.HtmlIds; +import jdk.javadoc.internal.doclets.formats.html.HtmlOptions; +import jdk.javadoc.internal.doclets.formats.html.markup.ContentBuilder; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlId; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.formats.html.markup.Text; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.formats.html.taglets.Taglet.UnsupportedTagletOperationException; +import jdk.javadoc.internal.doclets.toolkit.DocletElement; +import jdk.javadoc.internal.doclets.toolkit.Resources; +import jdk.javadoc.internal.doclets.toolkit.util.CommentHelper; +import jdk.javadoc.internal.doclets.toolkit.util.DocLink; +import jdk.javadoc.internal.doclets.toolkit.util.IndexItem; +import jdk.javadoc.internal.doclets.toolkit.util.Utils; + +/** + * Context and utility methods for taglet classes. + */ +public class TagletWriter { + + /** + * A class that provides the information about the enclosing context for + * a series of {@code DocTree} nodes. + * This context may be used to determine the content that should be generated from the tree nodes. + */ + public static class Context { + /** + * Whether the trees are appearing in a context of just the first sentence, + * such as in the summary table of the enclosing element. + */ + public final boolean isFirstSentence; + /** + * Whether the trees are appearing in the "summary" section of the + * page for a declaration. + */ + public final boolean inSummary; + /** + * The set of enclosing kinds of tags. + */ + public final Set inTags; + + /** + * Creates an outermost context, with no enclosing tags. + * + * @param isFirstSentence {@code true} if the trees are appearing in a context of just the + * first sentence and {@code false} otherwise + * @param inSummary {@code true} if the trees are appearing in the "summary" section + * of the page for a declaration and {@code false} otherwise + */ + public Context(boolean isFirstSentence, boolean inSummary) { + this(isFirstSentence, inSummary, EnumSet.noneOf(DocTree.Kind.class)); + } + + private Context(boolean isFirstSentence, boolean inSummary, Set inTags) { + this.isFirstSentence = isFirstSentence; + this.inSummary = inSummary; + this.inTags = inTags; + } + + /** + * Creates a new {@code Context} that includes an extra tag kind in the set of enclosing + * kinds of tags. + * + * @param tree the enclosing tree + * + * @return the new {@code Context} + */ + public Context within(DocTree tree) { + var newInTags = EnumSet.copyOf(inTags); + newInTags.add(tree.getKind()); + return new Context(isFirstSentence, inSummary, newInTags); + } + } + + public final HtmlDocletWriter htmlWriter; + public final HtmlConfiguration configuration; + public final HtmlOptions options; + public final Utils utils; + public final Resources resources; + + /** + * The context in which to generate the output for a series of {@code DocTree} nodes. + */ + public final Context context; + /** + * Creates a taglet writer. + * + * @param htmlWriter the {@code HtmlDocletWriter} for the page + * @param isFirstSentence {@code true} if this taglet writer is being used for a + * "first sentence" summary + */ + public TagletWriter(HtmlDocletWriter htmlWriter, boolean isFirstSentence) { + this(htmlWriter, isFirstSentence, false); + } + + /** + * Creates a taglet writer. + * + * @param htmlWriter the {@code HtmlDocletWriter} for the page + * @param isFirstSentence {@code true} if this taglet writer is being used for a + * "first sentence" summary, and {@code false} otherwise + * @param inSummary {@code true} if this taglet writer is being used for the content + * of a {@code {@summary ...}} tag, and {@code false} otherwise + */ + public TagletWriter(HtmlDocletWriter htmlWriter, boolean isFirstSentence, boolean inSummary) { + this(htmlWriter, new Context(isFirstSentence, inSummary)); + } + + /** + * Creates a taglet writer. + * + * @param htmlWriter the {@code HtmlDocletWriter} for the page + * @param context the enclosing context for any tags + */ + public TagletWriter(HtmlDocletWriter htmlWriter, Context context) { + this.htmlWriter = Objects.requireNonNull(htmlWriter); + this.context = Objects.requireNonNull(context); + configuration = htmlWriter.configuration; + options = configuration.getOptions(); + utils = configuration.utils; + resources = configuration.getDocResources(); + } + + public Context getContext() { + return context; + } + + /** + * Returns an instance of an output object. + * + * @return an instance of an output object + */ + public Content getOutputInstance() { + return new ContentBuilder(); + } + + /** + * Returns the output for an invalid tag. The returned content uses special styling to + * highlight the problem. Depending on the presence of the {@code detail} string the method + * returns a plain text span or an expandable component. + * + * @param summary the single-line summary message + * @param detail the optional detail message which may contain preformatted text + * @return the output + */ + public Content invalidTagOutput(String summary, Optional detail) { + return htmlWriter.invalidTagOutput(summary, + detail.isEmpty() || detail.get().isEmpty() + ? Optional.empty() + : Optional.of(Text.of(Text.normalizeNewlines(detail.get())))); + } + + /** + * Returns the main type element of the current page or null for pages that don't have one. + * + * @return the type element of the current page or null. + */ + public TypeElement getCurrentPageElement() { + return htmlWriter.getCurrentPageElement(); + } + + /** + * Returns the content generated from the block tags for a given element. + * The content is generated according to the order of the list of taglets. + * The result is a possibly-empty list of the output generated by each + * of the given taglets for all of the tags they individually support. + * + * @param tagletManager the manager that manages the taglets + * @param element the element that we are to write tags for + * @param taglets the taglets for the tags to write + * + * @return the content + */ + public Content getBlockTagOutput(TagletManager tagletManager, + Element element, + List taglets) { + for (Taglet t : taglets) { + if (!t.isBlockTag()) { + throw new IllegalArgumentException(t.getName()); + } + } + + Content output = getOutputInstance(); + tagletManager.checkTags(element, utils.getBlockTags(element)); + tagletManager.checkTags(element, utils.getFullBody(element)); + for (Taglet taglet : taglets) { + if (utils.isTypeElement(element) && taglet instanceof ParamTaglet) { + // The type parameters and state components are documented in a special + // section away from the tag info, so skip here. + continue; + } + + if (element.getKind() == ElementKind.MODULE && taglet instanceof BaseTaglet t) { + switch (t.getTagKind()) { + // @uses and @provides are handled separately, so skip here. + // See ModuleWriterImpl.computeModulesData + case USES: + case PROVIDES: + continue; + } + } + + if (taglet instanceof DeprecatedTaglet) { + //Deprecated information is documented "inline", not in tag info + //section. + continue; + } + + if (taglet instanceof SimpleTaglet st && !st.isEnabled()) { + // taglet has been disabled + continue; + } + + try { + Content tagletOutput = taglet.getAllBlockTagOutput(element, this); + if (tagletOutput != null) { + tagletManager.seenTag(taglet.getName()); + output.add(tagletOutput); + } + } catch (UnsupportedTagletOperationException e) { + // malformed taglet: + // claims to support block tags (see Taglet.isBlockTag) but does not provide the + // appropriate method, Taglet.getAllBlockTagOutput. + } + } + return output; + } + + /** + * Returns the content generated from an inline tag in the doc comment for a given element, + * or {@code null} if the tag is not supported or does not return any output. + * + * @param holder the element associated with the doc comment + * @param inlineTag the inline tag to be documented + * + * @return the content, or {@code null} + */ + public Content getInlineTagOutput(Element holder, + InlineTagTree inlineTag) { + var tagletManager = configuration.tagletManager; + Map inlineTags = tagletManager.getInlineTaglets(); + CommentHelper ch = configuration.utils.getCommentHelper(holder); + final String inlineTagName = ch.getTagName(inlineTag); + Taglet t = inlineTags.get(inlineTagName); + if (t == null) { + return null; + } + + try { + Content tagletOutput = t.getInlineTagOutput(holder, inlineTag, this); + tagletManager.seenTag(t.getName()); + return tagletOutput; + } catch (UnsupportedTagletOperationException e) { + // malformed taglet: + // claims to support inline tags (see Taglet.isInlineTag) but does not provide the + // appropriate method, Taglet.getInlineTagOutput. + return null; + } + } + + /** + * Converts inline tags and text to content, expanding the + * inline tags along the way. Called wherever text can contain + * an inline tag, such as in comments or in free-form text arguments + * to block tags. + * + * @param holderTree the tree that holds the documentation + * @param trees list of {@code DocTree} nodes containing text and inline tags (often alternating) + * present in the text of interest for this doc + * + * @return the generated content + */ + public Content commentTagsToOutput(DocTree holderTree, List trees) { + return commentTagsToOutput(null, holderTree, trees, false); + } + + /** + * Converts inline tags and text to content, expanding the + * inline tags along the way. Called wherever text can contain + * an inline tag, such as in comments or in free-form text arguments + * to block tags. + * + * @param element The element that owns the documentation + * @param trees list of {@code DocTree} nodes containing text and inline tags (often alternating) + * present in the text of interest for this doc + * + * @return the generated content + */ + public Content commentTagsToOutput(Element element, List trees) { + return commentTagsToOutput(element, null, trees, false); + } + + /** + * Converts inline tags and text to content, expanding the + * inline tags along the way. Called wherever text can contain + * an inline tag, such as in comments or in free-form text arguments + * to non-inline tags. + * + * @param element the element where comment resides + * @param holder the tag that holds the documentation + * @param trees array of text tags and inline tags (often alternating) + * present in the text of interest for this doc + * @param isFirstSentence true if this is the first sentence + * + * @return the generated content + */ + public Content commentTagsToOutput(Element element, + DocTree holder, + List trees, + boolean isFirstSentence) + { + return htmlWriter.commentTagsToContent(element, + trees, holder == null ? context : context.within(holder)); + } + + public Content createAnchorAndSearchIndex(Element element, String tagText, String desc, DocTree tree) { + return createAnchorAndSearchIndex(element, tagText, Text.of(tagText), desc, tree); + } + + @SuppressWarnings("preview") + Content createAnchorAndSearchIndex(Element element, String tagText, Content tagContent, String desc, DocTree tree) { + Content result; + if (context.isFirstSentence && context.inSummary || context.inTags.contains(DocTree.Kind.INDEX)) { + result = tagContent; + } else { + HtmlId id = HtmlIds.forText(tagText, htmlWriter.indexAnchorTable); + result = HtmlTree.SPAN(id, HtmlStyle.searchTagResult, tagContent); + if (options.createIndex() && !tagText.isEmpty()) { + 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; + } + + public String getHolderName(Element element) { + return new SimpleElementVisitor14() { + + @Override + public String visitModule(ModuleElement e, Void p) { + return resources.getText("doclet.module") + + " " + utils.getFullyQualifiedName(e); + } + + @Override + public String visitPackage(PackageElement e, Void p) { + return resources.getText("doclet.package") + + " " + utils.getFullyQualifiedName(e); + } + + @Override + public String visitType(TypeElement e, Void p) { + return utils.getTypeElementKindName(e, true) + + " " + 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 visitVariable(VariableElement e, Void p) { + return utils.getFullyQualifiedName(utils.getEnclosingTypeElement(e)) + + "." + utils.getSimpleName(e); + } + + @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 + protected String defaultAction(Element e, Void p) { + return utils.getFullyQualifiedName(e); + } + }.visit(element); + } + + private String getHolderName(DocletElement de) { + PackageElement pe = de.getPackageElement(); + if (pe.isUnnamed()) { + // if package is unnamed use enclosing module only if it is named + Element ee = pe.getEnclosingElement(); + if (ee instanceof ModuleElement && !((ModuleElement)ee).isUnnamed()) { + return resources.getText("doclet.module") + " " + utils.getFullyQualifiedName(ee); + } + return pe.toString(); // "Unnamed package" or similar + } + return resources.getText("doclet.package") + " " + utils.getFullyQualifiedName(pe); + } + + Content tagList(List items) { + // Use a different style if any list item is longer than 30 chars or contains commas. + boolean hasLongLabels = items.stream().anyMatch(this::isLongOrHasComma); + var list = HtmlTree.UL(hasLongLabels ? HtmlStyle.tagListLong : HtmlStyle.tagList); + items.stream() + .filter(Predicate.not(Content::isEmpty)) + .forEach(item -> list.add(HtmlTree.LI(item))); + return list; + } + + // Threshold for length of list item for switching from inline to block layout. + private static final int TAG_LIST_ITEM_MAX_INLINE_LENGTH = 30; + + private boolean isLongOrHasComma(Content c) { + String s = c.toString() + .replaceAll("<.*?>", "") // ignore HTML + .replaceAll("&#?[A-Za-z0-9]+;", " ") // entities count as a single character + .replaceAll("\\R", "\n"); // normalize newlines + return s.length() > TAG_LIST_ITEM_MAX_INLINE_LENGTH || s.contains(","); + } +} diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/ThrowsTaglet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/ThrowsTaglet.java similarity index 90% rename from src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/ThrowsTaglet.java rename to src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/ThrowsTaglet.java index 72cc98de572..f7e1ce94d6f 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/ThrowsTaglet.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/ThrowsTaglet.java @@ -23,7 +23,7 @@ * questions. */ -package jdk.javadoc.internal.doclets.toolkit.taglets; +package jdk.javadoc.internal.doclets.formats.html.taglets; import java.util.Arrays; import java.util.EnumSet; @@ -53,13 +53,14 @@ import com.sun.source.doctree.DocTree; import com.sun.source.doctree.InheritDocTree; import com.sun.source.doctree.ThrowsTree; -import com.sun.source.util.DocTreePath; -import jdk.javadoc.doclet.Taglet.Location; +import jdk.javadoc.doclet.Taglet; +import jdk.javadoc.internal.doclets.formats.html.Contents; +import jdk.javadoc.internal.doclets.formats.html.HtmlConfiguration; +import jdk.javadoc.internal.doclets.formats.html.HtmlLinkInfo; import jdk.javadoc.internal.doclets.formats.html.markup.ContentBuilder; -import jdk.javadoc.internal.doclets.toolkit.BaseConfiguration; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; import jdk.javadoc.internal.doclets.toolkit.Content; import jdk.javadoc.internal.doclets.toolkit.util.DocFinder; -import jdk.javadoc.internal.doclets.toolkit.util.DocFinder.Result; import jdk.javadoc.internal.doclets.toolkit.util.Utils; import jdk.javadoc.internal.doclets.toolkit.util.VisibleMemberTable; @@ -156,19 +157,19 @@ public class ThrowsTaglet extends BaseTaglet implements InheritableTaglet { * mA as a member of the supertype of C that names A. */ - public ThrowsTaglet(BaseConfiguration configuration) { + private final HtmlConfiguration config; + private final Contents contents; + + ThrowsTaglet(HtmlConfiguration config) { // of all language elements only constructors and methods can declare // thrown exceptions and, hence, document them - super(DocTree.Kind.THROWS, false, EnumSet.of(Location.CONSTRUCTOR, Location.METHOD)); - this.configuration = configuration; - this.utils = this.configuration.utils; + super(config, DocTree.Kind.THROWS, false, EnumSet.of(Taglet.Location.CONSTRUCTOR, Taglet.Location.METHOD)); + this.config = config; + contents = config.contents; } - private final BaseConfiguration configuration; - private final Utils utils; - @Override - public Output inherit(Element dst, Element src, DocTree tag, boolean isFirstSentence, BaseConfiguration configuration) { + public Output inherit(Element dst, Element src, DocTree tag, boolean isFirstSentence) { // This method shouldn't be called because {@inheritDoc} tags inside // exception tags aren't dealt with individually. {@inheritDoc} tags // inside exception tags are collectively dealt with in @@ -177,13 +178,13 @@ public class ThrowsTaglet extends BaseTaglet implements InheritableTaglet { } @Override - public Content getAllBlockTagOutput(Element holder, TagletWriter writer) { + public Content getAllBlockTagOutput(Element holder, TagletWriter tagletWriter) { + this.tagletWriter = tagletWriter; try { - return getAllBlockTagOutput0(holder, writer); + return getAllBlockTagOutput0(holder); } catch (Failure f) { // note that `f.holder()` is not necessarily the same as `holder` var ch = utils.getCommentHelper(f.holder()); - var messages = configuration.getMessages(); if (f instanceof Failure.ExceptionTypeNotFound e) { var path = ch.getDocTreePath(e.tag().getExceptionName()); messages.warning(path, "doclet.throws.reference_not_found"); @@ -210,14 +211,13 @@ public class ThrowsTaglet extends BaseTaglet implements InheritableTaglet { // since {@inheritDoc} in @throws is processed by ThrowsTaglet (this taglet) rather than // InheritDocTaglet, we have to duplicate some of the behavior of the latter taglet String signature = utils.getSimpleName(holder) - + utils.flatSignature((ExecutableElement) holder, writer.getCurrentPageElement()); - configuration.getMessages().warning(holder, "doclet.noInheritedDoc", signature); + + utils.flatSignature((ExecutableElement) holder, tagletWriter.getCurrentPageElement()); + messages.warning(holder, "doclet.noInheritedDoc", signature); } - return writer.getOutputInstance(); // TODO: consider invalid rather than empty output + return tagletWriter.getOutputInstance(); // TODO: consider invalid rather than empty output } - private Content getAllBlockTagOutput0(Element holder, - TagletWriter writer) + private Content getAllBlockTagOutput0(Element holder) throws Failure.ExceptionTypeNotFound, Failure.NotExceptionType, Failure.Invalid, @@ -235,20 +235,20 @@ public class ThrowsTaglet extends BaseTaglet implements InheritableTaglet { } var executable = (ExecutableElement) holder; ExecutableType instantiatedType = utils.asInstantiatedMethodType( - writer.getCurrentPageElement(), executable); + tagletWriter.getCurrentPageElement(), executable); List substitutedExceptionTypes = instantiatedType.getThrownTypes(); List originalExceptionTypes = executable.getThrownTypes(); Map typeSubstitutions = getSubstitutedThrownTypes( utils.typeUtils, originalExceptionTypes, substitutedExceptionTypes); - var exceptionSection = new ExceptionSectionBuilder(writer); + var exceptionSection = new ExceptionSectionBuilder(tagletWriter, this); // Step 1: Document exception tags Set alreadyDocumentedExceptions = new HashSet<>(); List exceptionTags = utils.getThrowsTrees(executable); for (ThrowsTree t : exceptionTags) { Element exceptionElement = getExceptionType(t, executable); - outputAnExceptionTagDeeply(exceptionSection, exceptionElement, t, executable, alreadyDocumentedExceptions, typeSubstitutions, writer); + outputAnExceptionTagDeeply(exceptionSection, exceptionElement, t, executable, alreadyDocumentedExceptions, typeSubstitutions); } // Step 2: Document exception types from the `throws` clause (of a method) // @@ -277,7 +277,7 @@ public class ThrowsTaglet extends BaseTaglet implements InheritableTaglet { continue; } for (Map.Entry e : r.entrySet()) { - outputAnExceptionTagDeeply(exceptionSection, exceptionElement, e.getKey(), e.getValue(), alreadyDocumentedExceptions, typeSubstitutions, writer); + outputAnExceptionTagDeeply(exceptionSection, exceptionElement, e.getKey(), e.getValue(), alreadyDocumentedExceptions, typeSubstitutions); } } } @@ -299,13 +299,41 @@ public class ThrowsTaglet extends BaseTaglet implements InheritableTaglet { return exceptionSection.build(); } + /** + * Returns the header for the {@code @throws} tag. + * + * @return the header for the throws tag + */ + private Content getThrowsHeader() { + return HtmlTree.DT(contents.throws_); + } + + /** + * Returns the output for a default {@code @throws} tag. + * + * @param throwsType the type that is thrown + * @param content the optional content to add as a description + * + * @return the output + */ + private Content throwsTagOutput(TypeMirror throwsType, Optional content) { + var htmlWriter = tagletWriter.htmlWriter; + var linkInfo = new HtmlLinkInfo(config, HtmlLinkInfo.Kind.PLAIN, throwsType); + var link = htmlWriter.getLink(linkInfo); + var concat = new ContentBuilder(HtmlTree.CODE(link)); + if (content.isPresent()) { + concat.add(" - "); + concat.add(content.get()); + } + return HtmlTree.DD(concat); + } + private void outputAnExceptionTagDeeply(ExceptionSectionBuilder exceptionSection, Element originalExceptionElement, ThrowsTree tag, ExecutableElement holder, Set alreadyDocumentedExceptions, - Map typeSubstitutions, - TagletWriter writer) + Map typeSubstitutions) throws Failure.ExceptionTypeNotFound, Failure.NotExceptionType, Failure.Invalid, @@ -314,7 +342,7 @@ public class ThrowsTaglet extends BaseTaglet implements InheritableTaglet { Failure.NoOverrideFound, DocFinder.NoOverriddenMethodFound { - outputAnExceptionTagDeeply(exceptionSection, originalExceptionElement, tag, holder, true, alreadyDocumentedExceptions, typeSubstitutions, writer); + outputAnExceptionTagDeeply(exceptionSection, originalExceptionElement, tag, holder, true, alreadyDocumentedExceptions, typeSubstitutions); } private void outputAnExceptionTagDeeply(ExceptionSectionBuilder exceptionSection, @@ -323,8 +351,7 @@ public class ThrowsTaglet extends BaseTaglet implements InheritableTaglet { ExecutableElement holder, boolean beginNewEntry, Set alreadyDocumentedExceptions, - Map typeSubstitutions, - TagletWriter writer) + Map typeSubstitutions) throws Failure.ExceptionTypeNotFound, Failure.NotExceptionType, Failure.Invalid, @@ -355,7 +382,7 @@ public class ThrowsTaglet extends BaseTaglet implements InheritableTaglet { exceptionSection.beginEntry(exceptionType); } // append to the current entry - exceptionSection.continueEntry(writer.commentTagsToOutput(holder, description)); + exceptionSection.continueEntry(tagletWriter.commentTagsToOutput(holder, description)); if (beginNewEntry) { // if added a new entry, end it exceptionSection.endEntry(); } @@ -373,7 +400,7 @@ public class ThrowsTaglet extends BaseTaglet implements InheritableTaglet { if (i > 0) { // if there's anything preceding {@inheritDoc}, assume an entry has been added before assert exceptionSection.debugEntryBegun(); - Content beforeInheritDoc = writer.commentTagsToOutput(holder, description.subList(0, i)); + Content beforeInheritDoc = tagletWriter.commentTagsToOutput(holder, description.subList(0, i)); exceptionSection.continueEntry(beforeInheritDoc); } @@ -386,7 +413,7 @@ public class ThrowsTaglet extends BaseTaglet implements InheritableTaglet { if (supertype == null) { throw new Failure.NoOverrideFound(tag, holder, inheritDoc); } - VisibleMemberTable visibleMemberTable = configuration.getVisibleMemberTable(supertype); + VisibleMemberTable visibleMemberTable = config.getVisibleMemberTable(supertype); List methods = visibleMemberTable.getAllVisibleMembers(VisibleMemberTable.Kind.METHODS); for (Element e : methods) { ExecutableElement m = (ExecutableElement) e; @@ -422,11 +449,11 @@ public class ThrowsTaglet extends BaseTaglet implements InheritableTaglet { throw new Failure.Invalid(tag, holder); } for (Map.Entry e : tags.entrySet()) { - outputAnExceptionTagDeeply(exceptionSection, originalExceptionElement, e.getKey(), e.getValue(), addNewEntryRecursively, alreadyDocumentedExceptions, typeSubstitutions, writer); + outputAnExceptionTagDeeply(exceptionSection, originalExceptionElement, e.getKey(), e.getValue(), addNewEntryRecursively, alreadyDocumentedExceptions, typeSubstitutions); } // this might be an empty list, which is fine if (!loneInheritDoc) { - Content afterInheritDoc = writer.commentTagsToOutput(holder, description.subList(i + 1, description.size())); + Content afterInheritDoc = tagletWriter.commentTagsToOutput(holder, description.subList(i + 1, description.size())); exceptionSection.continueEntry(afterInheritDoc); } if (add) { @@ -604,7 +631,7 @@ public class ThrowsTaglet extends BaseTaglet implements InheritableTaglet { return toResult(exceptionType, method, tags); }; } - Result> result; + DocFinder.Result> result; try { if (src.isPresent()) { result = utils.docFinder().search(src.get(), criterion); @@ -623,20 +650,20 @@ public class ThrowsTaglet extends BaseTaglet implements InheritableTaglet { } catch (Failure f) { throw newAssertionError(f); } - if (result instanceof Result.Conclude> c) { + if (result instanceof DocFinder.Result.Conclude> c) { return c.value(); } return Map.of(); // an empty map is effectively ordered } - private static Result> toResult(Element target, - ExecutableElement holder, - List tags) { + private static DocFinder.Result> toResult(Element target, + ExecutableElement holder, + List tags) { if (!tags.isEmpty()) { // if there are tags for the target exception type, conclude search successfully - return Result.CONCLUDE(toExceptionTags(holder, tags)); + return DocFinder.Result.CONCLUDE(toExceptionTags(holder, tags)); } - return Result.CONTINUE(); + return DocFinder.Result.CONTINUE(); // TODO: reintroduce this back for JDK-8295800: // if (holder.getThrownTypes().contains(target.asType())) { // // if there are no tags for the target exception type, BUT that type is @@ -694,14 +721,16 @@ public class ThrowsTaglet extends BaseTaglet implements InheritableTaglet { private static class ExceptionSectionBuilder { private final TagletWriter writer; + private final ThrowsTaglet taglet; private final Content result; - private ContentBuilder current; + private Content current; private boolean began; private boolean headerAdded; private TypeMirror exceptionType; - ExceptionSectionBuilder(TagletWriter writer) { + ExceptionSectionBuilder(TagletWriter writer, ThrowsTaglet taglet) { this.writer = writer; + this.taglet = taglet; this.result = writer.getOutputInstance(); } @@ -710,7 +739,7 @@ public class ThrowsTaglet extends BaseTaglet implements InheritableTaglet { throw new IllegalStateException(); } began = true; - current = new ContentBuilder(); + current = writer.getOutputInstance(); this.exceptionType = exceptionType; } @@ -728,9 +757,10 @@ public class ThrowsTaglet extends BaseTaglet implements InheritableTaglet { began = false; if (!headerAdded) { headerAdded = true; - result.add(writer.getThrowsHeader()); + result.add(taglet.getThrowsHeader()); } - result.add(writer.throwsTagOutput(exceptionType, current.isEmpty() ? Optional.empty() : Optional.of(current))); + result.add(taglet.throwsTagOutput(exceptionType, + current.isEmpty() ? Optional.empty() : Optional.of(current))); current = null; } diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/UserTaglet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/UserTaglet.java similarity index 66% rename from src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/UserTaglet.java rename to src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/UserTaglet.java index 768f4bb20f4..149d06f90a8 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/UserTaglet.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/UserTaglet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 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 @@ * questions. */ -package jdk.javadoc.internal.doclets.toolkit.taglets; +package jdk.javadoc.internal.doclets.formats.html.taglets; import java.util.List; import java.util.Set; @@ -31,11 +31,9 @@ import java.util.Set; import javax.lang.model.element.Element; import com.sun.source.doctree.DocTree; + import jdk.javadoc.internal.doclets.formats.html.markup.RawHtml; import jdk.javadoc.internal.doclets.toolkit.Content; -import jdk.javadoc.internal.doclets.toolkit.util.Utils; - -import static jdk.javadoc.doclet.Taglet.Location.*; /** * A taglet wrapper, allows the public taglet {@link jdk.javadoc.doclet.Taglet} @@ -45,7 +43,7 @@ public final class UserTaglet implements Taglet { private final jdk.javadoc.doclet.Taglet userTaglet; - public UserTaglet(jdk.javadoc.doclet.Taglet t) { + UserTaglet(jdk.javadoc.doclet.Taglet t) { userTaglet = t; } @@ -54,41 +52,6 @@ public final class UserTaglet implements Taglet { return userTaglet.getAllowedLocations(); } - @Override - public boolean inField() { - return userTaglet.getAllowedLocations().contains(FIELD); - } - - @Override - public boolean inConstructor() { - return userTaglet.getAllowedLocations().contains(CONSTRUCTOR); - } - - @Override - public boolean inMethod() { - return userTaglet.getAllowedLocations().contains(METHOD); - } - - @Override - public boolean inOverview() { - return userTaglet.getAllowedLocations().contains(OVERVIEW); - } - - @Override - public boolean inModule() { - return userTaglet.getAllowedLocations().contains(MODULE); - } - - @Override - public boolean inPackage() { - return userTaglet.getAllowedLocations().contains(PACKAGE); - } - - @Override - public boolean inType() { - return userTaglet.getAllowedLocations().contains(TYPE); - } - @Override public boolean isInlineTag() { return userTaglet.isInlineTag(); @@ -105,17 +68,17 @@ public final class UserTaglet implements Taglet { } @Override - public Content getInlineTagOutput(Element element, DocTree tag, TagletWriter writer) { - Content output = writer.getOutputInstance(); + public Content getInlineTagOutput(Element element, DocTree tag, TagletWriter tagletWriter) { + Content output = tagletWriter.getOutputInstance(); output.add(RawHtml.of(userTaglet.toString(List.of(tag), element))); return output; } @Override - public Content getAllBlockTagOutput(Element holder, TagletWriter writer) { - Content output = writer.getOutputInstance(); - Utils utils = writer.configuration().utils; - List tags = utils.getBlockTags(holder, this); + public Content getAllBlockTagOutput(Element holder, TagletWriter tagletWriter) { + Content output = tagletWriter.getOutputInstance(); + var utils = tagletWriter.utils; + List tags = utils.getBlockTags(holder, getName()); if (!tags.isEmpty()) { String tagString = userTaglet.toString(tags, holder); if (tagString != null) { diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/ValueTaglet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/ValueTaglet.java similarity index 73% rename from src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/ValueTaglet.java rename to src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/ValueTaglet.java index 6ee2228c301..166a1bcc7f0 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/ValueTaglet.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/ValueTaglet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 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,23 +23,26 @@ * questions. */ -package jdk.javadoc.internal.doclets.toolkit.taglets; +package jdk.javadoc.internal.doclets.formats.html.taglets; import java.util.EnumSet; import java.util.IllegalFormatException; import java.util.Optional; + import javax.lang.model.element.Element; import javax.lang.model.element.VariableElement; import com.sun.source.doctree.DocTree; import com.sun.source.doctree.TextTree; import com.sun.source.doctree.ValueTree; -import jdk.javadoc.doclet.Taglet.Location; + +import jdk.javadoc.doclet.Taglet; +import jdk.javadoc.internal.doclets.formats.html.HtmlConfiguration; +import jdk.javadoc.internal.doclets.formats.html.HtmlLinkInfo; +import jdk.javadoc.internal.doclets.formats.html.markup.Text; import jdk.javadoc.internal.doclets.toolkit.BaseConfiguration; import jdk.javadoc.internal.doclets.toolkit.Content; -import jdk.javadoc.internal.doclets.toolkit.Messages; import jdk.javadoc.internal.doclets.toolkit.util.CommentHelper; -import jdk.javadoc.internal.doclets.toolkit.util.Utils; /** * An inline taglet representing the value tag. This tag should only be used with @@ -52,11 +55,8 @@ import jdk.javadoc.internal.doclets.toolkit.util.Utils; */ public class ValueTaglet extends BaseTaglet { - /** - * Construct a new ValueTaglet. - */ - public ValueTaglet() { - super(DocTree.Kind.VALUE, true, EnumSet.allOf(Location.class)); + ValueTaglet(HtmlConfiguration config) { + super(config, DocTree.Kind.VALUE, true, EnumSet.allOf(Taglet.Location.class)); } /** @@ -83,11 +83,9 @@ public class ValueTaglet extends BaseTaglet { } @Override - public Content getInlineTagOutput(Element holder, DocTree tag, TagletWriter writer) { - BaseConfiguration configuration = writer.configuration(); - Utils utils = configuration.utils; - Messages messages = configuration.getMessages(); - VariableElement field = getVariableElement(holder, configuration, tag); + public Content getInlineTagOutput(Element holder, DocTree tag, TagletWriter tagletWriter) { + this.tagletWriter = tagletWriter; + VariableElement field = getVariableElement(holder, config, tag); if (field == null) { if (tag.toString().isEmpty()) { //Invalid use of @value @@ -107,25 +105,40 @@ public class ValueTaglet extends BaseTaglet { f = f.substring(1, f.length() - 1); } try { - text = String.format(configuration.getLocale(), f, field.getConstantValue()); + text = String.format(config.getLocale(), f, field.getConstantValue()); } catch (IllegalFormatException e) { messages.error(holder, "doclet.value_tag_invalid_format", format); - return writer.invalidTagOutput( + return tagletWriter.invalidTagOutput( messages.getResources().getText("doclet.value_tag_invalid_format", format), Optional.empty()); } } else { text = utils.constantValueExpression(field); } - return writer.valueTagOutput(field, - text, - !field.equals(holder)); + return valueTagOutput(field, text, !field.equals(holder)); } else { //Referenced field is not a constant. messages.warning(holder, - "doclet.value_tag_invalid_constant", utils.getSimpleName(field)); + "doclet.value_tag_invalid_constant", utils.getSimpleName(field)); } - return writer.getOutputInstance(); + return tagletWriter.getOutputInstance(); + } + + /** + * Returns the output for a {@code {@value}} tag. + * + * @param field the constant field that holds the value tag + * @param constantVal the constant value to document + * @param includeLink true if we should link the constant text to the + * constant field itself + * + * @return the output + */ + private Content valueTagOutput(VariableElement field, String constantVal, boolean includeLink) { + var htmlWriter = tagletWriter.htmlWriter; + return includeLink + ? htmlWriter.getDocLink(HtmlLinkInfo.Kind.LINK_TYPE_PARAMS_AND_BOUNDS, field, constantVal) + : Text.of(constantVal); } } diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/IndexTaglet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/package-info.java similarity index 54% rename from src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/IndexTaglet.java rename to src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/package-info.java index 41afe06cf1b..0b936261928 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/IndexTaglet.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 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,29 +23,21 @@ * questions. */ -package jdk.javadoc.internal.doclets.toolkit.taglets; - -import java.util.EnumSet; -import javax.lang.model.element.Element; - -import com.sun.source.doctree.DocTree; -import com.sun.source.doctree.IndexTree; -import jdk.javadoc.doclet.Taglet.Location; -import jdk.javadoc.internal.doclets.toolkit.Content; - /** - * An inline taglet used to index a word or a phrase. - * The enclosed text is interpreted as not containing HTML markup or - * nested javadoc tags. + * Classes used to build the output for documentation comment tags. + * + * Tags are either inline tags, meaning they can be used within a + * sentence or phrase, or are block tags, meaning that they provide + * additional details that follow the main description in a comment. + * Taglets model that distinction. + * + * Inline tags are always processed individually, within the surrounding + * context. In general, inline tags always generate some (non-empty) output, + * even if the output is some form indicating an error. It is almost never + * correct to not generate any output to place between the parts of the + * comment that come before and after the tag in the underlying comment. + * + * Conversely, block tags of any given kind are always processed as a + * group, even if they do not appear contiguously in the underlying comment. */ -public class IndexTaglet extends BaseTaglet { - - IndexTaglet() { - super(DocTree.Kind.INDEX, true, EnumSet.allOf(Location.class)); - } - - @Override - public Content getInlineTagOutput(Element element, DocTree tag, TagletWriter writer) { - return writer.indexTagOutput(element, (IndexTree) tag); - } -} +package jdk.javadoc.internal.doclets.formats.html.taglets; diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/snippet/Action.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/snippet/Action.java similarity index 95% rename from src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/snippet/Action.java rename to src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/snippet/Action.java index 3a13ff36e8c..7f8422fbe64 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/snippet/Action.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/snippet/Action.java @@ -23,7 +23,7 @@ * questions. */ -package jdk.javadoc.internal.doclets.toolkit.taglets.snippet; +package jdk.javadoc.internal.doclets.formats.html.taglets.snippet; /** * An action described by markup. Such an action is typically an opaque compound diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/snippet/AddStyle.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/snippet/AddStyle.java similarity index 97% rename from src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/snippet/AddStyle.java rename to src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/snippet/AddStyle.java index c9a20cddec8..4cdf71797ae 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/snippet/AddStyle.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/snippet/AddStyle.java @@ -23,7 +23,7 @@ * questions. */ -package jdk.javadoc.internal.doclets.toolkit.taglets.snippet; +package jdk.javadoc.internal.doclets.formats.html.taglets.snippet; import java.util.Set; import java.util.regex.Matcher; diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/snippet/Attribute.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/snippet/Attribute.java similarity index 97% rename from src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/snippet/Attribute.java rename to src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/snippet/Attribute.java index 7a4e9e5dcbb..724b1835f69 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/snippet/Attribute.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/snippet/Attribute.java @@ -23,7 +23,7 @@ * questions. */ -package jdk.javadoc.internal.doclets.toolkit.taglets.snippet; +package jdk.javadoc.internal.doclets.formats.html.taglets.snippet; import java.util.Objects; diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/snippet/Attributes.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/snippet/Attributes.java similarity index 97% rename from src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/snippet/Attributes.java rename to src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/snippet/Attributes.java index c52588bd1ba..52279ff13a8 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/snippet/Attributes.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/snippet/Attributes.java @@ -23,7 +23,7 @@ * questions. */ -package jdk.javadoc.internal.doclets.toolkit.taglets.snippet; +package jdk.javadoc.internal.doclets.formats.html.taglets.snippet; import java.util.Collection; import java.util.List; diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/snippet/Bookmark.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/snippet/Bookmark.java similarity index 96% rename from src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/snippet/Bookmark.java rename to src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/snippet/Bookmark.java index cf68277d5b6..375a4cc95d3 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/snippet/Bookmark.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/snippet/Bookmark.java @@ -23,7 +23,7 @@ * questions. */ -package jdk.javadoc.internal.doclets.toolkit.taglets.snippet; +package jdk.javadoc.internal.doclets.formats.html.taglets.snippet; /** * An action that associates text with a name. diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/snippet/MarkupParser.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/snippet/MarkupParser.java similarity index 99% rename from src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/snippet/MarkupParser.java rename to src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/snippet/MarkupParser.java index 6e539064794..2f43e8cb005 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/snippet/MarkupParser.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/snippet/MarkupParser.java @@ -23,7 +23,7 @@ * questions. */ -package jdk.javadoc.internal.doclets.toolkit.taglets.snippet; +package jdk.javadoc.internal.doclets.formats.html.taglets.snippet; import java.util.ArrayList; import java.util.List; diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/snippet/ParseException.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/snippet/ParseException.java similarity index 96% rename from src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/snippet/ParseException.java rename to src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/snippet/ParseException.java index 53e27fbd6f2..b316e98f1e2 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/snippet/ParseException.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/snippet/ParseException.java @@ -23,7 +23,7 @@ * questions. */ -package jdk.javadoc.internal.doclets.toolkit.taglets.snippet; +package jdk.javadoc.internal.doclets.formats.html.taglets.snippet; import java.util.function.Supplier; diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/snippet/Parser.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/snippet/Parser.java similarity index 99% rename from src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/snippet/Parser.java rename to src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/snippet/Parser.java index b42f0bf7513..7c60da78dbe 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/snippet/Parser.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/snippet/Parser.java @@ -23,7 +23,7 @@ * questions. */ -package jdk.javadoc.internal.doclets.toolkit.taglets.snippet; +package jdk.javadoc.internal.doclets.formats.html.taglets.snippet; import java.util.ArrayList; import java.util.Iterator; @@ -38,8 +38,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; +import jdk.javadoc.internal.doclets.formats.html.taglets.SnippetTaglet; import jdk.javadoc.internal.doclets.toolkit.Resources; -import jdk.javadoc.internal.doclets.toolkit.taglets.SnippetTaglet; /* * Semantics of a EOL comment; plus diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/snippet/Replace.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/snippet/Replace.java similarity index 97% rename from src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/snippet/Replace.java rename to src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/snippet/Replace.java index 8963cb11d62..a9927fe5943 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/snippet/Replace.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/snippet/Replace.java @@ -23,7 +23,7 @@ * questions. */ -package jdk.javadoc.internal.doclets.toolkit.taglets.snippet; +package jdk.javadoc.internal.doclets.formats.html.taglets.snippet; import java.util.ArrayList; import java.util.Set; diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/snippet/Style.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/snippet/Style.java similarity index 96% rename from src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/snippet/Style.java rename to src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/snippet/Style.java index 92788a32346..279a803d6c3 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/snippet/Style.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/snippet/Style.java @@ -23,7 +23,7 @@ * questions. */ -package jdk.javadoc.internal.doclets.toolkit.taglets.snippet; +package jdk.javadoc.internal.doclets.formats.html.taglets.snippet; /** * A style of a snippet text character. diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/snippet/StyledText.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/snippet/StyledText.java similarity index 99% rename from src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/snippet/StyledText.java rename to src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/snippet/StyledText.java index c24addac830..f575abe7f0b 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/snippet/StyledText.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/snippet/StyledText.java @@ -23,7 +23,7 @@ * questions. */ -package jdk.javadoc.internal.doclets.toolkit.taglets.snippet; +package jdk.javadoc.internal.doclets.formats.html.taglets.snippet; import java.lang.ref.WeakReference; import java.util.ArrayList; diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/AbstractDoclet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/AbstractDoclet.java index 7ecbce89131..b2da7d1fe7e 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/AbstractDoclet.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/AbstractDoclet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 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 @@ -25,7 +25,6 @@ package jdk.javadoc.internal.doclets.toolkit; -import java.util.Map; import java.util.SortedSet; import java.util.TreeSet; import java.util.function.Function; @@ -36,8 +35,6 @@ import javax.lang.model.element.TypeElement; import jdk.javadoc.doclet.Doclet; import jdk.javadoc.doclet.DocletEnvironment; -import jdk.javadoc.doclet.StandardDoclet; -import jdk.javadoc.internal.doclets.formats.html.HtmlDoclet; import jdk.javadoc.internal.doclets.toolkit.builders.AbstractBuilder; import jdk.javadoc.internal.doclets.toolkit.builders.BuilderFactory; import jdk.javadoc.internal.doclets.toolkit.util.ClassTree; @@ -68,25 +65,6 @@ public abstract class AbstractDoclet implements Doclet { */ protected Utils utils; - /** - * The only doclet that may use this toolkit is {@value} - */ - private static final String TOOLKIT_DOCLET_NAME = - jdk.javadoc.internal.doclets.formats.html.HtmlDoclet.class.getName(); - - /** - * Verify that the only doclet that is using this toolkit is - * #TOOLKIT_DOCLET_NAME. - */ - private boolean isValidDoclet() { - if (!getClass().getName().equals(TOOLKIT_DOCLET_NAME)) { - messages.error("doclet.Toolkit_Usage_Violation", - TOOLKIT_DOCLET_NAME); - return false; - } - return true; - } - /** * The method that starts the execution of the doclet. * @@ -101,13 +79,9 @@ public abstract class AbstractDoclet implements Doclet { messages = configuration.getMessages(); BaseOptions options = configuration.getOptions(); - if (!isValidDoclet()) { - return false; - } - try { try { - startGeneration(); + generateFiles(); return true; } catch (UncheckedDocletException e) { throw (DocletException) e.getCause(); @@ -151,7 +125,7 @@ public abstract class AbstractDoclet implements Doclet { } private void reportInternalError(Throwable t) { - if (getClass().equals(StandardDoclet.class) || getClass().equals(HtmlDoclet.class)) { + if (getClass().getModule() == AbstractDoclet.class.getModule()) { System.err.println(configuration.getDocResources().getText("doclet.internal.report.bug")); } dumpStack(true, t); @@ -188,7 +162,7 @@ public abstract class AbstractDoclet implements Doclet { * * @throws DocletException if there is a problem while generating the documentation */ - private void startGeneration() throws DocletException { + protected void generateFiles() throws DocletException { // Modules with no documented classes may be specified on the // command line to specify a service provider, allow these. @@ -211,7 +185,6 @@ public abstract class AbstractDoclet implements Doclet { generateModuleFiles(); generateOtherFiles(classTree); - configuration.tagletManager.printReport(); } /** diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/BaseConfiguration.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/BaseConfiguration.java index 463f669e620..01bab84819f 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/BaseConfiguration.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/BaseConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 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 @@ -26,12 +26,7 @@ package jdk.javadoc.internal.doclets.toolkit; -import java.io.File; -import java.io.IOException; -import java.nio.file.InvalidPathException; -import java.nio.file.Path; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -53,10 +48,8 @@ import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; import javax.lang.model.util.Elements; import javax.lang.model.util.SimpleElementVisitor14; -import javax.tools.DocumentationTool; import javax.tools.JavaFileManager; import javax.tools.JavaFileObject; -import javax.tools.StandardJavaFileManager; import com.sun.source.tree.CompilationUnitTree; import com.sun.source.util.TreePath; @@ -68,7 +61,6 @@ import jdk.javadoc.doclet.Reporter; import jdk.javadoc.doclet.StandardDoclet; import jdk.javadoc.doclet.Taglet; import jdk.javadoc.internal.doclets.toolkit.builders.BuilderFactory; -import jdk.javadoc.internal.doclets.toolkit.taglets.TagletManager; import jdk.javadoc.internal.doclets.toolkit.util.Comparators; import jdk.javadoc.internal.doclets.toolkit.util.DocFile; import jdk.javadoc.internal.doclets.toolkit.util.DocFileFactory; @@ -100,11 +92,6 @@ public abstract class BaseConfiguration { */ protected BuilderFactory builderFactory; - /** - * The taglet manager. - */ - public TagletManager tagletManager; - /** * The meta tag keywords instance. */ @@ -374,28 +361,6 @@ public abstract class BaseConfiguration { } typeElementCatalog = new TypeElementCatalog(includedTypeElements, this); - String snippetPath = options.snippetPath(); - if (snippetPath != null) { - Messages messages = getMessages(); - JavaFileManager fm = getFileManager(); - if (fm instanceof StandardJavaFileManager) { - try { - List sp = Arrays.stream(snippetPath.split(File.pathSeparator)) - .map(Path::of) - .toList(); - StandardJavaFileManager sfm = (StandardJavaFileManager) fm; - sfm.setLocationFromPaths(DocumentationTool.Location.SNIPPET_PATH, sp); - } catch (IOException | InvalidPathException e) { - throw new SimpleDocletException(messages.getResources().getText( - "doclet.error_setting_snippet_path", snippetPath, e), e); - } - } else { - throw new SimpleDocletException(messages.getResources().getText( - "doclet.cannot_use_snippet_path", snippetPath)); - } - } - - initTagletManager(options.customTagStrs()); options.groupPairs().forEach(grp -> { if (showModules) { group.checkModuleGroups(grp.first, grp.second); @@ -451,100 +416,6 @@ public abstract class BaseConfiguration { DocFileFactory.getFactory(this).setDestDir(destDirName); } - /** - * Initialize the taglet manager. The strings to initialize the simple custom tags should - * be in the following format: "[tag name]:[location str]:[heading]". - * - * @param customTagStrs the set two dimensional arrays of strings. These arrays contain - * either -tag or -taglet arguments. - */ - private void initTagletManager(Set> customTagStrs) { - tagletManager = tagletManager != null ? tagletManager : new TagletManager(this); - JavaFileManager fileManager = getFileManager(); - Messages messages = getMessages(); - try { - tagletManager.initTagletPath(fileManager); - tagletManager.loadTaglets(fileManager); - - for (List args : customTagStrs) { - if (args.get(0).equals("-taglet")) { - tagletManager.addCustomTag(args.get(1), fileManager); - continue; - } - /* Since there are few constraints on the characters in a tag name, - * and real world examples with ':' in the tag name, we cannot simply use - * String.split(regex); instead, we tokenize the string, allowing - * special characters to be escaped with '\'. */ - List tokens = tokenize(args.get(1), 3); - switch (tokens.size()) { - case 1 -> { - String tagName = args.get(1); - if (tagletManager.isKnownCustomTag(tagName)) { - //reorder a standard tag - tagletManager.addNewSimpleCustomTag(tagName, null, ""); - } else { - //Create a simple tag with the heading that has the same name as the tag. - StringBuilder heading = new StringBuilder(tagName + ":"); - heading.setCharAt(0, Character.toUpperCase(tagName.charAt(0))); - tagletManager.addNewSimpleCustomTag(tagName, heading.toString(), "a"); - } - } - - case 2 -> - //Add simple taglet without heading, probably to excluding it in the output. - tagletManager.addNewSimpleCustomTag(tokens.get(0), tokens.get(1), ""); - - case 3 -> - tagletManager.addNewSimpleCustomTag(tokens.get(0), tokens.get(2), tokens.get(1)); - - default -> - messages.error("doclet.Error_invalid_custom_tag_argument", args.get(1)); - } - } - } catch (IOException e) { - messages.error("doclet.taglet_could_not_set_location", e.toString()); - } - } - - /** - * Given a string, return an array of tokens, separated by ':'. - * The separator character can be escaped with the '\' character. - * The '\' character may also be escaped with the '\' character. - * - * @param s the string to tokenize - * @param maxTokens the maximum number of tokens returned. If the - * max is reached, the remaining part of s is appended - * to the end of the last token. - * @return an array of tokens - */ - private List tokenize(String s, int maxTokens) { - List tokens = new ArrayList<>(); - StringBuilder token = new StringBuilder(); - boolean prevIsEscapeChar = false; - for (int i = 0; i < s.length(); i += Character.charCount(i)) { - int currentChar = s.codePointAt(i); - if (prevIsEscapeChar) { - // Case 1: escaped character - token.appendCodePoint(currentChar); - prevIsEscapeChar = false; - } else if (currentChar == ':' && tokens.size() < maxTokens - 1) { - // Case 2: separator - tokens.add(token.toString()); - token = new StringBuilder(); - } else if (currentChar == '\\') { - // Case 3: escape character - prevIsEscapeChar = true; - } else { - // Case 4: regular character - token.appendCodePoint(currentChar); - } - } - if (token.length() > 0) { - tokens.add(token.toString()); - } - return tokens; - } - /** * Return true if the given doc-file subdirectory should be excluded and * false otherwise. diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/BaseOptions.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/BaseOptions.java index 891cc476a9d..f71a491902a 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/BaseOptions.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/BaseOptions.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 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 @@ -32,29 +32,20 @@ import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; -import java.nio.file.Path; -import java.time.Instant; -import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; -import java.time.temporal.ChronoUnit; -import java.time.temporal.TemporalUnit; import java.util.ArrayList; import java.util.Arrays; -import java.util.Calendar; import java.util.HashSet; -import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.MissingResourceException; import java.util.Set; -import java.util.StringTokenizer; import java.util.TreeSet; import jdk.javadoc.doclet.Doclet; import jdk.javadoc.doclet.Reporter; -import jdk.javadoc.internal.doclets.toolkit.util.DocletConstants; import jdk.javadoc.internal.doclets.toolkit.util.Utils; import static javax.tools.Diagnostic.Kind.ERROR; @@ -87,11 +78,6 @@ public abstract class BaseOptions { */ private boolean copyDocfileSubdirs = false; - /** - * Arguments for command-line option {@code -tag} and {@code -taglet}. - */ - private final LinkedHashSet> customTagStrs = new LinkedHashSet<>(); - /** * Argument for command-line option {@code --date}. * {@code null} if option not given. @@ -265,12 +251,6 @@ public abstract class BaseOptions { */ private boolean showAuthor = false; - /** - * Argument for command-line option {@code --show-taglets}. - * Show taglets (internal debug switch) - */ - private boolean showTaglets = false; - /** * Argument for command-line option {@code -version}. * Generate version specific information for the all the classes @@ -313,18 +293,6 @@ public abstract class BaseOptions { */ private boolean summarizeOverriddenMethods = false; - /** - * Argument for command-line option {@code -tagletpath}. - * The path to Taglets - */ - private String tagletPath = null; - - /** - * Argument for command-line option {@code --snippet-path}. - * The path for external snippets. - */ - private String snippetPath = null; - // private final BaseConfiguration config; @@ -617,44 +585,6 @@ public abstract class BaseOptions { } }, - new Option(resources, "-tag", 1) { - @Override - public boolean process(String opt, List args) { - ArrayList list = new ArrayList<>(); - list.add(opt); - list.add(args.get(0)); - customTagStrs.add(list); - return true; - } - }, - - new Option(resources, "-taglet", 1) { - @Override - public boolean process(String opt, List args) { - ArrayList list = new ArrayList<>(); - list.add(opt); - list.add(args.get(0)); - customTagStrs.add(list); - return true; - } - }, - - new Option(resources, "-tagletpath", 1) { - @Override - public boolean process(String opt, List args) { - tagletPath = args.get(0); - return true; - } - }, - - new Option(resources, "--snippet-path", 1) { - @Override - public boolean process(String opt, List args) { - snippetPath = args.get(0); - return true; - } - }, - new Option(resources, "-version") { @Override public boolean process(String opt, List args) { @@ -705,14 +635,6 @@ public abstract class BaseOptions { disableJavaFxStrictChecks = true; return true; } - }, - - new Hidden(resources, "--show-taglets") { - @Override - public boolean process(String opt, List args) { - showTaglets = true; - return true; - } } ); return new TreeSet<>(options); @@ -801,13 +723,6 @@ public abstract class BaseOptions { return copyDocfileSubdirs; } - /** - * Arguments for command-line option {@code -tag} and {@code -taglet}. - */ - LinkedHashSet> customTagStrs() { - return customTagStrs; - } - /** * Argument for command-line option {@code --date}. */ @@ -1023,20 +938,12 @@ public abstract class BaseOptions { * Generate author specific information for all the classes if @author * tag is used in the doc comment and if -author option is used. * showauthor is set to true if -author option is used. - * Default is don't show author information. + * Default is to not show author information. */ public boolean showAuthor() { return showAuthor; } - /** - * Argument for command-line option {@code --show-taglets}. - * Show taglets (internal debug switch) - */ - public boolean showTaglets() { - return showTaglets; - } - /** * Argument for command-line option {@code -version}. * Generate version specific information for the all the classes @@ -1089,22 +996,6 @@ public abstract class BaseOptions { return summarizeOverriddenMethods; } - /** - * Argument for command-line option {@code -tagletpath}. - * The path to Taglets - */ - public String tagletPath() { - return tagletPath; - } - - /** - * Argument for command-line option {@code --snippet-path}. - * The path for external snippets. - */ - public String snippetPath() { - return snippetPath; - } - protected abstract static class Option implements Doclet.Option, Comparable