update jdt.core to I20191120-1800 for 2019-12 M3
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 f9e3b52..51fdf97 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
@@ -706,9 +706,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));
@@ -723,6 +725,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));
@@ -730,6 +734,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));
@@ -738,11 +744,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));
@@ -1763,9 +1773,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));
@@ -1780,6 +1792,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));
@@ -1787,6 +1801,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);
@@ -1795,11 +1811,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.tests.model/src/org/eclipse/jdt/core/tests/formatter/FormatterBugsTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/formatter/FormatterBugsTests.java
index 96e0469..3296f5a 100644
--- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/formatter/FormatterBugsTests.java
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/formatter/FormatterBugsTests.java
@@ -13036,4 +13036,23 @@
 		"\n" + 
 		"}}");
 }
+/**
+ * https://bugs.eclipse.org/220713 - [formatter] Formatting of array initializers in method calls
+ */
+public void testBug220713() {
+	this.formatterPrefs.alignment_for_arguments_in_method_invocation = Alignment.M_NEXT_PER_LINE_SPLIT | Alignment.M_INDENT_ON_COLUMN | Alignment.M_FORCE;
+	this.formatterPrefs.alignment_for_expressions_in_array_initializer = Alignment.M_NEXT_SHIFTED_SPLIT | Alignment.M_INDENT_ON_COLUMN | Alignment.M_FORCE;
+	this.formatterPrefs.insert_new_line_before_closing_brace_in_array_initializer = true;
+	formatSource(
+		"public class A {\n" + 
+		"	void f() {\n" + 
+		"		methodWithArrays(	new Object[] {\n" + 
+		"											null,\n" + 
+		"							},\n" + 
+		"							new Object[] {\n" + 
+		"											null,\n" + 
+		"							});\n" + 
+		"	}\n" + 
+		"}");
+}
 }
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/formatter/FormatterRegressionTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/formatter/FormatterRegressionTests.java
index 567280f..3286bd9 100644
--- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/formatter/FormatterRegressionTests.java
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/formatter/FormatterRegressionTests.java
@@ -15385,4 +15385,115 @@
 	this.formatterPrefs.continuation_indentation = 2;
 	formatSourceInWorkspace("test549436", "in.java", "K_out.java");
 }
+/**
+ * https://bugs.eclipse.org/54627 - [formatter] Blank lines between Javadoc tags
+ */
+public void testBug54627a() throws JavaModelException {
+	this.formatterPrefs.comment_insert_empty_line_between_different_tags = true;
+	String input =
+		"public class Test {\n" + 
+		"	/**\n" + 
+		"	 * Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.\n" + 
+		"	 * @param a Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n" + 
+		"	 * @param b Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n" + 
+		"	 * @param c Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n" + 
+		"	 * @throws IOException Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium.\n" + 
+		"	 * @throws SQLException Totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.\n" + 
+		"	 * @return Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.\n" + 
+		"	 */\n" + 
+		"	public String f(int a, int b, int c) throws IOException, SQLException {\n" + 
+		"		return \"\";\n" + 
+		"	}\n" + 
+		"}";
+	formatSource(input,
+		"public class Test {\n" + 
+		"	/**\n" + 
+		"	 * Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod\n" + 
+		"	 * tempor incididunt ut labore et dolore magna aliqua. Neque porro quisquam est,\n" + 
+		"	 * qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia\n" + 
+		"	 * non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam\n" + 
+		"	 * quaerat voluptatem.\n" + 
+		"	 * \n" + 
+		"	 * @param a Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris\n" + 
+		"	 *          nisi ut aliquip ex ea commodo consequat.\n" + 
+		"	 * @param b Duis aute irure dolor in reprehenderit in voluptate velit esse\n" + 
+		"	 *          cillum dolore eu fugiat nulla pariatur.\n" + 
+		"	 * @param c Excepteur sint occaecat cupidatat non proident, sunt in culpa qui\n" + 
+		"	 *          officia deserunt mollit anim id est laborum.\n" + 
+		"	 * \n" + 
+		"	 * @throws IOException  Sed ut perspiciatis unde omnis iste natus error sit\n" + 
+		"	 *                      voluptatem accusantium doloremque laudantium.\n" + 
+		"	 * @throws SQLException Totam rem aperiam, eaque ipsa quae ab illo inventore\n" + 
+		"	 *                      veritatis et quasi architecto beatae vitae dicta sunt\n" + 
+		"	 *                      explicabo.\n" + 
+		"	 * \n" + 
+		"	 * @return Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut\n" + 
+		"	 *         fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem\n" + 
+		"	 *         sequi nesciunt.\n" + 
+		"	 */\n" + 
+		"	public String f(int a, int b, int c) throws IOException, SQLException {\n" + 
+		"		return \"\";\n" + 
+		"	}\n" + 
+		"}");
+}
+/**
+ * https://bugs.eclipse.org/54627 - [formatter] Blank lines between Javadoc tags
+ */
+public void testBug54627b() throws JavaModelException {
+	this.formatterPrefs.comment_insert_empty_line_between_different_tags = true;
+	String input =
+		"public class Test {\n" + 
+		"	\n" + 
+		"	/**\n" + 
+		"	 * Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.\n" + 
+		"	 * @param a Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n" + 
+		"	 * @param b Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n" + 
+		"	 * @return Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.\n" + 
+		"	 * @@org.example.transaction.interceptor.RuleBasedTransactionAttribute()\n" + 
+		"	 * @@org.example.transaction.interceptor.RollbackRuleAttribute(Exception.class)\n" + 
+		"	 * @@org.example.transaction.interceptor.NoRollbackRuleAttribute(\"ServletException\")\n" + 
+		"	 */\n" + 
+		"	public String f(int a, int b, int c) {\n" + 
+		"		return \"\";\n" + 
+		"	}\n" + 
+		"}";
+	formatSource(input,
+		"public class Test {\n" + 
+		"\n" + 
+		"	/**\n" + 
+		"	 * Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod\n" + 
+		"	 * tempor incididunt ut labore et dolore magna aliqua. Neque porro quisquam est,\n" + 
+		"	 * qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia\n" + 
+		"	 * non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam\n" + 
+		"	 * quaerat voluptatem.\n" + 
+		"	 * \n" + 
+		"	 * @param a Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris\n" + 
+		"	 *          nisi ut aliquip ex ea commodo consequat.\n" + 
+		"	 * @param b Duis aute irure dolor in reprehenderit in voluptate velit esse\n" + 
+		"	 *          cillum dolore eu fugiat nulla pariatur.\n" + 
+		"	 * \n" + 
+		"	 * @return Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut\n" + 
+		"	 *         fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem\n" + 
+		"	 *         sequi nesciunt.\n" + 
+		"	 * \n" + 
+		"	 * @@org.example.transaction.interceptor.RuleBasedTransactionAttribute()\n" + 
+		"	 * @@org.example.transaction.interceptor.RollbackRuleAttribute(Exception.class)\n" + 
+		"	 * @@org.example.transaction.interceptor.NoRollbackRuleAttribute(\"ServletException\")\n" + 
+		"	 */\n" + 
+		"	public String f(int a, int b, int c) {\n" + 
+		"		return \"\";\n" + 
+		"	}\n" + 
+		"}");
+}
+/**
+ * https://bugs.eclipse.org/547261 - [formatter] Separate option for space after not (!) operator 
+ */
+public void testBug547261() throws JavaModelException {
+	this.formatterPrefs.insert_space_after_not_operator = true;
+	String input = "class C {boolean b=!a&&!(c||d)&&(f!=-5);}";
+	formatSource(input,
+		"class C {\n" + 
+		"	boolean b = ! a && ! (c || d) && (f != -5);\n" + 
+		"}");
+}
 }
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/NullAnnotationModelTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/NullAnnotationModelTests.java
index 7dbfd9d..b36337b 100644
--- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/NullAnnotationModelTests.java
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/NullAnnotationModelTests.java
@@ -19,8 +19,10 @@
 import java.lang.annotation.Target;
 import java.io.ByteArrayInputStream;
 import java.net.URL;
