8309566: Migrate away from TagletWriter and TagletWriterImpl

8311974: Clean up Utils.getBlockTags

Reviewed-by: prappo
This commit is contained in:
Jonathan Gibbons 2023-07-13 01:03:52 +00:00
parent fb90af881b
commit e51472e9a8
57 changed files with 2432 additions and 2630 deletions

View File

@ -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));
}

View File

@ -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));
}

View File

@ -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<Path> 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<List<String>> customTagStrs) {
tagletManager = tagletManager != null ? tagletManager : new TagletManager(this);
JavaFileManager fileManager = getFileManager();
Messages messages = getMessages();
try {
tagletManager.initTagletPath(fileManager);
tagletManager.loadTaglets(fileManager);
for (List<String> 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<String> 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<String> tokenize(String s, int maxTokens) {
List<String> 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;
}
}

View File

@ -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<String> files = Arrays.asList(
DocPaths.JQUERY_JS.getPath(),

View File

@ -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<String, Integer> indexAnchorTable = new HashMap<>();
public final Map<String, Integer> 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<? extends DocTree> 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<? extends DocTree> 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<Content> detail) {
public Content invalidTagOutput(String summary, Optional<Content> 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<DocPath> getLocalStylesheets(Element element) throws DocFileIOException {
List<DocPath> stylesheets = new ArrayList<>();

View File

@ -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<String, Integer> counts) {
public static HtmlId forText(String text, Map<String, Integer> 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);

View File

@ -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<List<String>> 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<String> args) {
snippetPath = args.get(0);
return true;
}
},
new Option(resources, "-tag", 1) {
@Override
public boolean process(String opt, List<String> args) {
ArrayList<String> 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<String> args) {
ArrayList<String> 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<String> args) {
tagletPath = args.get(0);
return true;
}
},
new Option(resources, "-top", 1) {
@Override
public boolean process(String opt, List<String> 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<String> args) {
showTaglets = true;
return true;
}
}
);
Set<BaseOptions.Option> 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<List<String>> 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}.
*/

View File

@ -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);
}

View File

@ -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;
/**

View File

@ -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<DocTree.Kind> 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<DocTree.Kind> 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<? extends DeprecatedTree> 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<? extends DocTree> 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<? extends DocTree> 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<? extends DocTree> 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<? extends DocTree> 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<? extends SeeTree> seeTags) {
List<Content> 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<? extends DocTree> 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<? extends DocTree> ref = seeTag.getReference();
assert !ref.isEmpty();
DocTree ref0 = ref.get(0);
switch (ref0.getKind()) {
case TEXT, START_ELEMENT -> {
// @see "Reference"
// @see <a href="...">...</a>
return htmlWriter.commentTagsToContent(element, ref, false, false);
}
case REFERENCE -> {
// @see reference label...
CommentHelper ch = utils.getCommentHelper(element);
String refSignature = ch.getReferencedSignature(ref0);
List<? extends DocTree> 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<String, Object[]> 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<? extends DocTree> simpleTags, String header) {
CommentHelper ch = utils.getCommentHelper(element);
ContentBuilder body = new ContentBuilder();
boolean many = false;
for (DocTree simpleTag : simpleTags) {
if (many) {
body.add(", ");
}
List<? extends DocTree> 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<String> 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<? extends SpecTree> specTags) {
if (specTags.isEmpty()) {
return Text.EMPTY;
}
List<Content> 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<? extends DocTree> 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<? extends DocTree> 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> 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<String> 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<? extends DocTree> tags) {
return commentTagsToOutput(null, holder, tags, false);
}
@Override
public Content commentTagsToOutput(Element element, List<? extends DocTree> tags) {
return commentTagsToOutput(element, null, tags, false);
}
@Override
public Content commentTagsToOutput(Element holder,
DocTree holderTag,
List<? extends DocTree> 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<String, Void>() {
@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);
}
}

View File

@ -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<Location> sites;
BaseTaglet(DocTree.Kind tagKind, boolean inline, Set<Location> 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<Location> sites) {
this(config, tagKind.tagName, tagKind, inline, sites);
}
BaseTaglet(String name, boolean inline, Set<Location> 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<Location> 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<Location> sites) {
private BaseTaglet(HtmlConfiguration config, String name, DocTree.Kind tagKind, boolean inline, Set<Location> 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() + ".");
}
}

View File

@ -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<? extends DeprecatedTree> 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<? extends DocTree> 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<? extends DocTree> 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;
}
}

View File

@ -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());
}
}

View File

@ -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("<[^>]+>", "");
}
}

View File

@ -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<Element> 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);
}
}

View File

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

View File

@ -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<String, Object[]> 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);
}
}

View File

@ -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:
* <blockquote> {@code {@literal a<B>c}} </blockquote>
* displays as:
* <blockquote> {@literal a<B>c} </blockquote>
*
* <p> The tag {@code {@code ...}} is equivalent to
* {@code <code>{@literal ...}</code>}.
*
* For example, the text:
* <blockquote> The type {@code {@code List<P>}} </blockquote>
* displays as:
* <blockquote> The type {@code List<P>} </blockquote>
*/
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;
}
}

