Bug 549855 - [javadoc] Add error checking for @uses and @provides tags

- change AbstractCommentParser.getTokenEndPosition() to be protected
- add module-info support to CompilationUnitDeclaration.resolve()
- add new IProblem values for @provides and @uses errors
- add new code to Javadoc resolve(Methodscope) method to support
  parsing module-info javadoc
- add resolveUsesTags() and resolveProvidesTags() methods to Javadoc to
  check tags against actual uses and provides statements
- add parseUsesReference() and parseProvidesReference() methods to
  JavadocParser to parse @uses and @provides tags
- add new IProblem messages to messages.properties
- add javadoc field to ModuleDeclaration
- add new uses and provides tag error support to ProblemReporter
- in Parser.consumeModuleDeclaration() set up compilationUnit javadoc
- add new javadocModuleMissing method to ProblemReporter
- add new JavadocTestForModule test class
- add new IJavadocTypeReference interface and have
  JavaQualifiedTypeReference and JavaSingleTypeReference implement it

Change-Id: Ib4eff519fe53c1fdb9ceca5d480dccb55aa23ab8
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/CompilerInvocationTests.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/CompilerInvocationTests.java
index ce9c925..252ac42 100644
--- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/CompilerInvocationTests.java
+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/CompilerInvocationTests.java
@@ -702,9 +702,11 @@
 		expectedProblemAttributes.put("JavadocAmbiguousMethodReference", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC));
 		expectedProblemAttributes.put("JavadocAmbiguousType", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC));
 		expectedProblemAttributes.put("JavadocDuplicateParamName", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC));
+		expectedProblemAttributes.put("JavadocDuplicateProvidesTag", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC));
 		expectedProblemAttributes.put("JavadocDuplicateReturnTag", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC));
 		expectedProblemAttributes.put("JavadocDuplicateTag", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC));
 		expectedProblemAttributes.put("JavadocDuplicateThrowsClassName", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC));
+		expectedProblemAttributes.put("JavadocDuplicateUsesTag", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC));
 		expectedProblemAttributes.put("JavadocEmptyReturnTag", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC));
 		expectedProblemAttributes.put("JavadocGenericConstructorTypeArgumentMismatch", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC));
 		expectedProblemAttributes.put("JavadocGenericMethodTypeArgumentMismatch", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC));
@@ -719,6 +721,8 @@
 		expectedProblemAttributes.put("JavadocInvalidParamName", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC));
 		expectedProblemAttributes.put("JavadocInvalidParamTagName", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC));
 		expectedProblemAttributes.put("JavadocInvalidParamTagTypeParameter", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC));
+		expectedProblemAttributes.put("JavadocInvalidProvidesClass", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC));
+		expectedProblemAttributes.put("JavadocInvalidProvidesClassName", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC));
 		expectedProblemAttributes.put("JavadocInvalidSeeArgs", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC));
 		expectedProblemAttributes.put("JavadocInvalidSeeHref", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC));
 		expectedProblemAttributes.put("JavadocInvalidSeeReference", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC));
@@ -726,6 +730,8 @@
 		expectedProblemAttributes.put("JavadocInvalidTag", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC));
 		expectedProblemAttributes.put("JavadocInvalidThrowsClass", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC));
 		expectedProblemAttributes.put("JavadocInvalidThrowsClassName", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC));
+		expectedProblemAttributes.put("JavadocInvalidUsesClass", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC));
+		expectedProblemAttributes.put("JavadocInvalidUsesClassName", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC));
 		expectedProblemAttributes.put("JavadocInvalidValueReference", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC));
 		expectedProblemAttributes.put("JavadocMalformedSeeReference", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC));
 		expectedProblemAttributes.put("JavadocMessagePrefix", new ProblemAttributes(CategorizedProblem.CAT_INTERNAL));
@@ -734,11 +740,15 @@
 		expectedProblemAttributes.put("JavadocMissingIdentifier", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC));
 		expectedProblemAttributes.put("JavadocMissingParamName", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC));
 		expectedProblemAttributes.put("JavadocMissingParamTag", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC));
+		expectedProblemAttributes.put("JavadocMissingProvidesClassName", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC));
+		expectedProblemAttributes.put("JavadocMissingProvidesTag", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC));
 		expectedProblemAttributes.put("JavadocMissingReturnTag", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC));
 		expectedProblemAttributes.put("JavadocMissingSeeReference", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC));
 		expectedProblemAttributes.put("JavadocMissingTagDescription", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC));
 		expectedProblemAttributes.put("JavadocMissingThrowsClassName", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC));
 		expectedProblemAttributes.put("JavadocMissingThrowsTag", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC));
+		expectedProblemAttributes.put("JavadocMissingUsesClassName", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC));
+		expectedProblemAttributes.put("JavadocMissingUsesTag", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC));
 		expectedProblemAttributes.put("JavadocNoMessageSendOnArrayType", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC));
 		expectedProblemAttributes.put("JavadocNoMessageSendOnBaseType", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC));
 		expectedProblemAttributes.put("JavadocNonGenericConstructor", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC));
@@ -1674,9 +1684,11 @@
 		expectedProblemAttributes.put("JavadocAmbiguousMethodReference", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC));
 		expectedProblemAttributes.put("JavadocAmbiguousType", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC));
 		expectedProblemAttributes.put("JavadocDuplicateParamName", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC));
+		expectedProblemAttributes.put("JavadocDuplicateProvidesTag", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC));
 		expectedProblemAttributes.put("JavadocDuplicateReturnTag", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC));
 		expectedProblemAttributes.put("JavadocDuplicateTag", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC));
 		expectedProblemAttributes.put("JavadocDuplicateThrowsClassName", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC));
+		expectedProblemAttributes.put("JavadocDuplicateUsesTag", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC));
 		expectedProblemAttributes.put("JavadocEmptyReturnTag", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC));
 		expectedProblemAttributes.put("JavadocGenericConstructorTypeArgumentMismatch", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC));
 		expectedProblemAttributes.put("JavadocGenericMethodTypeArgumentMismatch", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC));
@@ -1691,6 +1703,8 @@
 		expectedProblemAttributes.put("JavadocInvalidParamName", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC));
 		expectedProblemAttributes.put("JavadocInvalidParamTagName", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC));
 		expectedProblemAttributes.put("JavadocInvalidParamTagTypeParameter", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC));
+		expectedProblemAttributes.put("JavadocInvalidProvidesClass", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC));
+		expectedProblemAttributes.put("JavadocInvalidProvidesClassName", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC));
 		expectedProblemAttributes.put("JavadocInvalidSeeArgs", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC));
 		expectedProblemAttributes.put("JavadocInvalidSeeHref", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC));
 		expectedProblemAttributes.put("JavadocInvalidSeeReference", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC));
@@ -1698,6 +1712,8 @@
 		expectedProblemAttributes.put("JavadocInvalidTag", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC));
 		expectedProblemAttributes.put("JavadocInvalidThrowsClass", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC));
 		expectedProblemAttributes.put("JavadocInvalidThrowsClassName", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC));
+		expectedProblemAttributes.put("JavadocInvalidUsesClass", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC));
+		expectedProblemAttributes.put("JavadocInvalidUsesClassName", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC));
 		expectedProblemAttributes.put("JavadocInvalidValueReference", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC));
 		expectedProblemAttributes.put("JavadocMalformedSeeReference", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC));
 		expectedProblemAttributes.put("JavadocMessagePrefix", SKIP);
@@ -1706,11 +1722,17 @@
 		expectedProblemAttributes.put("JavadocMissingIdentifier", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC));
 		expectedProblemAttributes.put("JavadocMissingParamName", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC));
 		expectedProblemAttributes.put("JavadocMissingParamTag", new ProblemAttributes(JavaCore.COMPILER_PB_MISSING_JAVADOC_TAGS));
+		expectedProblemAttributes.put("JavadocMissingProvidesClass", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC));
+		expectedProblemAttributes.put("JavadocMissingProvidesClassName", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC));
+		expectedProblemAttributes.put("JavadocMissingProvidesTag", new ProblemAttributes(JavaCore.COMPILER_PB_MISSING_JAVADOC_TAGS));
 		expectedProblemAttributes.put("JavadocMissingReturnTag", new ProblemAttributes(JavaCore.COMPILER_PB_MISSING_JAVADOC_TAGS));
 		expectedProblemAttributes.put("JavadocMissingSeeReference", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC));
 		expectedProblemAttributes.put("JavadocMissingTagDescription", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC));
 		expectedProblemAttributes.put("JavadocMissingThrowsClassName", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC));
 		expectedProblemAttributes.put("JavadocMissingThrowsTag", new ProblemAttributes(JavaCore.COMPILER_PB_MISSING_JAVADOC_TAGS));
