diff --git a/src/java.compiler/share/classes/javax/tools/DocumentationTool.java b/src/java.compiler/share/classes/javax/tools/DocumentationTool.java index b7e3af636d53..859ec424f5c4 100644 --- a/src/java.compiler/share/classes/javax/tools/DocumentationTool.java +++ b/src/java.compiler/share/classes/javax/tools/DocumentationTool.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2021, 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 @@ -184,7 +184,12 @@ enum Location implements JavaFileManager.Location { /** * Location to search for taglets. */ - TAGLET_PATH; + TAGLET_PATH, + + /** + * Location to search for snippets. + */ + SNIPPET_PATH; public String getName() { return name(); } diff --git a/src/jdk.compiler/share/classes/com/sun/source/doctree/AttributeTree.java b/src/jdk.compiler/share/classes/com/sun/source/doctree/AttributeTree.java index b3f3b324d1f2..facdbb79567c 100644 --- a/src/jdk.compiler/share/classes/com/sun/source/doctree/AttributeTree.java +++ b/src/jdk.compiler/share/classes/com/sun/source/doctree/AttributeTree.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2021, 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 @@ -29,7 +29,7 @@ import javax.lang.model.element.Name; /** - * A tree node for an attribute in an HTML element. + * A tree node for an attribute in an HTML element or tag. * * @since 1.8 */ diff --git a/src/jdk.compiler/share/classes/com/sun/source/doctree/DocTree.java b/src/jdk.compiler/share/classes/com/sun/source/doctree/DocTree.java index be317c28bad5..b0375d200c3f 100644 --- a/src/jdk.compiler/share/classes/com/sun/source/doctree/DocTree.java +++ b/src/jdk.compiler/share/classes/com/sun/source/doctree/DocTree.java @@ -37,7 +37,7 @@ public interface DocTree { enum Kind { /** * Used for instances of {@link AttributeTree} - * representing an HTML attribute. + * representing an attribute in an HTML element or tag. */ ATTRIBUTE, @@ -204,6 +204,12 @@ enum Kind { */ SINCE("since"), + /** + * Used for instances of {@link SnippetTree} + * representing an {@code @snippet} tag. + */ + SNIPPET("snippet"), + /** * Used for instances of {@link EndElementTree} * representing the start of an HTML element. diff --git a/src/jdk.compiler/share/classes/com/sun/source/doctree/DocTreeVisitor.java b/src/jdk.compiler/share/classes/com/sun/source/doctree/DocTreeVisitor.java index 33ef57dd8795..dc1e6a8deca2 100644 --- a/src/jdk.compiler/share/classes/com/sun/source/doctree/DocTreeVisitor.java +++ b/src/jdk.compiler/share/classes/com/sun/source/doctree/DocTreeVisitor.java @@ -287,6 +287,21 @@ default R visitProvides(ProvidesTree node, P p) { */ R visitSince(SinceTree node, P p); + /** + * Visits a {@code SnippetTree} node. + * + * @implSpec Visits the provided {@code SnippetTree} node + * by calling {@code visitOther(node, p)}. + * + * @param node the node being visited + * @param p a parameter value + * @return a result value + * @since 18 + */ + default R visitSnippet(SnippetTree node, P p) { + return visitOther(node, p); + } + /** * Visits a {@code StartElementTree} node. * @param node the node being visited diff --git a/src/jdk.compiler/share/classes/com/sun/source/doctree/SnippetTree.java b/src/jdk.compiler/share/classes/com/sun/source/doctree/SnippetTree.java new file mode 100644 index 000000000000..9247b790fdac --- /dev/null +++ b/src/jdk.compiler/share/classes/com/sun/source/doctree/SnippetTree.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2020, 2021, 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 @snippet} inline tag. + * + *
+ *    {@snippet :
+ *     body
+ *    }
+ *
+ *    {@snippet attributes}
+ *
+ *    {@snippet attributes :
+ *     body
+ *    }
+ * 
+ * + * @since 18 + */ +public interface SnippetTree extends InlineTagTree { + + /** + * Returns the list of the attributes of the {@code @snippet} tag. + * + * @return the list of the attributes + */ + List getAttributes(); + + /** + * Returns the body of the {@code @snippet} tag, or {@code null} if there is no body. + * + * @apiNote + * An instance of {@code SnippetTree} with an empty body differs from an + * instance of {@code SnippetTree} with no body. + * If a tag has no body, then calling this method returns {@code null}. + * If a tag has an empty body, then this method returns a {@code TextTree} + * whose {@link TextTree#getBody()} returns an empty string. + * + * @return the body of the tag, or {@code null} if there is no body + */ + TextTree getBody(); +} diff --git a/src/jdk.compiler/share/classes/com/sun/source/util/DocTreeFactory.java b/src/jdk.compiler/share/classes/com/sun/source/util/DocTreeFactory.java index 8499273e8abe..52c859fb7287 100644 --- a/src/jdk.compiler/share/classes/com/sun/source/util/DocTreeFactory.java +++ b/src/jdk.compiler/share/classes/com/sun/source/util/DocTreeFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2021, 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 @@ -58,6 +58,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.StartElementTree; import com.sun.source.doctree.SummaryTree; import com.sun.source.doctree.SystemPropertyTree; @@ -79,7 +80,7 @@ */ public interface DocTreeFactory { /** - * Creates a new {@code AttributeTree} object, to represent an HTML attribute in an HTML tag. + * Creates a new {@code AttributeTree} object, to represent an attribute in an HTML element or tag. * @param name the name of the attribute * @param vkind the kind of the attribute value * @param value the value, if any, of the attribute @@ -326,6 +327,15 @@ default ReturnTree newReturnTree(boolean isInline, List descr */ SinceTree newSinceTree(List text); + /** + * Creates a new {@code SnippetTree} object, to represent a {@code {@snippet }} tag. + * @param attributes the attributes of the tag + * @param text the body of the tag, or {@code null} if the tag has no body (not to be confused with an empty body) + * @return a {@code SnippetTree} object + * @since 18 + */ + SnippetTree newSnippetTree(List attributes, TextTree text); + /** * Creates a new {@code StartElementTree} object, to represent the start of an HTML element. * @param name the name of the HTML element diff --git a/src/jdk.compiler/share/classes/com/sun/source/util/DocTreeScanner.java b/src/jdk.compiler/share/classes/com/sun/source/util/DocTreeScanner.java index 54ce3b12ffa2..ef92d0b52b54 100644 --- a/src/jdk.compiler/share/classes/com/sun/source/util/DocTreeScanner.java +++ b/src/jdk.compiler/share/classes/com/sun/source/util/DocTreeScanner.java @@ -492,6 +492,23 @@ public R visitSince(SinceTree node, P p) { return scan(node.getBody(), p); } + /** + * {@inheritDoc} + * + * @implSpec This implementation scans the children in left to right order. + * + * @param node {@inheritDoc} + * @param p {@inheritDoc} + * @return the result of scanning + * @since 18 + */ + @Override + public R visitSnippet(SnippetTree node, P p) { + R r = scan(node.getAttributes(), p); + r = scanAndReduce(node.getBody(), p, r); + return r; + } + /** * {@inheritDoc} * diff --git a/src/jdk.compiler/share/classes/com/sun/source/util/SimpleDocTreeVisitor.java b/src/jdk.compiler/share/classes/com/sun/source/util/SimpleDocTreeVisitor.java index 2461ac4cf189..fea8778a9e07 100644 --- a/src/jdk.compiler/share/classes/com/sun/source/util/SimpleDocTreeVisitor.java +++ b/src/jdk.compiler/share/classes/com/sun/source/util/SimpleDocTreeVisitor.java @@ -448,6 +448,21 @@ public R visitSince(SinceTree node, P 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 18 + */ + @Override + public R visitSnippet(SnippetTree node, P p) { + return defaultAction(node, p); + } + /** * {@inheritDoc} * diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/DocCommentParser.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/DocCommentParser.java index ac018124d118..ff29793cb113 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/DocCommentParser.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/DocCommentParser.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2021, 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 @@ -1062,6 +1062,13 @@ protected boolean isWhitespace(char ch) { return Character.isWhitespace(ch); } + protected boolean isHorizontalWhitespace(char ch) { + // This parser treats `\f` as a line break (see `nextChar`). + // To be consistent with that behaviour, this method does the same. + // (see JDK-8273809) + return ch == ' ' || ch == '\t'; + } + protected void skipWhitespace() { while (bp < buflen && isWhitespace(ch)) { nextChar(); @@ -1397,6 +1404,93 @@ public DCTree parse(int pos) { } }, + // {@snippet attributes : + // body} + new TagParser(TagParser.Kind.INLINE, DCTree.Kind.SNIPPET) { + @Override + DCTree parse(int pos) throws ParseException { + skipWhitespace(); + List attributes = tagAttrs(); + // expect "}" or ":" + if (ch == '}') { + nextChar(); + return m.at(pos).newSnippetTree(attributes, null); + } else if (ch == ':') { + newline = false; + // consume ':' + nextChar(); + // expect optional whitespace followed by mandatory newline + while (bp < buflen && isHorizontalWhitespace(ch)) { + nextChar(); + } + // check that we are looking at newline + if (!newline) { + if (bp >= buf.length - 1) { + throw new ParseException("dc.no.content"); + } + throw new ParseException("dc.unexpected.content"); + } + // consume newline + nextChar(); + DCText text = inlineText(WhitespaceRetentionPolicy.RETAIN_ALL); + nextChar(); + return m.at(pos).newSnippetTree(attributes, text); + } else if (bp >= buf.length - 1) { + throw new ParseException("dc.no.content"); + } else { + throw new ParseException("dc.unexpected.content"); + } + } + + /* + * Reads a series of inline snippet tag attributes. + * + * Attributes are terminated by the first of ":" (colon) or + * an unmatched "}" (closing curly). + */ + private List tagAttrs() { + ListBuffer attrs = new ListBuffer<>(); + skipWhitespace(); + while (bp < buflen && isIdentifierStart(ch)) { + int namePos = bp; + Name name = readAttributeName(); + skipWhitespace(); + List value = null; + ValueKind vkind = ValueKind.EMPTY; + if (ch == '=') { + ListBuffer v = new ListBuffer<>(); + nextChar(); + skipWhitespace(); + if (ch == '\'' || ch == '"') { + newline = false; + vkind = (ch == '\'') ? ValueKind.SINGLE : ValueKind.DOUBLE; + char quote = ch; + nextChar(); + textStart = bp; + while (bp < buflen && ch != quote) { + nextChar(); + } + addPendingText(v, bp - 1); + nextChar(); + } else { + vkind = ValueKind.UNQUOTED; + textStart = bp; + // Stop on '}' and ':' for them to be re-consumed by non-attribute parts of tag + while (bp < buflen && (ch != '}' && ch != ':' && !isUnquotedAttrValueTerminator(ch))) { + nextChar(); + } + addPendingText(v, bp - 1); + } + skipWhitespace(); + value = v.toList(); + } + DCAttribute attr = m.at(namePos).newAttributeTree(name, vkind, value); + attrs.add(attr); + } + return attrs.toList(); + } + }, + // {@summary summary-text} new TagParser(TagParser.Kind.INLINE, DCTree.Kind.SUMMARY) { @Override diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DCTree.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DCTree.java index ec9443dc73aa..079fc1577c43 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DCTree.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DCTree.java @@ -857,6 +857,36 @@ public List getBody() { } } + public static class DCSnippet extends DCInlineTag implements SnippetTree { + public final List attributes; + public final DCText body; + + public DCSnippet(List attributes, DCText body) { + this.body = body; + this.attributes = attributes; + } + + @Override @DefinedBy(Api.COMPILER_TREE) + public Kind getKind() { + return Kind.SNIPPET; + } + + @Override @DefinedBy(Api.COMPILER_TREE) + public R accept(DocTreeVisitor v, D d) { + return v.visitSnippet(this, d); + } + + @Override @DefinedBy(Api.COMPILER_TREE) + public List getAttributes() { + return attributes; + } + + @Override @DefinedBy(Api.COMPILER_TREE) + public TextTree getBody() { + return body; + } + } + public static class DCStartElement extends DCEndPosTree implements StartElementTree { public final Name name; public final List attrs; diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocPretty.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocPretty.java index e2bf78c612a3..8356c20bff20 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocPretty.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocPretty.java @@ -490,6 +490,27 @@ public Void visitSince(SinceTree node, Void p) { return null; } + @Override @DefinedBy(Api.COMPILER_TREE) + public Void visitSnippet(SnippetTree node, Void p) { + try { + print("{"); + printTagName(node); + List attrs = node.getAttributes(); + if (!attrs.isEmpty()) { + print(" "); + print(attrs, " "); + } + if (node.getBody() != null) { + print(" :\n"); + print(node.getBody()); + } + print("}"); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + return null; + } + @Override @DefinedBy(Api.COMPILER_TREE) public Void visitStartElement(StartElementTree node, Void p) { try { diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocTreeMaker.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocTreeMaker.java index f8d843ee93c1..b885becb7d30 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocTreeMaker.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocTreeMaker.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2021, 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 @@ -29,7 +29,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.EnumSet; import java.util.List; import java.util.ListIterator; import java.util.Set; @@ -76,6 +75,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.DCStartElement; import com.sun.tools.javac.tree.DCTree.DCSummary; import com.sun.tools.javac.tree.DCTree.DCSystemProperty; @@ -431,6 +431,13 @@ public DCSince newSinceTree(List text) { return tree; } + @Override @DefinedBy(Api.COMPILER_TREE) + public DCSnippet newSnippetTree(List attributes, TextTree text) { + DCSnippet tree = new DCSnippet(cast(attributes), (DCText) text); + tree.pos = pos; + return tree; + } + @Override @DefinedBy(Api.COMPILER_TREE) public DCStartElement newStartElementTree(Name name, List attrs, boolean selfClosing) { DCStartElement tree = new DCStartElement(name, cast(attrs), selfClosing); diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDocletWriter.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDocletWriter.java index 0d6400b871f0..bca521bfe341 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDocletWriter.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDocletWriter.java @@ -1142,6 +1142,133 @@ public Content seeTagToContent(Element element, DocTree see, TagletWriterImpl.Co } } + // TODO: this method and seeTagToContent share much of the code; consider factoring common pieces out + public Content linkToContent(Element referrer, Element target, String targetSignature, String text) { + CommentHelper ch = utils.getCommentHelper(referrer); + + boolean isLinkPlain = false; // TODO: for now + Content labelContent = plainOrCode(isLinkPlain, Text.of(text)); + + TypeElement refClass = ch.getReferencedClass(target); + Element refMem = ch.getReferencedMember(target); + String refMemName = ch.getReferencedMemberName(targetSignature); + + if (refMemName == null && refMem != null) { + refMemName = refMem.toString(); + } + if (refClass == null) { + ModuleElement refModule = ch.getReferencedModule(target); + if (refModule != null && utils.isIncluded(refModule)) { + return getModuleLink(refModule, labelContent); + } + //@see is not referencing an included class + PackageElement refPackage = ch.getReferencedPackage(target); + if (refPackage != null && utils.isIncluded(refPackage)) { + //@see is referencing an included package + if (labelContent.isEmpty()) + labelContent = plainOrCode(isLinkPlain, + Text.of(refPackage.getQualifiedName())); + return getPackageLink(refPackage, labelContent); + } else { + // @see is not referencing an included class, module or package. Check for cross links. + String refModuleName = ch.getReferencedModuleName(targetSignature); + DocLink elementCrossLink = (refPackage != null) ? getCrossPackageLink(refPackage) : + (configuration.extern.isModule(refModuleName)) + ? getCrossModuleLink(utils.elementUtils.getModuleElement(refModuleName)) + : null; + if (elementCrossLink != null) { + // Element cross link found + return links.createExternalLink(elementCrossLink, labelContent); + } else { + // No cross link found so print warning +// TODO: +// messages.warning(ch.getDocTreePath(see), +// "doclet.see.class_or_package_not_found", +// "@" + tagName, +// seeText); + return labelContent; + } + } + } else if (refMemName == null) { + // Must be a class reference since refClass is not null and refMemName is null. + if (labelContent.isEmpty()) { + if (!refClass.getTypeParameters().isEmpty() && targetSignature.contains("<")) { + // If this is a generic type link try to use the TypeMirror representation. + +// TODO: +// TypeMirror refType = ch.getReferencedType(target); + TypeMirror refType = target.asType(); + + if (refType != null) { + return plainOrCode(isLinkPlain, getLink( + new HtmlLinkInfo(configuration, HtmlLinkInfo.Kind.DEFAULT, refType))); + } + } + labelContent = plainOrCode(isLinkPlain, Text.of(utils.getSimpleName(refClass))); + } + return getLink(new HtmlLinkInfo(configuration, HtmlLinkInfo.Kind.DEFAULT, refClass) + .label(labelContent)); + } else if (refMem == null) { + // Must be a member reference since refClass is not null and refMemName is not null. + // However, refMem is null, so this referenced member does not exist. + return labelContent; + } 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 (targetSignature.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 (this instanceof ClassWriterImpl writer) { + containing = writer.getTypeElement(); + } else if (!utils.isPublic(containing)) { +// TODO: +// messages.warning( +// ch.getDocTreePath(see), "doclet.see.class_or_package_not_accessible", +// tagName, utils.getFullyQualifiedName(containing)); + } else { +// TODO: +// messages.warning( +// ch.getDocTreePath(see), "doclet.see.class_or_package_not_found", +// tagName, seeText); + } + } + 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 getDocLink(HtmlLinkInfo.Kind.SEE_TAG, containing, + refMem, (labelContent.isEmpty() + ? plainOrCode(isLinkPlain, Text.of(text)) + : labelContent), null, false); + } + } + private String removeTrailingSlash(String s) { return s.endsWith("/") ? s.substring(0, s.length() -1) : s; } diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/TagletWriterImpl.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/TagletWriterImpl.java index caa4a80a9148..f4c352fe5464 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/TagletWriterImpl.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/TagletWriterImpl.java @@ -27,6 +27,7 @@ import java.util.ArrayList; import java.util.EnumSet; +import java.util.HashSet; import java.util.List; import java.util.Set; @@ -47,8 +48,10 @@ 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 com.sun.source.util.DocTreePath; 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; @@ -63,6 +66,8 @@ 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; @@ -374,6 +379,81 @@ public Content simpleBlockTagOutput(Element element, List sim HtmlTree.DD(body)); } + @Override + protected Content snippetTagOutput(Element element, SnippetTree tag, StyledText content) { + HtmlTree result = new HtmlTree(TagName.PRE).setStyle(HtmlStyle.snippet); + result.add(Text.of(utils.normalizeNewlines("\n"))); + content.consumeBy((styles, sequence) -> { + CharSequence text = utils.normalizeNewlines(sequence); + if (styles.isEmpty()) { + result.add(text); + } else { + Element e = null; + String t = null; + boolean linkEncountered = false; + Set classes = new HashSet<>(); + for (Style s : styles) { + if (s instanceof Style.Name n) { + classes.add(n.name()); + } else if (s instanceof Style.Link l) { + assert !linkEncountered; // TODO: do not assert; pick the first link report on subsequent + linkEncountered = true; + t = l.target(); + e = getLinkedElement(element, t); + if (e == null) { + // TODO: diagnostic output + } + } else if (s instanceof Style.Markup) { + } 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 (linkEncountered) { + assert e != null; + String line = sequence.toString(); + String strippedLine = line.strip(); + int idx = line.indexOf(strippedLine); + assert idx >= 0; // because the stripped line is a substring of the line being stripped + Text whitespace = Text.of(line.substring(0, idx)); + // If the leading whitespace is not excluded from the link, + // browsers might exhibit unwanted behavior. For example, a + // browser might display hand-click cursor while user hovers + // over that whitespace portion of the line; or use + // underline decoration. + c = new ContentBuilder(whitespace, htmlWriter.linkToContent(element, e, t, strippedLine)); + // We don't care about trailing whitespace. + } else { + c = HtmlTree.SPAN(Text.of(sequence)); + classes.forEach(((HtmlTree) c)::addStyle); + } + result.add(c); + } + }); + return result; + } + + /* + * 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 protected Content systemPropertyTagOutput(Element element, SystemPropertyTree tag) { String tagText = tag.getPropertyName().toString(); diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlStyle.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlStyle.java index f2bad7228246..08900de9b7f3 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlStyle.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlStyle.java @@ -75,6 +75,11 @@ public enum HtmlStyle { typeNameLabel, typeNameLink, + /** + * The class of the {@code pre} element presenting a snippet. + */ + snippet, + // // // The following constants are used for the main navigation bar that appears in the @@ -803,6 +808,7 @@ public enum HtmlStyle { * The class of the {@code body} element for the page for the class hierarchy. */ treePage, + // // diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard.properties b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard.properties index 0bb526fabf58..312e42bcf044 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard.properties +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard.properties @@ -538,6 +538,12 @@ doclet.usage.taglet.description=\ doclet.usage.tagletpath.description=\ The path to Taglets +doclet.usage.snippet-path.parameters=\ + + +doclet.usage.snippet-path.description=\ + The path for external snippets + doclet.usage.charset.parameters=\ doclet.usage.charset.description=\ diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/BaseConfiguration.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/BaseConfiguration.java index f3943d5e610e..e5f89ad2d3cd 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/BaseConfiguration.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/BaseConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2021, 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,9 +26,13 @@ package jdk.javadoc.internal.doclets.toolkit; +import java.io.File; import java.io.IOException; import java.io.InputStream; +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; @@ -50,8 +54,10 @@ 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.DocTreePath; @@ -374,6 +380,28 @@ protected boolean finishOptionSettings0() throws DocletException { extern.checkPlatformLinks(options.linkPlatformProperties(), reporter); } typeElementCatalog = new TypeElementCatalog(includedTypeElements, this); + + String snippetPath = options.snippetPath(); + if (snippetPath != null) { + Messages messages = getMessages(); + JavaFileManager fm = getFileManager(); + if (fm instanceof StandardJavaFileManager) { + try { + List sp = Arrays.stream(snippetPath.split(File.pathSeparator)) + .map(Path::of) + .toList(); + StandardJavaFileManager sfm = (StandardJavaFileManager) fm; + sfm.setLocationFromPaths(DocumentationTool.Location.SNIPPET_PATH, sp); + } catch (IOException | InvalidPathException e) { + throw new SimpleDocletException(messages.getResources().getText( + "doclet.error_setting_snippet_path", snippetPath, e), e); + } + } else { + throw new SimpleDocletException(messages.getResources().getText( + "doclet.cannot_use_snippet_path", snippetPath)); + } + } + initTagletManager(options.customTagStrs()); options.groupPairs().forEach(grp -> { if (showModules) { diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/BaseOptions.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/BaseOptions.java index 492ff91a2fe9..5fe0e5148843 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/BaseOptions.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/BaseOptions.java @@ -282,6 +282,12 @@ public abstract class BaseOptions { */ private String tagletPath = null; + /** + * Argument for command-line option {@code --snippet-path}. + * The path for external snippets. + */ + private String snippetPath = null; + // private final BaseConfiguration config; @@ -554,6 +560,14 @@ public boolean process(String opt, List args) { } }, + new Option(resources, "--snippet-path", 1) { + @Override + public boolean process(String opt, List args) { + snippetPath = args.get(0); + return true; + } + }, + new Option(resources, "-version") { @Override public boolean process(String opt, List args) { @@ -962,6 +976,14 @@ 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