8175346: javadoc does not handle Locations correctly with --patch-module
authorksrini
Thu, 16 Mar 2017 18:50:50 -0700
changeset 44301 2f97c71f06f4
parent 44300 4bd6416db3de
child 44302 d16aebbb56d3
child 44384 853dc621c62a
child 44385 f777a2822087
8175346: javadoc does not handle Locations correctly with --patch-module Reviewed-by: jjg
langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/ElementsTable.java
langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/resources/javadoc.properties
langtools/test/jdk/javadoc/tool/modules/ModuleTestBase.java
langtools/test/jdk/javadoc/tool/modules/Modules.java
langtools/test/jdk/javadoc/tool/modules/PatchModules.java
langtools/test/jdk/javadoc/tool/modules/ReleaseOptions.java
--- a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/ElementsTable.java	Thu Mar 16 17:13:10 2017 -0700
+++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/ElementsTable.java	Thu Mar 16 18:50:50 2017 -0700
@@ -53,6 +53,7 @@
 import javax.tools.StandardLocation;
 
 import com.sun.tools.javac.code.Kinds.Kind;
+import com.sun.tools.javac.code.Source;
 import com.sun.tools.javac.code.Symbol;
 import com.sun.tools.javac.code.Symbol.ClassSymbol;
 import com.sun.tools.javac.code.Symbol.CompletionFailure;
@@ -60,8 +61,11 @@
 import com.sun.tools.javac.code.Symbol.PackageSymbol;
 import com.sun.tools.javac.code.Symtab;
 import com.sun.tools.javac.comp.Modules;
+import com.sun.tools.javac.main.JavaCompiler;
 import com.sun.tools.javac.tree.JCTree.JCClassDecl;
 import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
+import com.sun.tools.javac.tree.JCTree.JCModuleDecl;
+import com.sun.tools.javac.tree.TreeInfo;
 import com.sun.tools.javac.util.Context;
 import com.sun.tools.javac.util.ListBuffer;
 import com.sun.tools.javac.util.Name;
@@ -70,7 +74,9 @@
 import jdk.javadoc.doclet.DocletEnvironment.ModuleMode;
 
 import static com.sun.tools.javac.code.Scope.LookupKind.NON_RECURSIVE;
+
 import static javax.tools.JavaFileObject.Kind.*;
+
 import static jdk.javadoc.internal.tool.Main.Result.*;
 import static jdk.javadoc.internal.tool.JavadocTool.isValidClassName;
 
@@ -153,10 +159,11 @@
     private final Symtab syms;
     private final Names names;
     private final JavaFileManager fm;
-    private final Location location;
+    private final List<Location> locations;
     private final Modules modules;
     private final Map<ToolOption, Object> opts;
     private final Messager messager;
+    private final JavaCompiler compiler;
 
     private final Map<String, Entry> entries = new LinkedHashMap<>();
 
@@ -201,12 +208,22 @@
         this.modules = Modules.instance(context);
         this.opts = opts;
         this.messager = Messager.instance0(context);
+        this.compiler = JavaCompiler.instance(context);
+        Source source = Source.instance(context);
 
-        this.location = modules.multiModuleMode
-                ? StandardLocation.MODULE_SOURCE_PATH
-                : toolEnv.fileManager.hasLocation(StandardLocation.SOURCE_PATH)
-                    ? StandardLocation.SOURCE_PATH
-                    : StandardLocation.CLASS_PATH;
+        List<Location> locs = new ArrayList<>();
+        if (modules.multiModuleMode) {
+            locs.add(StandardLocation.MODULE_SOURCE_PATH);
+        } else {
+            if (toolEnv.fileManager.hasLocation(StandardLocation.SOURCE_PATH))
+                locs.add(StandardLocation.SOURCE_PATH);
+            else
+                locs.add(StandardLocation.CLASS_PATH);
+        }
+        if (source.allowModules() && toolEnv.fileManager.hasLocation(StandardLocation.PATCH_MODULE_PATH))
+            locs.add(StandardLocation.PATCH_MODULE_PATH);
+        this.locations = Collections.unmodifiableList(locs);
+
         getEntry("").excluded = false;
 
         accessFilter = new ModifierFilter(opts);
@@ -341,6 +358,52 @@
         return this;
     }
 