+		expectedProblemAttributes.put("JavadocMissingUsesClass", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC));
+		expectedProblemAttributes.put("JavadocMissingUsesClassName", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC));
+		expectedProblemAttributes.put("JavadocMissingUsesTag", new ProblemAttributes(JavaCore.COMPILER_PB_MISSING_JAVADOC_TAGS));
 		expectedProblemAttributes.put("JavadocNoMessageSendOnArrayType", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC));
 		expectedProblemAttributes.put("JavadocNoMessageSendOnBaseType", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC));
 		expectedProblemAttributes.put("JavadocNonGenericConstructor", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC));
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/JavadocTestForModule.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/JavadocTestForModule.java
new file mode 100644
index 0000000..00ecdbc
--- /dev/null
+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/JavadocTestForModule.java
@@ -0,0 +1,961 @@
+/*******************************************************************************
+ * Copyright (c) 2019 IBM Corporation and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *     Red Hat Inc. - copied from ModuleCompilationTests and used for Javadoc
+ *******************************************************************************/
+package org.eclipse.jdt.core.tests.compiler.regression;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.file.DirectoryNotEmptyException;
+import java.nio.file.FileSystems;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.jdt.core.tests.util.Util;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.Test;
+
+public class JavadocTestForModule extends AbstractBatchCompilerTest {
+
+	static {
+//		 TESTS_NAMES = new String[] { "testBug549855a" };
+		// TESTS_NUMBERS = new int[] { 1 };
+		// TESTS_RANGE = new int[] { 298, -1 };
+	}
+
+	public JavadocTestForModule(String name) {
+		super(name);
+	}
+
+	public static Test suite() {
+		return buildMinimalComplianceTestSuite(testClass(), F_9);
+	}
+
+	public static Class<?> testClass() {
+		return JavadocTestForModule.class;
+	}
+
+	protected void writeFileCollecting(List<String> collectedFiles, String directoryName, String fileName, String source) {
+		writeFile(directoryName, fileName, source);
+		collectedFiles.add(directoryName+File.separator+fileName);
+	}
+
+	protected void writeFile(String directoryName, String fileName, String source) {
+		File directory = new File(directoryName);
+		if (!directory.exists()) {
+			if (!directory.mkdirs()) {
+				System.out.println("Could not create " + directoryName);
+				return;
+			}
+		}
+		String filePath = directory.getAbsolutePath() + File.separator + fileName;
+		try {
+			BufferedWriter writer = new BufferedWriter(new FileWriter(filePath));
+			writer.write(source);
+			writer.flush();
+			writer.close();
+		} catch (IOException e) {
+			e.printStackTrace();
+			return;
+		}
+	}
+
+	class Runner extends AbstractRegressionTest.Runner {
+		StringBuffer commandLine = new StringBuffer();
+		String outputDir = OUTPUT_DIR + File.separator + "javac";
+		List<String> fileNames = new ArrayList<>();
+		/** will replace any -8, -9 ... option for javac */
+		String javacVersionOptions;
+
+		Runner() {
+			this.javacTestOptions = JavacTestOptions.DEFAULT;
+			this.expectedOutputString = "";
+			this.expectedErrorString = "";
+		}
+		/** Create a source file and add the filename to the compiler command line. */
+		void createFile(String directoryName, String fileName, String source) {
+			writeFileCollecting(this.fileNames, directoryName, fileName, source);
+		}
+		Set<String> runConformModuleTest() {
+			if (!this.fileNames.isEmpty()) {
+				this.shouldFlushOutputDirectory = false;
+				if (this.testFiles == null)
+					this.testFiles = new String[0];
+				for (String fileName : this.fileNames) {
+					this.commandLine.append(" \"").append(fileName).append("\"");
+				}
+			}
+			String commandLineString = this.commandLine.toString();
+			String javacCommandLine = adjustForJavac(commandLineString, this.javacVersionOptions);
+			return JavadocTestForModule.this.runConformModuleTest(this.testFiles, commandLineString,
+					this.expectedOutputString, this.expectedErrorString,
+					this.shouldFlushOutputDirectory, this.outputDir,
+					this.javacTestOptions, javacCommandLine);
+		}
+	}
+
+	void runConformModuleTest(List<String> testFileNames, StringBuffer commandLine,
+			String expectedFailureOutOutputString, String expectedFailureErrOutputString,
+			boolean shouldFlushOutputDirectory)
+	{
+		runConformModuleTest(testFileNames, commandLine,
+				expectedFailureOutOutputString, expectedFailureErrOutputString, shouldFlushOutputDirectory, OUTPUT_DIR + File.separator + "javac");
+	}
+
+	void runConformModuleTest(List<String> testFileNames, StringBuffer commandLine,
+			String expectedFailureOutOutputString, String expectedFailureErrOutputString,
+			boolean shouldFlushOutputDirectory, String output)
+	{
+		for (String file : testFileNames)
+			commandLine.append(" \"").append(file).append("\"");
+		runConformModuleTest(new String[0], commandLine.toString(),
+				expectedFailureOutOutputString, expectedFailureErrOutputString, shouldFlushOutputDirectory,
+				output, JavacTestOptions.DEFAULT, null);
+	}
+
+	Set<String> runConformModuleTest(String[] testFiles, String commandLine,
+			String expectedFailureOutOutputString, String expectedFailureErrOutputString,
+			boolean shouldFlushOutputDirectory)
+	{
+		return runConformModuleTest(testFiles, commandLine, expectedFailureErrOutputString, expectedFailureErrOutputString,
+				shouldFlushOutputDirectory, OUTPUT_DIR, JavacTestOptions.DEFAULT, null);
+	}
+
+	Set<String> runConformModuleTest(String[] testFiles, String commandLine,
+			String expectedFailureOutOutputString, String expectedFailureErrOutputString,
+			boolean shouldFlushOutputDirectory, String output, JavacTestOptions options, String javacCommandLine)
+	{
+		this.runConformTest(testFiles, commandLine, expectedFailureOutOutputString, expectedFailureErrOutputString, shouldFlushOutputDirectory);
+		if (RUN_JAVAC) {
+			File outputDir = new File(output);
+			final Set<String> outFiles = new HashSet<>();
+			walkOutFiles(output, outFiles, true);
+			String[] testFileNames = new String[testFiles.length/2];
+			for (int i = 0; i < testFileNames.length; i++) {
+				testFileNames[i] = testFiles[i*2];
+			}
+			if (javacCommandLine == null) {
+				javacCommandLine = adjustForJavac(commandLine, null);
+			}
+			for (JavacCompiler javacCompiler : javacCompilers) {
+				if (javacCompiler.compliance < ClassFileConstants.JDK9)
+					continue;
+				if (options.skip(javacCompiler)) {
+					System.err.println("Skip testing javac in "+testName());
+					continue;
+				}
+				StringBuffer log = new StringBuffer();
+				try {
+					long compileResult = javacCompiler.compile(
+											outputDir, /* directory */
+											javacCommandLine /* options */,
+											testFileNames /* source file names */,
+											log,
+											false); // don't repeat filenames on the command line
+					if (compileResult != 0) {
+						System.err.println("Previous error was from "+testName());
+						fail("Unexpected error from javac");
+					}
+				} catch (IOException | InterruptedException e) {
+					e.printStackTrace();
+					throw new AssertionFailedError(e.getMessage());
+				}
+				final Set<String> expectedFiles = new HashSet<>(outFiles);
+				walkOutFiles(output, expectedFiles, false);
+				for (String missingFile : expectedFiles)
+					System.err.println("Missing output file from javac:    "+missingFile);
+			}
+			return outFiles;
+		}
+		return null;
+	}
+
+	void runNegativeModuleTest(List<String> testFileNames, StringBuffer commandLine,
+			String expectedFailureOutOutputString, String expectedFailureErrOutputString,
+			boolean shouldFlushOutputDirectory, String javacErrorMatch) {
+		runNegativeModuleTest(testFileNames, commandLine, expectedFailureOutOutputString,
+				expectedFailureErrOutputString, shouldFlushOutputDirectory, javacErrorMatch, OUTPUT_DIR + File.separator + "javac");
+	}
+
+	void runNegativeModuleTest(List<String> testFileNames, StringBuffer commandLine,
+			String expectedFailureOutOutputString, String expectedFailureErrOutputString,
+			boolean shouldFlushOutputDirectory, String javacErrorMatch, String output)
+	{
+		runNegativeModuleTest(testFileNames, commandLine, expectedFailureOutOutputString, expectedFailureErrOutputString,
+				shouldFlushOutputDirectory, javacErrorMatch, output, JavacTestOptions.DEFAULT);
+	}
+	void runNegativeModuleTest(List<String> testFileNames, StringBuffer commandLine,
+			String expectedFailureOutOutputString, String expectedFailureErrOutputString,
+			boolean shouldFlushOutputDirectory, String javacErrorMatch, String output, JavacTestOptions options)
+	{
+		for (String file : testFileNames)
+			commandLine.append(" \"").append(file).append("\"");
+		runNegativeModuleTest(new String[0], commandLine.toString(),
+				expectedFailureOutOutputString, expectedFailureErrOutputString, shouldFlushOutputDirectory, javacErrorMatch, output,
+				options);
+	}
+	void runNegativeModuleTest(String[] testFiles, String commandLine,
+			String expectedFailureOutOutputString, String expectedFailureErrOutputString,
+			boolean shouldFlushOutputDirectory, String javacErrorMatch) {
+		runNegativeModuleTest(testFiles, commandLine, expectedFailureOutOutputString, expectedFailureErrOutputString,
+				shouldFlushOutputDirectory, javacErrorMatch, OUTPUT_DIR, JavacTestOptions.DEFAULT);
+	}
+
+	void runNegativeModuleTest(String[] testFiles, String commandLine,
+			String expectedFailureOutOutputString, String expectedFailureErrOutputString,
+			boolean shouldFlushOutputDirectory, String javacErrorMatch, String output, JavacTestOptions options)
+	{
+		this.runNegativeTest(testFiles, commandLine, expectedFailureOutOutputString, expectedFailureErrOutputString, shouldFlushOutputDirectory);
+		if (RUN_JAVAC) {
+			String[] testFileNames = new String[testFiles.length/2];
+			for (int i = 0; i < testFileNames.length; i++) {
+				testFileNames[i] = testFiles[i*2];
+			}
+			File outputDir = new File(OUTPUT_DIR);
+			final Set<String> outFiles = new HashSet<>();
+			walkOutFiles(output, outFiles, true);
+			for (JavacCompiler javacCompiler : javacCompilers) {
+				if (javacCompiler.compliance < ClassFileConstants.JDK9)
+					continue;
+				JavacTestOptions.Excuse excuse = options.excuseFor(javacCompiler);
+
+				commandLine = adjustForJavac(commandLine, null);
+				StringBuffer log = new StringBuffer();
+				int mismatch = 0;
+				try {
+					long compileResult = javacCompiler.compile(
+											outputDir, /* directory */
+											commandLine /* options */,
+											testFileNames /* source file names */,
+											log);
+					if (compileResult == 0) {
+						mismatch = JavacTestOptions.MismatchType.EclipseErrorsJavacNone;
+						javacErrorMatch = expectedFailureErrOutputString;
+						System.err.println("Previous error was from "+testName());
+					} else if (!log.toString().contains(javacErrorMatch)) {
+						mismatch = JavacTestOptions.MismatchType.CompileErrorMismatch;
+						System.err.println(testName()+": Error match " + javacErrorMatch + " not found in \n"+log.toString());
+					}
+				} catch (IOException | InterruptedException e) {
+					e.printStackTrace();
+					throw new AssertionFailedError(e.getMessage());
+				}
+				handleMismatch(javacCompiler, testName(), testFiles, javacErrorMatch,
+						"", "", log, "", "",
+						excuse, mismatch);
+				final Set<String> expectedFiles = new HashSet<>(outFiles);
+				walkOutFiles(output, expectedFiles, false);
+				for (String missingFile : expectedFiles)
+					System.err.println("Missing output file from javac:    "+missingFile);
+			}
+		}
+	}
+
+	/**
+	 * @param commandLine command line arguments as used for ecj
+	 * @param versionOptions if non-null use this to replace any ecj-specific -8, -9 etc. arg.
+	 * 		If ecj-specific arg is not found, append anyway
+	 * @return commandLine adjusted for javac
+	 */
+	String adjustForJavac(String commandLine, String versionOptions) {
+		String[] tokens = commandLine.split(" ");
+		StringBuffer buf = new StringBuffer();
+		boolean skipNext = false;
+		for (int i = 0; i < tokens.length; i++) {
+			if (skipNext) {
+				skipNext = false;
+				continue;
+			}
+			if (tokens[i].trim().equals("-9")) {
+				if (versionOptions == null)
+					buf.append(' ').append(" --release 9 ");
+				continue;
+			}
+			if (tokens[i].trim().equals("-8")) {
+				if (versionOptions == null)
+					buf.append(' ').append(" --release 8 ");
+				continue;
+			}
+			if (tokens[i].startsWith("-warn") || tokens[i].startsWith("-err") || tokens[i].startsWith("-info")) {
+				if (tokens[i].contains("exports") && !tokens[i].contains("-exports"))
+					buf.append(" -Xlint:exports ");
+				continue;
+			}
+			if (tokens[i].trim().equals("-classNames")) {
+				skipNext = true;
+				continue;
+			}
+			buf.append(tokens[i]).append(' ');
+		}
+		if (versionOptions != null) {
+			buf.append(versionOptions);
+		}
+		return buf.toString();
+	}
+
+	private void walkOutFiles(final String outputLocation, final Set<String> fileNames, boolean add) {
+		if (!(new File(outputLocation)).exists())
+			return;
+		try {
+			Files.walkFileTree(FileSystems.getDefault().getPath(outputLocation), new SimpleFileVisitor<Path>() {
+				@Override
+				public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+					if (file.toString().endsWith(".class")) {
+						if (add) {
+							fileNames.add(file.toString());
+						} else {
+							if (!fileNames.remove(file.toString()))
+								System.err.println("Unexpected output file from javac: "+file.toString());
+						}
+						Files.delete(file);
+					}
+					return FileVisitResult.CONTINUE;
+				}
+				@Override
+				public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
+					if (!dir.toString().equals(outputLocation)) {
+						try {
+							Files.delete(dir);
+						} catch (DirectoryNotEmptyException ex) {
+							// expected
+						}
+					}
+			        return FileVisitResult.CONTINUE;
+				}
+			});
+		} catch (IOException e) {
+			e.printStackTrace();
+			throw new AssertionFailedError(e.getMessage());
+		}
+	}
+
+	public void testBug549855a() {
+		File outputDirectory = new File(OUTPUT_DIR);
+		Util.flushDirectoryContent(outputDirectory);
+		String out = "bin";
+		String directory = OUTPUT_DIR + File.separator + "src";
+		String moduleLoc = directory + File.separator + "mod.one";
+		List<String> files = new ArrayList<>();
+		writeFileCollecting(files, moduleLoc, "module-info.java",
+						"/**\n" +
+						" */\n" +
+						"module mod.one { \n" +
+						" exports p;\n" +
+						" provides p.I1 with p.P1;\n" +
+						" uses java.util.Currency;\n" +
+						"}");
+		writeFileCollecting(files, moduleLoc + File.separator + "p", "I1.java",
+				"package p;\n" +
+				"/**\n" +
+				" * interface I1\n" +
+				" */\n" +
+				"public interface I1 {\n" +
+				"	/**\n" +
+				"	 * Method foo\n" +
+				"    * @return int\n" +
+				"    */\n" +
+				"	public int foo();\n" +
+				"}");
+		writeFileCollecting(files, moduleLoc + File.separator + "p", "P1.java",
+				"package p;\n" +
+				"/**\n" +
+				" * class P1\n" +
+				" */\n" +
+				"public class P1 implements I1 {\n" +
+				"	@Override\n" +
+				"	public int foo() { return 0; }\n" +
+				"}");
+
+		StringBuilder buffer = new StringBuilder();
+		buffer.append("-d " + OUTPUT_DIR + File.separator + out )
+			.append(" -9 ")
+			.append(" -enableJavadoc ")
+			.append(" -err:allJavadoc ")
+			.append(" -classpath \"")
+			.append(Util.getJavaClassLibsAsString())
+			.append("\" ")
+			.append(" -warn:-unused")
+			.append(" --module-source-path " + "\"" + directory + "\" ")
+			.append(moduleLoc + File.separator + "module-info.java ")
+			.append(moduleLoc + File.separator + "p" + File.separator + "I1.java ")
+			.append(moduleLoc + File.separator + "p" + File.separator + "P1.java");
+
+		runNegativeModuleTest(
+				new String[0],
+				buffer.toString(),
+				"",
+				"----------\n" +
+				"1. ERROR in ---OUTPUT_DIR_PLACEHOLDER---/src/mod.one/module-info.java (at line 5)\n" +
+				"	provides p.I1 with p.P1;\n" +
+				"	^^^^^^^^^^^^^^^^^^^^^^^\n" +
+				"Javadoc: Missing provides tag\n" +
+				"----------\n" +
+				"2. ERROR in ---OUTPUT_DIR_PLACEHOLDER---/src/mod.one/module-info.java (at line 6)\n" +
+				"	uses java.util.Currency;\n" +
+				"	^^^^^^^^^^^^^^^^^^^^^^^\n" +
+				"Javadoc: Missing uses tag\n" +
+				"----------\n" +
+				"2 problems (2 errors)\n",
+				false,
+				"missing tags");
+	}
+
+	public void testBug549855b() {
+		File outputDirectory = new File(OUTPUT_DIR);
+		Util.flushDirectoryContent(outputDirectory);
+		String out = "bin";
+		String directory = OUTPUT_DIR + File.separator + "src";
+		String moduleLoc = directory + File.separator + "mod.one";
+		List<String> files = new ArrayList<>();
+		writeFileCollecting(files, moduleLoc, "module-info.java",
+						"/**\n" +
+						" @provides p.I\n" +
+						" @uses java.util.Currenc\n" +
+						" */\n" +
+						"module mod.one { \n" +
+						" exports p;\n" +
+						" provides p.I1 with p.P1;\n" +
+						" uses java.util.Currency;\n" +
+						"}");
+		writeFileCollecting(files, moduleLoc + File.separator + "p", "I1.java",
+				"package p;\n" +
+				"/**\n" +
+				" * interface I1\n" +
+				" */\n" +
+				"public interface I1 {\n" +
+				"	/**\n" +
+				"	 * Method foo\n" +
+				"    * @return int\n" +
+				"    */\n" +
+				"	public int foo();\n" +
+				"}");
+		writeFileCollecting(files, moduleLoc + File.separator + "p", "P1.java",
+				"package p;\n" +
+				"/**\n" +
+				" * class P1\n" +
+				" */\n" +
+				"public class P1 implements I1 {\n" +
+				"	@Override\n" +
+				"	public int foo() { return 0; }\n" +
+				"}");
+
+		StringBuilder buffer = new StringBuilder();
+		buffer.append("-d " + OUTPUT_DIR + File.separator + out )
+			.append(" -9 ")
+			.append(" -enableJavadoc ")
+			.append(" -err:allJavadoc ")
+			.append(" -classpath \"")
+			.append(Util.getJavaClassLibsAsString())
+			.append("\" ")
+			.append(" -warn:-unused")
+			.append(" --module-source-path " + "\"" + directory + "\" ")
+			.append(moduleLoc + File.separator + "module-info.java ")
+			.append(moduleLoc + File.separator + "p" + File.separator + "I1.java ")
+			.append(moduleLoc + File.separator + "p" + File.separator + "P1.java");
+
+		runNegativeModuleTest(
+				new String[0],
+				buffer.toString(),
+				"",
+				"----------\n" +
+				"1. ERROR in ---OUTPUT_DIR_PLACEHOLDER---/src/mod.one/module-info.java (at line 2)\n" +
+				"	@provides p.I\n" +
+				"	          ^^^\n" +
+				"Javadoc: Invalid provides class\n" +
+				"----------\n" +
+				"2. ERROR in ---OUTPUT_DIR_PLACEHOLDER---/src/mod.one/module-info.java (at line 3)\n" +
+				"	@uses java.util.Currenc\n" +
+				"	      ^^^^^^^^^^^^^^^^^\n" +
+				"Javadoc: Invalid uses class\n" +
+				"----------\n" +
+				"3. ERROR in ---OUTPUT_DIR_PLACEHOLDER---/src/mod.one/module-info.java (at line 7)\n" +
+				"	provides p.I1 with p.P1;\n" +
+				"	^^^^^^^^^^^^^^^^^^^^^^^\n" +
+				"Javadoc: Missing provides tag\n" +
+				"----------\n" +
+				"4. ERROR in ---OUTPUT_DIR_PLACEHOLDER---/src/mod.one/module-info.java (at line 8)\n" +
+				"	uses java.util.Currency;\n" +
+				"	^^^^^^^^^^^^^^^^^^^^^^^\n" +
+				"Javadoc: Missing uses tag\n" +
+				"----------\n" +
+				"4 problems (4 errors)\n",
+				false,
+				"missing and invalid tags");
+	}
+
+	public void testBug549855c() {
+		File outputDirectory = new File(OUTPUT_DIR);
+		Util.flushDirectoryContent(outputDirectory);
+		String out = "bin";
+		String directory = OUTPUT_DIR + File.separator + "src";
+		String moduleLoc = directory + File.separator + "mod.one";
+		List<String> files = new ArrayList<>();
+		writeFileCollecting(files, moduleLoc, "module-info.java",
+						"/**\n" +
+						" @provides p.I1\n" +
+						" @uses java.util.Currency\n" +
+						" @provides p.I1\n" +
+						" @uses java.util.Currency\n" +
+						" */\n" +
+						"module mod.one { \n" +
+						" exports p;\n" +
+						" provides p.I1 with p.P1;\n" +
+						" uses java.util.Currency;\n" +
+						"}");
+		writeFileCollecting(files, moduleLoc + File.separator + "p", "I1.java",
+				"package p;\n" +
+				"/**\n" +
+				" * interface I1\n" +
+				" */\n" +
+				"public interface I1 {\n" +
+				"	/**\n" +
+				"	 * Method foo\n" +
+				"    * @return int\n" +
+				"    */\n" +
+				"	public int foo();\n" +
+				"}");
+		writeFileCollecting(files, moduleLoc + File.separator + "p", "P1.java",
+				"package p;\n" +
+				"/**\n" +
+				" * class P1\n" +
+				" */\n" +
+				"public class P1 implements I1 {\n" +
+				"	@Override\n" +
+				"	public int foo() { return 0; }\n" +
+				"}");
+
+		StringBuilder buffer = new StringBuilder();
+		buffer.append("-d " + OUTPUT_DIR + File.separator + out )
+			.append(" -9 ")
+			.append(" -enableJavadoc ")
+			.append(" -err:allJavadoc ")
+			.append(" -classpath \"")
+			.append(Util.getJavaClassLibsAsString())
+			.append("\" ")
+			.append(" -warn:-unused")
+			.append(" --module-source-path " + "\"" + directory + "\" ")
+			.append(moduleLoc + File.separator + "module-info.java ")
+			.append(moduleLoc + File.separator + "p" + File.separator + "I1.java ")
+			.append(moduleLoc + File.separator + "p" + File.separator + "P1.java");
+
+		runNegativeModuleTest(
+				new String[0],
+				buffer.toString(),
+				"",
+				"----------\n" +
+				"1. ERROR in ---OUTPUT_DIR_PLACEHOLDER---/src/mod.one/module-info.java (at line 4)\n" +
+				"	@provides p.I1\n" +
+				"	          ^^^^\n" +
+				"Javadoc: Duplicate provides tag\n" +
+				"----------\n" +
+				"2. ERROR in ---OUTPUT_DIR_PLACEHOLDER---/src/mod.one/module-info.java (at line 5)\n" +
+				"	@uses java.util.Currency\n" +
+				"	      ^^^^^^^^^^^^^^^^^^\n" +
+				"Javadoc: Duplicate uses tag\n" +
+				"----------\n" +
+				"2 problems (2 errors)\n",
+				false,
+				"duplicate tags");
+	}
+
+	public void testBug549855d() {
+		File outputDirectory = new File(OUTPUT_DIR);
+		Util.flushDirectoryContent(outputDirectory);
+		String out = "bin";
+		String directory = OUTPUT_DIR + File.separator + "src";
+		String moduleLoc = directory + File.separator + "mod.one";
+		List<String> files = new ArrayList<>();
+		writeFileCollecting(files, moduleLoc, "module-info.java",
+						"/**\n" +
+						" @provides p.I1\n" +
+						" @uses java.util.Currency\n" +
+						" */\n" +
+						"module mod.one { \n" +
+						" exports p;\n" +
+						" provides p.I1 with p.P1;\n" +
+						" uses java.util.Currency;\n" +
+						"}");
+		writeFileCollecting(files, moduleLoc + File.separator + "p", "I1.java",
+				"package p;\n" +
+				"/**\n" +
+				" * interface I1\n" +
+				" */\n" +
+				"public interface I1 {\n" +
+				"	/**\n" +
+				"	 * Method foo\n" +
+				"    * @return int\n" +
+				"    */\n" +
+				"	public int foo();\n" +
+				"}");
+		writeFileCollecting(files, moduleLoc + File.separator + "p", "P1.java",
+				"package p;\n" +
+				"/**\n" +
+				" * class P1\n" +
+				" */\n" +
+				"public class P1 implements I1 {\n" +
+				"	@Override\n" +
+				"	public int foo() { return 0; }\n" +
+				"}");
+
+		StringBuffer buffer = new StringBuffer();
+		buffer.append("-d " + OUTPUT_DIR + File.separator + out )
+			.append(" -9 ")
+			.append(" -enableJavadoc ")
+			.append(" -err:allJavadoc ")
+			.append(" -classpath \"")
+			.append(Util.getJavaClassLibsAsString())
+			.append("\" ")
+			.append(" -warn:-unused")
+			.append(" --module-source-path " + "\"" + directory + "\" ");
+
+		runConformModuleTest(files, buffer, "", "", false);
+	}
+
+	public void testBug549855e() {
+		File outputDirectory = new File(OUTPUT_DIR);
+		Util.flushDirectoryContent(outputDirectory);
+		String out = "bin";
+		String directory = OUTPUT_DIR + File.separator + "src";
+		String moduleLoc = directory + File.separator + "mod.one";
+		List<String> files = new ArrayList<>();
+		writeFileCollecting(files, moduleLoc, "module-info.java",
+						"/**\n" +
+						" @provides p.I1\n" +
+						" */\n" +
+						"module mod.one { \n" +
+						" exports p;\n" +
+						" provides p.I1 with p.P1;\n" +
+						" uses java.util.Currency;\n" +
+						"}");
+		writeFileCollecting(files, moduleLoc + File.separator + "p", "I1.java",
+				"package p;\n" +
+				"/**\n" +
+				" * interface I1\n" +
+				" */\n" +
+				"public interface I1 {\n" +
+				"	/**\n" +
+				"	 * Method foo\n" +
+				"    * @return int\n" +
+				"    */\n" +
+				"	public int foo();\n" +
+				"}");
+		writeFileCollecting(files, moduleLoc + File.separator + "p", "P1.java",
+				"package p;\n" +
+				"/**\n" +
+				" * class P1\n" +
+				" */\n" +
+				"public class P1 implements I1 {\n" +
+				"	@Override\n" +
+				"	public int foo() { return 0; }\n" +
+				"}");
+
+		StringBuilder buffer = new StringBuilder();
+		buffer.append("-d " + OUTPUT_DIR + File.separator + out )
+			.append(" -9 ")
+			.append(" -enableJavadoc ")
+			.append(" -err:allJavadoc ")
+			.append(" -classpath \"")
+			.append(Util.getJavaClassLibsAsString())
+			.append("\" ")
+			.append(" -warn:-unused")
+			.append(" --module-source-path " + "\"" + directory + "\" ")
+			.append(moduleLoc + File.separator + "module-info.java ")
+			.append(moduleLoc + File.separator + "p" + File.separator + "I1.java ")
+			.append(moduleLoc + File.separator + "p" + File.separator + "P1.java");
+
+		runNegativeModuleTest(
+				new String[0],
+				buffer.toString(),
+				"",
+				"----------\n" +
+				"1. ERROR in ---OUTPUT_DIR_PLACEHOLDER---/src/mod.one/module-info.java (at line 7)\n" +
+				"	uses java.util.Currency;\n" +
+				"	^^^^^^^^^^^^^^^^^^^^^^^\n" +
+				"Javadoc: Missing uses tag\n" +
+				"----------\n" +
+				"1 problem (1 error)\n",
+				false,
+				"missing tags");
+	}
+
+	public void testBug549855f() {
+		File outputDirectory = new File(OUTPUT_DIR);
+		Util.flushDirectoryContent(outputDirectory);
+		String out = "bin";
+		String directory = OUTPUT_DIR + File.separator + "src";
+		String moduleLoc = directory + File.separator + "mod.one";
+		List<String> files = new ArrayList<>();
+		writeFileCollecting(files, moduleLoc, "module-info.java",
+						"/**\n" +
+						" @uses java.util.Currency\n" +
+						" */\n" +
+						"module mod.one { \n" +
+						" exports p;\n" +
+						" provides p.I1 with p.P1;\n" +
+						" uses java.util.Currency;\n" +
+						"}");
+		writeFileCollecting(files, moduleLoc + File.separator + "p", "I1.java",
+				"package p;\n" +
+				"/**\n" +
+				" * interface I1\n" +
+				" */\n" +
+				"public interface I1 {\n" +
+				"	/**\n" +
+				"	 * Method foo\n" +
+				"    * @return int\n" +
+				"    */\n" +
+				"	public int foo();\n" +
+				"}");
+		writeFileCollecting(files, moduleLoc + File.separator + "p", "P1.java",
+				"package p;\n" +
+				"/**\n" +
+				" * class P1\n" +
+				" */\n" +
+				"public class P1 implements I1 {\n" +
+				"	@Override\n" +
+				"	public int foo() { return 0; }\n" +
+				"}");
+
+		StringBuilder buffer = new StringBuilder();
+		buffer.append("-d " + OUTPUT_DIR + File.separator + out )
+			.append(" -9 ")
+			.append(" -enableJavadoc ")
+			.append(" -err:allJavadoc ")
+			.append(" -classpath \"")
+			.append(Util.getJavaClassLibsAsString())
+			.append("\" ")
+			.append(" -warn:-unused")
+			.append(" --module-source-path " + "\"" + directory + "\" ")
+			.append(moduleLoc + File.separator + "module-info.java ")
+			.append(moduleLoc + File.separator + "p" + File.separator + "I1.java ")
+			.append(moduleLoc + File.separator + "p" + File.separator + "P1.java");
+
+		runNegativeModuleTest(
+				new String[0],
+				buffer.toString(),
+				"",
+				"----------\n" +
+				"1. ERROR in ---OUTPUT_DIR_PLACEHOLDER---/src/mod.one/module-info.java (at line 6)\n" +
+				"	provides p.I1 with p.P1;\n" +
+				"	^^^^^^^^^^^^^^^^^^^^^^^\n" +
+				"Javadoc: Missing provides tag\n" +
+				"----------\n" +
+				"1 problem (1 error)\n",
+				false,
+				"missing tags");
+	}
+
+	public void testBug549855g() {
+		File outputDirectory = new File(OUTPUT_DIR);
+		Util.flushDirectoryContent(outputDirectory);
+		String out = "bin";
+		String directory = OUTPUT_DIR + File.separator + "src";
+		String moduleLoc = directory + File.separator + "mod.one";
+		List<String> files = new ArrayList<>();
+		writeFileCollecting(files, moduleLoc, "module-info.java",
+						"/**\n" +
+						" */\n" +
+						"module mod.one { \n" +
+						" exports p;\n" +
+						" provides p.I1 with p.P1;\n" +
+						" uses java.util.Currency;\n" +
+						"}");
+		writeFileCollecting(files, moduleLoc + File.separator + "p", "I1.java",
+				"package p;\n" +
+				"/**\n" +
+				" * interface I1\n" +
+				" */\n" +
+				"public interface I1 {\n" +
+				"	/**\n" +
+				"	 * Method foo\n" +
+				"    * @return int\n" +
+				"    */\n" +
+				"	public int foo();\n" +
+				"}");
+		writeFileCollecting(files, moduleLoc + File.separator + "p", "P1.java",
+				"package p;\n" +
+				"/**\n" +
+				" * class P1\n" +
+				" */\n" +
+				"public class P1 implements I1 {\n" +
+				"	@Override\n" +
+				"	public int foo() { return 0; }\n" +
+				"}");
+
+		StringBuilder buffer = new StringBuilder();
+		buffer.append("-d " + OUTPUT_DIR + File.separator + out )
+			.append(" -9 ")
+			.append(" -enableJavadoc ")
+			.append(" -err:allJavadoc ")
+			.append(" -classpath \"")
+			.append(Util.getJavaClassLibsAsString())
+			.append("\" ")
+			.append(" -warn:-unused")
+			.append(" --module-source-path " + "\"" + directory + "\" ")
+			.append(moduleLoc + File.separator + "module-info.java ")
+			.append(moduleLoc + File.separator + "p" + File.separator + "I1.java ")
+			.append(moduleLoc + File.separator + "p" + File.separator + "P1.java");
+
+		runNegativeModuleTest(
+				new String[0],
+				buffer.toString(),
+				"",
+				"----------\n" +
+				"1. ERROR in ---OUTPUT_DIR_PLACEHOLDER---/src/mod.one/module-info.java (at line 5)\n" +
+				"	provides p.I1 with p.P1;\n" +
+				"	^^^^^^^^^^^^^^^^^^^^^^^\n" +
+				"Javadoc: Missing provides tag\n" +
+				"----------\n" +
+				"2. ERROR in ---OUTPUT_DIR_PLACEHOLDER---/src/mod.one/module-info.java (at line 6)\n" +
+				"	uses java.util.Currency;\n" +
+				"	^^^^^^^^^^^^^^^^^^^^^^^\n" +
+				"Javadoc: Missing uses tag\n" +
+				"----------\n" +
+				"2 problems (2 errors)\n",
+				false,
+				"missing tags");
+	}
+
+	public void testBug549855h() {
+		File outputDirectory = new File(OUTPUT_DIR);
+		Util.flushDirectoryContent(outputDirectory);
+		String out = "bin";
+		String directory = OUTPUT_DIR + File.separator + "src";
+		String moduleLoc = directory + File.separator + "mod.one";
+		List<String> files = new ArrayList<>();
+		writeFileCollecting(files, moduleLoc, "module-info.java",
+						"/**\n" +
+						" * @provides p.I\n" +
+						" * @uses java.util.Currenc\n" +
+						" */\n" +
+						"module mod.one { \n" +
+						" exports p;\n" +
+						" provides p.I1 with p.P1;\n" +
+						" uses java.util.Currency;\n" +
+						"}");
+		writeFileCollecting(files, moduleLoc + File.separator + "p", "I1.java",
+				"package p;\n" +
+				"/**\n" +
+				" * interface I1\n" +
+				" */\n" +
+				"public interface I1 {\n" +
+				"	/**\n" +
+				"	 * Method foo\n" +
+				"    * @return int\n" +
+				"    */\n" +
+				"	public int foo();\n" +
+				"}");
+		writeFileCollecting(files, moduleLoc + File.separator + "p", "P1.java",
+				"package p;\n" +
+				"/**\n" +
+				" * class P1\n" +
+				" */\n" +
+				"public class P1 implements I1 {\n" +
+				"	@Override\n" +
+				"	public int foo() { return 0; }\n" +
+				"}");
+
+		StringBuilder buffer = new StringBuilder();
+		buffer.append("-d " + OUTPUT_DIR + File.separator + out )
+			.append(" -9 ")
+			.append(" -enableJavadoc ")
+			.append(" -err:allJavadoc ")
+			.append(" -classpath \"")
+			.append(Util.getJavaClassLibsAsString())
+			.append("\" ")
+			.append(" -warn:-unused")
+			.append(" --module-source-path " + "\"" + directory + "\" ")
+			.append(moduleLoc + File.separator + "module-info.java ")
+			.append(moduleLoc + File.separator + "p" + File.separator + "I1.java ")
+			.append(moduleLoc + File.separator + "p" + File.separator + "P1.java");
+
+		runNegativeModuleTest(
+				new String[0],
+				buffer.toString(),
+				"",
+				"----------\n" +
+				"1. ERROR in ---OUTPUT_DIR_PLACEHOLDER---/src/mod.one/module-info.java (at line 2)\n" +
+				"	* @provides p.I\n" +
+				"	            ^^^\n" +
+				"Javadoc: Invalid provides class\n" +
+				"----------\n" +
+				"2. ERROR in ---OUTPUT_DIR_PLACEHOLDER---/src/mod.one/module-info.java (at line 3)\n" +
+				"	* @uses java.util.Currenc\n" +
+				"	        ^^^^^^^^^^^^^^^^^\n" +
+				"Javadoc: Invalid uses class\n" +
+				"----------\n" +
+				"3. ERROR in ---OUTPUT_DIR_PLACEHOLDER---/src/mod.one/module-info.java (at line 7)\n" +
+				"	provides p.I1 with p.P1;\n" +
+				"	^^^^^^^^^^^^^^^^^^^^^^^\n" +
+				"Javadoc: Missing provides tag\n" +
+				"----------\n" +
+				"4. ERROR in ---OUTPUT_DIR_PLACEHOLDER---/src/mod.one/module-info.java (at line 8)\n" +
+				"	uses java.util.Currency;\n" +
+				"	^^^^^^^^^^^^^^^^^^^^^^^\n" +
+				"Javadoc: Missing uses tag\n" +
+				"----------\n" +
+				"4 problems (4 errors)\n",
+				false,
+				"invalid tags");
+	}
+
+	public void testBug549855i() {
+		File outputDirectory = new File(OUTPUT_DIR);
+		Util.flushDirectoryContent(outputDirectory);
+		String out = "bin";
+		String directory = OUTPUT_DIR + File.separator + "src";
+		String moduleLoc = directory + File.separator + "mod.one";
+		List<String> files = new ArrayList<>();
+		writeFileCollecting(files, moduleLoc, "module-info.java",
+						"module mod.one {\n" +
+						"}");
+
+		StringBuilder buffer = new StringBuilder();
+		buffer.append("-d " + OUTPUT_DIR + File.separator + out )
+			.append(" -9 ")
+			.append(" -enableJavadoc ")
+			.append(" -err:allJavadoc ")
+			.append(" -classpath \"")
+			.append(Util.getJavaClassLibsAsString())
+			.append("\" ")
+			.append(" -warn:-unused")
+			.append(" --module-source-path " + "\"" + directory + "\" ")
+			.append(moduleLoc + File.separator + "module-info.java ");
+
+		runNegativeModuleTest(
+				new String[0],
+				buffer.toString(),
+				"",
+				"----------\n" +
+				"1. ERROR in ---OUTPUT_DIR_PLACEHOLDER---/src/mod.one/module-info.java (at line 1)\n" +
+				"	module mod.one {\n" +
+				"	^^^^^^^^^^^^^^^\n" +
+				"Javadoc: Missing comment for module declaration\n" +
+				"----------\n" +
+				"1 problem (1 error)\n",
+				false,
+				"missing comment");
+	}
+
+}
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/core/compiler/IProblem.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/core/compiler/IProblem.java
index 64220c7..949332e 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/core/compiler/IProblem.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/core/compiler/IProblem.java
@@ -1192,6 +1192,29 @@
 	int JavadocUnexpectedText = Javadoc + Internal + 518;
 	/** @since 3.1 */
 	int JavadocInvalidParamTagName = Javadoc + Internal + 519;