View File

@ -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<String, Integer> stringIntegerMap = mapNameToPosition(configuration.utils, parameterElements);
CommentHelper ch = configuration.utils.getCommentHelper(dst);
Map<String, Integer> 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<Documentation> 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<? extends Element> parameters,
TagletWriter writer) {
Map<Integer, ParamTree> tagOfPosition = new HashMap<>();
Messages messages = writer.configuration().getMessages();
CommentHelper ch = writer.configuration().utils.getCommentHelper(e);
CommentHelper ch = utils.getCommentHelper(e);
if (!tags.isEmpty()) {
Map<String, Integer> positionOfName = mapNameToPosition(writer.configuration().utils, parameters);
Map<String, Integer> 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<? extends DocTree> 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<Documentation> 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;
}
}

View File

@ -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<Documentation> 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<? extends ReturnTree> 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<? extends DocTree> 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<Documentation> extract(Utils utils, ExecutableElement method) {

View File

@ -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<? extends SeeTree> tags = utils.getSeeTrees(holder);
Element e = holder;
if (utils.isMethod(holder)) {
var docFinder = utils.docFinder();
Optional<Documentation> 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<? extends SeeTree> seeTags) {
htmlWriter = tagletWriter.htmlWriter;
List<Content> 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<? extends SeeTree> seeTrees, ExecutableElement method) { }
private static Optional<Documentation> extract(Utils utils, ExecutableElement method) {
List<? extends SeeTree> 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<? extends DocTree> ref = seeTag.getReference();
assert !ref.isEmpty();
DocTree ref0 = ref.get(0);
switch (ref0.getKind()) {
case TEXT, START_ELEMENT -> {
// @see "Reference"
// @see <a href="...">...</a>
return htmlWriter.commentTagsToContent(element, ref, false, false);
}
case REFERENCE -> {
// @see reference label...
CommentHelper ch = utils.getCommentHelper(element);
String refSignature = ch.getReferencedSignature(ref0);
List<? extends DocTree> 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());
}
}
}

View File