+import java.util.HashMap;
 import java.util.Hashtable;
 import java.util.List;
+import java.util.Map;
 
 import junit.framework.Test;
 
@@ -43,13 +45,19 @@
 import org.eclipse.jdt.core.JavaCore;
 import org.eclipse.jdt.core.JavaModelException;
 import org.eclipse.jdt.core.compiler.IProblem;
+import org.eclipse.jdt.core.dom.AST;
 import org.eclipse.jdt.core.dom.ASTParser;
+import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
 import org.eclipse.jdt.core.dom.CompilationUnit;
+import org.eclipse.jdt.core.dom.FieldDeclaration;
+import org.eclipse.jdt.core.dom.IAnnotationBinding;
+import org.eclipse.jdt.core.dom.ITypeBinding;
 import org.eclipse.jdt.core.dom.MarkerAnnotation;
 import org.eclipse.jdt.core.dom.MethodDeclaration;
 import org.eclipse.jdt.core.dom.Modifier;
 import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
 import org.eclipse.jdt.core.dom.TypeDeclaration;
+import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
 import org.eclipse.jdt.core.tests.util.Util;
 import org.osgi.framework.Bundle;
 
@@ -1061,4 +1069,29 @@
     		deleteProject(project);
     	}
 	}
+	
+	// was: NPE in SourceTypeBinding.getAnnotationTagBits
+	@SuppressWarnings("deprecation")
+	public void testBug551426() throws CoreException, Exception {
+		ASTParser astParser = ASTParser.newParser(AST.JLS8);
+		Map<String, String> options = new HashMap<>();
+		astParser.setResolveBindings(true);
+		astParser.setEnvironment(new String[] {}, new String[] {}, new String[] {}, true);
+		options.put(JavaCore.COMPILER_SOURCE, "1.8");
+		options.put(JavaCore.COMPILER_COMPLIANCE, "1.8");
+		astParser.setCompilerOptions(options);
+		astParser.setUnitName("C.java");
+		String source = 
+				"class C {\n" + 
+				"  public static final Object f = new Object() {};\n" +
+				"}\n";
+		astParser.setSource(source.toCharArray());
+		CompilationUnit astNode = (CompilationUnit) astParser.createAST(null);
+		AbstractTypeDeclaration typeDeclaration = (AbstractTypeDeclaration) astNode.types().get(0);
+		FieldDeclaration fieldDeclaration = (FieldDeclaration) typeDeclaration.bodyDeclarations().get(0);
+		VariableDeclarationFragment fragment = (VariableDeclarationFragment) fieldDeclaration.fragments().get(0);
+		ITypeBinding typeBinding = fragment.getInitializer().resolveTypeBinding();
+		IAnnotationBinding[] annotations = typeBinding.getAnnotations();
+		assertEquals(0, annotations.length);
+	}
 }
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 0ca57f9..b0bd5c0 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
@@ -1197,6 +1197,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 7764774..67bb4e1 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
@@ -15,7 +15,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;
 