+	/*
+	 * IDs for module errors in Javadoc
+	 */
+	/** @since 3.20 */
+	int JavadocMissingUsesTag = Javadoc + Internal + 1800;
+	/** @since 3.20 */
+	int JavadocDuplicateUsesTag = Javadoc + Internal + 1801;
+	/** @since 3.20 */
+	int JavadocMissingUsesClassName = Javadoc + Internal + 1802;
+	/** @since 3.20 */
+	int JavadocInvalidUsesClassName = Javadoc + Internal + 1803;
+	/** @since 3.20 */
+	int JavadocInvalidUsesClass = Javadoc + Internal + 1804;
+	/** @since 3.20 */
+	int JavadocMissingProvidesTag = Javadoc + Internal + 1805;
+	/** @since 3.20 */
+	int JavadocDuplicateProvidesTag = Javadoc + Internal + 1806;
+	/** @since 3.20 */
+	int JavadocMissingProvidesClassName = Javadoc + Internal + 1807;
+	/** @since 3.20 */
+	int JavadocInvalidProvidesClassName = Javadoc + Internal + 1808;
+	/** @since 3.20 */
+	int JavadocInvalidProvidesClass = Javadoc + Internal + 1809;
 
 	/**
 	 * Generics
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CompilationUnitDeclaration.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CompilationUnitDeclaration.java
index 3e17e15..aacb0bb 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CompilationUnitDeclaration.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CompilationUnitDeclaration.java
@@ -13,7 +13,8 @@
  *     Stephan Herrmann  - Contribution for bug 295551
  *     Jesper S Moller   - Contributions for
  *							  Bug 405066 - [1.8][compiler][codegen] Implement code generation infrastructure for JSR335
- *     Frits Jalvingh    - contributions for bug 533830.             
+ *     Frits Jalvingh    - contributions for bug 533830.
+ *     Red Hat Inc.	     - add module-info Javadoc support
  *******************************************************************************/
 package org.eclipse.jdt.internal.compiler.ast;
 