+    /*
+     * This method sanity checks the following cases:
+     * a. a source-path containing a single module and many modules specified with --module
+     * b. no modules on source-path
+     * c. mismatched source-path and many modules specified with --module
+     */
+    void sanityCheckSourcePathModules(List<String> moduleNames) throws ToolException {
+        if (!haveSourceLocationWithModule)
+            return;
+
+        if (moduleNames.size() > 1) {
+            String text = messager.getText("main.cannot_use_sourcepath_for_modules",
+                    String.join(", ", moduleNames));
+            throw new ToolException(CMDERR, text);
+        }
+
+        String foundModule = getModuleName(StandardLocation.SOURCE_PATH);
+        if (foundModule == null) {
+            String text = messager.getText("main.module_not_found_on_sourcepath", moduleNames.get(0));
+            throw new ToolException(CMDERR, text);
+        }
+
+        if (!moduleNames.get(0).equals(foundModule)) {
+            String text = messager.getText("main.sourcepath_does_not_contain_module", moduleNames.get(0));
+            throw new ToolException(CMDERR, text);
+        }
+    }
+
+    private String getModuleName(Location location) throws ToolException {
+        try {
+            JavaFileObject jfo = fm.getJavaFileForInput(location,
+                    "module-info", JavaFileObject.Kind.SOURCE);
+            if (jfo != null) {
+                JCCompilationUnit jcu = compiler.parse(jfo);
+                JCModuleDecl module = TreeInfo.getModule(jcu);
+                if (module != null) {
+                    return module.getName().toString();
+                }
+            }
+        } catch (IOException ioe) {
+            String text = messager.getText("main.file.manager.list", location);
+            throw new ToolException(SYSERR, text, ioe);
+        }
+        return null;
+    }
+
     @SuppressWarnings("unchecked")
     ElementsTable scanSpecifiedItems() throws ToolException {
 
@@ -349,15 +412,17 @@
                 s -> Collections.EMPTY_LIST);
         List<String> mlist = new ArrayList<>();
         for (String m : moduleNames) {
-            Location moduleLoc = getModuleLocation(location, m);
-            if (moduleLoc == null) {
+            List<Location> moduleLocations = getModuleLocation(locations, m);
+            if (moduleLocations.isEmpty()) {
                 String text = messager.getText("main.module_not_found", m);
                 throw new ToolException(CMDERR, text);
-            } else {
-                mlist.add(m);
-                ModuleSymbol msym = syms.enterModule(names.fromString(m));
-                specifiedModuleElements.add((ModuleElement) msym);
+            }
+            if (moduleLocations.contains(StandardLocation.SOURCE_PATH)) {
+                sanityCheckSourcePathModules(moduleNames);
             }
+            mlist.add(m);
+            ModuleSymbol msym = syms.enterModule(names.fromString(m));
+            specifiedModuleElements.add((ModuleElement) msym);
         }
 
         // scan for modules with qualified packages
@@ -448,35 +513,41 @@
         });
 
         for (ModulePackage modpkg : subPackages) {
-            Location packageLocn = getLocation(modpkg);
-            Iterable<JavaFileObject> list = null;
-            try {
-                list = fm.list(packageLocn, modpkg.packageName, sourceKinds, true);
-            } catch (IOException ioe) {
-                String text = messager.getText("main.file.manager.list", modpkg.packageName);
-                throw new ToolException(SYSERR, text, ioe);
+            List<Location> locs = getLocation(modpkg);
+            for (Location loc : locs) {
+                addPackagesFromLocations(loc, modpkg);
             }
-            for (JavaFileObject fo : list) {
-                String binaryName = fm.inferBinaryName(packageLocn, fo);
-                String pn = getPackageName(binaryName);
-                String simpleName = getSimpleName(binaryName);
-                Entry e = getEntry(pn);
-                if (!e.isExcluded() && isValidClassName(simpleName)) {
-                    ModuleSymbol msym = (modpkg.hasModule())
-                            ? syms.getModule(names.fromString(modpkg.moduleName))
-                            : findModuleOfPackageName(modpkg.packageName);
+        }
+    }
 
-                    if (msym != null && !msym.isUnnamed()) {
-                        syms.enterPackage(msym, names.fromString(pn));
-                        ModulePackage npkg = new ModulePackage(msym.toString(), pn);
-                        cmdLinePackages.add(npkg);
-                    } else {
-                        cmdLinePackages.add(e.modpkg);
-                    }
-                    e.files = (e.files == null
-                            ? com.sun.tools.javac.util.List.of(fo)
-                            : e.files.prepend(fo));
+    private void addPackagesFromLocations(Location packageLocn, ModulePackage modpkg) throws ToolException {
+        Iterable<JavaFileObject> list = null;
+        try {
+            list = fm.list(packageLocn, modpkg.packageName, sourceKinds, true);
+        } catch (IOException ioe) {
+            String text = messager.getText("main.file.manager.list", modpkg.packageName);
+            throw new ToolException(SYSERR, text, ioe);
+        }
+        for (JavaFileObject fo : list) {
+            String binaryName = fm.inferBinaryName(packageLocn, fo);
+            String pn = getPackageName(binaryName);
+            String simpleName = getSimpleName(binaryName);
+            Entry e = getEntry(pn);
+            if (!e.isExcluded() && isValidClassName(simpleName)) {
+                ModuleSymbol msym = (modpkg.hasModule())
+                        ? syms.getModule(names.fromString(modpkg.moduleName))
+                        : findModuleOfPackageName(modpkg.packageName);
+
+                if (msym != null && !msym.isUnnamed()) {
+                    syms.enterPackage(msym, names.fromString(pn));
+                    ModulePackage npkg = new ModulePackage(msym.toString(), pn);
+                    cmdLinePackages.add(npkg);
+                } else {
+                    cmdLinePackages.add(e.modpkg);
                 }
+                e.files = (e.files == null
+                        ? com.sun.tools.javac.util.List.of(fo)
+                        : e.files.prepend(fo));
             }
         }
     }
@@ -544,20 +615,23 @@
     private Set<PackageElement> getAllModulePackages(ModuleElement mdle) throws ToolException {
         Set<PackageElement> result = new HashSet<>();
         ModuleSymbol msym = (ModuleSymbol) mdle;
-        Location msymloc = getModuleLocation(location, msym.name.toString());
-        try {
-            for (JavaFileObject fo : fm.list(msymloc, "", sourceKinds, true)) {
-                if (fo.getName().endsWith("module-info.java"))
-                    continue;
-                String binaryName = fm.inferBinaryName(msymloc, fo);
-                String pn = getPackageName(binaryName);
-                PackageSymbol psym = syms.enterPackage(msym, names.fromString(pn));
-                result.add((PackageElement) psym);
+        List<Location> msymlocs = getModuleLocation(locations, msym.name.toString());
+        for (Location msymloc : msymlocs) {
+            try {
+                for (JavaFileObject fo : fm.list(msymloc, "", sourceKinds, true)) {
+                    if (fo.getName().endsWith("module-info.java")) {
+                        continue;
+                    }
+                    String binaryName = fm.inferBinaryName(msymloc, fo);
+                    String pn = getPackageName(binaryName);
+                    PackageSymbol psym = syms.enterPackage(msym, names.fromString(pn));
+                    result.add((PackageElement) psym);
+                }
+
+            } catch (IOException ioe) {
+                String text = messager.getText("main.file.manager.list", msymloc.getName());
+                throw new ToolException(SYSERR, text, ioe);
             }
-
-        } catch (IOException ioe) {
-            String text = messager.getText("main.file.manager.list", msymloc.getName());
-            throw new ToolException(SYSERR, text, ioe);
         }
         return result;
     }
@@ -741,25 +815,25 @@
         }
 
         ListBuffer<JavaFileObject> lb = new ListBuffer<>();
-        Location packageLocn = getLocation(modpkg);
-        if (packageLocn == null) {
+        List<Location> locs = getLocation(modpkg);
+        if (locs.isEmpty()) {
             return Collections.emptyList();
         }
         String pname = modpkg.packageName;
-
-        try {
-            for (JavaFileObject fo : fm.list(packageLocn, pname, sourceKinds, recurse)) {
-                String binaryName = fm.inferBinaryName(packageLocn, fo);
-                String simpleName = getSimpleName(binaryName);
-                if (isValidClassName(simpleName)) {
-                    lb.append(fo);
+        for (Location packageLocn : locs) {
+            try {
+                for (JavaFileObject fo : fm.list(packageLocn, pname, sourceKinds, recurse)) {
+                    String binaryName = fm.inferBinaryName(packageLocn, fo);
+                    String simpleName = getSimpleName(binaryName);
+                    if (isValidClassName(simpleName)) {
+                        lb.append(fo);
+                    }
                 }
+            } catch (IOException ioe) {
+                String text = messager.getText("main.file.manager.list", pname);
+                throw new ToolException(SYSERR, text, ioe);
             }
-        } catch (IOException ioe) {
-            String text = messager.getText("main.file.manager.list", pname);
-            throw new ToolException(SYSERR, text, ioe);
         }
-
         return lb.toList();
     }
 
@@ -774,24 +848,49 @@
             return null;
     }
 
-    private Location getLocation(ModulePackage modpkg) throws ToolException {
-        if (location != StandardLocation.MODULE_SOURCE_PATH) {
-            return location;
+    private List<Location> getLocation(ModulePackage modpkg) throws ToolException {
+        if (locations.size() == 1 && !locations.contains(StandardLocation.MODULE_SOURCE_PATH)) {
+            return Collections.singletonList(locations.get(0));
         }
 
         if (modpkg.hasModule()) {
-            return getModuleLocation(location, modpkg.moduleName);
+            return getModuleLocation(locations, modpkg.moduleName);
         }
         // TODO: handle invalid results better.
         ModuleSymbol msym = findModuleOfPackageName(modpkg.packageName);
         if (msym == null) {
-            return null;
+            return Collections.emptyList();
         }
-        return getModuleLocation(location, msym.name.toString());
+        return getModuleLocation(locations, msym.name.toString());
     }
 
-    private Location getModuleLocation(Location location, String msymName)
-            throws ToolException {
+    boolean haveSourceLocationWithModule = false;
+
+    private List<Location> getModuleLocation(List<Location> locations, String msymName) throws ToolException {
+        List<Location> out = new ArrayList<>();
+        // search in the patch module first, this overrides others
+        if (locations.contains(StandardLocation.PATCH_MODULE_PATH)) {
+            Location loc = getModuleLocation(StandardLocation.PATCH_MODULE_PATH, msymName);
+            if (loc != null)
+                out.add(loc);
+        }
+        for (Location location : locations) {
+            // skip patch module, already done
+            if (location == StandardLocation.PATCH_MODULE_PATH) {
+                continue;
+            } else if (location == StandardLocation.MODULE_SOURCE_PATH) {
+                Location loc = getModuleLocation(location, msymName);
+                if (loc != null)
+                    out.add(loc);
+            } else if (location == StandardLocation.SOURCE_PATH) {
+                haveSourceLocationWithModule = true;
+                out.add(StandardLocation.SOURCE_PATH);
+            }
+        }
+        return out;
+    }
+
+    private Location getModuleLocation(Location location, String msymName) throws ToolException {
         try {
             return fm.getLocationForModule(location, msymName);
         } catch (IOException ioe) {
--- a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/resources/javadoc.properties	Thu Mar 16 17:13:10 2017 -0700
+++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/resources/javadoc.properties	Thu Mar 16 18:50:50 2017 -0700
@@ -258,6 +258,9 @@
 main.invalid_flag=invalid flag: {0}
 main.No_modules_packages_or_classes_specified=No modules, packages or classes specified.
 main.module_not_found=module {0} not found.\n
+main.cannot_use_sourcepath_for_modules=cannot use source path for multiple modules {0}
+main.module_not_found_on_sourcepath=module {0} not found on source path
+main.sourcepath_does_not_contain_module=source path does not contain module {0}
 main.cant.read=cannot read {0}
 main.Loading_source_files_for_package=Loading source files for package {0}...
 main.Loading_source_file=Loading source file {0}...
--- a/langtools/test/jdk/javadoc/tool/modules/ModuleTestBase.java	Thu Mar 16 17:13:10 2017 -0700
+++ b/langtools/test/jdk/javadoc/tool/modules/ModuleTestBase.java	Thu Mar 16 18:50:50 2017 -0700
@@ -27,7 +27,7 @@
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Arrays;
-import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Set;
@@ -158,6 +158,12 @@
         }
     }
 
+    void checkTypesSelected(String... args) throws Exception {
+        for (String arg : args) {
+            checkDocletOutputPresent("Selected", ElementKind.CLASS, arg);
+        }
+    }
+
     void checkMembersSelected(String... args) throws Exception {
         for (String arg : args) {
             checkDocletOutputPresent("Selected", ElementKind.METHOD, arg);
@@ -280,6 +286,17 @@
         StringWriter sw = new StringWriter();
         PrintWriter ps = new PrintWriter(sw);
 
+        DocletEnvironment docEnv = null;
+
+        boolean hasDocComments = false;
+
+        String hasDocComments(Element e) {
+            String comment = docEnv.getElementUtils().getDocComment(e);
+            return comment != null && !comment.isEmpty()
+                    ? "hasDocComments"
+                    : "noDocComments";
+        }
+
         // csv style output, for simple regex verification
         void printDataSet(String header, Set<? extends Element> set) {
             for (Element e : set) {
@@ -290,7 +307,12 @@
                         ps.print(FS);
                         ps.print(e.getKind());
                         ps.print(FS);
-                        ps.println(e.getQualifiedName());
+                        ps.print(e.getQualifiedName());
+                        if (hasDocComments) {
+                            ps.print(FS);
+                            ps.print(hasDocComments(e));
+                        }
+                        ps.println();
                         return null;
                     }
 
@@ -299,7 +321,12 @@
                         ps.print(FS);
                         ps.print(e.getKind());
                         ps.print(FS);
-                        ps.println(e.getQualifiedName());
+                        ps.print(e.getQualifiedName());
+                        if (hasDocComments) {
+                            ps.print(FS);
+                            ps.print(hasDocComments(e));
+                        }
+                        ps.println();
                         return null;
                     }
 
@@ -308,7 +335,12 @@
                         ps.print(FS);
                         ps.print(ElementKind.CLASS);
                         ps.print(FS);
-                        ps.println(e.getQualifiedName());
+                        ps.print(e.getQualifiedName());
+                        if (hasDocComments) {
+                            ps.print(FS);
+                            ps.print(hasDocComments(e));
+                        }
+                        ps.println();
                         return null;
                     }
 
@@ -338,7 +370,12 @@
                         ps.print(FS);
                         ps.print(fqn);
                         ps.print(".");
-                        ps.println(e.getSimpleName());
+                        ps.print(e.getSimpleName());
+                        if (hasDocComments) {
+                            ps.print(FS);
+                            ps.print(hasDocComments(e));
+                        }
+                        ps.println();
                         return null;
                     }
                 }.visit(e);
@@ -347,6 +384,7 @@
 
         @Override
         public boolean run(DocletEnvironment docenv) {
+            this.docEnv = docenv;
             ps.println("ModuleMode" + FS + docenv.getModuleMode());
             printDataSet("Specified", docenv.getSpecifiedElements());
             printDataSet("Included", docenv.getIncludedElements());
@@ -369,7 +407,9 @@
                 addEnclosedElements(docenv, result, me);
             }
             for (PackageElement pe : ElementFilter.packagesIn(elements)) {
-                addEnclosedElements(docenv, result, docenv.getElementUtils().getModuleOf(pe));
+                ModuleElement mdle = docenv.getElementUtils().getModuleOf(pe);
+                if (mdle != null)
+                    addEnclosedElements(docenv, result, mdle);
                 addEnclosedElements(docenv, result, pe);
             }
             for (TypeElement te : ElementFilter.typesIn(elements)) {
@@ -390,7 +430,45 @@
 
         @Override
         public Set<Doclet.Option> getSupportedOptions() {
-            return Collections.emptySet();
+            Option[] options = {
+                new Option() {
+                    private final List<String> someOption = Arrays.asList(
+                            "-hasDocComments"
+                    );
+
+                    @Override
+                    public int getArgumentCount() {
+                        return 0;
+                    }
+
+                    @Override
+                    public String getDescription() {
+                        return "print disposition of doc comments on an element";
+                    }
+
+                    @Override
+                    public Option.Kind getKind() {
+                        return Option.Kind.STANDARD;
+                    }
+
+                    @Override
+                    public List<String> getNames() {
+                        return someOption;
+                    }
+
+                    @Override
+                    public String getParameters() {
+                        return "flag";
+                    }
+
+                    @Override
+                    public boolean process(String opt, List<String> arguments) {
+                        hasDocComments = true;
+                        return true;
+                    }
+                }
+            };
+            return new HashSet<>(Arrays.asList(options));
         }
 
         @Override
--- a/langtools/test/jdk/javadoc/tool/modules/Modules.java	Thu Mar 16 17:13:10 2017 -0700
+++ b/langtools/test/jdk/javadoc/tool/modules/Modules.java	Thu Mar 16 18:50:50 2017 -0700
@@ -35,6 +35,7 @@
  * @run main Modules
  */
 
+import java.io.File;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -321,38 +322,6 @@
     }
 
     @Test
-    public void testPatchModuleOption(Path base) throws Exception {
-        Path src = base.resolve("src");
-        Path modulePath = base.resolve("modules");
-        Path patchPath = base.resolve("patch");
-
-        ModuleBuilder mb1 = new ModuleBuilder(tb, "m1");
-        mb1.comment("Module on module path.")
-                .exports("pkg1")
-                .classes("package pkg1; /** Class A */ public class A { }")
-                .build(modulePath);
-
-        tb.writeJavaFiles(patchPath, "package pkg1; /** Class A */ public class A { public static int k; }");
-        new JavacTask(tb)
-                .files(patchPath.resolve("pkg1/A.java"))
-                .run();
-
-        ModuleBuilder mb2 = new ModuleBuilder(tb, "m2");
-        mb2.comment("The second module.")
-                .exports("pkg2")
-                .requires("m1")
-                .classes("package pkg2; /** Class B */ public class B { /** Field f */ public int f = pkg1.A.k; }")
-                .write(src);
-        execTask("--module-source-path", src.toString(),
-                "--patch-module", "m1=" + patchPath.toString(),
-                "--module-path", modulePath.toString(),
-                "--module", "m2");
-        checkModulesSpecified("m2");
-        checkPackagesIncluded("pkg2");
-        checkMembersSelected("pkg2.B.f");
-    }
-
-    @Test
     public void testAddReadsOption(Path base) throws Exception {
         Path src = base.resolve("src");
         Path modulePath = base.resolve("modules");
@@ -550,6 +519,52 @@
         assertMessagePresent("javadoc: error - module MIA not found");
     }
 
+    @Test
+    public void testSingleModuleOptionWithSourcePath(Path base) throws Exception {
+        Path src = base.resolve("src");
+        Path mod = createSimpleModule(src, "m1");
+        execTask("--source-path", mod.toString(),
+                 "--module", "m1");
+        checkModulesSpecified("m1");
+        checkPackagesIncluded("p");
+        checkTypesIncluded("p.C");
+    }
+
+    @Test
+    public void testSingleModuleOptionWithMissingModuleInSourcePath(Path base) throws Exception {
+        Path src = base.resolve("src");
+        Path mod = createSimpleModule(src, "m1");
+        execNegativeTask("--source-path", mod.toString(),
+                 "--module", "m2");
+        assertMessagePresent("source path does not contain module m2");
+    }
+
+    @Test
+    public void testMultipleModuleOptionWithSourcePath(Path base) throws Exception {
+        Path src = base.resolve("src");
+        Path mod = createSimpleModule(src, "m1");
+        execNegativeTask("--source-path", mod.toString(),
+                 "--module", "m1,m2,m3");
+        assertMessagePresent("cannot use source path for multiple modules m1, m2, m3");
+    }
+
+    @Test
+    public void testSingleModuleOptionWithNoModuleOnSourcePath(Path base) throws Exception {
+        Path src = base.resolve("src");
+        Path mod1 = Paths.get(src.toString(), "m1");
+        execNegativeTask("--source-path", mod1.toString(),
+                 "--module", "m1");
+        assertMessagePresent("module m1 not found on source path");
+    }
+
+    Path createSimpleModule(Path src, String mname) throws IOException {
+        Path mpath = Paths.get(src.toString(), mname);
+        tb.writeJavaFiles(mpath,
+                "module " + mname + " { exports p; }",
+                "package p; public class C { }");
+        return mpath;
+    }
+
     void createAuxiliaryModules(Path src) throws IOException {
 
         new ModuleBuilder(tb, "J")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/javadoc/tool/modules/PatchModules.java	Thu Mar 16 18:50:50 2017 -0700
@@ -0,0 +1,210 @@
+/*
+ * Copyright (c) 2017, 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 8175346
+ * @summary Test patch module options
+ * @modules
+ *      jdk.javadoc/jdk.javadoc.internal.api
+ *      jdk.javadoc/jdk.javadoc.internal.tool
+ *      jdk.compiler/com.sun.tools.javac.api
+ *      jdk.compiler/com.sun.tools.javac.main
+ * @library /tools/lib
+ * @build toolbox.ToolBox toolbox.TestRunner
+ * @run main PatchModules
+ */
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import toolbox.*;
+
+public class PatchModules extends ModuleTestBase {
+
+    public static void main(String... args) throws Exception {
+        new PatchModules().runTests();
+    }
+
+    // Case A.1, m2 augmenting m1
+    @Test
+    public void testPatchModuleOption(Path base) throws Exception {
+        Path src = base.resolve("src");
+        Path modulePath = base.resolve("modules");
+        Path patchPath = base.resolve("patch");
+
+        ModuleBuilder mb1 = new ModuleBuilder(tb, "m1");
+        mb1.comment("Module on module path.")
+                .exports("pkg1")
+                .classes("package pkg1; /** Class A */ public class A { }")
+                .build(modulePath);
+
+        tb.writeJavaFiles(patchPath, "package pkg1; /** Class A */ public class A { public static int k; }");
+        new JavacTask(tb)
+                .files(patchPath.resolve("pkg1/A.java"))
+                .run();
+
+        ModuleBuilder mb2 = new ModuleBuilder(tb, "m2");
+        mb2.comment("The second module.")
+                .exports("pkg2")
+                .requires("m1")
+                .classes("package pkg2; /** Class B */ public class B { /** Field f */ public int f = pkg1.A.k; }")
+                .write(src);
+        execTask("--module-source-path", src.toString(),
+                "--patch-module", "m1=" + patchPath.toString(),
+                "--module-path", modulePath.toString(),
+                "--module", "m2");
+        checkModulesSpecified("m2");
+        checkPackagesIncluded("pkg2");
+        checkMembersSelected("pkg2.B.f");
+    }
+
+    // Case A.2: use package, source form of m1 augmenting binary form of m1
+    @Test
+    public void testPatchModuleWithPackage(Path base)  throws Exception {
+        Path modulePath = base.resolve("modules");
+        Path moduleSrcPath = base.resolve("modulesSrc");
+
+        Path mpath = Paths.get(moduleSrcPath.toString(),  "m1");
+
+        ModuleBuilder mb1 = new ModuleBuilder(tb, "m1");
+        mb1.comment("Module m1.")
+                .exports("pkg1")
+                .classes("package pkg1; /** Class A */ public class A { }")
+                .classes("package pkg1.pkg2; /** Class B */ public class B { }")
+                .build(modulePath);
+
+        execTask("-hasDocComments",
+                "--module-path", modulePath.toString(),
+                "--add-modules", "m1",
+                "--patch-module", "m1=" + mpath.toString(),
+                "pkg1");
+        checkTypesIncluded("pkg1.A hasDocComments");
+    }
+
+     // Case A.2: use subpackages, source form of m1 augmenting binary form of m1
+    @Test
+    public void testPatchModuleWithSubPackages(Path base) throws Exception {
+        Path modulePath = base.resolve("modules");
+        Path moduleSrcPath = base.resolve("modulesSrc");
+
+        Path mpath = Paths.get(moduleSrcPath.toString(),  "m1");
+
+        ModuleBuilder mb1 = new ModuleBuilder(tb, "m1");
+        mb1.comment("Module m1.")
+                .exports("pkg1")
+                .classes("package pkg1; /** Class A */ public class A { }")
+                .classes("package pkg1.pkg2; /** Class B */ public class B { }")
+                .build(modulePath);
+
+        execTask("-hasDocComments",
+                "--module-path", modulePath.toString(),
+                "--add-modules", "m1",
+                "--patch-module", "m1=" + mpath.toString(),
+                "-subpackages", "pkg1");
+        checkTypesIncluded("pkg1.A hasDocComments");
+        checkTypesIncluded("pkg1.pkg2.B hasDocComments");
+    }
+
+    // Case B.1: (jsr166) augment and override system module
+    @Test
+    public void testPatchModuleModifyingSystemModule(Path base) throws Exception {
+        Path src = base.resolve("src");
+        Path patchSrc = base.resolve("patch");
+
+        // build the patching sources
+        tb.writeJavaFiles(patchSrc, "package java.util;\n" +
+                "/** Class Collection */\n" +
+                "public interface Collection<K> {}");
+
+        tb.writeJavaFiles(patchSrc, "package java.util;\n"
+                + "/** Class MyCollection */\n" +
+                "public interface MyCollection<K> extends Collection {}");
+
+        execTask("-hasDocComments", "--patch-module", "java.base=" + patchSrc.toString(),
+                "java.util");
+
+        checkPackagesSpecified("java.util");
+        checkTypesIncluded("java.util.Collection hasDocComments",
+                "java.util.MyCollection hasDocComments");
+    }
+
+    // Case C.1: patch a user module's sources using source path
+    @Test
+    public void testPatchModuleUsingSourcePath(Path base) throws Exception {
+        Path src = base.resolve("src");
+        Path patchSrc = base.resolve("patch");
+
+        ModuleBuilder mb1 = new ModuleBuilder(tb, "m1");
+        mb1.comment("Module m1.")
+                .exports("pkg1")
+                .classes("package pkg1; /** Class A */ public class A { }")
+                .write(src);
+
+        // build the patching module
+        tb.writeJavaFiles(patchSrc, "package pkg1;\n" +
+                "/** Class A */ public class A extends java.util.ArrayList { }");
+        tb.writeJavaFiles(patchSrc, "package pkg1;\n"
+                + "/** Class B */ public class B { }");
+
+        Path m1src = Paths.get(src.toString(), "m1");
+
+        execTask("--source-path", m1src.toString(),
+                "--patch-module", "m1=" + patchSrc.toString(),
+                "pkg1");
+
+        checkPackagesSpecified("pkg1");
+        checkModulesIncluded("m1");
+        checkTypesIncluded("pkg1.A", "pkg1.B");
+    }
+
+    // Case C.2: patch a user module's sources using module source path
+    @Test
+    public void testPatchModuleWithModuleSourcePath(Path base) throws Exception {
+        Path src = base.resolve("src");
+        Path patchSrc = base.resolve("patch");
+
+        ModuleBuilder mb1 = new ModuleBuilder(tb, "m1");
+        mb1.comment("Module on module-source-path.")
+                .exports("pkg1")
+                .classes("package pkg1; /** Class A */ public class A { }")
+                .write(src);
+
+        // build the patching module
+        tb.writeJavaFiles(patchSrc, "package pkg1;\n" +
+                "/** Class A */ public class A extends java.util.ArrayList { }");
+        tb.writeJavaFiles(patchSrc, "package pkg1;\n"
+                + "/** Class B */ public class B { }");
+
+
+        execTask("--module-source-path", src.toString(),
+                "--add-modules", "m1",
+                "--patch-module", "m1=" + patchSrc.toString(),
+                "pkg1");
+
+        checkPackagesSpecified("pkg1");
+        checkModulesIncluded("m1");
+        checkTypesIncluded("pkg1.A", "pkg1.B");
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/javadoc/tool/modules/ReleaseOptions.java	Thu Mar 16 18:50:50 2017 -0700
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2017, 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 8175346
+ * @summary Test release option interactions
+ * @modules
+ *      jdk.javadoc/jdk.javadoc.internal.api
+ *      jdk.javadoc/jdk.javadoc.internal.tool
+ *      jdk.compiler/com.sun.tools.javac.api
+ *      jdk.compiler/com.sun.tools.javac.main
+ * @library /tools/lib
+ * @build toolbox.ToolBox toolbox.TestRunner
+ * @run main ReleaseOptions
+ */
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import toolbox.*;
+
+public class ReleaseOptions extends ModuleTestBase {
+
+    public static void main(String... args) throws Exception {
+        new ReleaseOptions().runTests();
+    }
+
+    @Test
+    public void testReleaseWithPatchModule(Path base) throws Exception {
+        Path src = Paths.get(base.toString(), "src");
+        Path mpath = Paths.get(src. toString(), "m");
+
+        tb.writeJavaFiles(mpath,
+                "module m { exports p; }",
+                "package p; public class C { }");
+
+        Task.Result result = execNegativeTask("--release", "8",
+                "--patch-module", "m=" + mpath.toString(),
+                "p");
+        assertMessagePresent(".*No source files for package p.*");
+        assertMessageNotPresent(".*Exception*");
+        assertMessageNotPresent(".java.lang.AssertionError.*");
+    }
+
+    @Test
+    public void testReleaseWithSourcepath(Path base) throws Exception {
+        Path src = Paths.get(base.toString(), "src");
+        Path mpath = Paths.get(src. toString(), "m");
+
+        tb.writeJavaFiles(mpath,
+                "module m { exports p; }",
+                "package p; public class C { }");
+
+        Task.Result result = execNegativeTask("--release", "8",
+                "--source-path", mpath.toString(),
+                "--module", "m");
+        assertMessagePresent(".*(use -source 9 or higher to enable modules).*");
+        assertMessageNotPresent(".*Exception*");
+        assertMessageNotPresent(".java.lang.AssertionError.*");
+    }
+
+//    @Test TBD, JDK-8175277, argument validation should fail on this
+//    public void testReleaseWithModuleSourcepath(Path base) throws Exception {
+//        Path src = Paths.get(base.toString(), "src");
+//        Path mpath = Paths.get(src.toString(), "m");
+//
+//        tb.writeJavaFiles(mpath,
+//                "module m { exports p; }",
+//                "package p; public class C { }");
+//
+//        Task.Result result = execNegativeTask("--release", "8",
+//                "--module-source-path", src.toString(),
+//                "--module", "m");
+//        assertMessagePresent(".*(use -source 9 or higher to enable modules).*");
+//        assertMessageNotPresent(".*Exception*");
+//        assertMessageNotPresent(".java.lang.AssertionError.*");
+//    }
+}