@ -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<Location> locations) {
this(tagKind, header, locations, true);
SimpleTaglet(HtmlConfiguration config, DocTree.Kind tagKind, String header, Set<Taglet.Location> 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<Location> locations) {
this(tagName, header, locations, true);
SimpleTaglet(HtmlConfiguration config, String tagName, String header, Set<Taglet.Location> 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<Location> locations, boolean enabled) {
super(tagName, false, locations);
private SimpleTaglet(HtmlConfiguration config, String tagName, String header, Set<Taglet.Location> 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<Location> locations, boolean enabled) {
super(tagKind, false, locations);
protected SimpleTaglet(HtmlConfiguration config, DocTree.Kind tagKind, String header, Set<Taglet.Location> locations, boolean enabled) {
super(config, tagKind, false, locations);
this.header = header;
this.enabled = enabled;
}
private static Set<Location> getLocations(String locations) {
Set<Location> 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<Documentation> 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<? extends DocTree> description, ExecutableElement method) { }
private Optional<Documentation> extractFirst(ExecutableElement m) {
List<? extends DocTree> 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<? extends DocTree> 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<? extends DocTree> 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<? extends DocTree> 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<Taglet.Location> getLocations(String locations) {
Set<Taglet.Location> 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<Documentation> 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<? extends DocTree> description, ExecutableElement method) { }
private Optional<Documentation> extractFirst(ExecutableElement m, Utils utils) {
List<? extends DocTree> 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<? extends DocTree> tags = utils.getBlockTags(holder, this);
if (header == null || tags.isEmpty()) {
return null;
}
return writer.simpleBlockTagOutput(holder, tags, header);
}
}

View File

@ -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<String> 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<String> 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);
}
}

View File

@ -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<? extends SpecTree> tags = utils.getSpecTrees(holder);
Element e = holder;
if (utils.isMethod(holder)) {
var docFinder = utils.docFinder();
Optional<Documentation> 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<? extends SpecTree> 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<? extends SpecTree> specTrees, ExecutableElement method) { }
private static Optional<Documentation> extract(Utils utils, ExecutableElement method) {
List<? extends SpecTree> 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<? extends DocTree> 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<? extends DocTree> 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);
}
}
}

View File

@ -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);
}
}

View File

@ -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));
}
}

View File

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

View File

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

View File

@ -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<DocTree.Kind> 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<DocTree.Kind> 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<String> 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<Taglet> 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<String, Taglet> 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<? extends DocTree> 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<? extends DocTree> 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<? extends DocTree> 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<String, Void>() {
@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<Content> 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(",");
}
}

View File