@@ -22,6 +23,7 @@
 
 import org.eclipse.jdt.core.compiler.CategorizedProblem;
 import org.eclipse.jdt.core.compiler.CharOperation;
+import org.eclipse.jdt.core.compiler.IProblem;
 import org.eclipse.jdt.internal.compiler.ASTVisitor;
 import org.eclipse.jdt.internal.compiler.ClassFile;
 import org.eclipse.jdt.internal.compiler.CompilationResult;
@@ -345,7 +347,7 @@
 														String key = CompilerOptions.optionKeyFromIrritant(id);
 														this.scope.problemReporter().problemNotAnalysed(inits[iToken], key);
 													} else {
-														this.scope.problemReporter().unusedWarningToken(inits[iToken]);														
+														this.scope.problemReporter().unusedWarningToken(inits[iToken]);
 													}
 												}
 											}
@@ -557,7 +559,7 @@
 	if (context instanceof LambdaExpression && context != ((LambdaExpression) context).original())
 		return true; // Do not record from copies. See https://bugs.eclipse.org/bugs/show_bug.cgi?id=441929
 	Scope cScope = context instanceof AbstractMethodDeclaration ? ((AbstractMethodDeclaration) context).scope :
-		context instanceof TypeDeclaration ? ((TypeDeclaration) context).scope : 
+		context instanceof TypeDeclaration ? ((TypeDeclaration) context).scope :
 		context instanceof LambdaExpression ? ((LambdaExpression) context).scope :
 			null;
 	return cScope != null ? isLambdaExpressionCopyContext(cScope.parent.referenceContext()) : false;