@@ -24,6 +25,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;
@@ -427,7 +429,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]);
 													}
 												}
 											}
@@ -651,7 +653,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;
@@ -690,7 +692,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];
@@ -728,7 +730,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) {
@@ -746,6 +748,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];
@@ -764,6 +767,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 193c9c3..635ad28 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
@@ -48,6 +48,8 @@
 //{ObjectTeams: store @role references
 	public JavadocSingleTypeReference[] roleReferences; // @role
 // SH}
+	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
@@ -318,8 +320,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.)
 	}
 
 	/*
@@ -374,7 +376,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());
 								}
@@ -382,7 +384,7 @@
 									superRef = true;
 								}
 							}
-						}						
+						}
 					}
 				}
 			}
@@ -441,6 +443,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) {
@@ -617,6 +624,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) {
@@ -660,7 +809,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) {
@@ -886,7 +1035,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 a0ef9b7..b211eb1 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
@@ -32,7 +32,7 @@
 import org.eclipse.objectteams.otdt.internal.core.compiler.lookup.OTClassScope;
 
 
-public class JavadocSingleTypeReference extends SingleTypeReference {
+public class JavadocSingleTypeReference extends SingleTypeReference implements IJavadocTypeReference {
 
 	public int tagSourceStart, tagSourceEnd;
 	public PackageBinding packageBinding;
@@ -154,4 +154,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/lookup/SourceTypeBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SourceTypeBinding.java
index eb6c859..10cac23 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SourceTypeBinding.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SourceTypeBinding.java
@@ -1381,37 +1381,38 @@
 	if (!isPrototype())
 		return this.prototype.getAnnotationTagBits();
 	
-	if ((this.tagBits & TagBits.EndHierarchyCheck) == 0) {
-		CompilationUnitScope pkgCUS = this.scope.compilationUnitScope();
-		boolean current = pkgCUS.connectingHierarchy;
-		pkgCUS.connectingHierarchy = true;
-		try {
-			return internalGetAnnotationTagBits();
-		} finally {
-			pkgCUS.connectingHierarchy = current;
+	if ((this.tagBits & TagBits.AnnotationResolved) == 0 && this.scope != null) {
+		if ((this.tagBits & TagBits.EndHierarchyCheck) == 0) {
+			CompilationUnitScope pkgCUS = this.scope.compilationUnitScope();
+			boolean current = pkgCUS.connectingHierarchy;
+			pkgCUS.connectingHierarchy = true;
+			try {
+				initAnnotationTagBits();
+			} finally {
+				pkgCUS.connectingHierarchy = current;
+			}
+		} else {
+			initAnnotationTagBits();
 		}
 	}
-	return internalGetAnnotationTagBits();
+	return this.tagBits;
 }
-private long internalGetAnnotationTagBits() {
-	if ((this.tagBits & TagBits.AnnotationResolved) == 0 && this.scope != null) {
-		TypeDeclaration typeDecl = this.scope.referenceContext;
-		boolean old = typeDecl.staticInitializerScope.insideTypeAnnotation;
-		try {
-			typeDecl.staticInitializerScope.insideTypeAnnotation = true;
-			ASTNode.resolveAnnotations(typeDecl.staticInitializerScope, typeDecl.annotations, this);
-		} finally {
-			typeDecl.staticInitializerScope.insideTypeAnnotation = old;
-		}
-		if ((this.tagBits & TagBits.AnnotationDeprecated) != 0)
-			this.modifiers |= ClassFileConstants.AccDeprecated;
+private void initAnnotationTagBits() {
+	TypeDeclaration typeDecl = this.scope.referenceContext;
+	boolean old = typeDecl.staticInitializerScope.insideTypeAnnotation;
+	try {
+		typeDecl.staticInitializerScope.insideTypeAnnotation = true;
+		ASTNode.resolveAnnotations(typeDecl.staticInitializerScope, typeDecl.annotations, this);
+	} finally {
+		typeDecl.staticInitializerScope.insideTypeAnnotation = old;
 //{ObjectTeams: @Instantation can only be applied to role classes		
 		if (   (this.tagBits & TagBits.AnnotationInstantiation) != 0
 			&& (!isRole() || isInterface()))
 			this.scope.problemReporter().instantiationAnnotationInNonRole(typeDecl);
 // SH}
 	}
-	return this.tagBits;
+	if ((this.tagBits & TagBits.AnnotationDeprecated) != 0)
+		this.modifiers |= ClassFileConstants.AccDeprecated;
 }
 public MethodBinding[] getDefaultAbstractMethods() {
 	if (!isPrototype())
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 dc3a9fb..c033178 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
@@ -19,6 +19,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;
 
@@ -103,6 +104,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;
@@ -460,6 +469,7 @@
 			}
 			updateDocComment();
 		} catch (Exception ex) {
+			ex.printStackTrace();
 			validComment = false;
 		}
 		return validComment;
@@ -505,7 +515,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 793a33e..da6e0b7 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
@@ -18,7 +18,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;
 
@@ -28,6 +44,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];
 
@@ -630,7 +647,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':
@@ -694,7 +713,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':
@@ -1026,6 +1047,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;
@@ -1100,4 +1133,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 5af152a..5ef5fe8 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
@@ -7304,6 +7304,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 8bb2c12..19f3dbb 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
@@ -607,6 +607,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:
@@ -650,8 +658,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:
@@ -5818,6 +5828,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);
 }
@@ -5844,6 +5857,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;
@@ -6291,6 +6309,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);
 }
@@ -6369,6 +6405,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);
@@ -6399,6 +6453,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;
@@ -6436,6 +6504,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);
@@ -6486,6 +6574,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 ebfd8e9..894ad69 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
@@ -1015,6 +1015,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/formatter/org/eclipse/jdt/core/formatter/DefaultCodeFormatterConstants.java b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/core/formatter/DefaultCodeFormatterConstants.java
index 31f10d7..a7dbea6 100644
--- a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/core/formatter/DefaultCodeFormatterConstants.java
+++ b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/core/formatter/DefaultCodeFormatterConstants.java
@@ -1396,6 +1396,18 @@
 	 * @since 3.1
 	 */
 	public final static String FORMATTER_COMMENT_INSERT_EMPTY_LINE_BEFORE_ROOT_TAGS = "org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags"; //$NON-NLS-1$
