6251738: Want a top-level summary page that itemizes all spec documents referenced from javadocs (OEM spec)

Reviewed-by: hannesw
This commit is contained in:
Jonathan Gibbons 2022-09-26 21:28:39 +00:00
parent aca4276e89
commit b88ee1ee22
42 changed files with 1514 additions and 46 deletions

View File

@ -69,8 +69,6 @@ JAVADOC_TAGS := \
-tag beaninfo:X \
-tag revised:X \
-tag since.unbundled:X \
-tag spec:X \
-tag specdefault:X \
-tag Note:X \
-tag ToDo:X \
-tag 'apiNote:a:API Note:' \
@ -86,6 +84,7 @@ JAVADOC_TAGS := \
-tag since \
-tag serialData \
-tag factory \
-tag spec \
-tag see \
-taglet build.tools.taglet.ExtLink \
-taglet build.tools.taglet.Incubating \

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2011, 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
@ -218,6 +218,14 @@ public interface DocTree {
*/
SNIPPET("snippet"),
/**
* Used for instances of {@link SpecTree}
* representing an {@code @spec} tag.
*
* @since 20
*/
SPEC("spec"),
/**
* Used for instances of {@link EndElementTree}
* representing the start of an HTML element.

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2011, 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
@ -302,6 +302,22 @@ public interface DocTreeVisitor<R,P> {
return visitOther(node, p);
}
/**
* Visits a {@code SpecTree} node.
*
* @implSpec Visits the provided {@code SpecTree} node
* by calling {@code visitOther(node, p)}.
*
* @param node the node being visited
* @param p a parameter value
* @return a result value
*
* @since 20
*/
default R visitSpec(SpecTree node, P p) {
return visitOther(node, p);
}
/**
* Visits a {@code StartElementTree} node.
* @param node the node being visited

View File

@ -0,0 +1,49 @@
/*
* Copyright (c) 2019, 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 com.sun.source.doctree;
import java.util.List;
/**
* A tree node for an {@code @spec} block tag.
*
* <pre>
* &#064;spec url title
* </pre>
*
* @since 20
*/
public interface SpecTree extends BlockTagTree {
/**
* {@return the URL}
*/
TextTree getURL();
/**
* {@return the title}
*/
List<? extends DocTree> getTitle();
}

View File

@ -59,6 +59,7 @@ import com.sun.source.doctree.SerialFieldTree;
import com.sun.source.doctree.SerialTree;
import com.sun.source.doctree.SinceTree;
import com.sun.source.doctree.SnippetTree;
import com.sun.source.doctree.SpecTree;
import com.sun.source.doctree.StartElementTree;
import com.sun.source.doctree.SummaryTree;
import com.sun.source.doctree.SystemPropertyTree;
@ -336,6 +337,15 @@ public interface DocTreeFactory {
*/
SnippetTree newSnippetTree(List<? extends DocTree> attributes, TextTree text);
/**
* Creates a new {@code SpecTree} object, to represent an {@code @spec} tag.
* @param url the url
* @param title the title
* @return a {@code SpecTree} object
* @since 20
*/
SpecTree newSpecTree(TextTree url, List<? extends DocTree> title);
/**
* Creates a new {@code StartElementTree} object, to represent the start of an HTML element.
* @param name the name of the HTML element

View File

@ -515,6 +515,23 @@ public class DocTreeScanner<R,P> implements DocTreeVisitor<R,P> {
return r;
}
/**
* {@inheritDoc}
*
* @implSpec This implementation scans the children in left to right order.
*
* @param node {@inheritDoc}
* @param p {@inheritDoc}
* @return the result of scanning
* @since 20
*/
@Override
public R visitSpec(SpecTree node, P p) {
R r = scan(node.getURL(), p);
r = scanAndReduce(node.getTitle(), p, r);
return r;
}
/**
* {@inheritDoc}
*

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2005, 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
@ -463,6 +463,23 @@ public class SimpleDocTreeVisitor<R,P> implements DocTreeVisitor<R, P> {
return defaultAction(node, p);
}
/**
* {@inheritDoc}
*
* @implSpec This implementation calls {@code defaultAction}.
*
* @param node {@inheritDoc}
* @param p {@inheritDoc}
*
* @return the result of {@code defaultAction}
*
* @since 20
*/
@Override
public R visitSpec(SpecTree node, P p) {
return defaultAction(node, p);
}
/**
* {@inheritDoc}
*

View File

@ -1551,6 +1551,24 @@ public class DocCommentParser {
}
},
// @spec url label
new TagParser(TagParser.Kind.BLOCK, DCTree.Kind.SPEC) {
@Override
public DCTree parse(int pos) throws ParseException {
skipWhitespace();
DCText url = inlineWord();
if (url == null || url.isBlank()) {
throw new ParseException("dc.no.url");
}
skipWhitespace();
List<DCTree> title = blockContent();
if (title.isEmpty() || DCTree.isBlank(title)) {
throw new ParseException("dc.no.title");
}
return m.at(pos).newSpecTree(url, title);
}
},
// {@summary summary-text}
new TagParser(TagParser.Kind.INLINE, DCTree.Kind.SUMMARY) {
@Override

View File

@ -3322,6 +3322,12 @@ compiler.err.dc.no.content=\
compiler.err.dc.no.tag.name=\
no tag name after '@'
compiler.err.dc.no.url=\
no URL
compiler.err.dc.no.title=\
no title
compiler.err.dc.gt.expected=\
''>'' expected

View File

@ -42,7 +42,6 @@ import com.sun.tools.javac.util.Assert;
import com.sun.tools.javac.util.DefinedBy;
import com.sun.tools.javac.util.DefinedBy.Api;
import com.sun.tools.javac.util.JCDiagnostic;
import com.sun.tools.javac.util.Position;
import static com.sun.tools.javac.util.Position.NOPOS;
@ -225,6 +224,14 @@ public abstract class DCTree implements DocTree {
return NOPOS;
}
public boolean isBlank() {
return false;
}
public static boolean isBlank(List<? extends DCTree> list) {
return list.stream().allMatch(DCTree::isBlank);
}
/**
* Convert a tree to a pretty-printed string.
*/
@ -1080,6 +1087,41 @@ public abstract class DCTree implements DocTree {
}
}
public static class DCSpec extends DCBlockTag implements SpecTree {
public final DCText uri;
public final List<DCTree> title;
DCSpec(DCText uri, List<DCTree> title) {
this.uri = uri;
this.title = title;
}
@Override
public String getTagName() {
return "spec";
}
@Override @DefinedBy(Api.COMPILER_TREE)
public Kind getKind() {
return Kind.SPEC;
}
@Override @DefinedBy(Api.COMPILER_TREE)
public <R, D> R accept(DocTreeVisitor<R, D> v, D d) {
return v.visitSpec(this, d);
}
@Override @DefinedBy(Api.COMPILER_TREE)
public TextTree getURL() {
return uri;
}
@Override @DefinedBy(Api.COMPILER_TREE)
public List<? extends DocTree> getTitle() {
return title;
}
}
public static class DCStartElement extends DCEndPosTree<DCStartElement> implements StartElementTree {
public final Name name;
public final List<DCTree> attrs;
@ -1170,6 +1212,11 @@ public abstract class DCTree implements DocTree {
this.text = text;
}
@Override
public boolean isBlank() {
return text.isBlank();
}
@Override @DefinedBy(Api.COMPILER_TREE)
public Kind getKind() {
return Kind.TEXT;

View File

@ -504,6 +504,20 @@ public class DocPretty implements DocTreeVisitor<Void,Void> {
return null;
}
@Override @DefinedBy(Api.COMPILER_TREE)
public Void visitSpec(SpecTree node, Void p) {
try {
printTagName(node);
print(" ");
print(node.getURL());
print(" ");
print(node.getTitle());
} catch (IOException e) {
throw new UncheckedIOException(e);
}
return null;
}
@Override @DefinedBy(Api.COMPILER_TREE)
public Void visitStartElement(StartElementTree node, Void p) {
try {

View File

@ -76,6 +76,7 @@ import com.sun.tools.javac.tree.DCTree.DCSerialData;
import com.sun.tools.javac.tree.DCTree.DCSerialField;
import com.sun.tools.javac.tree.DCTree.DCSince;
import com.sun.tools.javac.tree.DCTree.DCSnippet;
import com.sun.tools.javac.tree.DCTree.DCSpec;
import com.sun.tools.javac.tree.DCTree.DCStartElement;
import com.sun.tools.javac.tree.DCTree.DCSummary;
import com.sun.tools.javac.tree.DCTree.DCSystemProperty;
@ -89,9 +90,7 @@ import com.sun.tools.javac.tree.DCTree.DCVersion;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.DefinedBy;
import com.sun.tools.javac.util.DefinedBy.Api;
import com.sun.tools.javac.util.DiagnosticSource;
import com.sun.tools.javac.util.JCDiagnostic;
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
import com.sun.tools.javac.util.ListBuffer;
import com.sun.tools.javac.util.Pair;
import com.sun.tools.javac.util.Position;
@ -124,7 +123,7 @@ public class DocTreeMaker implements DocTreeFactory {
/** The position at which subsequent trees will be created.
*/
public int pos = Position.NOPOS;
public int pos;
private final JavacTrees trees;
@ -238,9 +237,8 @@ public class DocTreeMaker implements DocTreeFactory {
}
};
Pair<List<DCTree>, List<DCTree>> pair = splitBody(fullBody);
DCDocComment tree = new DCDocComment(c, fBody, pair.fst, pair.snd, cast(tags),
return new DCDocComment(c, fBody, pair.fst, pair.snd, cast(tags),
cast(preamble), cast(postamble));
return tree;
}
@Override @DefinedBy(Api.COMPILER_TREE)
@ -421,6 +419,13 @@ public class DocTreeMaker implements DocTreeFactory {
return tree;
}
@Override @DefinedBy(Api.COMPILER_TREE)
public DCSpec newSpecTree(TextTree url, List<? extends DocTree> title) {
DCSpec tree = new DCSpec((DCText) url, cast(title));
tree.pos = pos;
return tree;
}
@Override @DefinedBy(Api.COMPILER_TREE)
public DCStartElement newStartElementTree(Name name, List<? extends DocTree> attrs, boolean selfClosing) {
DCStartElement tree = new DCStartElement(name, cast(attrs), selfClosing);

View File

@ -95,6 +95,7 @@ public class Contents {
public final Content exceptionClass;
public final Content exceptionClasses;
public final Content exportedTo;
public final Content externalSpecifications;
public final Content fieldLabel;
public final Content fieldDetailsLabel;
public final Content fieldSummaryLabel;
@ -169,6 +170,7 @@ public class Contents {
public final Content seeAlso;
public final Content serializedForm;
public final Content servicesLabel;
public final Content specificationLabel;
public final Content specifiedByLabel;
public final Content subclassesLabel;
public final Content subinterfacesLabel;
@ -239,6 +241,7 @@ public class Contents {
exceptionClass = getContent("doclet.ExceptionClass");
exceptionClasses = getContent("doclet.ExceptionClasses");
exportedTo = getContent("doclet.ExportedTo");
externalSpecifications = getContent("doclet.External_Specifications");
fieldDetailsLabel = getContent("doclet.Field_Detail");
fieldSummaryLabel = getContent("doclet.Field_Summary");
fieldLabel = getContent("doclet.Field");
@ -313,6 +316,7 @@ public class Contents {
seeAlso = getContent("doclet.See_Also");
serializedForm = getContent("doclet.Serialized_Form");
servicesLabel = getContent("doclet.Services");
specificationLabel = getContent("doclet.Specification");
specifiedByLabel = getContent("doclet.Specified_By");
subclassesLabel = getContent("doclet.Subclasses");
subinterfacesLabel = getContent("doclet.Subinterfaces");

View File

@ -0,0 +1,297 @@
/*
* Copyright (c) 2019, 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.formats.html;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.WeakHashMap;
import java.util.function.Predicate;
import javax.lang.model.element.Element;
import javax.tools.Diagnostic;
import com.sun.source.doctree.DocTree;
import com.sun.source.doctree.SpecTree;
import com.sun.source.util.DocTreePath;
import com.sun.source.util.TreePath;
import jdk.javadoc.internal.doclets.formats.html.Navigation.PageMode;
import jdk.javadoc.internal.doclets.formats.html.markup.BodyContents;
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.formats.html.markup.Text;
import jdk.javadoc.internal.doclets.toolkit.Content;
import jdk.javadoc.internal.doclets.toolkit.DocletElement;
import jdk.javadoc.internal.doclets.toolkit.OverviewElement;
import jdk.javadoc.internal.doclets.toolkit.util.DocFileIOException;
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 static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList;
/**
* Generates the file with the summary of all the references to external specifications.
*/
public class ExternalSpecsWriter extends HtmlDocletWriter {
private final Navigation navBar;
/**
* Cached contents of {@code <title>...</title>} tags of the HTML pages.
*/
final Map<Element, String> titles = new WeakHashMap<>();
/**
* Constructs ExternalSpecsWriter object.
*
* @param configuration The current configuration
* @param filename Path to the file which is getting generated.
*/
public ExternalSpecsWriter(HtmlConfiguration configuration, DocPath filename) {
super(configuration, filename);
this.navBar = new Navigation(null, configuration, PageMode.EXTERNAL_SPECS, path);
}
public static void generate(HtmlConfiguration configuration) throws DocFileIOException {
generate(configuration, DocPaths.EXTERNAL_SPECS);
}
private static void generate(HtmlConfiguration configuration, DocPath fileName) throws DocFileIOException {
boolean hasExternalSpecs = configuration.mainIndex != null
&& !configuration.mainIndex.getItems(DocTree.Kind.SPEC).isEmpty();
if (!hasExternalSpecs) {
return;
}
ExternalSpecsWriter w = new ExternalSpecsWriter(configuration, fileName);
w.buildExternalSpecsPage();
configuration.conditionalPages.add(HtmlConfiguration.ConditionalPage.EXTERNAL_SPECS);
}
/**
* Prints all the "external specs" to the file.
*/
protected void buildExternalSpecsPage() throws DocFileIOException {
checkUniqueItems();
String title = resources.getText("doclet.External_Specifications");
HtmlTree body = getBody(getWindowTitle(title));
Content mainContent = new ContentBuilder();
addExternalSpecs(mainContent);
body.add(new BodyContents()
.setHeader(getHeader(PageMode.EXTERNAL_SPECS))
.addMainContent(HtmlTree.DIV(HtmlStyle.header,
HtmlTree.HEADING(Headings.PAGE_TITLE_HEADING,
contents.getContent("doclet.External_Specifications"))))
.addMainContent(mainContent)
.setFooter(getFooter()));
printHtmlDocument(null, "external specifications", body);
if (configuration.mainIndex != null) {
configuration.mainIndex.add(IndexItem.of(IndexItem.Category.TAGS, title, path));
}
}
protected void checkUniqueItems() {
Map<String, Map<String, List<IndexItem>>> itemsByURL = new HashMap<>();
Map<String, Map<String, List<IndexItem>>> itemsByTitle = new HashMap<>();
for (IndexItem ii : configuration.mainIndex.getItems(DocTree.Kind.SPEC)) {
if (ii.getDocTree() instanceof SpecTree st) {
String url = st.getURL().toString();
String title = st.getTitle().toString();
itemsByTitle
.computeIfAbsent(title, l -> new HashMap<>())
.computeIfAbsent(url, u -> new ArrayList<>())
.add(ii);
itemsByURL
.computeIfAbsent(url, u -> new HashMap<>())
.computeIfAbsent(title, l -> new ArrayList<>())
.add(ii);
}
}
itemsByURL.forEach((url, title) -> {
if (title.size() > 1) {
messages.error("doclet.extSpec.spec.has.multiple.titles", url,
title.values().stream().distinct().count());
title.forEach((t, list) ->
list.forEach(ii ->
report(ii, "doclet.extSpec.url.title", url, t)));
}
});
itemsByTitle.forEach((title, urls) -> {
if (urls.size() > 1) {
messages.error("doclet.extSpec.title.for.multiple.specs", title,
urls.values().stream().distinct().count());
urls.forEach((u, list) ->
list.forEach(ii ->
report(ii, "doclet.extSpec.title.url", title, u)));
}
});
}
private void report(IndexItem ii, String key, Object... args) {
String message = messages.getResources().getText(key, args);
Element e = ii.getElement();
if (e == null) {
configuration.reporter.print(Diagnostic.Kind.NOTE, message);
} else {
TreePath tp = utils.getTreePath(e);
DocTreePath dtp = new DocTreePath(new DocTreePath(tp, utils.getDocCommentTree(e)), ii.getDocTree());
configuration.reporter.print(Diagnostic.Kind.NOTE, dtp, message);
}
}
/**
* Adds all the references to external specifications to the content tree.
*
* @param content HtmlTree content to which the links will be added
*/
protected void addExternalSpecs(Content content) {
final int USE_DETAILS_THRESHHOLD = 20;
Map<String, List<IndexItem>> searchIndexMap = groupExternalSpecs();
Table table = new Table(HtmlStyle.summaryTable)
.setCaption(contents.externalSpecifications)
.setHeader(new TableHeader(contents.specificationLabel, contents.referencedIn))
.setColumnStyles(HtmlStyle.colFirst, HtmlStyle.colLast);
for (List<IndexItem> searchIndexItems : searchIndexMap.values()) {
Content specName = createSpecLink(searchIndexItems.get(0));
Content referencesList = HtmlTree.UL(HtmlStyle.refList, searchIndexItems,
item -> HtmlTree.LI(createLink(item)));
Content references = searchIndexItems.size() < USE_DETAILS_THRESHHOLD
? referencesList
: HtmlTree.DETAILS()
.add(HtmlTree.SUMMARY(contents.getContent("doclet.references",
String.valueOf(searchIndexItems.size()))))
.add(referencesList);
table.addRow(specName, references);
}
content.add(table);
}
private Map<String, List<IndexItem>> groupExternalSpecs() {
return configuration.mainIndex.getItems(DocTree.Kind.SPEC).stream()
.collect(groupingBy(IndexItem::getLabel, () -> new TreeMap<>(getTitleComparator()), toList()));
}
Comparator<String> getTitleComparator() {
Collator collator = Collator.getInstance();
return new Comparator<>() {
@Override
public int compare(String s1, String s2) {
int i1 = 0;
int i2 = 0;
while (i1 < s1.length() && i2 < s2.length()) {
int j1 = find(s1, i1, Character::isDigit);
int j2 = find(s2, i2, Character::isDigit);
int cmp = collator.compare(s1.substring(i1, j1), s2.substring(i2, j2));
if (cmp != 0) {
return cmp;
}
if (j1 == s1.length() || j2 == s2.length()) {
i1 = j1;
i2 = j2;
break;
}
int k1 = find(s1, j1, ch -> !Character.isDigit(ch));
int k2 = find(s2, j2, ch -> !Character.isDigit(ch));
cmp = Integer.compare(Integer.parseInt(s1.substring(j1, k1)), Integer.parseInt(s2.substring(j2, k2)));
if (cmp != 0) {
return cmp;
}
i1 = k1;
i2 = k2;
}
return i1 < s1.length() ? 1 : i2 < s2.length() ? -1 : 0;
}
};
}
private static int find(String s, int start, Predicate<Character> p) {
int i = start;
while (i < s.length() && !p.test(s.charAt(i))) {
i++;
}
return i;
}
private Content createLink(IndexItem i) {
assert i.getDocTree().getKind() == DocTree.Kind.SPEC : i;
Element element = i.getElement();
if (element instanceof OverviewElement) {
return links.createLink(pathToRoot.resolve(i.getUrl()),
resources.getText("doclet.Overview"));
} else if (element instanceof DocletElement) {
DocletElement e = (DocletElement) element;
// Implementations of DocletElement do not override equals and
// hashCode; putting instances of DocletElement in a map is not
// incorrect, but might well be inefficient
String t = titles.computeIfAbsent(element, utils::getHTMLTitle);
if (t.isBlank()) {
// The user should probably be notified (a warning?) that this
// file does not have a title
Path p = Path.of(e.getFileObject().toUri());
t = p.getFileName().toString();
}
ContentBuilder b = new ContentBuilder();
b.add(HtmlTree.CODE(Text.of(i.getHolder() + ": ")));
// non-program elements should be displayed using a normal font
b.add(t);
return links.createLink(pathToRoot.resolve(i.getUrl()), b);
} else {
// program elements should be displayed using a code font
Content link = links.createLink(pathToRoot.resolve(i.getUrl()), i.getHolder());
return HtmlTree.CODE(link);
}
}
private Content createSpecLink(IndexItem i) {
assert i.getDocTree().getKind() == DocTree.Kind.SPEC : i;
SpecTree specTree = (SpecTree) i.getDocTree();
Content title = Text.of(i.getLabel());
URI specURI;
try {
specURI = new URI(specTree.getURL().getBody());
} catch (URISyntaxException e) {
// should not happen: items with bad URIs should not make it into the index
return title;
}
return HtmlTree.A(resolveExternalSpecURI(specURI), title);
}
}

View File

@ -390,6 +390,15 @@ public class HelpWriter extends HtmlDocletWriter {
pageKindsSection.add(section);
}
// External Specification
if (configuration.conditionalPages.contains(HtmlConfiguration.ConditionalPage.EXTERNAL_SPECS)) {
section = newHelpSection(contents.externalSpecifications, PageMode.EXTERNAL_SPECS, subTOC);
Content extSpecsBody = getContent("doclet.help.externalSpecifications.body",
links.createLink(DocPaths.EXTERNAL_SPECS, resources.getText("doclet.External_Specifications")));
section.add(HtmlTree.P(extSpecsBody));
pageKindsSection.add(section);
}
// Index
if (options.createIndex()) {
if (!configuration.packages.isEmpty()) {

View File

@ -149,7 +149,7 @@ public class HtmlConfiguration extends BaseConfiguration {
// Note: this should (eventually) be merged with Navigation.PageMode,
// which performs a somewhat similar role
public enum ConditionalPage {
CONSTANT_VALUES, DEPRECATED, PREVIEW, SERIALIZED_FORM, SYSTEM_PROPERTIES, NEW
CONSTANT_VALUES, DEPRECATED, EXTERNAL_SPECS, PREVIEW, SERIALIZED_FORM, SYSTEM_PROPERTIES, NEW
}
/**

View File

@ -30,7 +30,14 @@ import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.function.Function;
import javax.lang.model.SourceVersion;
@ -256,6 +263,7 @@ public class HtmlDoclet extends AbstractDoclet {
}
if (options.createIndex()) {
ExternalSpecsWriter.generate(configuration);
SystemPropertiesWriter.generate(configuration);
configuration.mainIndex.addElements();
IndexBuilder allClassesIndex = new IndexBuilder(configuration, nodeprecated, true);

View File

@ -25,6 +25,7 @@
package jdk.javadoc.internal.doclets.formats.html;
import java.net.URI;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.EnumSet;
@ -2176,4 +2177,19 @@ public class HtmlDocletWriter {
HtmlTree.CODE(Text.of(className)),
links);
}
public URI resolveExternalSpecURI(URI specURI) {
if (!specURI.isAbsolute()) {
URI baseURI = configuration.getOptions().specBaseURI();
if (baseURI == null) {
baseURI = URI.create("../specs/");
}
if (!baseURI.isAbsolute() && !pathToRoot.isEmpty()) {
baseURI = URI.create(pathToRoot.getPath() + "/").resolve(baseURI);
}
specURI = baseURI.resolve(specURI);
}
return specURI;
}
}

View File

@ -22,6 +22,7 @@
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.javadoc.internal.doclets.formats.html;
import java.util.ArrayList;
@ -85,6 +86,7 @@ public class Navigation {
CONSTANT_VALUES,
DEPRECATED,
DOC_FILE,
EXTERNAL_SPECS,
HELP,
INDEX,
MODULE,
@ -316,6 +318,7 @@ public class Navigation {
case ALL_CLASSES:
case ALL_PACKAGES:
case CONSTANT_VALUES:
case EXTERNAL_SPECS:
case SERIALIZED_FORM:
case SEARCH:
case SYSTEM_PROPERTIES:

View File

@ -25,6 +25,8 @@
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;
@ -34,6 +36,7 @@ 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;
@ -54,9 +57,11 @@ 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.doctree.TextTree;
import com.sun.source.util.DocTreePath;
import jdk.javadoc.internal.doclets.formats.html.markup.ContentBuilder;
import jdk.javadoc.internal.doclets.formats.html.markup.HtmlAttr;
@ -80,6 +85,7 @@ 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.DocletConstants;
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;
@ -400,6 +406,20 @@ public class TagletWriterImpl extends TagletWriter {
return s.length() > SEE_TAG_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(" "));
}
private void appendSeparatorIfNotEmpty(ContentBuilder body) {
if (!body.isEmpty()) {
body.add(", ");
body.add(Text.NL);
}
}
/**
* {@return the output for a single {@code @see} tag}
*
@ -718,6 +738,54 @@ public class TagletWriterImpl extends TagletWriter {
return utils.docTrees.getElement(fabricatedPath);
}
@Override
public Content specTagOutput(Element holder, List<? extends SpecTree> specTags) {
ContentBuilder body = new ContentBuilder();
for (SpecTree st : specTags) {
appendSeparatorIfNotEmpty(body);
body.add(specTagToContent(holder, st));
}
if (body.isEmpty())
return body;
return new ContentBuilder(
HtmlTree.DT(contents.externalSpecifications),
HtmlTree.DD(body));
}
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), 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();
@ -810,13 +878,22 @@ public class TagletWriterImpl extends TagletWriter {
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 = Text.of(tagText);
result = tagContent;
} else {
HtmlId id = HtmlIds.forText(tagText, htmlWriter.indexAnchorTable);
result = HtmlTree.SPAN(id, HtmlStyle.searchTagResult, Text.of(tagText));
result = HtmlTree.SPAN(id, HtmlStyle.searchTagResult, tagContent);
if (options.createIndex() && !tagText.isEmpty()) {
String holder = new SimpleElementVisitor14<String, Void>() {

View File

@ -743,6 +743,11 @@ public enum HtmlStyle {
*/
docFilePage,
/**
* The class of the {@code body} element for the "external specifications" page.
*/
externalSpecsPage,
/**
* The class of the {@code body} element for the "help" page.
*/
@ -1007,6 +1012,11 @@ public enum HtmlStyle {
*/
packageUses,
/**
* The class for the list of references to an external specification.
*/
refList,
/**
* The class of a {@code section} element for a package in the serialized
* form page.

View File

@ -387,6 +387,15 @@ public class HtmlTree extends Content {
.add(body);
}
/**
* Creates an HTML {@code DETAILS} element.
*
* @return the element
*/
public static HtmlTree DETAILS() {
return new HtmlTree(TagName.DETAILS);
}
/**
* Creates an HTML {@code DETAILS} element.
*

View File

@ -160,11 +160,16 @@ doclet.Enclosing_Class=Enclosing class:
doclet.Enclosing_Interface=Enclosing interface:
doclet.Inheritance_Tree=Inheritance Tree
doclet.ReferencedIn=Referenced In
doclet.External_Specification=External Specification
doclet.External_Specifications=External Specifications
doclet.Specification=Specification
doclet.System_Property=System Property
doclet.systemProperties=System Properties
doclet.systemPropertiesSummary=System Properties Summary
doclet.Window_Source_title=Source code
doclet.Window_Help_title=API Help
# 0: number of references (> 1)
doclet.references={0} references
doclet.Window_Search_title=Search
doclet.search.main_heading=Search
@ -318,6 +323,9 @@ doclet.help.constants.body=\
# 0: link to System Properties page
doclet.help.systemProperties.body=\
The {0} page lists references to system properties.
# 0: link to External Specifications page
doclet.help.externalSpecifications.body=\
The {0} page lists references to external specifications.
doclet.help.footnote=\
This help file applies to API documentation generated by the standard doclet.
doclet.help.enum.intro=\
@ -403,6 +411,22 @@ doclet.Error_copying_legal_notices=Error while copying legal notices: {0}
# 0: the path; 1: the detail message for the exception
doclet.Error_invalid_path_for_legal_notices=Invalid path ''{0}'' for legal notices: {1}
# 0: URL; 1: an integer
doclet.extSpec.spec.has.multiple.titles=\
{1} different titles given in @spec tags for the external specification at {0}
# 0: name; 1: an integer
doclet.extSpec.title.for.multiple.specs=\
The title "{0}" is used for {1} different external specifications in @spec tags
# 0: name; 1: url
doclet.extSpec.title.url=\
title: "{0}", url: {1}
# 0: url; 1: name
doclet.extSpec.url.title=\
url: {0}, title: "{1}"
# option specifiers
doclet.usage.add-script.parameters=\
<file>
@ -663,6 +687,14 @@ doclet.usage.xdoclint-package.description=\
of the given package. Prefix the package specifier with - to\n\
disable checks for the specified packages.
doclet.usage.spec-base-url=\
<URL>
doclet.usage.spec-base-url.description=\
Specify a base URL for relative URLs in @spec tags
doclet.Invalid_URL=\
invalid URL: {0}
# L10N: do not localize the option name --no-frames
doclet.NoFrames_specified=\
The --no-frames option is no longer required and may be removed\n\

View File

@ -30,6 +30,9 @@ import java.io.IOException;
import java.io.OutputStream;
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;
@ -295,6 +298,12 @@ public abstract class BaseOptions {
*/
private int sourceTabSize;
/**
* Argument for command-line option {@code --spec-base-url}.
* The base URL for relative URLs in {@code @spec} tags.
*/
private URI specBaseURI;
/**
* Value for command-line option {@code --override-methods summary}
* or {@code --override-methods detail}.
@ -670,6 +679,26 @@ public abstract class BaseOptions {
}
},
new Option(resources, "--spec-base-url", 1) {
@Override
public boolean process(String opt, List<String> args) {
String arg = args.get(0);
try {
if (!arg.endsWith("/")) {
// to ensure that URI.resolve works as expected
arg += "/";
}
specBaseURI = new URI(arg);
return true;
} catch (URISyntaxException e) {
config.reporter.print(ERROR,
config.getDocResources().getText("doclet.Invalid_URL",
e.getMessage()));
return false;
}
}
},
new Hidden(resources, "--disable-javafx-strict-checks") {
@Override
public boolean process(String opt, List<String> args) {
@ -1050,6 +1079,14 @@ public abstract class BaseOptions {
return sourceTabSize;
}
/**
* Argument for command-line option {@code --spec-base-url}.
* The base URL for relative URLs in {@code @spec} tags.
*/
public URI specBaseURI() {
return specBaseURI;
}
/**
* Value for command-line option {@code --override-methods summary}
* or {@code --override-methods detail}.

View File

@ -365,6 +365,14 @@ ul.summary-list > li {
margin-bottom:15px;
line-height:1.4;
}
ul.ref-list {
margin-left:0;
padding:0;
margin:0;
}
ul.ref-list > li {
list-style:none;
}
.summary-table dl, .summary-table dl dt, .summary-table dl dd {
margin-top:0;
margin-bottom:1px;

View File

@ -0,0 +1,83 @@
/*
* Copyright (c) 2019, 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 java.util.List;
import javax.lang.model.element.Element;
import com.sun.source.doctree.DocTree;
import com.sun.source.doctree.SpecTree;
import jdk.javadoc.doclet.Taglet.Location;
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.DocFinder.Input;
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, true, EnumSet.allOf(Location.class));
}
@Override
public boolean isBlockTag() {
return true;
}
@Override
public void inherit(Input input, DocFinder.Output output) {
List<? extends SpecTree> tags = input.utils.getSpecTrees(input.element);
if (!tags.isEmpty()) {
CommentHelper ch = input.utils.getCommentHelper(input.element);
output.holder = input.element;
output.holderTag = tags.get(0);
output.inlineTags = input.isFirstSentence
? ch.getFirstSentenceTrees(output.holderTag)
: ch.getTags(output.holderTag);
}
}
@Override
public Content getAllBlockTagOutput(Element holder, TagletWriter writer) {
Utils utils = writer.configuration().utils;
List<? extends SpecTree> tags = utils.getSpecTrees(holder);
Element e = holder;
if (tags.isEmpty() && utils.isExecutableElement(holder)) {
Input input = new Input(utils, holder, this);
DocFinder.Output inheritedDoc = DocFinder.search(writer.configuration(), input);
if (inheritedDoc.holder != null) {
tags = utils.getSpecTrees(inheritedDoc.holder);
e = inheritedDoc.holder;
}
}
return writer.specTagOutput(e, tags);
}
}

View File

@ -621,6 +621,7 @@ public class TagletManager {
allTaglets.put(factoryTaglet.getName(), factoryTaglet);
addStandardTaglet(new SeeTaglet());
addStandardTaglet(new SpecTaglet());
// Standard inline tags
addStandardTaglet(new DocRootTaglet());

View File

@ -35,6 +35,7 @@ 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;
@ -195,10 +196,20 @@ public abstract class TagletWriter {
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 element the element that owns the doc comment
* @param systemPropertyTag the system property tag
*
* @return the output

View File

@ -25,28 +25,12 @@
package jdk.javadoc.internal.doclets.toolkit.util;
import java.util.List;
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.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import com.sun.source.doctree.AttributeTree;
import com.sun.source.doctree.AttributeTree.ValueKind;
import com.sun.source.doctree.AuthorTree;
import com.sun.source.doctree.BlockTagTree;
import com.sun.source.doctree.CommentTree;
import com.sun.source.doctree.DeprecatedTree;
import com.sun.source.doctree.DocCommentTree;
import com.sun.source.doctree.DocTree;
import com.sun.source.doctree.EndElementTree;
import com.sun.source.doctree.EntityTree;
import com.sun.source.doctree.IdentifierTree;
import com.sun.source.doctree.InlineTagTree;
import com.sun.source.doctree.LinkTree;
@ -60,7 +44,7 @@ import com.sun.source.doctree.SerialDataTree;
import com.sun.source.doctree.SerialFieldTree;
import com.sun.source.doctree.SerialTree;
import com.sun.source.doctree.SinceTree;
import com.sun.source.doctree.StartElementTree;
import com.sun.source.doctree.SpecTree;
import com.sun.source.doctree.TextTree;
import com.sun.source.doctree.ThrowsTree;
import com.sun.source.doctree.UnknownBlockTagTree;
@ -73,7 +57,18 @@ import com.sun.source.util.SimpleDocTreeVisitor;
import com.sun.source.util.TreePath;
import jdk.javadoc.internal.doclets.toolkit.BaseConfiguration;
import static com.sun.source.doctree.DocTree.Kind.*;
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.TypeKind;
import javax.lang.model.type.TypeMirror;
import java.util.List;
import static com.sun.source.doctree.DocTree.Kind.SEE;
import static com.sun.source.doctree.DocTree.Kind.SERIAL_FIELD;
/**
* A utility class.
@ -482,6 +477,11 @@ public class CommentHelper {
return node.getDescription();
}
@Override
public List<? extends DocTree> visitSpec(SpecTree node, Void p) {
return node.getTitle();
}
@Override
public List<? extends DocTree> visitThrows(ThrowsTree node, Void p) {
return node.getDescription();

View File

@ -64,6 +64,9 @@ public class DocPaths {
/** The name of the file for the element list. */
public static final DocPath ELEMENT_LIST = DocPath.create("element-list");
/** The name of the file for all references to external specifications. */
public static final DocPath EXTERNAL_SPECS = DocPath.create("external-specs.html");
/** The name of the image file showing a magnifying glass on the search box. */
public static final DocPath GLASS_IMG = DocPath.create("glass.png");

View File

@ -209,7 +209,7 @@ public class IndexItem {
Objects.requireNonNull(link);
switch (docTree.getKind()) {
case INDEX, SYSTEM_PROPERTY -> { }
case INDEX, SPEC, SYSTEM_PROPERTY -> { }
default -> throw new IllegalArgumentException(docTree.getKind().toString());
}
@ -337,7 +337,7 @@ public class IndexItem {
protected Category getCategory(DocTree docTree) {
return switch (docTree.getKind()) {
case INDEX, SYSTEM_PROPERTY -> Category.TAGS;
case INDEX, SPEC, SYSTEM_PROPERTY -> Category.TAGS;
default -> throw new IllegalArgumentException(docTree.getKind().toString());
};
}
@ -562,12 +562,12 @@ public class IndexItem {
String holder = getHolder();
String description = getDescription();
item.append("{")
.append("\"l\":\"").append(label).append("\",")
.append("\"l\":\"").append(escapeQuotes(label)).append("\",")
.append("\"h\":\"").append(holder).append("\",");
if (!description.isEmpty()) {
item.append("\"d\":\"").append(description).append("\",");
item.append("\"d\":\"").append(escapeQuotes(description)).append("\",");
}
item.append("\"u\":\"").append(url).append("\"")
item.append("\"u\":\"").append(escapeQuotes(url)).append("\"")
.append("}");
break;
@ -576,4 +576,8 @@ public class IndexItem {
}
return item.toString();
}
}
private String escapeQuotes(String s) {
return s.replace("\"", "\\\"");
}
}

View File

@ -101,6 +101,7 @@ import com.sun.source.doctree.SeeTree;
import com.sun.source.doctree.SerialDataTree;
import com.sun.source.doctree.SerialFieldTree;
import com.sun.source.doctree.SerialTree;
import com.sun.source.doctree.SpecTree;
import com.sun.source.doctree.StartElementTree;
import com.sun.source.doctree.TextTree;
import com.sun.source.doctree.ThrowsTree;
@ -2402,6 +2403,10 @@ public class Utils {
return getBlockTags(field, DocTree.Kind.SERIAL_FIELD, SerialFieldTree.class);
}
public List<? extends SpecTree> getSpecTrees(Element element) {
return getBlockTags(element, SPEC, SpecTree.class);
}
public List<ThrowsTree> getThrowsTrees(Element element) {
return getBlockTags(element,
t -> switch (t.getKind()) { case EXCEPTION, THROWS -> true; default -> false; },

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 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
@ -23,7 +23,7 @@
/*
* @test
* @bug 8254721
* @bug 8254721 6251738
* @summary Improve support for conditionally generated files
* @library /tools/lib ../../lib
* @modules jdk.javadoc/jdk.javadoc.internal.tool
@ -73,6 +73,17 @@ public class TestConditionalPages extends JavadocTester {
b -> checkOutput("index-all.html", b, "Deprecated"));
}
@Test
public void testExternalSpecs(Path base) throws IOException {
test(base, """
package p;
/** @spec http://example.com label. */
public class C { }
""",
"external-specs.html",
b -> checkOutput("index-all.html", b, "External&nbsp;Specifications"));
}
@Test
public void testSerializedForm(Path base) throws IOException {
test(base, """

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 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
@ -149,6 +149,7 @@ public class TestMetadata extends JavadocTester {
"constants-summary-page",
"deprecated-list-page",
"doc-file-page",
"external-specs-page",
"help-page",
"index-page",
"index-redirect-page",
@ -215,6 +216,7 @@ public class TestMetadata extends JavadocTester {
"ConstantsSummaryWriterImpl",
"DeprecatedListWriter",
"DocFileWriter",
"ExternalSpecsWriter",
"HelpWriter",
"IndexRedirectWriter",
"IndexWriter",
@ -343,6 +345,10 @@ public class TestMetadata extends JavadocTester {
passed("no constraint for user-provided doc-files");
break;
case "ExternalSpecsWriter":
check(generator, content, content.startsWith("external specifications"));
break;
case "HelpWriter":
check(generator, content, content.contains("help"));
break;
@ -396,7 +402,16 @@ public class TestMetadata extends JavadocTester {
case PACKAGES:
tb.writeJavaFiles(src,
"/** Package pA. {@systemProperty exampleProperty} */ package pA;",
"/** Class pA.CA. */ package pA; public class CA { @Deprecated public static final int ZERO = 0; }",
"""
/** Class pA.CA. */
package pA; public class CA {
/**
* First sentence.
* @spec http://example.com example reference
*/
@Deprecated public static final int ZERO = 0;
}
""",
"/** Anno pA.Anno, */ package pA; public @interface Anno { }",
"/** Serializable pA.Ser, */ package pA; public class Ser implements java.io.Serializable { }",
"/** Package pB. */ package pB;",
@ -411,7 +426,17 @@ public class TestMetadata extends JavadocTester {
new ModuleBuilder(tb, "mA")
.exports("pA")
.classes("/** Package mA/pA. */ package pA;")
.classes("/** Class mA/pA.CA. */ package pA; public class CA { @Deprecated public static int ZERO = 0; }")
.classes("""
/** Class mA/pA.CA. */
package pA;
public class CA {
/**
* First sentence.
* @spec http://example.com example reference
*/
@Deprecated public static int ZERO = 0;
}
""")
.write(src);
new ModuleBuilder(tb, "mB")
.exports("pB")

View File

@ -0,0 +1,432 @@
/*
* Copyright (c) 2021, 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.
*
* 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.
*/
/*
* @test
* @bug 6251738 8226279
* @summary JDK-8226279 javadoc should support a new at-spec tag
* @library /tools/lib ../../lib
* @modules jdk.javadoc/jdk.javadoc.internal.tool
* @build toolbox.ToolBox javadoc.tester.*
* @run main TestSpecTag
*/
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import javadoc.tester.JavadocTester;
import toolbox.ToolBox;
public class TestSpecTag extends JavadocTester {
public static void main(String... args) throws Exception {
TestSpecTag tester = new TestSpecTag();
tester.runTests(m -> new Object[] { Path.of(m.getName()) });
}
ToolBox tb = new ToolBox();
enum LinkKind { ABSOLUTE, RELATIVE }
@Test
public void testBadSpecBaseURI(Path base) throws IOException {
Path src = base.resolve("src");
tb.writeJavaFiles(src, "package p; public class C { }");
javadoc("-d", base.resolve("out").toString(),
"--spec-base-url", "http://[",
"--source-path", src.toString(),
"p");
checkExit(Exit.CMDERR);
checkOutput(Output.OUT, true,
"error: invalid URL: Expected closing bracket for IPv6 address at index 8: http://[");
}
@Test
public void testBadSpecURI(Path base) throws IOException {
Path src = base.resolve("src");
tb.writeJavaFiles(src, "package p; /** @spec http://[ label */ public class C { }");
javadoc("-d", base.resolve("out").toString(),
"--source-path", src.toString(),
"p");
checkExit(Exit.ERROR);
checkOutput(Output.OUT, true,
"testBadSpecURI/src/p/C.java:1:".replace('/', File.separatorChar)
+ " error: invalid URL: Expected closing bracket for IPv6 address at index 8: http://[");
checkOutput("p/C.html", true,
"""
<dl class="notes">
<dt>External Specifications</dt>
<dd><span id="label" class="search-tag-result">label</span></dd>
</dl>
""");
checkOutput("external-specs.html", true,
"""
<div class="col-first even-row-color">label</div>
<div class="col-last even-row-color">
<ul class="ref-list">
<li><code><a href="p/C.html#label">class p.C</a></code></li>
</ul>
</div>""");
}
@Test
public void testNavigation(Path base) throws IOException {
Path src = base.resolve("src");
tb.writeJavaFiles(src, "package p; /** @spec http://example.com label */ public class C { }");
javadoc("-d", base.resolve("out").toString(),
"--source-path", src.toString(),
"p");
checkExit(Exit.OK);
checkOutput("external-specs.html", true,
"""
<!-- ========= START OF TOP NAVBAR ======= -->
<div class="top-nav" id="navbar-top"><button id="navbar-toggle-button" aria-controls="navbar-top" aria-expanded="false" aria-label="Toggle navigation links"><span class="nav-bar-toggle-icon">&nbsp;</span><span class="nav-bar-toggle-icon">&nbsp;</span><span class="nav-bar-toggle-icon">&nbsp;</span></button>
<div class="skip-nav"><a href="#skip-navbar-top" title="Skip navigation links">Skip navigation links</a></div>
<ul id="navbar-top-firstrow" class="nav-list" title="Navigation">
<li><a href="p/package-summary.html">Package</a></li>
<li>Class</li>
<li><a href="p/package-tree.html">Tree</a></li>
<li><a href="index-all.html">Index</a></li>
<li><a href="help-doc.html#external-specs">Help</a></li>
</ul>
</div>
<div class="sub-nav">
<div id="navbar-sub-list"></div>
<div class="nav-list-search"><a href="search.html">SEARCH</a>
<input type="text" id="search-input" disabled placeholder="Search">
<input type="reset" id="reset-button" disabled value="reset">
</div>
</div>
<!-- ========= END OF TOP NAVBAR ========= -->
""");
}
@Test
public void testEncodedURI(Path base) throws IOException {
Path src = base.resolve("src");
// The default encoding for OpenJDK source files is ASCII.
// The following writes a file using UTF-8 containing a non-ASCII character (section)
// and a Unicode escape for another character (plus or minus)
tb.writeJavaFiles(src, """
package p;
/**
* @spec http://example.com/a+b space: plus
* @spec http://example.com/a%20b space: percent
* @spec http://example.com/a\u00A7b other: section; U+00A7, UTF-8 c2 a7
* @spec http://example.com/a\\u00B1b unicode: plus or minus; U+00B1, UTF-8 c2 b1
*/
public class C { }
""");
// Ensure the source file is read using UTF-8
javadoc("-d", base.resolve("out").toString(),
"--source-path", src.toString(),
"-encoding", "UTF-8",
"p");
checkExit(Exit.OK);
checkOutput("p/C.html", true,
"""
<dl class="notes">
<dt>External Specifications</dt>
<dd><a href="http://example.com/a+b"><span id="space:plus" class="search-tag-result">space: plus</span></a>,\s
<a href="http://example.com/a%20b"><span id="space:percent" class="search-tag-result">space: percent</span></a>,\s
<a href="http://example.com/a%C2%A7b"><span id="other:section;U+00A7,UTF-8c2a7" class="search-tag-result">other: section; U+00A7, UTF-8 c2 a7</span></a>,\s
<a href="http://example.com/a%C2%B1b"><span id="unicode:plusorminus;U+00B1,UTF-8c2b1" class="search-tag-result">unicode: plus or minus; U+00B1, UTF-8 c2 b1</span></a></dd>
</dl>
""");
checkOutput("external-specs.html", true,
"""
<div class="table-header col-first">Specification</div>
<div class="table-header col-last">Referenced In</div>
<div class="col-first even-row-color"><a href="http://example.com/a%C2%A7b">other: section; U+00A7, UTF-8 c2 a7</a></div>
<div class="col-last even-row-color">
<ul class="ref-list">
<li><code><a href="p/C.html#other:section;U+00A7,UTF-8c2a7">class p.C</a></code></li>
</ul>
</div>
<div class="col-first odd-row-color"><a href="http://example.com/a%20b">space: percent</a></div>
<div class="col-last odd-row-color">
<ul class="ref-list">
<li><code><a href="p/C.html#space:percent">class p.C</a></code></li>
</ul>
</div>
<div class="col-first even-row-color"><a href="http://example.com/a+b">space: plus</a></div>
<div class="col-last even-row-color">
<ul class="ref-list">
<li><code><a href="p/C.html#space:plus">class p.C</a></code></li>
</ul>
</div>
<div class="col-first odd-row-color"><a href="http://example.com/a%C2%B1b">unicode: plus or minus; U+00B1, UTF-8 c2 b1</a></div>
<div class="col-last odd-row-color">
<ul class="ref-list">
<li><code><a href="p/C.html#unicode:plusorminus;U+00B1,UTF-8c2b1">class p.C</a></code></li>
</ul>
</div>""");
}
@Test
public void testDuplicateRefs(Path base) throws IOException {
Path src = base.resolve("src");
tb.writeJavaFiles(src, """
package p;
/**
* @spec http://example.com/ example
*/
public class C {
/**
* @spec http://example.com/ example
*/
public void m() { }
/**
* @spec http://example.com/ example
*/
public int f;
}
""");
javadoc("-d", base.resolve("out").toString(),
"--source-path", src.toString(),
"p");
checkExit(Exit.OK);
checkOrder("p/C.html",
"<h1 title=\"Class C\" class=\"title\">Class C</h1>",
"""
<dt>External Specifications</dt>
<dd><a href="http://example.com/"><span id="example" class="search-tag-result">example</span></a></dd>
""",
"<section class=\"field-details\" id=\"field-detail\">",
"""
<dt>External Specifications</dt>
<dd><a href="http://example.com/"><span id="example-1" class="search-tag-result">example</span></a></dd>
""",
"<section class=\"detail\" id=\"m()\">",
"""
<dt>External Specifications</dt>
<dd><a href="http://example.com/"><span id="example-2" class="search-tag-result">example</span></a></dd>
""");
checkOutput("external-specs.html", true,
"""
<div class="col-first even-row-color"><a href="http://example.com/">example</a></div>
<div class="col-last even-row-color">
<ul class="ref-list">
<li><code><a href="p/C.html#example">class p.C</a></code></li>
<li><code><a href="p/C.html#example-1">p.C.f</a></code></li>
<li><code><a href="p/C.html#example-2">p.C.m()</a></code></li>
</ul>
</div>""");
}
@Test
public void testMultiple(Path base) throws IOException {
Path src = base.resolve("src");
tb.writeJavaFiles(src, """
package p;
/**
* First sentence.
* @spec http://example.com/1 example-1
* @spec http://example.com/2 example-2
*/
public class C { }
""");
javadoc("-d", base.resolve("out").toString(),
"--source-path", src.toString(),
"p");
checkExit(Exit.OK);
checkOutput("p/C.html", true,
"""
<dt>External Specifications</dt>
<dd><a href="http://example.com/1"><span id="example-1" class="search-tag-result">example-1</span></a>,\s
<a href="http://example.com/2"><span id="example-2" class="search-tag-result">example-2</span></a></dd>
""");
checkOutput("external-specs.html", true,
"""
<div class="col-first even-row-color"><a href="http://example.com/1">example-1</a></div>
<div class="col-last even-row-color">
<ul class="ref-list">
<li><code><a href="p/C.html#example-1">class p.C</a></code></li>
</ul>
</div>
<div class="col-first odd-row-color"><a href="http://example.com/2">example-2</a></div>
<div class="col-last odd-row-color">
<ul class="ref-list">
<li><code><a href="p/C.html#example-2">class p.C</a></code></li>
</ul>
</div>
""");
}
@Test
public void testMultipleTitlesForURL(Path base) throws IOException {
Path src = base.resolve("src");
tb.writeJavaFiles(src, """
package p;
/** Class C. */
public class C {
private C() { }
/**
* Method m1.
* @spec http://example.com/index.html first
*/
public void m1() { }
/**
* Method m2.
* @spec http://example.com/index.html second
*/
public void m2() { }
}
""");
javadoc("-d", base.resolve("out").toString(),
"--source-path", src.toString(),
"p");
checkExit(Exit.ERROR);
checkOutput(Output.OUT, true,
"""
error: 2 different titles given in @spec tags for the external specification at http://example.com/index.html
#FILE#:8: Note: url: http://example.com/index.html, title: "first"
* @spec http://example.com/index.html first
^
#FILE#:14: Note: url: http://example.com/index.html, title: "second"
* @spec http://example.com/index.html second
^
"""
.replace("#FILE#", src.resolve("p").resolve("C.java").toString()));
}
@Test
public void testMultipleURLsForTitle(Path base) throws IOException {
Path src = base.resolve("src");
tb.writeJavaFiles(src, """
package p;
/** Class C. */
public class C {
private C() { }
/**
* Method m1.
* @spec http://example.com/index1.html Example Title
*/
public void m1() { }
/**
* Method m2.
* @spec http://example.com/index2.html Example Title
*/
public void m2() { }
}
""");
javadoc("-d", base.resolve("out").toString(),
"--source-path", src.toString(),
"p");
checkExit(Exit.ERROR);
checkOutput(Output.OUT, true,
"""
error: The title "Example Title" is used for 2 different external specifications in @spec tags
#FILE#:8: Note: title: "Example Title", url: http://example.com/index1.html
* @spec http://example.com/index1.html Example Title
^
#FILE#:14: Note: title: "Example Title", url: http://example.com/index2.html
* @spec http://example.com/index2.html Example Title
^
"""
.replace("#FILE#", src.resolve("p").resolve("C.java").toString()));
}
@Test
public void testCombo(Path base) throws IOException {
for (LinkKind lk : LinkKind.values()) {
test(base, lk);
}
}
void test(Path base, LinkKind lk) throws IOException {
Path dir = Files.createDirectories(base.resolve(lk.toString()));
Path src = genSource(dir, lk);
javadoc("-d", dir.resolve("out").toString(),
"--spec-base-url", "http://example.com/",
"--source-path", src.toString(),
"p");
checkExit(Exit.OK);
checkOutput("p/C.html", true,
"""
<dl class="notes">
<dt>External Specifications</dt>
<dd><a href="http://example.com/#LK#"><span id="#LK#reference" \
class="search-tag-result">#LK# reference</span></a></dd>
</dl>"""
.replaceAll("#LK#", lk.toString().toLowerCase()));
checkOutput("external-specs.html", true,
"""
<div class="col-first even-row-color"><a href="http://example.com/#LK#">#LK# reference</a></div>
<div class="col-last even-row-color">
<ul class="ref-list">
<li><code><a href="p/C.html##LK#reference">class p.C</a></code></li>
</ul>
</div>"""
.replaceAll("#LK#", lk.toString().toLowerCase()));
}
Path genSource(Path base, LinkKind lk) throws IOException {
Path src = base.resolve("src");
String template = """
/**
* First sentence.
* @spec #SPEC#
*/
""";
String spec = switch (lk) {
case ABSOLUTE -> "http://example.com/absolute absolute reference";
case RELATIVE -> "relative relative reference";
};
String comment = template.replace("#SPEC#", spec);
tb.writeJavaFiles(src,
"package p;\n" + comment + "public class C { }");
return src;
}
}

View File

@ -21,6 +21,7 @@
@serialField: block ........ ...... ....... .... ........... ...... field ...... ........
@since: block overview module package type constructor method field ...... ........
{@snippet}: ..... overview module package type constructor method field inline ........
@spec: block overview module package type constructor method field inline ........
{@summary}: ..... overview module package type constructor method field inline ........
{@systemProperty}: ..... overview module package type constructor method field inline ........
@throws: block ........ ...... ....... .... constructor method ..... ...... ........

View File

@ -66,7 +66,7 @@ public class CheckManPageOptions {
static final PrintStream out = System.err;
List<String> MISSING_IN_MAN_PAGE = List.of();
List<String> MISSING_IN_MAN_PAGE = List.of("--spec-base-url");
void run(String... args) throws Exception {
var file = args.length == 0 ? findDefaultFile() : Path.of(args[0]);

View File

@ -0,0 +1,32 @@
/*
* Copyright (c) 2019, 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.
*
* 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.
*/
// key: compiler.err.dc.no.title
// key: compiler.note.note
// key: compiler.note.proc.messager
// run: backdoor
// options: -processor DocCommentProcessor -proc:only
/** @spec http://example.com */
class NoTitle { }

View File

@ -0,0 +1,32 @@
/*
* Copyright (c) 2019, 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.
*
* 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.
*/
// key: compiler.err.dc.no.url
// key: compiler.note.note
// key: compiler.note.proc.messager
// run: backdoor
// options: -processor DocCommentProcessor -proc:only
/** @spec */
class NoURL { }

View File

@ -599,6 +599,18 @@ public class DocCommentTester {
return null;
}
@Override
public Void visitSpec(SpecTree node, Void p) {
header(node);
indent(+1);
print("url", node.getURL());
print("title", node.getTitle());
indent(-1);
indent();
out.println("]");
return null;
}
@Override
public Void visitSnippet(SnippetTree node, Void p) {
header(node);

View File

@ -0,0 +1,94 @@
/*
* Copyright (c) 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.
*
* 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.
*/
/*
* @test
* @bug 6251738 8226279
* @summary javadoc should support a new at-spec tag
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.file
* jdk.compiler/com.sun.tools.javac.tree
* jdk.compiler/com.sun.tools.javac.util
* @build DocCommentTester
* @run main DocCommentTester SpecTest.java
*/
class SpecTest {
/**
* abc.
* @spec http://example.com title
*/
void block() {}
/*
DocComment[DOC_COMMENT, pos:1
firstSentence: 1
Text[TEXT, pos:1, abc.]
body: empty
block tags: 1
Spec[SPEC, pos:7
url:
Text[TEXT, pos:13, http://example.com]
title: 1
Text[TEXT, pos:32, title]
]
]
*/
/**
* abc.
* @spec
*/
void bad_no_url() {}
/*
DocComment[DOC_COMMENT, pos:1
firstSentence: 1
Text[TEXT, pos:1, abc.]
body: empty
block tags: 1
Erroneous[ERRONEOUS, pos:7, prefPos:11
code: compiler.err.dc.no.url
body: @spec
]
]
*/
/**
* abc.
* @spec http://example.com
*/
void bad_no_label() {}
/*
DocComment[DOC_COMMENT, pos:1
firstSentence: 1
Text[TEXT, pos:1, abc.]
body: empty
block tags: 1
Erroneous[ERRONEOUS, pos:7, prefPos:30
code: compiler.err.dc.no.title
body: @spec_http://example.com
]
]
*/
}

View File

@ -1142,6 +1142,12 @@ public class DPrinter {
return visitBlockTag(node, null);
}
public Void visitSpec(SpecTree node, Void p) {
printDocTree("url", node.getURL());
printList("title", node.getTitle());
return visitBlockTag(node, null);
}
public Void visitStartElement(StartElementTree node, Void p) {
printName("name", node.getName());
printList("attrs", node.getAttributes());