@@ -565,7 +567,7 @@
 public void recordSuppressWarnings(IrritantSet irritants, Annotation annotation, int scopeStart, int scopeEnd, ReferenceContext context) {
 	if (isLambdaExpressionCopyContext(context))
 		return; // Do not record from copies. See https://bugs.eclipse.org/bugs/show_bug.cgi?id=441929
-		
+
 	if (this.suppressWarningIrritants == null) {
 		this.suppressWarningIrritants = new IrritantSet[3];
 		this.suppressWarningAnnotations = new Annotation[3];
@@ -603,7 +605,7 @@
 }
 
 /*
- * Keep track of all lambda/method reference expressions, so as to be able to look it up later without 
+ * Keep track of all lambda/method reference expressions, so as to be able to look it up later without
  * having to traverse AST. Return the "ordinal" returned by the enclosing type.
  */
 public int record(FunctionalExpression expression) {
@@ -619,6 +621,7 @@
 public void resolve() {
 	int startingTypeIndex = 0;
 	boolean isPackageInfo = isPackageInfo();
+	boolean isModuleInfo = isModuleInfo();
 	if (this.types != null && isPackageInfo) {
 		// resolve synthetic type declaration
 		final TypeDeclaration syntheticTypeDeclaration = this.types[0];
@@ -637,6 +640,17 @@
 			this.javadoc.resolve(syntheticTypeDeclaration.staticInitializerScope);
 		}
 		startingTypeIndex = 1;
+	} else if (this.moduleDeclaration != null && isModuleInfo) {
+		if (this.javadoc != null) {
+			this.javadoc.resolve((MethodScope)this.moduleDeclaration.scope);
+		} else if (this.moduleDeclaration.binding != null) {
+			ProblemReporter reporter = this.scope.problemReporter();
+			int severity = reporter.computeSeverity(IProblem.JavadocMissing);
+			if (severity != ProblemSeverities.Ignore) {
+				reporter.javadocModuleMissing(this.moduleDeclaration.declarationSourceStart, this.moduleDeclaration.bodyStart,
+						severity);
+			}
+		}
 	} else {
 		// resolve compilation unit javadoc package if any
 		if (this.javadoc != null) {
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/IJavadocTypeReference.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/IJavadocTypeReference.java
new file mode 100644
index 0000000..c21cdae
--- /dev/null
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/IJavadocTypeReference.java
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * Copyright (c) 2019 Red Hat Inc. and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Red Hat Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.compiler.ast;
+
+/**
+ * Interface to allow Javadoc parser to collect both JavaSingleTypeReference and JavaQualifiedTypeReferences
+ *
+ * @author jjohnstn
+ *
+ */
+public interface IJavadocTypeReference {
+
+	public int getTagSourceStart();
+	public int getTagSourceEnd();
+
+}
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Javadoc.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Javadoc.java
index 308c39c..087d33f 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Javadoc.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Javadoc.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2018 IBM Corporation and others.
+ * Copyright (c) 2000, 2019 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -33,6 +33,8 @@
 	public TypeReference[] exceptionReferences; // @throws, @exception
 	public JavadocReturnStatement returnStatement; // @return
 	public Expression[] seeReferences; // @see
+	public IJavadocTypeReference[] usesReferences; // @uses
+	public IJavadocTypeReference[] providesReferences; // @provides
 	public long[] inheritedPositions = null;
 	// bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=51600
 	// Store param references for tag with invalid syntax
@@ -262,8 +264,8 @@
 			return;
 		}
 		// Do nothing - This is to mimic the SDK's javadoc tool behavior, which neither
-		// sanity checks nor generates documentation using comments at the CU scope 
-		// (unless the unit happens to be package-info.java - in which case we don't come here.) 
+		// sanity checks nor generates documentation using comments at the CU scope
+		// (unless the unit happens to be package-info.java - in which case we don't come here.)
 	}
 
 	/*
@@ -318,7 +320,7 @@
 								MethodBinding current = methDecl.binding;
 								// work 'against' better inference in 1.8 (otherwise comparing (G<T> with G<Object>) would fail):
 								if (methScope.compilerOptions().sourceLevel >= ClassFileConstants.JDK1_8
-									&& current.typeVariables != Binding.NO_TYPE_VARIABLES) 
+									&& current.typeVariables != Binding.NO_TYPE_VARIABLES)
 								{
 									current = current.asRawMethod(methScope.environment());
 								}
@@ -326,7 +328,7 @@
 									superRef = true;
 								}
 							}
-						}						
+						}
 					}
 				}
 			}
@@ -385,6 +387,11 @@
 		for (int i = 0; i < length; i++) {
 			this.invalidParameters[i].resolve(methScope, false, false);
 		}
+
+		if (methScope.isModuleScope()) {
+			resolveUsesTags(methScope, reportMissing);
+			resolveProvidesTags(methScope, reportMissing);
+		}
 	}
 
 	private void resolveReference(Expression reference, Scope scope) {
@@ -561,6 +568,148 @@
 	}
 
 	/*
+	 * Resolve @uses tags while block scope
+	 */
+	private void resolveUsesTags(BlockScope scope, boolean reportMissing) {
+		ModuleDeclaration moduleDecl = (ModuleDeclaration)scope.referenceContext();
+		int usesTagsSize = this.usesReferences == null ? 0 : this.usesReferences.length;
+
+		// If no referenced module then report a problem for each uses tag
+		if (moduleDecl == null) {
+			for (int i = 0; i < usesTagsSize; i++) {
+				IJavadocTypeReference uses = this.usesReferences[i];
+				scope.problemReporter().javadocUnexpectedTag(uses.getTagSourceStart(), uses.getTagSourceEnd());
+			}
+			return;
+		}
+
+		// If no uses tags then report a problem for each uses reference
+		int usesSize = moduleDecl.usesCount;
+		if (usesTagsSize == 0) {
+			if (reportMissing) {
+				for (int i = 0; i < usesSize; i++) {
+					UsesStatement uses = moduleDecl.uses[i];
+					scope.problemReporter().javadocMissingUsesTag(uses.serviceInterface, uses.sourceStart, uses.sourceEnd, moduleDecl.binding.modifiers);
+				}
+			}
+		} else {
+			TypeBinding[] bindings = new TypeBinding[usesTagsSize];
+			int maxBindings = 0;
+
+			// Scan all @uses tags
+			for (int i = 0; i < usesTagsSize; i++) {
+				TypeReference usesRef = (TypeReference)this.usesReferences[i];
+				try {
+					usesRef.resolve(scope);
+					if (usesRef.resolvedType != null && usesRef.resolvedType.isValidBinding()) {
+						// Verify duplicated tags
+						boolean found = false;
+						for (int j = 0; j < maxBindings && !found; j++) {
+							if (bindings[j].equals(usesRef.resolvedType)) {
+								scope.problemReporter().javadocDuplicatedUsesTag(usesRef.sourceStart, usesRef.sourceEnd);
+								found = true;
+							}
+						}
+						if (!found) {
+							bindings[maxBindings++] = usesRef.resolvedType;
+						}
+					}
+				} catch (Exception e) {
+					scope.problemReporter().javadocInvalidUsesClass(usesRef.sourceStart, usesRef.sourceEnd);
+				}
+			}
+
+			// Look for undocumented uses
+			if (reportMissing) {
+				for (int i = 0; i < usesSize; i++) {
+					UsesStatement uses = moduleDecl.uses[i];
+					boolean found = false;
+					for (int j = 0; j < maxBindings && !found; j++) {
+						TypeBinding binding = bindings[j];
+						if (uses.serviceInterface.getTypeBinding(scope).equals(binding)) {
+							found = true;
+						}
+					}
+					if (!found) {
+						scope.problemReporter().javadocMissingUsesTag(uses.serviceInterface, uses.sourceStart, uses.sourceEnd, moduleDecl.binding.modifiers);
+					}
+				}
+			}
+		}
+	}
+
+	/*
+	 * Resolve @provides tags while block scope
+	 */
+	private void resolveProvidesTags(BlockScope scope, boolean reportMissing) {
+		ModuleDeclaration moduleDecl = (ModuleDeclaration)scope.referenceContext();
+		int providesTagsSize = this.providesReferences == null ? 0 : this.providesReferences.length;
+
+		// If no referenced module then report a problem for each uses tag
+		if (moduleDecl == null) {
+			for (int i = 0; i < providesTagsSize; i++) {
+				IJavadocTypeReference provides = this.providesReferences[i];
+				scope.problemReporter().javadocUnexpectedTag(provides.getTagSourceStart(), provides.getTagSourceEnd());
+			}
+			return;
+		}
+
+		// If no uses tags then report a problem for each uses reference
+		int providesSize = moduleDecl.servicesCount;
+		if (providesTagsSize == 0) {
+			if (reportMissing) {
+				for (int i = 0; i < providesSize; i++) {
+					ProvidesStatement provides = moduleDecl.services[i];
+					scope.problemReporter().javadocMissingProvidesTag(provides.serviceInterface, provides.sourceStart, provides.sourceEnd, moduleDecl.binding.modifiers);
+				}
+			}
+		} else {
+			TypeBinding[] bindings = new TypeBinding[providesTagsSize];
+			int maxBindings = 0;
+
+			// Scan all @provides tags
+			for (int i = 0; i < providesTagsSize; i++) {
+				TypeReference providesRef = (TypeReference)this.providesReferences[i];
+				try {
+					providesRef.resolve(scope);
+					if (providesRef.resolvedType != null && providesRef.resolvedType.isValidBinding()) {
+						// Verify duplicated tags
+						boolean found = false;
+						for (int j = 0; j < maxBindings && !found; j++) {
+							if (bindings[j].equals(providesRef.resolvedType)) {
+								scope.problemReporter().javadocDuplicatedProvidesTag(providesRef.sourceStart, providesRef.sourceEnd);
+								found = true;
+							}
+						}
+						if (!found) {
+							bindings[maxBindings++] = providesRef.resolvedType;
+						}
+					}
+				} catch (Exception e) {
+					scope.problemReporter().javadocInvalidProvidesClass(providesRef.sourceStart, providesRef.sourceEnd);
+				}
+			}
+
+			// Look for undocumented uses
+			if (reportMissing) {
+				for (int i = 0; i < providesSize; i++) {
+					ProvidesStatement provides = moduleDecl.services[i];
+					boolean found = false;
+					for (int j = 0; j < maxBindings && !found; j++) {
+						TypeBinding binding = bindings[j];
+						if (provides.serviceInterface.getTypeBinding(scope).equals(binding)) {
+							found = true;
+						}
+					}
+					if (!found) {
+						scope.problemReporter().javadocMissingProvidesTag(provides.serviceInterface, provides.sourceStart, provides.sourceEnd, moduleDecl.binding.modifiers);
+					}
+				}
+			}
+		}
+	}
+
+	/*
 	 * Resolve @param tags for type parameters
 	 */
 	private void resolveTypeParameterTags(Scope scope, boolean reportMissing) {
@@ -604,7 +753,7 @@
 
 		// If no param tags then report a problem for each declaration type parameter
 		if (parameters != null) {
-			// https://bugs.eclipse.org/bugs/show_bug.cgi?id=324850, avoid secondary errors when <= 1.4 
+			// https://bugs.eclipse.org/bugs/show_bug.cgi?id=324850, avoid secondary errors when <= 1.4
 			reportMissing = reportMissing && scope.compilerOptions().sourceLevel >= ClassFileConstants.JDK1_5;
 			int typeParametersLength = parameters.length;
 			if (paramTypeParamLength == 0) {
@@ -830,7 +979,7 @@
 								mainLoop: for (int i=0; i<length; i++) {
 									char[][] compoundName = imports[i].compoundName;
 									int compoundNameLength = compoundName.length;
-									if ((imports[i].onDemand && compoundNameLength == computedCompoundName.length-1) 
+									if ((imports[i].onDemand && compoundNameLength == computedCompoundName.length-1)
 											|| (compoundNameLength == computedCompoundName.length)) {
 										for (int j = compoundNameLength; --j >= 0;) {
 											if (CharOperation.equals(imports[i].compoundName[j], computedCompoundName[j])) {
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/JavadocQualifiedTypeReference.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/JavadocQualifiedTypeReference.java
index 3ed38f0..ef06cca 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/JavadocQualifiedTypeReference.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/JavadocQualifiedTypeReference.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2014 IBM Corporation and others.
+ * Copyright (c) 2000, 2019 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -25,7 +25,7 @@
 import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
 
 
-public class JavadocQualifiedTypeReference extends QualifiedTypeReference {
+public class JavadocQualifiedTypeReference extends QualifiedTypeReference implements IJavadocTypeReference {
 
 	public int tagSourceStart, tagSourceEnd;
 	public PackageBinding packageBinding;
@@ -72,7 +72,7 @@
 	protected void reportDeprecatedType(TypeBinding type, Scope scope) {
 		scope.problemReporter().javadocDeprecatedType(type, this, scope.getDeclarationModifiers());
 	}
-	
+
 	@Override
 	protected void reportDeprecatedType(TypeBinding type, Scope scope, int index) {
 		scope.problemReporter().javadocDeprecatedType(type, this, scope.getDeclarationModifiers(), index);
@@ -107,4 +107,14 @@
 		visitor.visit(this, scope);
 		visitor.endVisit(this, scope);
 	}
+
+	@Override
+	public int getTagSourceStart() {
+		return this.tagSourceStart;
+	}
+
+	@Override
+	public int getTagSourceEnd() {
+		return this.tagSourceEnd;
+	}
 }
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/JavadocSingleTypeReference.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/JavadocSingleTypeReference.java
index b64689b..8a9ed40 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/JavadocSingleTypeReference.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/JavadocSingleTypeReference.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2018 IBM Corporation and others.
+ * Copyright (c) 2000, 2019 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -29,7 +29,7 @@
 import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
 
 
-public class JavadocSingleTypeReference extends SingleTypeReference {
+public class JavadocSingleTypeReference extends SingleTypeReference implements IJavadocTypeReference {
 
 	public int tagSourceStart, tagSourceEnd;
 	public PackageBinding packageBinding;
@@ -129,4 +129,14 @@
 		visitor.visit(this, scope);
 		visitor.endVisit(this, scope);
 	}
+
+	@Override
+	public int getTagSourceStart() {
+		return this.tagSourceStart;
+	}
+
+	@Override
+	public int getTagSourceEnd() {
+		return this.tagSourceEnd;
+	}
 }
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/AbstractCommentParser.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/AbstractCommentParser.java
index 4f7a7b9..9b90a4f 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/AbstractCommentParser.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/AbstractCommentParser.java
@@ -18,6 +18,7 @@
 
 import org.eclipse.jdt.core.compiler.CharOperation;
 import org.eclipse.jdt.core.compiler.InvalidInputException;
+import org.eclipse.jdt.internal.compiler.ast.TypeReference;
 import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
 import org.eclipse.jdt.internal.compiler.util.Util;
 
@@ -102,6 +103,14 @@
 	protected int astLengthPtr;
 	protected int[] astLengthStack;
 
+	// Uses stack
+	protected int usesReferencesPtr = -1;
+	protected TypeReference[] usesReferencesStack;
+
+	// Provides stack
+	protected int providesReferencesPtr = -1;
+	protected TypeReference[] providesReferencesStack;
+
 
 	protected AbstractCommentParser(Parser sourceParser) {
 		this.sourceParser = sourceParser;
@@ -456,6 +465,7 @@
 			}
 			updateDocComment();
 		} catch (Exception ex) {
+			ex.printStackTrace();
 			validComment = false;
 		}
 		return validComment;
@@ -501,7 +511,7 @@
 		return Util.getLineNumber(position, this.lineEnds, 0, this.lineEnds.length-1);
 	}
 
-	private int getTokenEndPosition() {
+	protected int getTokenEndPosition() {
 		if (this.scanner.getCurrentTokenEndPosition() > this.lineEnd) {
 			return this.lineEnd;
 		} else {
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/JavadocParser.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/JavadocParser.java
index 8663cab..0fe67da 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/JavadocParser.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/JavadocParser.java
@@ -17,7 +17,23 @@
 
 import org.eclipse.jdt.core.compiler.CharOperation;
 import org.eclipse.jdt.core.compiler.InvalidInputException;
-import org.eclipse.jdt.internal.compiler.ast.*;
+import org.eclipse.jdt.internal.compiler.ast.ASTNode;
+import org.eclipse.jdt.internal.compiler.ast.Expression;
+import org.eclipse.jdt.internal.compiler.ast.IJavadocTypeReference;
+import org.eclipse.jdt.internal.compiler.ast.Javadoc;
+import org.eclipse.jdt.internal.compiler.ast.JavadocAllocationExpression;
+import org.eclipse.jdt.internal.compiler.ast.JavadocArgumentExpression;
+import org.eclipse.jdt.internal.compiler.ast.JavadocArrayQualifiedTypeReference;
+import org.eclipse.jdt.internal.compiler.ast.JavadocArraySingleTypeReference;
+import org.eclipse.jdt.internal.compiler.ast.JavadocFieldReference;
+import org.eclipse.jdt.internal.compiler.ast.JavadocImplicitTypeReference;
+import org.eclipse.jdt.internal.compiler.ast.JavadocMessageSend;
+import org.eclipse.jdt.internal.compiler.ast.JavadocQualifiedTypeReference;
+import org.eclipse.jdt.internal.compiler.ast.JavadocReturnStatement;
+import org.eclipse.jdt.internal.compiler.ast.JavadocSingleNameReference;
+import org.eclipse.jdt.internal.compiler.ast.JavadocSingleTypeReference;
+import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.TypeReference;
 import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
 import org.eclipse.jdt.internal.compiler.util.Util;
 
@@ -27,6 +43,7 @@
 public class JavadocParser extends AbstractCommentParser {
 	private static final JavadocSingleNameReference[] NO_SINGLE_NAME_REFERENCE = new JavadocSingleNameReference[0];
 	private static final JavadocSingleTypeReference[] NO_SINGLE_TYPE_REFERENCE = new JavadocSingleTypeReference[0];
+	private static final JavadocQualifiedTypeReference[] NO_QUALIFIED_TYPE_REFERENCE = new JavadocQualifiedTypeReference[0];
 	private static final TypeReference[] NO_TYPE_REFERENCE = new TypeReference[0];
 	private static final Expression[] NO_EXPRESSION = new Expression[0];
 
@@ -629,7 +646,9 @@
 					}
 				} else if (length == TAG_PROVIDES_LENGTH && CharOperation.equals(TAG_PROVIDES, tagName, 0, length)) {
 					this.tagValue = TAG_PROVIDES_VALUE;
-					this.tagWaitingForDescription = this.tagValue;
+					if (!this.inlineTagStarted) {
+						valid = parseProvidesReference();
+					}
 				}
 				break;
 			case 'r':
@@ -677,7 +696,9 @@
 			case 'u':
 				if (length == TAG_USES_LENGTH && CharOperation.equals(TAG_USES, tagName, 0, length)) {
 					this.tagValue = TAG_USES_VALUE;
-					this.tagWaitingForDescription = this.tagValue;
+					if (!this.inlineTagStarted) {
+						valid = parseUsesReference();
+					}
 				}
 				break;
 			case 'v':
@@ -951,6 +972,18 @@
 			System.arraycopy(this.invalidParamReferencesStack, 0, this.docComment.invalidParameters, 0, this.invalidParamReferencesPtr+1);
 		}
 
+		this.docComment.usesReferences = this.usesReferencesPtr >= 0 ? new IJavadocTypeReference[this.usesReferencesPtr+1] : NO_QUALIFIED_TYPE_REFERENCE;
+		for (int i = 0; i <= this.usesReferencesPtr; ++i) {
+			TypeReference ref = this.usesReferencesStack[i];
+			this.docComment.usesReferences[i] = (IJavadocTypeReference)ref; 
+		}
+
+		this.docComment.providesReferences = this.providesReferencesPtr >= 0 ? new IJavadocTypeReference[this.providesReferencesPtr+1] : NO_QUALIFIED_TYPE_REFERENCE;
+		for (int i = 0; i <= this.providesReferencesPtr; ++i) {
+			TypeReference ref = this.providesReferencesStack[i];
+			this.docComment.providesReferences[i] = (IJavadocTypeReference)ref; 
+		}
+
 		// If no nodes stored return
 		if (this.astLengthPtr == -1) {
 			return;
@@ -1013,4 +1046,78 @@
 			System.arraycopy(this.docComment.paramTypeParameters, paramTypeParamPtr, this.docComment.paramTypeParameters = new JavadocSingleTypeReference[size - paramTypeParamPtr], 0, size - paramTypeParamPtr);
 		}
 	}
+
+	/*
+	 * Parse @uses tag declaration
+	 */
+	protected boolean parseUsesReference() {
+		int start = this.scanner.currentPosition;
+		try {
+			Object typeRef = parseQualifiedName(true);
+			if (this.abort) return false; // May be aborted by specialized parser
+			if (typeRef == null) {
+				if (this.reportProblems)
+					this.sourceParser.problemReporter().javadocMissingUsesClassName(this.tagSourceStart, this.tagSourceEnd, this.sourceParser.modifiers);
+			} else {
+				return pushUsesReference(typeRef);
+			}
+		} catch (InvalidInputException ex) {
+			if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidUsesClass(start, getTokenEndPosition());
+		}
+		return false;
+	}
+
+	protected boolean pushUsesReference(Object typeRef) {
+		// TODO Auto-generated method stub
+		if (this.usesReferencesPtr == -1l) {
+			this.usesReferencesStack = new TypeReference[10];
+		}
+		int stackLength = this.usesReferencesStack.length;
+		if (++this.usesReferencesPtr >= stackLength) {
+			System.arraycopy(
+				this.usesReferencesStack, 0,
+				this.usesReferencesStack = new TypeReference[stackLength + AST_STACK_INCREMENT], 0,
+				stackLength);
+		}
+		this.usesReferencesStack[this.usesReferencesPtr] = (TypeReference)typeRef;
+		return true;
+	}
+
+	/*
+	 * Parse @uses tag declaration
+	 */
+	protected boolean parseProvidesReference() {
+		int start = this.scanner.currentPosition;
+		try {
+			Object typeRef = parseQualifiedName(true);
+			if (this.abort) return false; // May be aborted by specialized parser
+			if (typeRef == null) {
+				if (this.reportProblems)
+					this.sourceParser.problemReporter().javadocMissingProvidesClassName(this.tagSourceStart, this.tagSourceEnd, this.sourceParser.modifiers);
+			} else {
+				return pushProvidesReference(typeRef);
+			}
+		} catch (InvalidInputException ex) {
+			if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidProvidesClass(start, getTokenEndPosition());
+		}
+		return false;
+	}
+
+	protected boolean pushProvidesReference(Object typeRef) {
+		// TODO Auto-generated method stub
+		if (this.providesReferencesPtr == -1l) {
+			this.providesReferencesStack = new TypeReference[10];
+		}
+		int stackLength = this.providesReferencesStack.length;
+		if (++this.providesReferencesPtr >= stackLength) {
+			System.arraycopy(
+				this.providesReferencesStack, 0,
+				this.providesReferencesStack = new TypeReference[stackLength + AST_STACK_INCREMENT], 0,
+				stackLength);
+		}
+		this.providesReferencesStack[this.providesReferencesPtr] = (TypeReference)typeRef;
+		return true;
+
+	}
+
 }
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/Parser.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/Parser.java
index db0af41..71f2ead 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/Parser.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/Parser.java
@@ -5935,6 +5935,9 @@
 }
 protected void consumeModuleDeclaration() {
 	// ModuleDeclaration ::= ModuleHeader ModuleBody
+	this.compilationUnit.javadoc = this.javadoc;
+	this.javadoc = null;
+
 	int length = this.astLengthStack[this.astLengthPtr--];
 	int[] flag = new int[length + 1]; //plus one -- see <HERE>
 	int size1 = 0, size2 = 0, size3 = 0, size4 = 0, size5 = 0;
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java
index c227f5b..d0bb1be 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java
@@ -546,6 +546,14 @@
 		case IProblem.JavadocInvalidThrowsClassName:
 		case IProblem.JavadocDuplicateThrowsClassName:
 		case IProblem.JavadocMissingThrowsClassName:
+		case IProblem.JavadocDuplicateProvidesTag:
+		case IProblem.JavadocDuplicateUsesTag:
+		case IProblem.JavadocInvalidUsesClass:
+		case IProblem.JavadocInvalidUsesClassName:
+		case IProblem.JavadocInvalidProvidesClass:
+		case IProblem.JavadocInvalidProvidesClassName:
+		case IProblem.JavadocMissingProvidesClassName:
+		case IProblem.JavadocMissingUsesClassName:
 		case IProblem.JavadocMissingSeeReference:
 		case IProblem.JavadocInvalidValueReference:
 		case IProblem.JavadocUndefinedField:
@@ -589,8 +597,10 @@
 			return CompilerOptions.InvalidJavadoc;
 
 		case IProblem.JavadocMissingParamTag:
+		case IProblem.JavadocMissingProvidesTag:
 		case IProblem.JavadocMissingReturnTag:
 		case IProblem.JavadocMissingThrowsTag:
+		case IProblem.JavadocMissingUsesTag:
 			return CompilerOptions.MissingJavadocTags;
 
 		case IProblem.JavadocMissing:
@@ -5219,6 +5229,9 @@
 			sourceEnd);
 	}
 }
+public void javadocDuplicatedProvidesTag(int sourceStart, int sourceEnd){
+	this.handle(IProblem.JavadocDuplicateProvidesTag, NoArgument, NoArgument, sourceStart, sourceEnd);
+}
 public void javadocDuplicatedReturnTag(int sourceStart, int sourceEnd){
 	this.handle(IProblem.JavadocDuplicateReturnTag, NoArgument, NoArgument, sourceStart, sourceEnd);
 }
@@ -5245,6 +5258,11 @@
 			typeReference.sourceEnd);
 	}
 }
+public void javadocDuplicatedUsesTag(
+		
+		int sourceStart, int sourceEnd){
+	this.handle(IProblem.JavadocDuplicateUsesTag, NoArgument, NoArgument, sourceStart, sourceEnd);
+}
 public void javadocEmptyReturnTag(int sourceStart, int sourceEnd, int modifiers) {
 	int severity = computeSeverity(IProblem.JavadocEmptyReturnTag);
 	if (severity == ProblemSeverities.Ignore) return;
@@ -5692,6 +5710,24 @@
 public void javadocInvalidParamTypeParameter(int sourceStart, int sourceEnd) {
 	this.handle(IProblem.JavadocInvalidParamTagTypeParameter, NoArgument, NoArgument, sourceStart, sourceEnd);
 }
+public void javadocInvalidProvidesClass(int sourceStart, int sourceEnd) {
+	this.handle(IProblem.JavadocInvalidProvidesClass, NoArgument, NoArgument, sourceStart, sourceEnd);
+}
+
+public void javadocInvalidProvidesClassName(TypeReference typeReference, int modifiers) {
+	int severity = computeSeverity(IProblem.JavadocInvalidProvidesClassName);
+	if (severity == ProblemSeverities.Ignore) return;
+	if (javadocVisibility(this.options.reportInvalidJavadocTagsVisibility, modifiers)) {
+		String[] arguments = new String[] {String.valueOf(typeReference.resolvedType.sourceName())};
+		this.handle(
+			IProblem.JavadocInvalidProvidesClassName,
+			arguments,
+			arguments,
+			severity,
+			typeReference.sourceStart,
+			typeReference.sourceEnd);
+	}
+}
 public void javadocInvalidReference(int sourceStart, int sourceEnd) {
 	this.handle(IProblem.JavadocInvalidSeeReference, NoArgument, NoArgument, sourceStart, sourceEnd);
 }
@@ -5770,6 +5806,24 @@
 			location.sourceEnd);
 	}
 }