+	/**
+	 * <pre>
+	 * FORMATTER / Option to insert an empty line between Javadoc tags of different type
+	 *     - option id:         "org.eclipse.jdt.core.formatter.comment.insert_new_line_between_different_tags"
+	 *     - possible values:   { INSERT, DO_NOT_INSERT }
+	 *     - default:           INSERT
+	 * </pre>
+	 * @see JavaCore#INSERT
+	 * @see JavaCore#DO_NOT_INSERT
+	 * @since 3.20
+	 */
+	public final static String FORMATTER_COMMENT_INSERT_EMPTY_LINE_BETWEEN_DIFFERENT_TAGS = "org.eclipse.jdt.core.formatter.comment.insert_new_line_between_different_tags"; //$NON-NLS-1$
 
 	/**
 	 * <pre>
@@ -3119,6 +3131,18 @@
 	public static final String FORMATTER_INSERT_SPACE_AFTER_UNARY_OPERATOR = JavaCore.PLUGIN_ID + ".formatter.insert_space_after_unary_operator"; //$NON-NLS-1$
 	/**
 	 * <pre>
+	 * FORMATTER / Option to insert a space after 'not' operator
+	 *     - option id:         "org.eclipse.jdt.core.formatter.insert_space_after_not_operator"
+	 *     - possible values:   { INSERT, DO_NOT_INSERT }
+	 *     - default:           DO_NOT_INSERT
+	 * </pre>
+	 * @see JavaCore#INSERT
+	 * @see JavaCore#DO_NOT_INSERT
+	 * @since 3.20
+	 */
+	public static final String FORMATTER_INSERT_SPACE_AFTER_NOT_OPERATOR = JavaCore.PLUGIN_ID + ".formatter.insert_space_after_not_operator"; //$NON-NLS-1$
+	/**
+	 * <pre>
 	 * FORMATTER / Option to insert a space before and in wildcard
 	 *     - option id:         "org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter"
 	 *     - possible values:   { INSERT, DO_NOT_INSERT }
diff --git a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/CommentsPreparator.java b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/CommentsPreparator.java
index 85d047e..c9be5d9 100644
--- a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/CommentsPreparator.java
+++ b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/CommentsPreparator.java
@@ -33,7 +33,7 @@
 import java.util.Map;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
-
+import java.util.stream.Collectors;
 import org.eclipse.jdt.core.dom.ASTNode;
 import org.eclipse.jdt.core.dom.ASTVisitor;
 import org.eclipse.jdt.core.dom.BlockComment;
@@ -109,7 +109,7 @@
 	private int noFormatTagOpenStart = -1;
 	private int formatCodeTagOpenEnd = -1;
 	private int lastFormatCodeClosingTagIndex = -1;
-	private Token firstTagToken;
+	private ArrayList<Integer> commonAttributeAnnotations = new ArrayList<Integer>();
 	private DefaultCodeFormatter commentCodeFormatter;
 
 	public CommentsPreparator(TokenManager tm, DefaultCodeFormatterOptions options, String sourceLevel) {
@@ -536,7 +536,7 @@
 		this.noFormatTagOpenStart = -1;
 		this.formatCodeTagOpenEnd = -1;
 		this.lastFormatCodeClosingTagIndex = -1;
-		this.firstTagToken = null;
+		this.commonAttributeAnnotations.clear();
 		this.ctm = null;
 
 		int commentIndex = this.tm.firstIndexIn(node, TokenNameCOMMENT_JAVADOC);
@@ -564,6 +564,7 @@
 		this.ctm = new TokenManager(commentToken.getInternalStructure(), this.tm);
 
 		handleJavadocTagAlignment(node);
+		handleJavadocBlankLines(node);
 
 		return true;
 	}
@@ -572,10 +573,6 @@
 	public void endVisit(Javadoc node) {
 		if (this.ctm == null)
 			return;
-		if (this.options.comment_insert_empty_line_before_root_tags && this.firstTagToken != null
-				&& this.ctm.indexOf(this.firstTagToken) > 1) {
-			this.firstTagToken.putLineBreaksBefore(2);
-		}
 		addSubstituteWraps();
 	}
 
@@ -599,10 +596,6 @@
 			Token startTokeen = this.ctm.get(startIndex);
 			if (startIndex > 1)
 				startTokeen.breakBefore();
-			int firstTagIndex;
-			if (this.firstTagToken == null || (firstTagIndex = this.ctm.indexOf(this.firstTagToken)) < 0
-					|| startIndex < firstTagIndex)
-				this.firstTagToken = startTokeen;
 
 			handleHtml(node);
 		}
@@ -697,6 +690,35 @@
 			}
 		}
 	}
+	
+	private void handleJavadocBlankLines(Javadoc node) {
+		List<TagElement> tagElements = node.tags();
+		List<Integer> tagIndexes = tagElements.stream()
+				.filter(t -> !t.isNested() && t.getTagName() != null && t.getTagName().length() > 1)
+				.map(t -> tokenStartingAt(t.getStartPosition()))
+				.collect(Collectors.toList());
+		tagIndexes.addAll(this.commonAttributeAnnotations);
+		Collections.sort(tagIndexes);
+		
+		String previousName = null;
+		if (!tagIndexes.isEmpty()) {
+			int firstIndex = tagIndexes.get(0);
+			previousName = this.ctm.toString(firstIndex);
+			if (this.options.comment_insert_empty_line_before_root_tags && firstIndex > 1)
+				this.ctm.get(firstIndex).putLineBreaksBefore(2);
+		}
+		if (this.options.comment_insert_empty_line_between_different_tags) {
+			for (int i = 1; i < tagIndexes.size(); i++) {
+				Token tagToken = this.ctm.get(tagIndexes.get(i));
+				String thisName = this.tm.toString(tagToken);
+				boolean sameType = previousName.equals(thisName)
+						|| (isCommonsAttributeAnnotation(previousName) && isCommonsAttributeAnnotation(thisName));
+				if (!sameType)
+					tagToken.putLineBreaksBefore(2);
+				previousName = thisName;
+			}
+		}
+	}
 
 	private void alignJavadocTag(List<Token> tagTokens, int paramNameAlign, int descriptionAlign) {
 		Token paramName = tagTokens.get(1);
@@ -1066,11 +1088,12 @@
 						if (this.tm.charAt(tokenStart) == '@') {
 							outputToken.setWrapPolicy(WrapPolicy.DISABLE_WRAP);
 							if (commentToken.tokenType == TokenNameCOMMENT_BLOCK && lineBreaks == 1
-									&& structure.size() > 1)
+									&& structure.size() > 1) {
 								outputToken.putLineBreaksBefore(cleanBlankLines ? 1 : 2);
-							if (this.tm.charAt(tokenStart + 1) == '@' && lineBreaks > 0 && this.firstTagToken == null) {
-								// Commons Attributes annotation, see bug 237051
-								this.firstTagToken = outputToken;
+							}
+							if (lineBreaks > 0 && isCommonsAttributeAnnotation(this.tm.toString(outputToken))) {
+								outputToken.breakBefore();
+								this.commonAttributeAnnotations.add(structure.size());
 							}
 						}
 						structure.add(outputToken);
@@ -1103,6 +1126,10 @@
 		return true;
 	}
 
+	private boolean isCommonsAttributeAnnotation(String tokenContent) {
+		return tokenContent.startsWith("@@"); //$NON-NLS-1$
+	}
+
 	private void noSubstituteWrapping(int from, int to) {
 		int commentStart = this.ctm.get(0).originalStart;
 		assert commentStart <= from && from <= to && to <= this.ctm.get(this.ctm.size() - 1).originalEnd;
diff --git a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/DefaultCodeFormatterOptions.java b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/DefaultCodeFormatterOptions.java
index 7dad994..d8bc8b5 100644
--- a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/DefaultCodeFormatterOptions.java
+++ b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/DefaultCodeFormatterOptions.java
@@ -237,6 +237,7 @@
 	public boolean comment_align_tags_names_descriptions;
 	public boolean comment_align_tags_descriptions_grouped;
 	public boolean comment_insert_empty_line_before_root_tags;
+	public boolean comment_insert_empty_line_between_different_tags;
 	public boolean comment_insert_new_line_for_parameter;
 	public boolean comment_preserve_white_space_between_code_and_line_comments;
 	public int comment_line_length;
@@ -334,6 +335,7 @@
 	public boolean insert_space_after_comma_in_type_parameters;
 	public boolean insert_space_after_ellipsis;
 	public boolean insert_space_after_lambda_arrow;
+	public boolean insert_space_after_not_operator;
 	public boolean insert_space_after_opening_angle_bracket_in_parameterized_type_reference;
 	public boolean insert_space_after_opening_angle_bracket_in_type_arguments;
 	public boolean insert_space_after_opening_angle_bracket_in_type_parameters;
@@ -608,6 +610,7 @@
 		options.put(DefaultCodeFormatterConstants.FORMATTER_COMMENT_ALIGN_TAGS_NAMES_DESCRIPTIONS, this.comment_align_tags_names_descriptions ? DefaultCodeFormatterConstants.TRUE : DefaultCodeFormatterConstants.FALSE);
 		options.put(DefaultCodeFormatterConstants.FORMATTER_COMMENT_ALIGN_TAGS_DESCREIPTIONS_GROUPED, this.comment_align_tags_descriptions_grouped ? DefaultCodeFormatterConstants.TRUE : DefaultCodeFormatterConstants.FALSE);
 		options.put(DefaultCodeFormatterConstants.FORMATTER_COMMENT_INSERT_EMPTY_LINE_BEFORE_ROOT_TAGS, this.comment_insert_empty_line_before_root_tags ? JavaCore.INSERT : JavaCore.DO_NOT_INSERT);
+		options.put(DefaultCodeFormatterConstants.FORMATTER_COMMENT_INSERT_EMPTY_LINE_BETWEEN_DIFFERENT_TAGS, this.comment_insert_empty_line_between_different_tags ? JavaCore.INSERT : JavaCore.DO_NOT_INSERT);
 		options.put(DefaultCodeFormatterConstants.FORMATTER_COMMENT_INSERT_NEW_LINE_FOR_PARAMETER, this.comment_insert_new_line_for_parameter ? JavaCore.INSERT : JavaCore.DO_NOT_INSERT);
 		options.put(DefaultCodeFormatterConstants.FORMATTER_COMMENT_PRESERVE_WHITE_SPACE_BETWEEN_CODE_AND_LINE_COMMENT, this.comment_preserve_white_space_between_code_and_line_comments ? DefaultCodeFormatterConstants.TRUE : DefaultCodeFormatterConstants.FALSE);
 		options.put(DefaultCodeFormatterConstants.FORMATTER_COMMENT_LINE_LENGTH, Integer.toString(this.comment_line_length));
@@ -717,6 +720,7 @@
 		options.put(DefaultCodeFormatterConstants.FORMATTER_INSERT_SPACE_AFTER_OPENING_BRACKET_IN_ARRAY_ALLOCATION_EXPRESSION, this.insert_space_after_opening_bracket_in_array_allocation_expression? JavaCore.INSERT : JavaCore.DO_NOT_INSERT);
 		options.put(DefaultCodeFormatterConstants.FORMATTER_INSERT_SPACE_AFTER_ELLIPSIS, this.insert_space_after_ellipsis ? JavaCore.INSERT : JavaCore.DO_NOT_INSERT);
 		options.put(DefaultCodeFormatterConstants.FORMATTER_INSERT_SPACE_AFTER_LAMBDA_ARROW, this.insert_space_after_lambda_arrow ? JavaCore.INSERT : JavaCore.DO_NOT_INSERT);
+		options.put(DefaultCodeFormatterConstants.FORMATTER_INSERT_SPACE_AFTER_NOT_OPERATOR, this.insert_space_after_not_operator? JavaCore.INSERT : JavaCore.DO_NOT_INSERT);
 		options.put(DefaultCodeFormatterConstants.FORMATTER_INSERT_SPACE_AFTER_OPENING_ANGLE_BRACKET_IN_PARAMETERIZED_TYPE_REFERENCE, this.insert_space_after_opening_angle_bracket_in_parameterized_type_reference? JavaCore.INSERT : JavaCore.DO_NOT_INSERT);
 		options.put(DefaultCodeFormatterConstants.FORMATTER_INSERT_SPACE_AFTER_OPENING_ANGLE_BRACKET_IN_TYPE_ARGUMENTS, this.insert_space_after_opening_angle_bracket_in_type_arguments? JavaCore.INSERT : JavaCore.DO_NOT_INSERT);
 		options.put(DefaultCodeFormatterConstants.FORMATTER_INSERT_SPACE_AFTER_OPENING_ANGLE_BRACKET_IN_TYPE_PARAMETERS, this.insert_space_after_opening_angle_bracket_in_type_parameters? JavaCore.INSERT : JavaCore.DO_NOT_INSERT);
@@ -1496,6 +1500,8 @@
 		if (commentInsertEmptyLineBeforeRootTagsOption != null) {
 			this.comment_insert_empty_line_before_root_tags = JavaCore.INSERT.equals(commentInsertEmptyLineBeforeRootTagsOption);
 		}
+		setBoolean(settings, DefaultCodeFormatterConstants.FORMATTER_COMMENT_INSERT_EMPTY_LINE_BETWEEN_DIFFERENT_TAGS, JavaCore.INSERT,
+				v -> this.comment_insert_empty_line_between_different_tags = v);
 		final Object commentInsertNewLineForParameterOption = settings.get(DefaultCodeFormatterConstants.FORMATTER_COMMENT_INSERT_NEW_LINE_FOR_PARAMETER);
 		if (commentInsertNewLineForParameterOption != null) {
 			this.comment_insert_new_line_for_parameter = JavaCore.INSERT.equals(commentInsertNewLineForParameterOption);
@@ -1791,6 +1797,8 @@
 		if (insertSpaceAfterLambdaArrowOption != null) {
 			this.insert_space_after_lambda_arrow = JavaCore.INSERT.equals(insertSpaceAfterLambdaArrowOption);
 		}
+		setBoolean(settings, DefaultCodeFormatterConstants.FORMATTER_INSERT_SPACE_AFTER_NOT_OPERATOR, JavaCore.INSERT,
+				v -> this.insert_space_after_not_operator = v);
 		final Object insertSpaceAfterOpeningAngleBracketInParameterizedTypeReferenceOption = settings.get(DefaultCodeFormatterConstants.FORMATTER_INSERT_SPACE_AFTER_OPENING_ANGLE_BRACKET_IN_PARAMETERIZED_TYPE_REFERENCE);
 		if (insertSpaceAfterOpeningAngleBracketInParameterizedTypeReferenceOption != null) {
 			this.insert_space_after_opening_angle_bracket_in_parameterized_type_reference = JavaCore.INSERT.equals(insertSpaceAfterOpeningAngleBracketInParameterizedTypeReferenceOption);
@@ -2804,6 +2812,10 @@
 			setInt(settings, DefaultCodeFormatterConstants.FORMATTER_BLANK_LINES_BEFORE_METHOD,
 					v -> this.blank_lines_before_abstract_method = v);
 		}
+		if (!settings.containsKey(DefaultCodeFormatterConstants.FORMATTER_INSERT_SPACE_AFTER_NOT_OPERATOR)) {
+			setBoolean(settings, DefaultCodeFormatterConstants.FORMATTER_INSERT_SPACE_AFTER_UNARY_OPERATOR, JavaCore.INSERT,
+					v -> this.insert_space_after_not_operator = v);
+		}
 	}
 
 	public void setDefaultSettings() {
@@ -2886,6 +2898,7 @@
 		this.comment_align_tags_names_descriptions = false;
 		this.comment_align_tags_descriptions_grouped = false;
 		this.comment_insert_empty_line_before_root_tags = true;
+		this.comment_insert_empty_line_between_different_tags = false;
 		this.comment_insert_new_line_for_parameter = true;
 		this.comment_new_lines_at_block_boundaries = true;
 		this.comment_new_lines_at_javadoc_boundaries = true;
@@ -2995,6 +3008,7 @@
 		this.insert_space_after_comma_in_type_parameters = true;
 		this.insert_space_after_ellipsis = true;
 		this.insert_space_after_lambda_arrow = true;
+		this.insert_space_after_not_operator = false;
 		this.insert_space_after_opening_angle_bracket_in_parameterized_type_reference = false;
 		this.insert_space_after_opening_angle_bracket_in_type_arguments = false;
 		this.insert_space_after_opening_angle_bracket_in_type_parameters = false;
@@ -3255,6 +3269,7 @@
 		this.comment_align_tags_names_descriptions = false;
 		this.comment_align_tags_descriptions_grouped = true;
 		this.comment_insert_empty_line_before_root_tags = true;
+		this.comment_insert_empty_line_between_different_tags = false;
 		this.comment_insert_new_line_for_parameter = false;
 		this.comment_new_lines_at_block_boundaries = true;
 		this.comment_new_lines_at_javadoc_boundaries = true;
@@ -3362,6 +3377,7 @@
 		this.insert_space_after_comma_in_type_parameters = true;
 		this.insert_space_after_ellipsis = true;
 		this.insert_space_after_lambda_arrow = true;
+		this.insert_space_after_not_operator = false;
 		this.insert_space_after_opening_angle_bracket_in_parameterized_type_reference = false;
 		this.insert_space_after_opening_angle_bracket_in_type_arguments = false;
 		this.insert_space_after_opening_angle_bracket_in_type_parameters = false;
diff --git a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/SpacePreparator.java b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/SpacePreparator.java
index 8fed55f..50b0382 100644
--- a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/SpacePreparator.java
+++ b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/SpacePreparator.java
@@ -819,8 +819,13 @@
 			handleOperator(operator.toString(), node.getOperand(),
 					this.options.insert_space_before_prefix_operator,
 					this.options.insert_space_after_prefix_operator);
+		} else if (operator.equals(PrefixExpression.Operator.NOT)) {
+			handleOperator(operator.toString(), node.getOperand(),
+					this.options.insert_space_before_unary_operator,
+					this.options.insert_space_after_not_operator);
 		} else {
-			handleOperator(operator.toString(), node.getOperand(), this.options.insert_space_before_unary_operator,
+			handleOperator(operator.toString(), node.getOperand(),
+					this.options.insert_space_before_unary_operator,
 					this.options.insert_space_after_unary_operator);
 		}
 		return true;
diff --git a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/linewrap/WrapExecutor.java b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/linewrap/WrapExecutor.java
index 2fb28a1..d3b453f 100644
--- a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/linewrap/WrapExecutor.java
+++ b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/linewrap/WrapExecutor.java
@@ -26,15 +26,15 @@
 import java.util.HashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
-
+import java.util.function.Predicate;
 import org.eclipse.jdt.internal.compiler.parser.ScannerHelper;
 import org.eclipse.jdt.internal.formatter.DefaultCodeFormatterOptions;
 import org.eclipse.jdt.internal.formatter.DefaultCodeFormatterOptions.Alignment;
 import org.eclipse.jdt.internal.formatter.Token;
-import org.eclipse.jdt.internal.formatter.TokenManager;
-import org.eclipse.jdt.internal.formatter.TokenTraverser;
 import org.eclipse.jdt.internal.formatter.Token.WrapMode;
 import org.eclipse.jdt.internal.formatter.Token.WrapPolicy;
+import org.eclipse.jdt.internal.formatter.TokenManager;
+import org.eclipse.jdt.internal.formatter.TokenTraverser;
 
 public class WrapExecutor {
 
@@ -227,6 +227,7 @@
 
 	private class WrapsApplier extends TokenTraverser {
 
+		private final TokenManager tm2 = WrapExecutor.this.tm;
 		private ArrayDeque<Token> stack = new ArrayDeque<>();
 		private int initialIndent;
 		private int currentIndent;
@@ -242,22 +243,57 @@
 				newLine(token, index);
 			} else if ((this.nextWrap != null && index == this.nextWrap.wrapTokenIndex)
 					|| checkForceWrap(token, index, this.currentIndent)
-					|| (token.isNextLineOnWrap() && WrapExecutor.this.tm
-							.get(WrapExecutor.this.tm.findFirstTokenInLine(index)).isWrappable())) {
+					|| (token.isNextLineOnWrap() && this.tm2.get(this.tm2.findFirstTokenInLine(index)).isWrappable())) {
 				token.breakBefore();
 				newLine(token, index);
 			} else {
+				checkOnColumnAlign(token, index);
 				setIndent(token, this.currentIndent);
 			}
 			return true;
 		}
 
+		private void checkOnColumnAlign(Token token, int index) {
+			// if some further tokens in a group are wrapped on column,
+			// the first one should be aligned on column even if it's not wrapped
+			WrapPolicy wrapPolicy = token.getWrapPolicy();
+			if (wrapPolicy == null || !wrapPolicy.indentOnColumn || !wrapPolicy.isFirstInGroup)
+				return;
+			int positionInLine = this.tm2.getPositionInLine(index);
+			if (this.tm2.toIndent(positionInLine, true) == positionInLine)
+				return;
+
+			Predicate<Token> aligner = t -> {
+				WrapPolicy wp = t.getWrapPolicy();
+				if (wp != null && wp.indentOnColumn && wp.wrapParentIndex == wrapPolicy.wrapParentIndex) {
+					this.currentIndent = this.tm2.toIndent(positionInLine, true);
+					token.setAlign(this.currentIndent);
+					this.stack.push(token);
+					return true;
+				}
+				return false;
+			};
+
+			// check all future wraps
+			WrapInfo furtherWrap = this.nextWrap;
+			while (furtherWrap != null) {
+				if (aligner.test(this.tm2.get(furtherWrap.wrapTokenIndex)))
+					return;
+				furtherWrap = WrapExecutor.this.wrapSearchResults.get(furtherWrap).nextWrap;
+			}
+			// check all tokens that are already wrapped
+			for (int i = index; i <= wrapPolicy.groupEndIndex; i++) {
+				Token t = this.tm2.get(i);
+				if (t.getLineBreaksBefore() > 0 && aligner.test(t))
+					return;
+			}
+		}
+
 		private void newLine(Token token, int index) {
 			while (!this.stack.isEmpty() && index > this.stack.peek().getWrapPolicy().groupEndIndex)
 				this.stack.pop();
 			if (token.getWrapPolicy() != null) {
 				setIndent(token, getWrapIndent(token));
-				handleOnColumnIndent(index, token.getWrapPolicy());
 				this.stack.push(token);
 			} else if (this.stack.isEmpty()) {
 				this.initialIndent = token.getIndent();
@@ -638,23 +674,6 @@
 		return result;
 	}
 
-	void handleOnColumnIndent(int tokenIndex, WrapPolicy wrapPolicy) {
-		if (wrapPolicy != null && wrapPolicy.indentOnColumn && !wrapPolicy.isFirstInGroup
-				&& this.options.tab_char == DefaultCodeFormatterOptions.TAB
-				&& !this.options.use_tabs_only_for_leading_indentations) {
-			// special case: first wrap in a group should be aligned on column even if it's not wrapped
-			for (int i = tokenIndex - 1; i >= 0; i--) {
-				Token token = this.tm.get(i);
-				WrapPolicy wrapPolicy2 = token.getWrapPolicy();
-				if (wrapPolicy2 != null && wrapPolicy2.isFirstInGroup
-						&& wrapPolicy2.wrapParentIndex == wrapPolicy.wrapParentIndex) {
-					token.setAlign(getWrapIndent(token));
-					break;
-				}
-			}
-		}
-	}
-
 	int getWrapIndent(Token token) {
 		WrapPolicy policy = token.getWrapPolicy();
 		if (policy == null)
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/IncrementalImageBuilder.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/IncrementalImageBuilder.java
index cd82cb7..a1e9071 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/IncrementalImageBuilder.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/IncrementalImageBuilder.java
@@ -1010,6 +1010,15 @@
 			addDependentsOf(new Path(fileName), true);
 			this.newState.wasStructurallyChanged(fileName);
 		}
+	} catch (JavaModelException jme) {
+		Throwable e = jme.getCause();
+		if (e instanceof CoreException) {
+			// assuming a ResourceException during IFile.getContents(), treat it like a corrupt file
+			addDependentsOf(new Path(fileName), true);
+			this.newState.wasStructurallyChanged(fileName);
+		} else {
+			throw jme;
+		}
 	} catch (ClassFormatException e) {
 		addDependentsOf(new Path(fileName), true);
 		this.newState.wasStructurallyChanged(fileName);