8164408: Add module support for @see, @link and @linkplain javadoc tags

Reviewed-by: jjg
This commit is contained in:
Hannes Wallnöfer 2020-06-09 18:18:22 +02:00
parent 9a8ace2c0a
commit ac2828ddf1
9 changed files with 649 additions and 123 deletions
src
jdk.compiler/share/classes/com/sun/tools/javac
jdk.javadoc/share/classes/jdk/javadoc/internal/doclets
test/langtools/jdk/javadoc/doclet

@ -464,31 +464,50 @@ public class JavacTrees extends DocTrees {
private Symbol attributeDocReference(TreePath path, DCReference ref) {
Env<AttrContext> env = getAttrContext(path);
if (env == null) return null;
if (ref.moduleName != null && ref.qualifierExpression == null && ref.memberName != null) {
// module name and member name without type
return null;
}
Log.DeferredDiagnosticHandler deferredDiagnosticHandler =
new Log.DeferredDiagnosticHandler(log);
try {
final TypeSymbol tsym;
final Name memberName;
final ModuleSymbol mdlsym;
if (ref.moduleName != null) {
mdlsym = modules.modulesInitialized() ?
modules.getObservableModule(names.fromString(ref.moduleName.toString()))
: null;
if (mdlsym == null) {
return null;
} else if (ref.qualifierExpression == null) {
return mdlsym;
}
} else {
mdlsym = modules.getDefaultModule();
}
if (ref.qualifierExpression == null) {
tsym = env.enclClass.sym;
memberName = (Name) ref.memberName;
} else {
// newSeeTree if the qualifierExpression is a type or package name.
// javac does not provide the exact method required, so
// we first check if qualifierExpression identifies a type,
// and if not, then we check to see if it identifies a package.
Type t = attr.attribType(ref.qualifierExpression, env);
if (t.isErroneous()) {
// Check if qualifierExpression is a type or package, using the methods javac provides.
// If no module name is given we check if qualifierExpression identifies a type.
// If that fails or we have a module name, use that to resolve qualifierExpression to
// a package or type.
Type t = ref.moduleName == null ? attr.attribType(ref.qualifierExpression, env) : null;
if (t == null || t.isErroneous()) {
JCCompilationUnit toplevel =
treeMaker.TopLevel(List.nil());
final ModuleSymbol msym = modules.getDefaultModule();
toplevel.modle = msym;
toplevel.packge = msym.unnamedPackage;
toplevel.modle = mdlsym;
toplevel.packge = mdlsym.unnamedPackage;
Symbol sym = attr.attribIdent(ref.qualifierExpression, toplevel);
if (sym == null)
if (sym == null) {
return null;
}
sym.complete();
@ -500,7 +519,15 @@ public class JavacTrees extends DocTrees {
return null;
}
} else {
if (ref.qualifierExpression.hasTag(JCTree.Tag.IDENT)) {
if (modules.modulesInitialized() && ref.moduleName == null && ref.memberName == null) {
// package/type does not exist, check if there is a matching module
ModuleSymbol moduleSymbol = modules.getObservableModule(names.fromString(ref.signature));
if (moduleSymbol != null) {
return moduleSymbol;
}
}
if (ref.qualifierExpression.hasTag(JCTree.Tag.IDENT) && ref.moduleName == null
&& ref.memberName == null) {
// fixup: allow "identifier" instead of "#identifier"
// for compatibility with javadoc
tsym = env.enclClass.sym;
@ -513,7 +540,7 @@ public class JavacTrees extends DocTrees {
Type e = t;
// If this is an array type convert to element type
while (e instanceof ArrayType)
e = ((ArrayType)e).elemtype;
e = ((ArrayType) e).elemtype;
tsym = e.tsym;
memberName = (Name) ref.memberName;
}

@ -428,9 +428,9 @@ public class DocCommentParser {
* Matching pairs of {@literal < >} are skipped. The text is terminated by the first
* unmatched }. It is an error if the beginning of the next tag is detected.
*/
// TODO: allowMember is currently ignored
// TODO: boolean allowMember should be enum FORBID, ALLOW, REQUIRE
// TODO: improve quality of parse to forbid bad constructions.
// TODO: update to use ReferenceParser
@SuppressWarnings("fallthrough")
protected DCReference reference(boolean allowMember) throws ParseException {
int pos = bp;
@ -485,91 +485,17 @@ public class DocCommentParser {
String sig = newString(pos, bp);
// Break sig apart into qualifiedExpr member paramTypes.
JCTree qualExpr;
Name member;
List<JCTree> paramTypes;
Log.DeferredDiagnosticHandler deferredDiagnosticHandler
= new Log.DeferredDiagnosticHandler(fac.log);
try {
int hash = sig.indexOf("#");
int lparen = sig.indexOf("(", hash + 1);
if (hash == -1) {
if (lparen == -1) {
qualExpr = parseType(sig);
member = null;
} else {
qualExpr = null;
member = parseMember(sig.substring(0, lparen));
}
} else {
qualExpr = (hash == 0) ? null : parseType(sig.substring(0, hash));
if (lparen == -1)
member = parseMember(sig.substring(hash + 1));
else
member = parseMember(sig.substring(hash + 1, lparen));
}
if (lparen < 0) {
paramTypes = null;
} else {
int rparen = sig.indexOf(")", lparen);
if (rparen != sig.length() - 1)
throw new ParseException("dc.ref.bad.parens");
paramTypes = parseParams(sig.substring(lparen + 1, rparen));
}
if (!deferredDiagnosticHandler.getDiagnostics().isEmpty())
throw new ParseException("dc.ref.syntax.error");
} finally {
fac.log.popDiagnosticHandler(deferredDiagnosticHandler);
ReferenceParser.Reference ref = new ReferenceParser(fac).parse(sig);
return m.at(pos).newReferenceTree(sig,
ref.moduleName, ref.qualExpr,
ref.member, ref.paramTypes)
.setEndPos(bp);
} catch (ReferenceParser.ParseException parseException) {
throw new ParseException(parseException.getMessage());
}
return m.at(pos).newReferenceTree(sig, qualExpr, member, paramTypes).setEndPos(bp);
}
JCTree parseType(String s) throws ParseException {
JavacParser p = fac.newParser(s, false, false, false);
JCTree tree = p.parseType();
if (p.token().kind != TokenKind.EOF)
throw new ParseException("dc.ref.unexpected.input");
return tree;
}
Name parseMember(String s) throws ParseException {
JavacParser p = fac.newParser(s, false, false, false);
Name name = p.ident();
if (p.token().kind != TokenKind.EOF)
throw new ParseException("dc.ref.unexpected.input");
return name;
}
List<JCTree> parseParams(String s) throws ParseException {
if (s.trim().isEmpty())
return List.nil();
JavacParser p = fac.newParser(s.replace("...", "[]"), false, false, false);
ListBuffer<JCTree> paramTypes = new ListBuffer<>();
paramTypes.add(p.parseType());
if (p.token().kind == TokenKind.IDENTIFIER)
p.nextToken();
while (p.token().kind == TokenKind.COMMA) {
p.nextToken();
paramTypes.add(p.parseType());
if (p.token().kind == TokenKind.IDENTIFIER)
p.nextToken();
}
if (p.token().kind != TokenKind.EOF)
throw new ParseException("dc.ref.unexpected.input");
return paramTypes.toList();
}
/**

@ -47,6 +47,7 @@ public class ReferenceParser {
* Any, but not all, of the member fields may be null.
*/
static public class Reference {
public final JCTree.JCExpression moduleName;
/** The type, if any, in the signature. */
public final JCTree qualExpr;
/** The member name, if any, in the signature. */
@ -54,7 +55,8 @@ public class ReferenceParser {
/** The parameter types, if any, in the signature. */
public final List<JCTree> paramTypes;
Reference(JCTree qualExpr, Name member, List<JCTree> paramTypes) {
Reference(JCTree.JCExpression moduleName, JCTree qualExpr, Name member, List<JCTree> paramTypes) {
this.moduleName = moduleName;
this.qualExpr = qualExpr;
this.member = member;
this.paramTypes = paramTypes;
@ -89,7 +91,8 @@ public class ReferenceParser {
*/
public Reference parse(String sig) throws ParseException {
// Break sig apart into qualifiedExpr member paramTypes.
// Break sig apart into moduleName qualifiedExpr member paramTypes.
JCTree.JCExpression moduleName;
JCTree qualExpr;
Name member;
List<JCTree> paramTypes;
@ -98,18 +101,27 @@ public class ReferenceParser {
= new Log.DeferredDiagnosticHandler(fac.log);
try {
int hash = sig.indexOf("#");
int lparen = sig.indexOf("(", hash + 1);
if (hash == -1) {
int slash = sig.indexOf("/");
int hash = sig.indexOf("#", slash + 1);
int lparen = sig.indexOf("(", Math.max(slash, hash) + 1);
if (slash > -1) {
moduleName = parseModule(sig.substring(0, slash));
} else {
moduleName = null;
}
if (slash > 0 && sig.length() == slash + 1) {
qualExpr = null;
member = null;
} else if (hash == -1) {
if (lparen == -1) {
qualExpr = parseType(sig);
qualExpr = parseType(sig.substring(slash + 1));
member = null;
} else {
qualExpr = null;
member = parseMember(sig.substring(0, lparen));
member = parseMember(sig.substring(slash + 1, lparen));
}
} else {
qualExpr = (hash == 0) ? null : parseType(sig.substring(0, hash));
qualExpr = (hash == slash + 1) ? null : parseType(sig.substring(slash + 1, hash));
if (lparen == -1)
member = parseMember(sig.substring(hash + 1));
else
@ -132,7 +144,15 @@ public class ReferenceParser {
fac.log.popDiagnosticHandler(deferredDiagnosticHandler);
}
return new Reference(qualExpr, member, paramTypes);
return new Reference(moduleName, qualExpr, member, paramTypes);
}
private JCTree.JCExpression parseModule(String s) throws ParseException {
JavacParser p = fac.newParser(s, false, false, false);
JCTree.JCExpression expr = p.qualident(false);
if (p.token().kind != TokenKind.EOF)
throw new ParseException("dc.ref.unexpected.input");
return expr;
}
private JCTree parseType(String s) throws ParseException {

@ -647,13 +647,15 @@ public abstract class DCTree implements DocTree {
// The following are not directly exposed through ReferenceTree
// use DocTrees.getElement(DocTreePath)
public final JCTree.JCExpression moduleName;
public final JCTree qualifierExpression;
public final Name memberName;
public final List<JCTree> paramTypes;
DCReference(String signature, JCTree qualExpr, Name member, List<JCTree> paramTypes) {
DCReference(String signature, JCTree.JCExpression moduleName, JCTree qualExpr, Name member, List<JCTree> paramTypes) {
this.signature = signature;
this.moduleName = moduleName;
qualifierExpression = qualExpr;
memberName = member;
this.paramTypes = paramTypes;

@ -378,7 +378,7 @@ public class DocTreeMaker implements DocTreeFactory {
public DCReference newReferenceTree(String signature) {
try {
ReferenceParser.Reference ref = referenceParser.parse(signature);
DCReference tree = new DCReference(signature, ref.qualExpr, ref.member, ref.paramTypes);
DCReference tree = new DCReference(signature, ref.moduleName, ref.qualExpr, ref.member, ref.paramTypes);
tree.pos = pos;
return tree;
} catch (ReferenceParser.ParseException e) {
@ -386,8 +386,8 @@ public class DocTreeMaker implements DocTreeFactory {
}
}
public DCReference newReferenceTree(String signature, JCTree qualExpr, Name member, List<JCTree> paramTypes) {
DCReference tree = new DCReference(signature, qualExpr, member, paramTypes);
public DCReference newReferenceTree(String signature, JCTree.JCExpression moduleName, JCTree qualExpr, Name member, List<JCTree> paramTypes) {
DCReference tree = new DCReference(signature, moduleName, qualExpr, member, paramTypes);
tree.pos = pos;
return tree;
}

@ -998,7 +998,7 @@ public class HtmlDocletWriter {
CommentHelper ch = utils.getCommentHelper(element);
String tagName = ch.getTagName(see);
String seetext = replaceDocRootDir(utils.normalizeNewlines(ch.getText(see)).toString());
String seetext = replaceDocRootDir(removeTrailingSlash(utils.normalizeNewlines(ch.getText(see)).toString()));
// Check if @see is an href or "string"
if (seetext.startsWith("<") || seetext.startsWith("\"")) {
return new RawHtml(seetext);
@ -1010,7 +1010,6 @@ public class HtmlDocletWriter {
Content text = plainOrCode(kind == LINK_PLAIN, new RawHtml(seetext));
TypeElement refClass = ch.getReferencedClass(see);
String refClassName = ch.getReferencedClassName(see);
Element refMem = ch.getReferencedMember(see);
String refMemName = ch.getReferencedMemberName(see);
@ -1018,6 +1017,10 @@ public class HtmlDocletWriter {
refMemName = refMem.toString();
}
if (refClass == null) {
ModuleElement refModule = ch.getReferencedModule(see);
if (refModule != null && utils.isIncluded(refModule)) {
return getModuleLink(refModule, label.isEmpty() ? text : label);
}
//@see is not referencing an included class
PackageElement refPackage = ch.getReferencedPackage(see);
if (refPackage != null && utils.isIncluded(refPackage)) {
@ -1028,9 +1031,11 @@ public class HtmlDocletWriter {
return getPackageLink(refPackage, label);
} else {
// @see is not referencing an included class, module or package. Check for cross links.
DocLink elementCrossLink = (configuration.extern.isModule(refClassName))
? getCrossModuleLink(utils.elementUtils.getModuleElement(refClassName)) :
(refPackage != null) ? getCrossPackageLink(refPackage) : null;
String refModuleName = ch.getReferencedModuleName(see);
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.createLink(elementCrossLink,
@ -1118,6 +1123,10 @@ public class HtmlDocletWriter {
}
}
private String removeTrailingSlash(String s) {
return s.endsWith("/") ? s.substring(0, s.length() -1) : s;
}
private Content plainOrCode(boolean plain, Content body) {
return (plain || body.isEmpty()) ? body : HtmlTree.CODE(body);
}

@ -31,7 +31,7 @@ import java.util.List;
import java.util.stream.Collectors;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.ModuleElement;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
@ -376,23 +376,18 @@ public class CommentHelper {
return null;
} else if (utils.isTypeElement(e)) {
return (TypeElement) e;
} else if (!utils.isPackage(e)) {
} else if (!utils.isPackage(e) && !utils.isModule(e)) {
return utils.getEnclosingTypeElement(e);
}
return null;
}
public String getReferencedClassName(DocTree dtree) {
Utils utils = configuration.utils;
Element e = getReferencedClass(dtree);
if (e != null) {
return utils.isTypeElement(e) ? utils.getSimpleName(e) : null;
}
public String getReferencedModuleName(DocTree dtree) {
String s = getReferencedSignature(dtree);
if (s == null) {
if (s == null || s.contains("#") || s.contains("(")) {
return null;
}
int n = s.indexOf("#");
int n = s.indexOf("/");
return (n == -1) ? s : s.substring(0, n);
}
@ -423,6 +418,15 @@ public class CommentHelper {
return null;
}
public ModuleElement getReferencedModule(DocTree dtree) {
Element e = getReferencedElement(dtree);
if (e != null && configuration.utils.isModule(e)) {
return (ModuleElement) e;
}
return null;
}
public List<? extends DocTree> getFirstSentenceTrees(List<? extends DocTree> body) {
return configuration.docEnv.getDocTrees().getFirstSentence(body);
}

@ -0,0 +1,255 @@
/*
* Copyright (c) 2020, 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 8164408
* @summary Add module support for see, link and linkplain javadoc tags
* @library /tools/lib ../../lib
* @modules
* jdk.javadoc/jdk.javadoc.internal.tool
* jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.main
* @build javadoc.tester.*
* @run main TestLinkTagletWithModule
*/
import java.nio.file.Path;
import java.nio.file.Paths;
import builder.ClassBuilder;
import builder.ClassBuilder.*;
import toolbox.ModuleBuilder;
import toolbox.ToolBox;
import javadoc.tester.JavadocTester;
public class TestLinkTagletWithModule extends JavadocTester {
final ToolBox tb;
private final Path src;
public static void main(String... args) throws Exception {
TestLinkTagletWithModule tester = new TestLinkTagletWithModule();
tester.runTests(m -> new Object[]{Paths.get(m.getName())});
}
TestLinkTagletWithModule() throws Exception {
tb = new ToolBox();
src = Paths.get("src");
generateSources();
}
@Test
public void testLinkModuleInternal(Path base) throws Exception {
Path out = base.resolve("out");
javadoc("-d", out.toString(),
"--module-source-path", src.toString(),
"--module", "m1,m2,m3",
"m2/com.m2.lib");
checkExit(Exit.OK);
checkOutput("m3/com/m3/app/App.html", true,
"""
<div class="block"><a href="../../../../m1/module-summary.html"><code>m1</code></a>
<a href="../../../../m1/module-summary.html"><code>m1</code></a>
<a href="../../../../m1/com/m1/lib/package-summary.html"><code>package link</code></a>
<a href="../../../../m1/com/m1/lib/Lib.html" title="class in com.m1.lib"><code>Lib</code></a>
<a href="../../../../m1/com/m1/lib/Lib.html#method(java.lang.String)"><code>Lib.method(java.lang.String)</code></a>
<a href="../../../../m1/com/m1/lib/Lib.html#method(java.lang.String)"><code>Lib.method(String)</code></a>
<a href="../../../../m2/module-summary.html">m2</a>
<a href="../../../../m2/module-summary.html">m2</a>
<a href="../../../../m2/com/m2/lib/package-summary.html">com.m2.lib</a>
<a href="../../../../m2/com/m2/lib/Lib.html" title="class in com.m2.lib">Lib</a>
<a href="../../../../m2/com/m2/lib/Lib.html#method(java.lang.String)">class link</a>
<a href="../../../../m2/com/m2/lib/Lib.html#method(java.lang.String)">Lib.method(String)</a></div>
""");
}
@Test
public void testLinkModuleExternal(Path base) throws Exception {
Path out1 = base.resolve("out1"), out2 = base.resolve("out2");
javadoc("-d", out1.toString(),
"--module-source-path", src.toString(),
"--module", "m1,m2",
"m2/com.m2.lib");
javadoc("-d", out2.toString(),
"--module-source-path", src.toString(),
"--add-modules", "m2",
"--module", "m3",
"-link", "../" + out1.getFileName());
checkExit(Exit.OK);
checkOutput("m3/com/m3/app/App.html", true,
"""
<div class="block"><a href="../../../../../out1/m1/module-summary.html" class="external-link"><code>m1</code></a>
<a href="../../../../../out1/m1/module-summary.html" class="external-link"><code>m1</code></a>
<a href="../../../../../out1/m1/com/m1/lib/package-summary.html" class="external-link"><code>package link</code></a>
<a href="../../../../../out1/m1/com/m1/lib/Lib.html" title="class or interface in com.m1.lib"\
class="external-link"><code>Lib</code></a>
<a href="../../../../../out1/m1/com/m1/lib/Lib.html#method(java.lang.String)" title="class or\
interface in com.m1.lib" class="external-link"><code>Lib.method(java.lang.String)</code></a>
<a href="../../../../../out1/m1/com/m1/lib/Lib.html#method(java.lang.String)" title="class or\
interface in com.m1.lib" class="external-link"><code>Lib.method(String)</code></a>
<a href="../../../../../out1/m2/module-summary.html" class="external-link">m2</a>
<a href="../../../../../out1/m2/module-summary.html" class="external-link">m2</a>
<a href="../../../../../out1/m2/com/m2/lib/package-summary.html" class="external-link">m2/com.m2.lib</a>
<a href="../../../../../out1/m2/com/m2/lib/Lib.html" title="class or interface in com.m2.lib" class="external-link">Lib</a>
<a href="../../../../../out1/m2/com/m2/lib/Lib.html#method(java.lang.String)" title="class or\
interface in com.m2.lib" class="external-link">class link</a>
<a href="../../../../../out1/m2/com/m2/lib/Lib.html#method(java.lang.String)" title="class or\
interface in com.m2.lib" class="external-link">Lib.method(String)</a></div>
""");
}
@Test
public void testLinkModuleSameNameInternal(Path base) throws Exception {
Path out = base.resolve("out");
javadoc("-d", out.toString(),
"--module-source-path", src.toString(),
"--module", "com.ex1,com.ex2");
checkExit(Exit.OK);
checkOutput("com.ex2/com/ex2/B.html", true,
"""
<div class="block"><a href="../../../com.ex1/com/ex1/package-summary.html"><code>package link</code></a>
<a href="../../../com.ex1/module-summary.html"><code>module link</code></a>
<a href="../../../com.ex1/com/ex1/package-summary.html"><code>com.ex1</code></a>
<a href="../../../com.ex1/com/ex1/A.html" title="class in com.ex1"><code>class link</code></a>
<a href="../../../com.ex1/com/ex1/A.html#m()"><code>A.m()</code></a>
<a href="../../../com.ex1/com/ex1/A.html#m()"><code>A.m()</code></a>
<a href="package-summary.html"><code>com.ex2</code></a>
<a href="../../module-summary.html"><code>com.ex2</code></a></div>
""");
}
@Test
public void testLinkModuleSameNameExternal(Path base) throws Exception {
Path out1 = base.resolve("out1"), out2 = base.resolve("out2");
javadoc("-d", out1.toString(),
"--module-source-path", src.toString(),
"--module", "com.ex1");
javadoc("-d", out2.toString(),
"--module-source-path", src.toString(),
"--module", "com.ex2",
"-link", "../" + out1.getFileName());
checkExit(Exit.OK);
checkOutput("com.ex2/com/ex2/B.html", true,
"""
<div class="block"><a href="../../../../out1/com.ex1/com/ex1/package-summary.html" class="external-link"><code>package link</code></a>
<a href="../../../../out1/com.ex1/module-summary.html" class="external-link"><code>module link</code></a>
<a href="../../../../out1/com.ex1/com/ex1/package-summary.html" class="external-link"><code>com.ex1/com.ex1</code></a>
<a href="../../../../out1/com.ex1/com/ex1/A.html" title="class or interface in com.ex1" class="external-link"><code>class link</code></a>
<a href="../../../../out1/com.ex1/com/ex1/A.html#m()" title="class or interface in com.ex1" class="external-link"><code>A.m()</code></a>
<a href="../../../../out1/com.ex1/com/ex1/A.html#m()" title="class or interface in com.ex1" class="external-link"><code>A.m()</code></a>
<a href="package-summary.html"><code>com.ex2</code></a>
<a href="../../module-summary.html"><code>com.ex2</code></a></div>
""");
}
void generateSources() throws Exception {
new ModuleBuilder(tb, "m1")
.exports("com.m1.lib")
.classes("""
package com.m1.lib;
public class Lib {
public String method(String s) {
return s;
}
}""")
.write(src);
new ModuleBuilder(tb, "m2")
.classes("""
package com.m2.lib;
public class Lib {
public String method(String s) {
return s;
}
}""")
.write(src);
new ModuleBuilder(tb, "m3")
.exports("com.m3.app")
.requires("m1")
.classes("""
package com.m3.app;\s
public class App{
/**
* {@link m1}
* {@link m1/}
* {@link m1/com.m1.lib package link}
* {@link m1/com.m1.lib.Lib}
* {@link m1/com.m1.lib.Lib#method}
* {@link m1/com.m1.lib.Lib#method(String)}
* {@linkplain m2}
* {@linkplain m2/}
* {@linkplain m2/com.m2.lib}
* {@linkplain m2/com.m2.lib.Lib}
* {@linkplain m2/com.m2.lib.Lib#method class link}
* {@linkplain m2/com.m2.lib.Lib#method(String)}
*/
public App(){}
}
""")
.write(src);
new ModuleBuilder(tb, "com.ex1")
.exports("com.ex1")
.classes("""
package com.ex1;
public class A{
public void m() {}
}""",
"""
package com.ex1;
public class B {}""")
.write(src);
new ModuleBuilder(tb, "com.ex2")
.requires("com.ex1")
.exports("com.ex2")
.classes("""
package com.ex2;\s
import com.ex1.A;
public class B{
/**
* {@link com.ex1 package link}
* {@link com.ex1/ module link}
* {@link com.ex1/com.ex1}
* {@link com.ex1/com.ex1.A class link}
* {@link com.ex1/com.ex1.A#m}
* {@link com.ex1/com.ex1.A#m()}
* {@link com.ex2}
* {@link com.ex2/}
*/
public B(A obj){}
}
""")
.write(src);
}
}

@ -0,0 +1,283 @@
/*
* Copyright (c) 2020, 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 8164408
* @summary Add module support for see, link and linkplain javadoc tags
* @library /tools/lib ../../lib
* @modules
* jdk.javadoc/jdk.javadoc.internal.tool
* jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.main
* @build javadoc.tester.*
* @run main TestSeeTagWithModule
*/
import java.nio.file.Path;
import java.nio.file.Paths;
import builder.ClassBuilder;
import builder.ClassBuilder.*;
import toolbox.ModuleBuilder;
import toolbox.ToolBox;
import javadoc.tester.JavadocTester;
public class TestSeeTagWithModule extends JavadocTester {
final ToolBox tb;
private final Path src;
public static void main(String... args) throws Exception {
TestSeeTagWithModule tester = new TestSeeTagWithModule();
tester.runTests(m -> new Object[]{Paths.get(m.getName())});
}
TestSeeTagWithModule() throws Exception {
tb = new ToolBox();
src = Paths.get("src");
generateSources();
}
@Test
public void testSeeModuleInternal(Path base) throws Exception {
Path out = base.resolve("out");
javadoc("-d", out.toString(),
"--module-source-path", src.toString(),
"--module", "m1,m2,m3",
"m2/com.m2.lib");
checkExit(Exit.OK);
checkOutput("m3/com/m3/app/App.html", true,
"""
<dt>See Also:</dt>
<dd><a href="../../../../m1/module-summary.html"><code>m1</code></a>,\s
<a href="../../../../m1/module-summary.html"><code>m1</code></a>,\s
<a href="../../../../m1/com/m1/lib/package-summary.html"><code>com.m1.lib</code></a>,\s
<a href="../../../../m1/com/m1/lib/Lib.html" title="class in com.m1.lib"><code>Lib</code></a>,\s
<a href="../../../../m1/com/m1/lib/Lib.html#method(java.lang.String)"><code>Lib.method(java.lang.String)</code></a>,\s
<a href="../../../../m1/com/m1/lib/Lib.html#method(java.lang.String)"><code>Lib.method(String)</code></a>,\s
<a href="../../../../m2/module-summary.html"><code>m2</code></a>,\s
<a href="../../../../m2/module-summary.html"><code>m2</code></a>,\s
<a href="../../../../m2/com/m2/lib/package-summary.html"><code>com.m2.lib</code></a>,\s
<a href="../../../../m2/com/m2/lib/Lib.html" title="class in com.m2.lib"><code>Lib</code></a>,\s
<a href="../../../../m2/com/m2/lib/Lib.html#method(java.lang.String)"><code>Lib.method(java.lang.String)</code></a>,\s
<a href="../../../../m2/com/m2/lib/Lib.html#method(java.lang.String)"><code>Lib.method(String)</code></a></dd>
""");
}
@Test
public void testSeeModuleExternal(Path base) throws Exception {
Path out1 = base.resolve("out1"), out2 = base.resolve("out2");
javadoc("-d", out1.toString(),
"--module-source-path", src.toString(),
"--module", "m1,m2",
"m2/com.m2.lib");
javadoc("-d", out2.toString(),
"--module-source-path", src.toString(),
"--add-modules", "m2",
"--module", "m3",
"-link", "../" + out1.getFileName());
checkExit(Exit.OK);
checkOutput("m3/com/m3/app/App.html", true,
"""
<dt>See Also:</dt>
<dd><a href="../../../../../out1/m1/module-summary.html" class="external-link"><code>m1</code></a>,\s
<a href="../../../../../out1/m1/module-summary.html" class="external-link"><code>m1</code></a>,\s
<a href="../../../../../out1/m1/com/m1/lib/package-summary.html" class="external-link"><code>m1/com.m1.lib</code></a>,\s
<a href="../../../../../out1/m1/com/m1/lib/Lib.html" title="class or interface in com.m1.lib" class="external-link"><code>Lib</code></a>,\s
<a href="../../../../../out1/m1/com/m1/lib/Lib.html#method(java.lang.String)" title="class or \
interface in com.m1.lib" class="external-link"><code>Lib.method(java.lang.String)</code></a>,\s
<a href="../../../../../out1/m1/com/m1/lib/Lib.html#method(java.lang.String)" title="class or \
interface in com.m1.lib" class="external-link"><code>Lib.method(String)</code></a>,\s
<a href="../../../../../out1/m2/module-summary.html" class="external-link"><code>m2</code></a>,\s
<a href="../../../../../out1/m2/module-summary.html" class="external-link"><code>m2</code></a>,\s
<a href="../../../../../out1/m2/com/m2/lib/package-summary.html" class="external-link"><code>m2/com.m2.lib</code></a>,\s
<a href="../../../../../out1/m2/com/m2/lib/Lib.html" title="class or interface in com.m2.lib" class="external-link"><code>Lib</code></a>,\s
<a href="../../../../../out1/m2/com/m2/lib/Lib.html#method(java.lang.String)" title="class or \
interface in com.m2.lib" class="external-link"><code>Lib.method(java.lang.String)</code></a>,\s
<a href="../../../../../out1/m2/com/m2/lib/Lib.html#method(java.lang.String)" title="class or \
interface in com.m2.lib" class="external-link"><code>Lib.method(String)</code></a></dd>
""");
}
@Test
public void testSeeModuleSameNameInternal(Path base) throws Exception {
Path out = base.resolve("out");
javadoc("-d", out.toString(),
"--module-source-path", src.toString(),
"--module", "com.ex1,com.ex2");
checkExit(Exit.OK);
checkOutput("com.ex2/com/ex2/B.html", true,
"""
<dt>See Also:</dt>
<dd><a href="../../../com.ex1/com/ex1/package-summary.html"><code>com.ex1</code></a>,\s
<a href="../../../com.ex1/module-summary.html"><code>com.ex1</code></a>,\s
<a href="../../../com.ex1/com/ex1/package-summary.html"><code>com.ex1</code></a>,\s
<a href="../../../com.ex1/com/ex1/A.html" title="class in com.ex1"><code>A</code></a>,\s
<a href="../../../com.ex1/com/ex1/A.html#m()"><code>A.m()</code></a>,\s
<a href="../../../com.ex1/com/ex1/A.html#m()"><code>A.m()</code></a>,\s
<a href="package-summary.html"><code>com.ex2</code></a>,\s
<a href="../../module-summary.html"><code>com.ex2</code></a></dd>
""");
}
@Test
public void testSeeModuleSameNameExternal(Path base) throws Exception {
Path out1 = base.resolve("out1"), out2 = base.resolve("out2");
javadoc("-d", out1.toString(),
"--module-source-path", src.toString(),
"--module", "com.ex1");
javadoc("-d", out2.toString(),
"--module-source-path", src.toString(),
"--module", "com.ex2",
"-link", "../" + out1.getFileName());
checkExit(Exit.OK);
checkOutput("com.ex2/com/ex2/B.html", true,
"""
<dt>See Also:</dt>
<dd><a href="../../../../out1/com.ex1/com/ex1/package-summary.html" class="external-link"><code>com.ex1</code></a>,\s
<a href="../../../../out1/com.ex1/module-summary.html" class="external-link"><code>com.ex1</code></a>,\s
<a href="../../../../out1/com.ex1/com/ex1/package-summary.html" class="external-link"><code>com.ex1/com.ex1</code></a>,\s
<a href="../../../../out1/com.ex1/com/ex1/A.html" title="class or interface in com.ex1" class="external-link"><code>A</code></a>,\s
<a href="../../../../out1/com.ex1/com/ex1/A.html#m()" title="class or interface in com.ex1" class="external-link"><code>A.m()</code></a>,\s
<a href="../../../../out1/com.ex1/com/ex1/A.html#m()" title="class or interface in com.ex1" class="external-link"><code>A.m()</code></a>,\s
<a href="package-summary.html"><code>com.ex2</code></a>,\s
<a href="../../module-summary.html"><code>com.ex2</code></a></dd>
""");
}
@Test
public void testMissingType(Path base) throws Exception {
Path out = base.resolve("outMissingType");
javadoc("-d", out.toString(),
"--module-source-path", src.toString(),
"--module", "fail");
checkExit(Exit.ERROR);
}
void generateSources() throws Exception {
new ModuleBuilder(tb, "m1")
.exports("com.m1.lib")
.classes("""
package com.m1.lib;
public class Lib {
public String method(String s) {
return s;
}
}""")
.write(src);
new ModuleBuilder(tb, "m2")
.classes("""
package com.m2.lib;
public class Lib {
public String method(String s) {
return s;
}
}""")
.write(src);
new ModuleBuilder(tb, "m3")
.exports("com.m3.app")
.requires("m1")
.classes("""
package com.m3.app;\s
public class App{
/**
* @see m1
* @see m1/
* @see m1/com.m1.lib
* @see m1/com.m1.lib.Lib
* @see m1/com.m1.lib.Lib#method
* @see m1/com.m1.lib.Lib#method(String)
* @see m2
* @see m2/
* @see m2/com.m2.lib
* @see m2/com.m2.lib.Lib
* @see m2/com.m2.lib.Lib#method
* @see m2/com.m2.lib.Lib#method(String)
*/
public App(){}
}
""")
.write(src);
new ModuleBuilder(tb, "com.ex1")
.exports("com.ex1")
.classes("""
package com.ex1;
public class A{
public void m() {}
}""",
"""
package com.ex1;
public class B {}""")
.write(src);
new ModuleBuilder(tb, "com.ex2")
.requires("com.ex1")
.exports("com.ex2")
.classes("""
package com.ex2;\s
import com.ex1.A;
public class B{
/**
* @see com.ex1
* @see com.ex1/
* @see com.ex1/com.ex1
* @see com.ex1/com.ex1.A
* @see com.ex1/com.ex1.A#m
* @see com.ex1/com.ex1.A#m()
* @see com.ex2
* @see com.ex2/
*/
public B(A obj){}
}
""")
.write(src);
new ModuleBuilder(tb, "fail")
.exports("pkg.fail")
.classes("""
package pkg.fail;
/**
* @see fail/#foo()
*/
public class F {
public void foo() {}
}
""")
.write(src);
}
}