+public void javadocInvalidUsesClass(int sourceStart, int sourceEnd) {
+	this.handle(IProblem.JavadocInvalidUsesClass, NoArgument, NoArgument, sourceStart, sourceEnd);
+}
+
+public void javadocInvalidUsesClassName(TypeReference typeReference, int modifiers) {
+	int severity = computeSeverity(IProblem.JavadocInvalidUsesClassName);
+	if (severity == ProblemSeverities.Ignore) return;
+	if (javadocVisibility(this.options.reportInvalidJavadocTagsVisibility, modifiers)) {
+		String[] arguments = new String[] {String.valueOf(typeReference.resolvedType.sourceName())};
+		this.handle(
+			IProblem.JavadocInvalidUsesClassName,
+			arguments,
+			arguments,
+			severity,
+			typeReference.sourceStart,
+			typeReference.sourceEnd);
+	}
+}
 public void javadocInvalidValueReference(int sourceStart, int sourceEnd, int modifiers) {
 	if (javadocVisibility(this.options.reportInvalidJavadocTagsVisibility, modifiers))
 		this.handle(IProblem.JavadocInvalidValueReference, NoArgument, NoArgument, sourceStart, sourceEnd);
@@ -5800,6 +5854,20 @@
 		}
 	}
 }
+public void javadocModuleMissing(int sourceStart, int sourceEnd, int severity){
+	if (severity == ProblemSeverities.Ignore) return;
+	boolean report = this.options.getSeverity(CompilerOptions.MissingJavadocComments) != ProblemSeverities.Ignore;
+	if (report) {
+			String[] arguments = new String[] { "module" }; //$NON-NLS-1$
+			this.handle(
+				IProblem.JavadocMissing,
+				arguments,
+				arguments,
+				severity,
+				sourceStart,
+				sourceEnd);
+	}
+}
 public void javadocMissingHashCharacter(int sourceStart, int sourceEnd, String ref){
 	int severity = computeSeverity(IProblem.JavadocMissingHashCharacter);
 	if (severity == ProblemSeverities.Ignore) return;
@@ -5837,6 +5905,26 @@
 			sourceEnd);
 	}
 }
