diff --git a/jdt-patch/e414/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/CompilerInvocationTests.java b/jdt-patch/e414/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/CompilerInvocationTests.java index ce9c925041..252ac420c8 100644 --- a/jdt-patch/e414/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/CompilerInvocationTests.java +++ b/jdt-patch/e414/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/CompilerInvocationTests.java @@ -702,9 +702,11 @@ class ProblemAttributes { expectedProblemAttributes.put("JavadocAmbiguousMethodReference", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC)); expectedProblemAttributes.put("JavadocAmbiguousType", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC)); expectedProblemAttributes.put("JavadocDuplicateParamName", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC)); + expectedProblemAttributes.put("JavadocDuplicateProvidesTag", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC)); expectedProblemAttributes.put("JavadocDuplicateReturnTag", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC)); expectedProblemAttributes.put("JavadocDuplicateTag", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC)); expectedProblemAttributes.put("JavadocDuplicateThrowsClassName", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC)); + expectedProblemAttributes.put("JavadocDuplicateUsesTag", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC)); expectedProblemAttributes.put("JavadocEmptyReturnTag", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC)); expectedProblemAttributes.put("JavadocGenericConstructorTypeArgumentMismatch", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC)); expectedProblemAttributes.put("JavadocGenericMethodTypeArgumentMismatch", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC)); @@ -719,6 +721,8 @@ class ProblemAttributes { expectedProblemAttributes.put("JavadocInvalidParamName", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC)); expectedProblemAttributes.put("JavadocInvalidParamTagName", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC)); expectedProblemAttributes.put("JavadocInvalidParamTagTypeParameter", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC)); + expectedProblemAttributes.put("JavadocInvalidProvidesClass", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC)); + expectedProblemAttributes.put("JavadocInvalidProvidesClassName", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC)); expectedProblemAttributes.put("JavadocInvalidSeeArgs", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC)); expectedProblemAttributes.put("JavadocInvalidSeeHref", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC)); expectedProblemAttributes.put("JavadocInvalidSeeReference", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC)); @@ -726,6 +730,8 @@ class ProblemAttributes { expectedProblemAttributes.put("JavadocInvalidTag", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC)); expectedProblemAttributes.put("JavadocInvalidThrowsClass", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC)); expectedProblemAttributes.put("JavadocInvalidThrowsClassName", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC)); + expectedProblemAttributes.put("JavadocInvalidUsesClass", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC)); + expectedProblemAttributes.put("JavadocInvalidUsesClassName", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC)); expectedProblemAttributes.put("JavadocInvalidValueReference", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC)); expectedProblemAttributes.put("JavadocMalformedSeeReference", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC)); expectedProblemAttributes.put("JavadocMessagePrefix", new ProblemAttributes(CategorizedProblem.CAT_INTERNAL)); @@ -734,11 +740,15 @@ class ProblemAttributes { expectedProblemAttributes.put("JavadocMissingIdentifier", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC)); expectedProblemAttributes.put("JavadocMissingParamName", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC)); expectedProblemAttributes.put("JavadocMissingParamTag", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC)); + expectedProblemAttributes.put("JavadocMissingProvidesClassName", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC)); + expectedProblemAttributes.put("JavadocMissingProvidesTag", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC)); expectedProblemAttributes.put("JavadocMissingReturnTag", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC)); expectedProblemAttributes.put("JavadocMissingSeeReference", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC)); expectedProblemAttributes.put("JavadocMissingTagDescription", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC)); expectedProblemAttributes.put("JavadocMissingThrowsClassName", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC)); expectedProblemAttributes.put("JavadocMissingThrowsTag", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC)); + expectedProblemAttributes.put("JavadocMissingUsesClassName", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC)); + expectedProblemAttributes.put("JavadocMissingUsesTag", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC)); expectedProblemAttributes.put("JavadocNoMessageSendOnArrayType", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC)); expectedProblemAttributes.put("JavadocNoMessageSendOnBaseType", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC)); expectedProblemAttributes.put("JavadocNonGenericConstructor", new ProblemAttributes(CategorizedProblem.CAT_JAVADOC)); @@ -1674,9 +1684,11 @@ class ProblemAttributes { expectedProblemAttributes.put("JavadocAmbiguousMethodReference", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC)); expectedProblemAttributes.put("JavadocAmbiguousType", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC)); expectedProblemAttributes.put("JavadocDuplicateParamName", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC)); + expectedProblemAttributes.put("JavadocDuplicateProvidesTag", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC)); expectedProblemAttributes.put("JavadocDuplicateReturnTag", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC)); expectedProblemAttributes.put("JavadocDuplicateTag", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC)); expectedProblemAttributes.put("JavadocDuplicateThrowsClassName", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC)); + expectedProblemAttributes.put("JavadocDuplicateUsesTag", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC)); expectedProblemAttributes.put("JavadocEmptyReturnTag", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC)); expectedProblemAttributes.put("JavadocGenericConstructorTypeArgumentMismatch", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC)); expectedProblemAttributes.put("JavadocGenericMethodTypeArgumentMismatch", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC)); @@ -1691,6 +1703,8 @@ class ProblemAttributes { expectedProblemAttributes.put("JavadocInvalidParamName", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC)); expectedProblemAttributes.put("JavadocInvalidParamTagName", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC)); expectedProblemAttributes.put("JavadocInvalidParamTagTypeParameter", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC)); + expectedProblemAttributes.put("JavadocInvalidProvidesClass", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC)); + expectedProblemAttributes.put("JavadocInvalidProvidesClassName", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC)); expectedProblemAttributes.put("JavadocInvalidSeeArgs", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC)); expectedProblemAttributes.put("JavadocInvalidSeeHref", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC)); expectedProblemAttributes.put("JavadocInvalidSeeReference", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC)); @@ -1698,6 +1712,8 @@ class ProblemAttributes { expectedProblemAttributes.put("JavadocInvalidTag", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC)); expectedProblemAttributes.put("JavadocInvalidThrowsClass", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC)); expectedProblemAttributes.put("JavadocInvalidThrowsClassName", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC)); + expectedProblemAttributes.put("JavadocInvalidUsesClass", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC)); + expectedProblemAttributes.put("JavadocInvalidUsesClassName", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC)); expectedProblemAttributes.put("JavadocInvalidValueReference", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC)); expectedProblemAttributes.put("JavadocMalformedSeeReference", new ProblemAttributes(JavaCore.COMPILER_PB_INVALID_JAVADOC)); expectedProblemAttributes.put("JavadocMessagePrefix", SKIP); @@ -1706,11 +1722,17 @@ class ProblemAttributes { 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/jdt-patch/e414/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/JavadocTestForModule.java b/jdt-patch/e414/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/JavadocTestForModule.java new file mode 100644 index 0000000000..00ecdbcc13 --- /dev/null +++ b/jdt-patch/e414/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 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 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 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 testFileNames, StringBuffer commandLine, + String expectedFailureOutOutputString, String expectedFailureErrOutputString, + boolean shouldFlushOutputDirectory) + { + runConformModuleTest(testFileNames, commandLine, + expectedFailureOutOutputString, expectedFailureErrOutputString, shouldFlushOutputDirectory, OUTPUT_DIR + File.separator + "javac"); + } + + void runConformModuleTest(List 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 runConformModuleTest(String[] testFiles, String commandLine, + String expectedFailureOutOutputString, String expectedFailureErrOutputString, + boolean shouldFlushOutputDirectory) + { + return runConformModuleTest(testFiles, commandLine, expectedFailureErrOutputString, expectedFailureErrOutputString, + shouldFlushOutputDirectory, OUTPUT_DIR, JavacTestOptions.DEFAULT, null); + } + + Set 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 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 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 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 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 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 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 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 fileNames, boolean add) { + if (!(new File(outputLocation)).exists()) + return; + try { + Files.walkFileTree(FileSystems.getDefault().getPath(outputLocation), new SimpleFileVisitor() { + @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 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 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 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 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 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 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 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 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 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/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/core/compiler/IProblem.java b/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/core/compiler/IProblem.java index 64220c7a4d..949332e962 100644 --- a/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/core/compiler/IProblem.java +++ b/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/core/compiler/IProblem.java @@ -1192,6 +1192,29 @@ public interface IProblem { 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/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CompilationUnitDeclaration.java b/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CompilationUnitDeclaration.java index 1e398e7619..8c4129749e 100644 --- a/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CompilationUnitDeclaration.java +++ b/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CompilationUnitDeclaration.java @@ -14,7 +14,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; @@ -23,6 +24,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; @@ -346,7 +348,7 @@ public void finalizeProblems() { 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]); } } } @@ -558,7 +560,7 @@ private boolean isLambdaExpressionCopyContext(ReferenceContext context) { 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; @@ -566,7 +568,7 @@ private boolean isLambdaExpressionCopyContext(ReferenceContext context) { 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]; @@ -604,7 +606,7 @@ public void record(LocalTypeBinding localType) { } /* - * 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) { @@ -620,6 +622,7 @@ public int record(FunctionalExpression expression) { 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]; @@ -638,6 +641,17 @@ public void resolve() { 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/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/IJavadocTypeReference.java b/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/IJavadocTypeReference.java new file mode 100644 index 0000000000..c21cdaec28 --- /dev/null +++ b/jdt-patch/e414/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/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Javadoc.java b/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Javadoc.java index 308c39c3be..087d33fc8f 100644 --- a/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Javadoc.java +++ b/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Javadoc.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2018 IBM Corporation and others. + * Copyright (c) 2000, 2019 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -33,6 +33,8 @@ public class Javadoc extends ASTNode { public TypeReference[] exceptionReferences; // @throws, @exception public JavadocReturnStatement returnStatement; // @return public Expression[] seeReferences; // @see + public IJavadocTypeReference[] usesReferences; // @uses + public IJavadocTypeReference[] providesReferences; // @provides public long[] inheritedPositions = null; // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=51600 // Store param references for tag with invalid syntax @@ -262,8 +264,8 @@ public void resolve(CompilationUnitScope unitScope) { return; } // Do nothing - This is to mimic the SDK's javadoc tool behavior, which neither - // sanity checks nor generates documentation using comments at the CU scope - // (unless the unit happens to be package-info.java - in which case we don't come here.) + // sanity checks nor generates documentation using comments at the CU scope + // (unless the unit happens to be package-info.java - in which case we don't come here.) } /* @@ -318,7 +320,7 @@ public void resolve(MethodScope methScope) { MethodBinding current = methDecl.binding; // work 'against' better inference in 1.8 (otherwise comparing (G with G) would fail): if (methScope.compilerOptions().sourceLevel >= ClassFileConstants.JDK1_8 - && current.typeVariables != Binding.NO_TYPE_VARIABLES) + && current.typeVariables != Binding.NO_TYPE_VARIABLES) { current = current.asRawMethod(methScope.environment()); } @@ -326,7 +328,7 @@ public void resolve(MethodScope methScope) { superRef = true; } } - } + } } } } @@ -385,6 +387,11 @@ public void resolve(MethodScope methScope) { 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) { @@ -560,6 +567,148 @@ private void resolveParamTags(MethodScope scope, boolean reportMissing, boolean } } + /* + * 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 */ @@ -604,7 +753,7 @@ private void resolveTypeParameterTags(Scope scope, boolean reportMissing) { // If no param tags then report a problem for each declaration type parameter if (parameters != null) { - // https://bugs.eclipse.org/bugs/show_bug.cgi?id=324850, avoid secondary errors when <= 1.4 + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=324850, avoid secondary errors when <= 1.4 reportMissing = reportMissing && scope.compilerOptions().sourceLevel >= ClassFileConstants.JDK1_5; int typeParametersLength = parameters.length; if (paramTypeParamLength == 0) { @@ -830,7 +979,7 @@ private void verifyTypeReference(Expression reference, Expression typeReference, mainLoop: for (int i=0; i= 0;) { if (CharOperation.equals(imports[i].compoundName[j], computedCompoundName[j])) { diff --git a/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/JavadocQualifiedTypeReference.java b/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/JavadocQualifiedTypeReference.java index 3ed38f0990..ef06cca0e4 100644 --- a/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/JavadocQualifiedTypeReference.java +++ b/jdt-patch/e414/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 @@ private TypeBinding internalResolveType(Scope scope, boolean checkBounds) { 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 @@ public void traverse(ASTVisitor visitor, ClassScope scope) { 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/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/JavadocSingleTypeReference.java b/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/JavadocSingleTypeReference.java index b64689b105..8a9ed4072d 100644 --- a/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/JavadocSingleTypeReference.java +++ b/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/JavadocSingleTypeReference.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2018 IBM Corporation and others. + * Copyright (c) 2000, 2019 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -29,7 +29,7 @@ import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; -public class JavadocSingleTypeReference extends SingleTypeReference { +public class JavadocSingleTypeReference extends SingleTypeReference implements IJavadocTypeReference { public int tagSourceStart, tagSourceEnd; public PackageBinding packageBinding; @@ -129,4 +129,14 @@ public void traverse(ASTVisitor visitor, ClassScope scope) { 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/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SourceTypeBinding.java b/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SourceTypeBinding.java index ccd6f28617..03fc6d5d8a 100644 --- a/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SourceTypeBinding.java +++ b/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SourceTypeBinding.java @@ -1036,33 +1036,34 @@ public long getAnnotationTagBits() { 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; - } - } - return internalGetAnnotationTagBits(); -} -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.EndHierarchyCheck) == 0) { + CompilationUnitScope pkgCUS = this.scope.compilationUnitScope(); + boolean current = pkgCUS.connectingHierarchy; + pkgCUS.connectingHierarchy = true; + try { + initAnnotationTagBits(); + } finally { + pkgCUS.connectingHierarchy = current; + } + } else { + initAnnotationTagBits(); } - if ((this.tagBits & TagBits.AnnotationDeprecated) != 0) - this.modifiers |= ClassFileConstants.AccDeprecated; } return this.tagBits; } +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; + } + if ((this.tagBits & TagBits.AnnotationDeprecated) != 0) + this.modifiers |= ClassFileConstants.AccDeprecated; +} public MethodBinding[] getDefaultAbstractMethods() { if (!isPrototype()) return this.prototype.getDefaultAbstractMethods(); diff --git a/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeBinding.java b/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeBinding.java index ad4a8215a9..645fd171f9 100644 --- a/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeBinding.java +++ b/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeBinding.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 diff --git a/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/AbstractCommentParser.java b/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/AbstractCommentParser.java index 4f7a7b95b6..9b90a4f978 100644 --- a/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/AbstractCommentParser.java +++ b/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/AbstractCommentParser.java @@ -18,6 +18,7 @@ import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.core.compiler.InvalidInputException; +import org.eclipse.jdt.internal.compiler.ast.TypeReference; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.util.Util; @@ -102,6 +103,14 @@ public abstract class AbstractCommentParser implements JavadocTagConstants { protected int astLengthPtr; protected int[] astLengthStack; + // Uses stack + protected int usesReferencesPtr = -1; + protected TypeReference[] usesReferencesStack; + + // Provides stack + protected int providesReferencesPtr = -1; + protected TypeReference[] providesReferencesStack; + protected AbstractCommentParser(Parser sourceParser) { this.sourceParser = sourceParser; @@ -456,6 +465,7 @@ protected boolean commentParse() { } updateDocComment(); } catch (Exception ex) { + ex.printStackTrace(); validComment = false; } return validComment; @@ -501,7 +511,7 @@ private int getLineNumber(int position) { 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/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/JavadocParser.java b/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/JavadocParser.java index 8663caba08..0fe67daee5 100644 --- a/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/JavadocParser.java +++ b/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/JavadocParser.java @@ -17,7 +17,23 @@ import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.core.compiler.InvalidInputException; -import org.eclipse.jdt.internal.compiler.ast.*; +import org.eclipse.jdt.internal.compiler.ast.ASTNode; +import org.eclipse.jdt.internal.compiler.ast.Expression; +import org.eclipse.jdt.internal.compiler.ast.IJavadocTypeReference; +import org.eclipse.jdt.internal.compiler.ast.Javadoc; +import org.eclipse.jdt.internal.compiler.ast.JavadocAllocationExpression; +import org.eclipse.jdt.internal.compiler.ast.JavadocArgumentExpression; +import org.eclipse.jdt.internal.compiler.ast.JavadocArrayQualifiedTypeReference; +import org.eclipse.jdt.internal.compiler.ast.JavadocArraySingleTypeReference; +import org.eclipse.jdt.internal.compiler.ast.JavadocFieldReference; +import org.eclipse.jdt.internal.compiler.ast.JavadocImplicitTypeReference; +import org.eclipse.jdt.internal.compiler.ast.JavadocMessageSend; +import org.eclipse.jdt.internal.compiler.ast.JavadocQualifiedTypeReference; +import org.eclipse.jdt.internal.compiler.ast.JavadocReturnStatement; +import org.eclipse.jdt.internal.compiler.ast.JavadocSingleNameReference; +import org.eclipse.jdt.internal.compiler.ast.JavadocSingleTypeReference; +import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; +import org.eclipse.jdt.internal.compiler.ast.TypeReference; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.util.Util; @@ -27,6 +43,7 @@ public class JavadocParser extends AbstractCommentParser { private static final JavadocSingleNameReference[] NO_SINGLE_NAME_REFERENCE = new JavadocSingleNameReference[0]; private static final JavadocSingleTypeReference[] NO_SINGLE_TYPE_REFERENCE = new JavadocSingleTypeReference[0]; + private static final JavadocQualifiedTypeReference[] NO_QUALIFIED_TYPE_REFERENCE = new JavadocQualifiedTypeReference[0]; private static final TypeReference[] NO_TYPE_REFERENCE = new TypeReference[0]; private static final Expression[] NO_EXPRESSION = new Expression[0]; @@ -629,7 +646,9 @@ protected boolean parseTag(int previousPosition) throws InvalidInputException { } } else if (length == TAG_PROVIDES_LENGTH && CharOperation.equals(TAG_PROVIDES, tagName, 0, length)) { this.tagValue = TAG_PROVIDES_VALUE; - this.tagWaitingForDescription = this.tagValue; + if (!this.inlineTagStarted) { + valid = parseProvidesReference(); + } } break; case 'r': @@ -677,7 +696,9 @@ protected boolean parseTag(int previousPosition) throws InvalidInputException { case 'u': if (length == TAG_USES_LENGTH && CharOperation.equals(TAG_USES, tagName, 0, length)) { this.tagValue = TAG_USES_VALUE; - this.tagWaitingForDescription = this.tagValue; + if (!this.inlineTagStarted) { + valid = parseUsesReference(); + } } break; case 'v': @@ -951,6 +972,18 @@ protected void updateDocComment() { System.arraycopy(this.invalidParamReferencesStack, 0, this.docComment.invalidParameters, 0, this.invalidParamReferencesPtr+1); } + this.docComment.usesReferences = this.usesReferencesPtr >= 0 ? new IJavadocTypeReference[this.usesReferencesPtr+1] : NO_QUALIFIED_TYPE_REFERENCE; + for (int i = 0; i <= this.usesReferencesPtr; ++i) { + TypeReference ref = this.usesReferencesStack[i]; + this.docComment.usesReferences[i] = (IJavadocTypeReference)ref; + } + + this.docComment.providesReferences = this.providesReferencesPtr >= 0 ? new IJavadocTypeReference[this.providesReferencesPtr+1] : NO_QUALIFIED_TYPE_REFERENCE; + for (int i = 0; i <= this.providesReferencesPtr; ++i) { + TypeReference ref = this.providesReferencesStack[i]; + this.docComment.providesReferences[i] = (IJavadocTypeReference)ref; + } + // If no nodes stored return if (this.astLengthPtr == -1) { return; @@ -1013,4 +1046,78 @@ else if (reference instanceof JavadocSingleTypeReference) 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/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/Parser.java b/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/Parser.java index 150517a8fa..14de345bd0 100644 --- a/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/Parser.java +++ b/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/Parser.java @@ -5936,6 +5936,9 @@ protected void consumeModuleHeader() { } 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 int size1 = 0, size2 = 0, size3 = 0, size4 = 0, size5 = 0; diff --git a/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java b/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java index df41870235..b34e4e280e 100644 --- a/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java +++ b/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java @@ -547,6 +547,14 @@ public static int getIrritant(int problemID) { 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: @@ -590,8 +598,10 @@ public static int getIrritant(int problemID) { return CompilerOptions.InvalidJavadoc; case IProblem.JavadocMissingParamTag: + case IProblem.JavadocMissingProvidesTag: case IProblem.JavadocMissingReturnTag: case IProblem.JavadocMissingThrowsTag: + case IProblem.JavadocMissingUsesTag: return CompilerOptions.MissingJavadocTags; case IProblem.JavadocMissing: @@ -5248,6 +5258,9 @@ public void javadocDuplicatedParamTag(char[] token, int sourceStart, int sourceE 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); } @@ -5274,6 +5287,11 @@ public void javadocDuplicatedThrowsClassName(TypeReference typeReference, int mo 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; @@ -5721,6 +5739,24 @@ public void javadocInvalidParamTagName(int sourceStart, int sourceEnd) { 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); } @@ -5799,6 +5835,24 @@ public void javadocInvalidType(ASTNode location, TypeBinding type, int modifiers 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); @@ -5829,6 +5883,20 @@ public void javadocMissing(int sourceStart, int sourceEnd, int severity, int mod } } } +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; @@ -5866,6 +5934,26 @@ public void javadocMissingParamTag(char[] name, int sourceStart, int sourceEnd, 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); @@ -5916,6 +6004,27 @@ public void javadocMissingThrowsTag(TypeReference typeRef, int modifiers){ 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/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/messages.properties b/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/messages.properties index c859ddd4db..8c4171797b 100644 --- a/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/messages.properties +++ b/jdt-patch/e414/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/messages.properties @@ -1013,6 +1013,18 @@ 1717 = yield may be a restricted identifier in future and may be disallowed as a type name 1718 = yield is a restricted identifier and cannot be used as type name +# Additional doc +1800 = Missing uses tag +1801 = Duplicate uses tag +1802 = Missing uses class name +1803 = Invalid uses class name +1804 = Invalid uses class +1805 = Missing provides tag +1806 = Duplicate provides tag +1807 = Missing provides class name +1808 = Invalid provides class name +1809 = Invalid provides class + ### ELABORATIONS ## Access restrictions 78592 = The type ''{1}'' is not API (restriction on classpath entry ''{0}'') diff --git a/jdt-patch/e414/org.eclipse.jdt.core/formatter/org/eclipse/jdt/core/formatter/DefaultCodeFormatterConstants.java b/jdt-patch/e414/org.eclipse.jdt.core/formatter/org/eclipse/jdt/core/formatter/DefaultCodeFormatterConstants.java index 31f10d7a03..a7dbea666e 100644 --- a/jdt-patch/e414/org.eclipse.jdt.core/formatter/org/eclipse/jdt/core/formatter/DefaultCodeFormatterConstants.java +++ b/jdt-patch/e414/org.eclipse.jdt.core/formatter/org/eclipse/jdt/core/formatter/DefaultCodeFormatterConstants.java @@ -1396,6 +1396,18 @@ public class DefaultCodeFormatterConstants { * @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$ + /** + *
+	 * 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
+	 * 
+ * @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$ /** *
@@ -3117,6 +3129,18 @@ public class DefaultCodeFormatterConstants {
 	 * @since 3.0
 	 */
 	public static final String FORMATTER_INSERT_SPACE_AFTER_UNARY_OPERATOR = JavaCore.PLUGIN_ID + ".formatter.insert_space_after_unary_operator"; //$NON-NLS-1$
+	/**
+	 * 
+	 * 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
+	 * 
+ * @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$ /** *
 	 * FORMATTER / Option to insert a space before and in wildcard
diff --git a/jdt-patch/e414/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/CommentsPreparator.java b/jdt-patch/e414/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/CommentsPreparator.java
index 85d047ec6e..c9be5d93ae 100644
--- a/jdt-patch/e414/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/CommentsPreparator.java
+++ b/jdt-patch/e414/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 @@ public class CommentsPreparator extends ASTVisitor {
 	private int noFormatTagOpenStart = -1;
 	private int formatCodeTagOpenEnd = -1;
 	private int lastFormatCodeClosingTagIndex = -1;
-	private Token firstTagToken;
+	private ArrayList commonAttributeAnnotations = new ArrayList();
 	private DefaultCodeFormatter commentCodeFormatter;
 
 	public CommentsPreparator(TokenManager tm, DefaultCodeFormatterOptions options, String sourceLevel) {
@@ -536,7 +536,7 @@ public boolean visit(Javadoc node) {
 		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 @@ public boolean visit(Javadoc node) {
 		this.ctm = new TokenManager(commentToken.getInternalStructure(), this.tm);
 
 		handleJavadocTagAlignment(node);
+		handleJavadocBlankLines(node);
 
 		return true;
 	}
@@ -572,10 +573,6 @@ public boolean visit(Javadoc node) {
 	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 @@ public boolean visit(TagElement node) {
 			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 handleJavadocTagAlignment(Javadoc node) {
 			}
 		}
 	}
+	
+	private void handleJavadocBlankLines(Javadoc node) {
+		List tagElements = node.tags();
+		List 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 tagTokens, int paramNameAlign, int descriptionAlign) {
 		Token paramName = tagTokens.get(1);
@@ -1066,11 +1088,12 @@ private boolean tokenizeMultilineComment(Token commentToken) {
 						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 @@ private boolean tokenizeMultilineComment(Token commentToken) {
 		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/jdt-patch/e414/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/DefaultCodeFormatterOptions.java b/jdt-patch/e414/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/DefaultCodeFormatterOptions.java
index 1828844fcc..99b5de47da 100644
--- a/jdt-patch/e414/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/DefaultCodeFormatterOptions.java
+++ b/jdt-patch/e414/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/DefaultCodeFormatterOptions.java
@@ -229,6 +229,7 @@ public static DefaultCodeFormatterOptions getJavaConventionsSettings() {
 	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;
@@ -326,6 +327,7 @@ public static DefaultCodeFormatterOptions getJavaConventionsSettings() {
 	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;
@@ -600,6 +602,7 @@ public Map getMap() {
 		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));
@@ -709,6 +712,7 @@ public Map getMap() {
 		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);
@@ -1479,6 +1483,8 @@ public void set(Map settings) {
 		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);
@@ -1774,6 +1780,8 @@ else if (JavaCore.SPACE.equals(settings.get(DefaultCodeFormatterConstants.FORMAT
 		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);
@@ -2787,6 +2795,10 @@ private void setDerivableOptions(Map settings) {
 			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() {
@@ -2869,6 +2881,7 @@ public void setDefaultSettings() {
 		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;
@@ -2978,6 +2991,7 @@ public void setDefaultSettings() {
 		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;
@@ -3234,6 +3248,7 @@ public void setJavaConventionsSettings() {
 		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;
@@ -3341,6 +3356,7 @@ public void setJavaConventionsSettings() {
 		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/jdt-patch/e414/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/SpacePreparator.java b/jdt-patch/e414/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/SpacePreparator.java
index d7fa6fadb1..0b5e41770e 100644
--- a/jdt-patch/e414/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/SpacePreparator.java
+++ b/jdt-patch/e414/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/SpacePreparator.java
@@ -6,7 +6,6 @@
  * which accompanies this distribution, and is available at
  * https://www.eclipse.org/legal/epl-2.0/
  *
- * SPDX-License-Identifier: EPL-2.0
  *
  * Contributors:
  *     Mateusz Matela  - [formatter] Formatter does not format Java code correctly, especially when max line width is set - https://bugs.eclipse.org/303519
@@ -812,8 +811,13 @@ public boolean visit(PrefixExpression node) {
 			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/jdt-patch/e414/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/linewrap/WrapExecutor.java b/jdt-patch/e414/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/linewrap/WrapExecutor.java
index 2fb28a1738..d3b453f58e 100644
--- a/jdt-patch/e414/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/linewrap/WrapExecutor.java
+++ b/jdt-patch/e414/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 boolean isActiveTopPriorityWrap(int index, WrapPolicy wrapPolicy) {
 
 	private class WrapsApplier extends TokenTraverser {
 
+		private final TokenManager tm2 = WrapExecutor.this.tm;
 		private ArrayDeque stack = new ArrayDeque<>();
 		private int initialIndent;
 		private int currentIndent;
@@ -242,22 +243,57 @@ protected boolean token(Token token, int index) {
 				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 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 @@ private int[] toArray(List list) {
 		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/jdt-patch/e414/org.eclipse.jdt.core/jdtCompilerAdapter.jar b/jdt-patch/e414/org.eclipse.jdt.core/jdtCompilerAdapter.jar
index f5a110881e..dd21253fb9 100644
Binary files a/jdt-patch/e414/org.eclipse.jdt.core/jdtCompilerAdapter.jar and b/jdt-patch/e414/org.eclipse.jdt.core/jdtCompilerAdapter.jar differ
diff --git a/jdt-patch/e414/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/IncrementalImageBuilder.java b/jdt-patch/e414/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/IncrementalImageBuilder.java
index 7fa7cc6842..2340538da5 100644
--- a/jdt-patch/e414/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/IncrementalImageBuilder.java
+++ b/jdt-patch/e414/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/IncrementalImageBuilder.java
@@ -966,6 +966,15 @@ protected boolean writeClassFileCheck(IFile file, String fileName, byte[] newByt
 			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);
diff --git a/jdt-patch/e414/org.eclipse.jdt.core/readme.txt b/jdt-patch/e414/org.eclipse.jdt.core/readme.txt
index 9c738c4fa7..8042d67d76 100644
--- a/jdt-patch/e414/org.eclipse.jdt.core/readme.txt
+++ b/jdt-patch/e414/org.eclipse.jdt.core/readme.txt
@@ -1,2 +1,3 @@
 2019-10-12: 0b941e3 (2019-12 M1)
 2019-11-09: 3106c52 (2019-12 M2)
+2019-11-23: f44d4bd (2019-12 M3)