@ -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<? extends TypeMirror> substitutedExceptionTypes = instantiatedType.getThrownTypes();
List<? extends TypeMirror> originalExceptionTypes = executable.getThrownTypes();
Map<TypeMirror, TypeMirror> typeSubstitutions = getSubstitutedThrownTypes(
utils.typeUtils,
originalExceptionTypes,
substitutedExceptionTypes);
var exceptionSection = new ExceptionSectionBuilder(writer);
var exceptionSection = new ExceptionSectionBuilder(tagletWriter, this);
// Step 1: Document exception tags
Set<TypeMirror> alreadyDocumentedExceptions = new HashSet<>();
List<ThrowsTree> 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<ThrowsTree, ExecutableElement> 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> 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<TypeMirror> alreadyDocumentedExceptions,
Map<TypeMirror, TypeMirror> typeSubstitutions,
TagletWriter writer)
Map<TypeMirror, TypeMirror> 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<TypeMirror> alreadyDocumentedExceptions,
Map<TypeMirror, TypeMirror> typeSubstitutions,
TagletWriter writer)
Map<TypeMirror, TypeMirror> 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<Element> 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<ThrowsTree, ExecutableElement> 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<Map<ThrowsTree, ExecutableElement>> result;
DocFinder.Result<Map<ThrowsTree, ExecutableElement>> 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<Map<ThrowsTree, ExecutableElement>> c) {
if (result instanceof DocFinder.Result.Conclude<Map<ThrowsTree, ExecutableElement>> c) {
return c.value();
}
return Map.of(); // an empty map is effectively ordered
}
private static Result<Map<ThrowsTree, ExecutableElement>> toResult(Element target,
ExecutableElement holder,
List<ThrowsTree> tags) {
private static DocFinder.Result<Map<ThrowsTree, ExecutableElement>> toResult(Element target,
ExecutableElement holder,
List<ThrowsTree> 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;
}

View File

@ -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<? extends DocTree> tags = utils.getBlockTags(holder, this);
public Content getAllBlockTagOutput(Element holder, TagletWriter tagletWriter) {
Content output = tagletWriter.getOutputInstance();
var utils = tagletWriter.utils;
List<? extends DocTree> tags = utils.getBlockTags(holder, getName());
if (!tags.isEmpty()) {
String tagString = userTaglet.toString(tags, holder);
if (tagString != null) {

View File

@ -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);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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();
}
/**

View File

@ -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<Path> 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<List<String>> customTagStrs) {
tagletManager = tagletManager != null ? tagletManager : new TagletManager(this);
JavaFileManager fileManager = getFileManager();
Messages messages = getMessages();
try {
tagletManager.initTagletPath(fileManager);
tagletManager.loadTaglets(fileManager);
for (List<String> 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<String> 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<String> tokenize(String s, int maxTokens) {
List<String> 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.

View File

@ -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<List<String>> 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;
//</editor-fold>
private final BaseConfiguration config;
@ -617,44 +585,6 @@ public abstract class BaseOptions {
}
},
new Option(resources, "-tag", 1) {
@Override
public boolean process(String opt, List<String> args) {
ArrayList<String> 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<String> args) {
ArrayList<String> 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<String> args) {
tagletPath = args.get(0);
return true;
}
},
new Option(resources, "--snippet-path", 1) {
@Override
public boolean process(String opt, List<String> args) {
snippetPath = args.get(0);
return true;
}
},
new Option(resources, "-version") {
@Override
public boolean process(String opt, List<String> 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<String> 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<List<String>> 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.
* <code>showauthor</code> 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<Option> {
private final String[] names;
private final String parameters;

View File

@ -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
@ -33,6 +33,12 @@ import jdk.javadoc.internal.doclets.toolkit.util.DocFileIOException;
* The interface for writing class output.
*/
public interface ClassWriter {
/**
* Returns an instance of an output object.
*
* @return an instance of an output object
*/
Content getOutputInstance();
/**
* Get the header of the page.

View File

@ -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
@ -37,7 +37,6 @@ import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import jdk.javadoc.internal.doclets.formats.html.markup.ContentBuilder;
import jdk.javadoc.internal.doclets.toolkit.ClassWriter;
import jdk.javadoc.internal.doclets.toolkit.CommentUtils;
import jdk.javadoc.internal.doclets.toolkit.Content;
@ -143,7 +142,7 @@ public class ClassBuilder extends AbstractBuilder {
* @throws DocletException if there is a problem while building the documentation
*/
protected void buildClassInfo(Content target) throws DocletException {
Content c = new ContentBuilder();
Content c = writer.getOutputInstance();
buildParamInfo(c);
buildSuperInterfacesInfo(c);
buildImplementedInterfacesInfo(c);

View File

@ -24,7 +24,6 @@
#
doclet.Generating_0=Generating {0}...
doclet.Toolkit_Usage_Violation=The Doclet Toolkit can only be used by {0}
doclet.MissingSerialTag=in class {0}, missing @serial tag for default serializable field: {1}.
doclet.MissingSerialDataTag=in class {0}, missing @serialData tag in method {1}.
doclet.Serializable_no_customization=No readObject or writeObject method declared.

View File

@ -1,49 +0,0 @@
/*
* Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* 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.toolkit.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.internal.doclets.toolkit.Content;
/**
* A taglet that represents the {@code @deprecated} tag.
*/
public class DeprecatedTaglet extends BaseTaglet {
public DeprecatedTaglet() {
super(DocTree.Kind.DEPRECATED, false,
EnumSet.of(Location.MODULE, Location.TYPE, Location.CONSTRUCTOR, Location.METHOD, Location.FIELD));
}
@Override
public Content getAllBlockTagOutput(Element holder, TagletWriter writer) {
return writer.deprecatedTagOutput(holder);
}
}

View File

@ -1,53 +0,0 @@
/*
* Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* 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.toolkit.taglets;
import java.util.EnumSet;
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.internal.doclets.toolkit.Content;
/**
* An inline taglet used to denote literal text.
* For example, the text:
* <blockquote> {@code {@literal a<B>c}} </blockquote>
* displays as:
* <blockquote> {@literal a<B>c} </blockquote>
*/
public class LiteralTaglet extends BaseTaglet {
LiteralTaglet() {
super(DocTree.Kind.LITERAL, true, EnumSet.allOf(Location.class));
}
@Override
public Content getInlineTagOutput(Element e, DocTree tag, TagletWriter writer) {
return writer.literalTagOutput(e, (LiteralTree) tag);
}
}

View File

@ -1,85 +0,0 @@
/*
* 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.toolkit.taglets;
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 com.sun.source.doctree.DocTree;
import com.sun.source.doctree.SeeTree;
import jdk.javadoc.doclet.Taglet.Location;
import jdk.javadoc.internal.doclets.toolkit.BaseConfiguration;
import jdk.javadoc.internal.doclets.toolkit.Content;
import jdk.javadoc.internal.doclets.toolkit.util.CommentHelper;
import jdk.javadoc.internal.doclets.toolkit.util.DocFinder.Result;
import jdk.javadoc.internal.doclets.toolkit.util.Utils;
/**
* A taglet that represents the {@code @see} tag.
*/
public class SeeTaglet extends BaseTaglet implements InheritableTaglet {
public SeeTaglet() {
super(DocTree.Kind.SEE, false, EnumSet.allOf(Location.class));
}
@Override
public Output inherit(Element dst, Element src, DocTree tag, boolean isFirstSentence, BaseConfiguration configuration) {
CommentHelper ch = configuration.utils.getCommentHelper(dst);
var path = ch.getDocTreePath(tag);
configuration.getMessages().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 writer) {
Utils utils = writer.configuration().utils;
List<? extends SeeTree> tags = utils.getSeeTrees(holder);
Element e = holder;
if (utils.isMethod(holder)) {
var docFinder = utils.docFinder();
Optional<Documentation> result = docFinder.search((ExecutableElement) holder,
m -> Result.fromOptional(extract(utils, m))).toOptional();
if (result.isPresent()) {
ExecutableElement m = result.get().method();
tags = utils.getSeeTrees(m);
e = m;
}
}
return writer.seeTagOutput(e, tags);
}
private record Documentation(List<? extends SeeTree> seeTrees, ExecutableElement method) { }
private static Optional<Documentation> extract(Utils utils, ExecutableElement method) {
List<? extends SeeTree> tags = utils.getSeeTrees(method);
return tags.isEmpty() ? Optional.empty() : Optional.of(new Documentation(tags, method));
}
}

View File

@ -1,83 +0,0 @@
/*
* 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.toolkit.taglets;
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 com.sun.source.doctree.DocTree;
import com.sun.source.doctree.SpecTree;
import jdk.javadoc.doclet.Taglet.Location;
import jdk.javadoc.internal.doclets.toolkit.BaseConfiguration;
import jdk.javadoc.internal.doclets.toolkit.Content;
import jdk.javadoc.internal.doclets.toolkit.util.CommentHelper;
import jdk.javadoc.internal.doclets.toolkit.util.DocFinder.Result;
import jdk.javadoc.internal.doclets.toolkit.util.Utils;
/**
* A taglet that represents the {@code @spec} tag.
*/
public class SpecTaglet extends BaseTaglet implements InheritableTaglet {
public SpecTaglet() {
super(DocTree.Kind.SPEC, false, EnumSet.allOf(Location.class));
}
@Override
public Output inherit(Element dst, Element src, DocTree tag, boolean isFirstSentence, BaseConfiguration configuration) {
CommentHelper ch = configuration.utils.getCommentHelper(dst);
var path = ch.getDocTreePath(tag);
configuration.getMessages().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 writer) {
Utils utils = writer.configuration().utils;
List<? extends SpecTree> tags = utils.getSpecTrees(holder);
Element e = holder;
if (utils.isMethod(holder)) {
var docFinder = utils.docFinder();
Optional<Documentation> result = docFinder.search((ExecutableElement) holder,
m -> Result.fromOptional(extract(utils, m))).toOptional();
if (result.isPresent()) {
e = result.get().method();
tags = result.get().specTrees();
}
}
return writer.specTagOutput(e, tags);
}
private record Documentation(List<? extends SpecTree> specTrees, ExecutableElement method) { }
private static Optional<Documentation> extract(Utils utils, ExecutableElement method) {
List<? extends SpecTree> tags = utils.getSpecTrees(method);
return tags.isEmpty() ? Optional.empty() : Optional.of(new Documentation(tags, method));
}
}

View File

@ -1,420 +0,0 @@
/*
* Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* 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.toolkit.taglets;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
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.SpecTree;
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.SystemPropertyTree;
import com.sun.source.doctree.ThrowsTree;
import jdk.javadoc.internal.doclets.toolkit.BaseConfiguration;
import jdk.javadoc.internal.doclets.toolkit.Content;
import jdk.javadoc.internal.doclets.toolkit.taglets.Taglet.UnsupportedTagletOperationException;
import jdk.javadoc.internal.doclets.toolkit.taglets.snippet.StyledText;
import jdk.javadoc.internal.doclets.toolkit.util.CommentHelper;
import jdk.javadoc.internal.doclets.toolkit.util.Utils;
/**
* The interface for the taglet writer.
*/
public abstract class TagletWriter {
/**
* True if we only want to write the first sentence.
*/
protected final boolean isFirstSentence;
protected TagletWriter(boolean isFirstSentence) {
this.isFirstSentence = isFirstSentence;
}
/**
* Returns an instance of an output object.
*
* @return an instance of an output object
*/
public abstract Content getOutputInstance();
/**
* Returns the output for a {@code {@code ...}} tag.
*
* @param element The element that owns the doc comment
* @param tag the tag
*
* @return the output
*/
protected abstract Content codeTagOutput(Element element, LiteralTree tag);
/**
* Returns the output for a {@code {@index...}} tag.
*
* @param element The element that owns the doc comment
* @param tag the tag
*
* @return the output
*/
protected abstract Content indexTagOutput(Element element, IndexTree tag);
/**
* Returns the output for a {@code {@docRoot}} tag.
*
* @return the output
*/
protected abstract Content getDocRootOutput();
/**
* Returns the output for a {@code @deprecated} tag.
*
* @param element The element that owns the doc comment
*
* @return the output
*/
protected abstract Content deprecatedTagOutput(Element element);
/**
* Returns the output for a {@code {@link ...}} or {@code {@linkplain ...}} tag.
*
* @param element The element that owns the doc comment
* @param tag the tag
*
* @return the output
*/
protected abstract Content linkTagOutput(Element element, LinkTree tag);
/**
* Returns the output for a {@code {@literal ...}} tag.
*
* @param element The element that owns the doc comment
* @param tag the tag
*
* @return the output
*/
protected abstract Content literalTagOutput(Element element, LiteralTree tag);
/**
* Returns the header for the {@code @param} tags.
*
* @param kind the kind of header that is required
*
* @return the header
*/
protected abstract Content getParamHeader(ParamTaglet.ParamKind kind);
/**
* Returns the output for a {@code @param} tag.
* Note we cannot rely on the name in the tag, because we might be
* inheriting the tag.
*
* @param element The element that owns the doc comment
* @param paramTag the parameter to document
* @param paramName the name of the parameter
*
* @return the output
*/
protected abstract Content paramTagOutput(Element element, ParamTree paramTag, String paramName);
/**
* 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
*/
protected abstract Content returnTagOutput(Element element, ReturnTree returnTag, boolean inline);
/**
* Returns the output for {@code @see} tags.
*
* @param element The element that owns the doc comment
* @param seeTags the list of tags
*
* @return the output
*/
protected abstract Content seeTagOutput(Element element, List<? extends SeeTree> seeTags);
/**
* 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
*/
protected abstract Content simpleBlockTagOutput(Element element, List<? extends DocTree> simpleTags, String header);
/**
* Returns the output for a {@code {@snippet ...}} tag.
*
* @param element The element that owns the doc comment
* @param snippetTag 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
*/
protected abstract Content snippetTagOutput(Element element, SnippetTree snippetTag, StyledText text,
String id, String lang);
/**
* Returns the output for one or more {@code @spec} tags.
*
* @param element the element that owns the doc comment
* @param specTags the array of @spec tags.
*
* @return the output
*/
protected abstract Content specTagOutput(Element element, List<? extends SpecTree> specTags);
/**
* Returns the output for a {@code {@systemProperty...}} tag.
*
* @param element the element that owns the doc comment
* @param systemPropertyTag the system property tag
*
* @return the output
*/
protected abstract Content systemPropertyTagOutput(Element element, SystemPropertyTree systemPropertyTag);
/**
* Returns the header for the {@code @throws} tag.
*
* @return the header for the throws tag
*/
protected abstract Content getThrowsHeader();
/**
* 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
*/
protected abstract Content throwsTagOutput(TypeMirror throwsType, Optional<Content> content);
/**
* 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
*/
protected abstract Content valueTagOutput(VariableElement field,
String constantVal, boolean includeLink);
/**
* 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
*/
protected abstract Content invalidTagOutput(String summary, Optional<String> detail);
/**
* 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.
*/
protected abstract TypeElement 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<Taglet> taglets) {
for (Taglet t : taglets) {
if (!t.isBlockTag()) {
throw new IllegalArgumentException(t.getName());
}
}
Content output = getOutputInstance();
Utils utils = configuration().utils;
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.enabled) {
// 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 tagletManager the taglet manager for the current doclet
* @param inlineTag the inline tag to be documented
*
* @return the content, or {@code null}
*/
public Content getInlineTagOutput(Element holder,
TagletManager tagletManager,
DocTree inlineTag) {
Map<String, Taglet> 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 abstract Content commentTagsToOutput(DocTree holderTree, List<? extends DocTree> trees);
/**
* 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 abstract Content commentTagsToOutput(Element element, List<? extends DocTree> trees);
/**
* 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 abstract Content commentTagsToOutput(Element element, DocTree holder,
List<? extends DocTree> trees, boolean isFirstSentence);
/**
* Returns an instance of the configuration used for this doclet.
*
* @return an instance of the configuration used for this doclet
*/
public abstract BaseConfiguration configuration();
}

View File

@ -1,46 +0,0 @@
/*
* Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* 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.
*/
/**
* This package has classes used to generate output for Javadoc tags.
*
* <p>Doclets no longer have to implement their own version of standard tags
* such as &#64;param and &#64;throws. Individual taglets provide
* common processing, independent of the output format.
* Each doclet must have a taglet writer that takes a taglet
* as input and writes doclet-dependent output. The taglet itself will
* do the tag processing. For example, suppose we are outputting
* &#64;throws tags. The taglet would:
* <ul>
* <li> Retrieve the list of throws tags to be documented.
* <li> Replace {&#64;inheritDoc} with the appropriate documentation.
* <li> Add throws documentation for exceptions that are declared in
* the signature of the method but not documented with the throws tags.
* </ul>
* After doing the steps above, the taglet would pass the information to
* the taglet writer for writing. The taglets are essentially builders for
* tags.
*/
package jdk.javadoc.internal.doclets.toolkit.taglets;

View File

@ -34,7 +34,6 @@ import java.text.ParseException;
import java.text.RuleBasedCollator;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
@ -116,13 +115,12 @@ import com.sun.source.tree.LineMap;
import com.sun.source.util.DocSourcePositions;
import com.sun.source.util.DocTrees;
import com.sun.source.util.TreePath;
import jdk.javadoc.internal.doclets.toolkit.BaseConfiguration;
import jdk.javadoc.internal.doclets.toolkit.BaseOptions;
import jdk.javadoc.internal.doclets.toolkit.CommentUtils;
import jdk.javadoc.internal.doclets.toolkit.CommentUtils.DocCommentInfo;
import jdk.javadoc.internal.doclets.toolkit.Resources;
import jdk.javadoc.internal.doclets.toolkit.taglets.BaseTaglet;
import jdk.javadoc.internal.doclets.toolkit.taglets.Taglet;
import static javax.lang.model.element.ElementKind.*;
import static javax.lang.model.type.TypeKind.*;
@ -991,7 +989,7 @@ public class Utils {
/**
* Return the type's dimension information, as a string.
* <p>
* For example, a two dimensional array of String returns "{@code [][]}".
* For example, a two-dimensional array of String returns "{@code [][]}".
*
* @return the type's dimension information as a string.
*/
@ -2087,47 +2085,102 @@ public class Utils {
commentHelperCache.remove(element);
}
/**
* Returns the "raw" list of block tags from the doc-comment tree for an element,
* or an empty list if there is no such comment.
*
* Note: The list may include {@code ErroneousTree} nodes.
*
* @param element the element
* @return the list
*/
public List<? extends DocTree> getBlockTags(Element element) {
return getBlockTags(getDocCommentTree(element));
}
/**
* Returns the "raw" list of block tags from a {@code DocCommentTree}, or an empty list
* if the doc-comment tree is {@code null}.
*
* Note: The list may include {@code ErroneousTree} nodes.
*
* @param dcTree the doc-comment tree
* @return the list
*/
public List<? extends DocTree> getBlockTags(DocCommentTree dcTree) {
return dcTree == null ? List.of() : dcTree.getBlockTags();
}
public List<? extends DocTree> getBlockTags(Element element, Predicate<DocTree> filter) {
/**
* Returns the list of block tags for the doc-comment tree for an element that match
* a given filter, or an empty list if there is no such doc-comment.
*
* @param element the element
* @param filter the filter
* @return the list
*/
public List<? extends BlockTagTree> getBlockTags(Element element, Predicate<? super BlockTagTree> filter) {
return getBlockTags(element).stream()
.filter(t -> t.getKind() != ERRONEOUS)
.map(t -> (BlockTagTree) t)
.filter(filter)
.toList();
}
public <T extends DocTree> List<T> getBlockTags(Element element, Predicate<DocTree> filter, Class<T> tClass) {
/**
* Returns the list of block tags for the doc-comment tree for an element that match
* a given filter, or an empty list if there is no such doc-comment.
*
* @param <T> the type of the required block tags
* @param element the element
* @param filter the filter
* @return the list
*/
public <T extends BlockTagTree> List<T> getBlockTags(Element element,
Predicate<? super BlockTagTree> filter,
Class<T> tClass) {
return getBlockTags(element).stream()
.filter(t -> t.getKind() != ERRONEOUS)
.map(t -> (BlockTagTree) t)
.filter(filter)
.map(tClass::cast)
.toList();
}
public List<? extends DocTree> getBlockTags(Element element, DocTree.Kind kind) {
/**
* Returns the list of block tags for the doc-comment tree for an element,
* or an empty list if there is no such doc-comment.
*
* @param element the element
* @return the list
*/
public List<? extends BlockTagTree> getBlockTags(Element element, DocTree.Kind kind) {
return getBlockTags(element, t -> t.getKind() == kind);
}
public <T extends DocTree> List<? extends T> getBlockTags(Element element, DocTree.Kind kind, Class<T> tClass) {
/**
* Returns the list of block tags for the doc-comment tree for an element that match a given kind,
* or an empty list if there is no such doc-comment.
*
* @param <T> the type of the required block tags
* @param element the element
* @param kind the kind for the required block tags
* @return the list
*/
public <T extends BlockTagTree> List<? extends T> getBlockTags(Element element, DocTree.Kind kind, Class<T> tClass) {
return getBlockTags(element, t -> t.getKind() == kind, tClass);
}
public List<? extends DocTree> getBlockTags(Element element, Taglet taglet) {
return getBlockTags(element, t -> {
if (taglet instanceof BaseTaglet baseTaglet) {
return baseTaglet.accepts(t);
} else if (t instanceof BlockTagTree blockTagTree) {
return blockTagTree.getTagName().equals(taglet.getName());
} else {
return false;
}
});
/**
* Returns the list of block tags for the doc-comment tree for an element that match a given name,
* or an empty list if there is no such doc-comment.
*
* @param element the element
* @param tagName the name of the required block tags
* @return the list
*/
public List<? extends BlockTagTree> getBlockTags(Element element, String tagName) {
return getBlockTags(element, t -> t.getTagName().equals(tagName));
}
public boolean hasBlockTag(Element element, DocTree.Kind kind) {