changeset 59678:aff89df892b7

8164408: Add module support for @see, @link and @linkplain javadoc tags. Reviewed-by: jjg
author hannesw
date Tue, 09 Jun 2020 18:18:22 +0200
parents 7c34ed647c97
children c2b620c5479f
files src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTrees.java src/jdk.compiler/share/classes/com/sun/tools/javac/parser/DocCommentParser.java src/jdk.compiler/share/classes/com/sun/tools/javac/parser/ReferenceParser.java src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DCTree.java src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocTreeMaker.java src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDocletWriter.java src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/CommentHelper.java test/langtools/jdk/javadoc/doclet/testLinkTaglet/TestLinkTagletWithModule.java test/langtools/jdk/javadoc/doclet/testSeeTag/TestSeeTagWithModule.java
diffstat 9 files changed, 649 insertions(+), 123 deletions(-) [+]
line wrap: on
line diff
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTrees.java	Tue Jun 09 16:09:55 2020 +0000
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTrees.java	Tue Jun 09 18:18:22 2020 +0200
@@ -464,31 +464,50 @@
     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 @@
                             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 @@
                     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;
                 }
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/DocCommentParser.java	Tue Jun 09 16:09:55 2020 +0000
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/DocCommentParser.java	Tue Jun 09 18:18:22 2020 +0200
@@ -428,9 +428,9 @@
      * 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 @@
 
         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();
     }
 
     /**
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/ReferenceParser.java	Tue Jun 09 16:09:55 2020 +0000
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/ReferenceParser.java	Tue Jun 09 18:18:22 2020 +0200
@@ -47,6 +47,7 @@
      * 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 @@
         /** 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 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 @@
                 = 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 @@
             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 {
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DCTree.java	Tue Jun 09 16:09:55 2020 +0000
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DCTree.java	Tue Jun 09 18:18:22 2020 +0200
@@ -647,13 +647,15 @@
 
         // 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;
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocTreeMaker.java	Tue Jun 09 16:09:55 2020 +0000
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocTreeMaker.java	Tue Jun 09 18:18:22 2020 +0200
@@ -378,7 +378,7 @@
     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 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;
     }
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDocletWriter.java	Tue Jun 09 16:09:55 2020 +0000
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDocletWriter.java	Tue Jun 09 18:18:22 2020 +0200
@@ -998,7 +998,7 @@
 
         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 @@
         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 @@
             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 @@
                 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 @@
         }
     }
 
+    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);
     }
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/CommentHelper.java	Tue Jun 09 16:09:55 2020 +0000
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/CommentHelper.java	Tue Jun 09 18:18:22 2020 +0200
@@ -31,7 +31,7 @@
 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 @@
             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 @@
         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);
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/langtools/jdk/javadoc/doclet/testLinkTaglet/TestLinkTagletWithModule.java	Tue Jun 09 18:18:22 2020 +0200
@@ -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);
+
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/langtools/jdk/javadoc/doclet/testSeeTag/TestSeeTagWithModule.java	Tue Jun 09 18:18:22 2020 +0200
@@ -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);
+
+    }
+
+}