+public void javadocMissingProvidesClassName(int sourceStart, int sourceEnd, int modifiers){
+	if (javadocVisibility(this.options.reportInvalidJavadocTagsVisibility, modifiers)) {
+		this.handle(IProblem.JavadocMissingProvidesClassName, NoArgument, NoArgument, sourceStart, sourceEnd);
+	}
+}
+public void javadocMissingProvidesTag(TypeReference typeRef, int sourceStart, int sourceEnd, int modifiers){
+	int severity = computeSeverity(IProblem.JavadocMissingProvidesTag);
+	if (severity == ProblemSeverities.Ignore) return;
+	boolean report = this.options.getSeverity(CompilerOptions.MissingJavadocTags) != ProblemSeverities.Ignore;
+	if (report) {
+		String[] arguments = new String[] { String.valueOf(typeRef.resolvedType.sourceName()) };
+		this.handle(
+			IProblem.JavadocMissingProvidesTag,
+			arguments,
+			arguments,
+			severity,
+			sourceStart,
+			sourceEnd);
+	}
+}
 public void javadocMissingReference(int sourceStart, int sourceEnd, int modifiers){
 	if (javadocVisibility(this.options.reportInvalidJavadocTagsVisibility, modifiers))
 		this.handle(IProblem.JavadocMissingSeeReference, NoArgument, NoArgument, sourceStart, sourceEnd);
@@ -5887,6 +5975,27 @@
 			typeRef.sourceEnd);
 	}
 }
+public void javadocMissingUsesClassName(int sourceStart, int sourceEnd, int modifiers){
+	if (javadocVisibility(this.options.reportInvalidJavadocTagsVisibility, modifiers)) {
+		this.handle(IProblem.JavadocMissingUsesClassName, NoArgument, NoArgument, sourceStart, sourceEnd);
+	}
+}
+
+public void javadocMissingUsesTag(TypeReference typeRef, int sourceStart, int sourceEnd, int modifiers){
+	int severity = computeSeverity(IProblem.JavadocMissingUsesTag);
+	if (severity == ProblemSeverities.Ignore) return;
+	boolean report = this.options.getSeverity(CompilerOptions.MissingJavadocTags) != ProblemSeverities.Ignore;
+	if (report) {
+		String[] arguments = new String[] { String.valueOf(typeRef.resolvedType.sourceName()) };
+		this.handle(
+			IProblem.JavadocMissingUsesTag,
+			arguments,
+			arguments,
+			severity,
+			sourceStart,
+			sourceEnd);
+	}
+}
 public void javadocUndeclaredParamTagName(char[] token, int sourceStart, int sourceEnd, int modifiers) {
 	int severity = computeSeverity(IProblem.JavadocInvalidParamName);
 	if (severity == ProblemSeverities.Ignore) return;
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/messages.properties b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/messages.properties
index c859ddd..8c41717 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/messages.properties
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/messages.properties
@@ -1013,6 +1013,18 @@
 1717 = yield may be a restricted identifier in future and may be disallowed as a type name
 1718 = yield is a restricted identifier and cannot be used as type name
 
+# Additional doc
+1800 = Missing uses tag
+1801 = Duplicate uses tag
+1802 = Missing uses class name
+1803 = Invalid uses class name
+1804 = Invalid uses class
+1805 = Missing provides tag
+1806 = Duplicate provides tag
+1807 = Missing provides class name
+1808 = Invalid provides class name
+1809 = Invalid provides class
+
 ### ELABORATIONS
 ## Access restrictions
 78592 = The type ''{1}'' is not API (restriction on classpath entry ''{0}'')
diff --git a/org.eclipse.jdt.core/jdtCompilerAdapter.jar b/org.eclipse.jdt.core/jdtCompilerAdapter.jar
index f5a1108..dd21253 100644
--- a/org.eclipse.jdt.core/jdtCompilerAdapter.jar
+++ b/org.eclipse.jdt.core/jdtCompilerAdapter.jar
Binary files differ