diff --git a/plexus-compilers/plexus-compiler-javac/src/main/java/org/codehaus/plexus/compiler/javac/JavacCompiler.java b/plexus-compilers/plexus-compiler-javac/src/main/java/org/codehaus/plexus/compiler/javac/JavacCompiler.java
index 0255c2a7..8c20da68 100644
--- a/plexus-compilers/plexus-compiler-javac/src/main/java/org/codehaus/plexus/compiler/javac/JavacCompiler.java
+++ b/plexus-compilers/plexus-compiler-javac/src/main/java/org/codehaus/plexus/compiler/javac/JavacCompiler.java
@@ -62,9 +62,11 @@
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
+import java.util.Objects;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentLinkedDeque;
+import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.codehaus.plexus.compiler.AbstractCompiler;
@@ -80,10 +82,14 @@
import org.codehaus.plexus.util.cli.CommandLineUtils;
import org.codehaus.plexus.util.cli.Commandline;
+import static org.codehaus.plexus.compiler.CompilerMessage.Kind.*;
+import static org.codehaus.plexus.compiler.javac.JavacCompiler.Messages.*;
+
/**
* @author Trygve Laugstøl
* @author Matthew Pocock
* @author Jörg Waßmer
+ * @author Alexander Kriegisch
* @author Others
*
*/
@@ -91,21 +97,123 @@
@Singleton
public class JavacCompiler extends AbstractCompiler {
- // see compiler.warn.warning in compiler.properties of javac sources
- private static final String[] WARNING_PREFIXES = {"warning: ", "\u8b66\u544a: ", "\u8b66\u544a\uff1a "};
-
- // see compiler.note.note in compiler.properties of javac sources
- private static final String[] NOTE_PREFIXES = {"Note: ", "\u6ce8: ", "\u6ce8\u610f\uff1a "};
-
- // see compiler.misc.verbose in compiler.properties of javac sources
- private static final String[] MISC_PREFIXES = {"["};
+ /**
+ * Multi-language compiler messages to parse from forked javac output.
+ *
+ * - OpenJDK 8+ is delivered with 3 locales (en, ja, zh_CN).
+ * - OpenJDK 21+ is delivered with 4 locales (en, ja, zh_CN, de).
+ *
+ * Instead of manually duplicating multi-language messages into this class, it would be preferable to fetch the
+ * strings directly from the running JDK:
+ * {@code
+ * new JavacMessages("com.sun.tools.javac.resources.javac", Locale.getDefault())
+ * .getLocalizedString("javac.msg.proc.annotation.uncaught.exception")
+ * }
+ * Hoewever, due to JMS module protection, it would be necessary to run Plexus Compiler (and hence also Maven
+ * Compiler and the whole Maven JVM) with {@code --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED}
+ * on more recent JDK versions. As this cannot be reliably expected and using internal APIs - even though stable
+ * since at least JDK 8 - it is not a future-proof approach. So we refrain from doing so, even though during Plexus
+ * Compiler development it might come in handy.
+ *
+ * TODO: Check compiler.properties and javac.properties in OpenJDK javac source code for
+ * message changes, relevant new messages, new locales.
+ */
+ protected static class Messages {
+ // compiler.properties -> compiler.err.error (en, ja, zh_CN, de)
+ protected static final String[] ERROR_PREFIXES = {"error: ", "エラー: ", "错误: ", "Fehler: "};
+
+ // compiler.properties -> compiler.warn.warning (en, ja, zh_CN, de)
+ protected static final String[] WARNING_PREFIXES = {"warning: ", "警告: ", "警告: ", "Warnung: "};
+
+ // compiler.properties -> compiler.note.note (en, ja, zh_CN, de)
+ protected static final String[] NOTE_PREFIXES = {"Note: ", "ノート: ", "注: ", "Hinweis: "};
+
+ // compiler.properties -> compiler.misc.verbose.*
+ protected static final String[] MISC_PREFIXES = {"["};
+
+ // Generic javac error prefix
+ // TODO: In JDK 8, this generic prefix no longer seems to be in use for javac error messages, at least not in
+ // the Java part of javac. Maybe in C sources? Does javac even use any native classes?
+ protected static final String[] JAVAC_GENERIC_ERROR_PREFIXES = {"javac:"};
+
+ // Hard-coded, English-only error header in JVM native code, *not* followed by stack trace, but rather
+ // by another text message
+ protected static final String[] VM_INIT_ERROR_HEADERS = {"Error occurred during initialization of VM"};
+
+ // Hard-coded, English-only error header in class System, followed by stack trace
+ protected static final String[] BOOT_LAYER_INIT_ERROR_HEADERS = {
+ "Error occurred during initialization of boot layer"
+ };
+
+ // javac.properties-> javac.msg.proc.annotation.uncaught.exception
+ // (en JDK-8, ja JDK-8, zh_CN JDK-8, en JDK-21, ja JDK-21, zh_CN JDK-21, de JDK-21)
+ protected static final String[] ANNOTATION_PROCESSING_ERROR_HEADERS = {
+ "\n\nAn annotation processor threw an uncaught exception.\nConsult the following stack trace for details.\n\n",
+ "\n\n注釈処理で捕捉されない例外がスローされました。\n詳細は次のスタック・トレースで調査してください。\n\n",
+ "\n\n批注处理程序抛出未捕获的异常错误。\n有关详细信息, 请参阅以下堆栈跟踪。\n\n",
+ "\n\nAn annotation processor threw an uncaught exception.\nConsult the following stack trace for details.\n\n",
+ "\n\n注釈処理で捕捉されない例外がスローされました。\n詳細は次のスタックトレースで調査してください。\n\n",
+ "\n\n批注处理程序抛出未捕获的异常错误。\n有关详细信息, 请参阅以下堆栈跟踪。\n\n",
+ "\n\nEin Annotationsprozessor hat eine nicht abgefangene Ausnahme ausgelöst.\nDetails finden Sie im folgenden Stacktrace.\n\n"
+ };
+
+ // javac.properties-> javac.msg.bug
+ // (en JDK-8, ja JDK-8, zh_CN JDK-8, en JDK-9, ja JDK-9, zh_CN JDK-9, en JDK-21, ja JDK-21, zh_CN JDK-21, de
+ // JDK-21)
+ protected static final String[] FILE_A_BUG_ERROR_HEADERS = {
+ "An exception has occurred in the compiler ({0}). Please file a bug at the Java Developer Connection (http://java.sun.com/webapps/bugreport) after checking the Bug Parade for duplicates. Include your program and the following diagnostic in your report. Thank you.\n",
+ "コンパイラで例外が発生しました({0})。Bug Paradeで重複がないかをご確認のうえ、Java Developer Connection (http://java.sun.com/webapps/bugreport)でbugの登録をお願いいたします。レポートには、そのプログラムと下記の診断内容を含めてください。ご協力ありがとうございます。\n",
+ "编译器 ({0}) 中出现异常错误。 如果在 Bug Parade 中没有找到该错误, 请在 Java Developer Connection (http://java.sun.com/webapps/bugreport) 中建立 Bug。请在报告中附上您的程序和以下诊断信息。谢谢。\n",
+ "An exception has occurred in the compiler ({0}). Please file a bug against the Java compiler via the Java bug reporting page (http://bugreport.java.com) after checking the Bug Database (http://bugs.java.com) for duplicates. Include your program and the following diagnostic in your report. Thank you.",
+ "コンパイラで例外が発生しました({0})。Bug Database (http://bugs.java.com)で重複がないかをご確認のうえ、Java bugレポート・ページ(http://bugreport.java.com)でJavaコンパイラに対するbugの登録をお願いいたします。レポートには、そのプログラムと下記の診断内容を含めてください。ご協力ありがとうございます。",
+ "编译器 ({0}) 中出现异常错误。如果在 Bug Database (http://bugs.java.com) 中没有找到该错误, 请通过 Java Bug 报告页 (http://bugreport.java.com) 建立该 Java 编译器 Bug。请在报告中附上您的程序和以下诊断信息。谢谢。",
+ "An exception has occurred in the compiler ({0}). Please file a bug against the Java compiler via the Java bug reporting page (https://bugreport.java.com) after checking the Bug Database (https://bugs.java.com) for duplicates. Include your program, the following diagnostic, and the parameters passed to the Java compiler in your report. Thank you.\n",
+ "コンパイラで例外が発生しました({0})。バグ・データベース(https://bugs.java.com)で重複がないかをご確認のうえ、Javaのバグ・レポート・ページ(https://bugreport.java.com)から、Javaコンパイラに対するバグの登録をお願いいたします。レポートには、該当のプログラム、次の診断内容、およびJavaコンパイラに渡されたパラメータをご入力ください。ご協力ありがとうございます。\n",
+ "编译器 ({0}) 中出现异常错误。如果在 Bug Database (https://bugs.java.com) 中没有找到有关该错误的 Java 编译器 Bug,请通过 Java Bug 报告页 (https://bugreport.java.com) 提交 Java 编译器 Bug。请在报告中附上您的程序、以下诊断信息以及传递到 Java 编译器的参数。谢谢。\n",
+ "Im Compiler ({0}) ist eine Ausnahme aufgetreten. Erstellen Sie auf der Java-Seite zum Melden von Bugs (https://bugreport.java.com) einen Bugbericht, nachdem Sie die Bugdatenbank (https://bugs.java.com) auf Duplikate geprüft haben. Geben Sie in Ihrem Bericht Ihr Programm, die folgende Diagnose und die Parameter an, die Sie dem Java-Compiler übergeben haben. Vielen Dank.\n"
+ };
+
+ // javac.properties-> javac.msg.resource
+ // (en JDK-8, ja JDK-8, zh_CN JDK-8, en JDK-21, ja JDK-21, zh_CN JDK-21, de JDK-21)
+ protected static final String[] SYSTEM_OUT_OF_RESOURCES_ERROR_HEADERS = {
+ "\n\nThe system is out of resources.\nConsult the following stack trace for details.\n",
+ "\n\nシステム・リソースが不足しています。\n詳細は次のスタック・トレースで調査してください。\n",
+ "\n\n系统资源不足。\n有关详细信息, 请参阅以下堆栈跟踪。\n",
+ "\n\nThe system is out of resources.\nConsult the following stack trace for details.\n",
+ "\n\nシステム・リソースが不足しています。\n詳細は次のスタックトレースで調査してください。\n",
+ "\n\n系统资源不足。\n有关详细信息, 请参阅以下堆栈跟踪。\n",
+ "\n\nDas System hat keine Ressourcen mehr.\nDetails finden Sie im folgenden Stacktrace.\n"
+ };
+
+ // javac.properties-> javac.msg.io
+ // (en JDK-8, ja JDK-8, zh_CN JDK-8, en JDK-21, ja JDK-21, zh_CN JDK-21, de JDK-21)
+ protected static final String[] IO_ERROR_HEADERS = {
+ "\n\nAn input/output error occurred.\nConsult the following stack trace for details.\n",
+ "\n\n入出力エラーが発生しました。\n詳細は次のスタック・トレースで調査してください。\n",
+ "\n\n发生输入/输出错误。\n有关详细信息, 请参阅以下堆栈跟踪。\n",
+ "\n\nAn input/output error occurred.\nConsult the following stack trace for details.\n",
+ "\n\n入出力エラーが発生しました。\n詳細は次のスタックトレースで調査してください。\n",
+ "\n\n发生输入/输出错误。\n有关详细信息, 请参阅以下堆栈跟踪。\n",
+ "\n\nEin Eingabe-/Ausgabefehler ist aufgetreten.\nDetails finden Sie im folgenden Stacktrace.\n"
+ };
+
+ // javac.properties-> javac.msg.plugin.uncaught.exception
+ // (en JDK-8, ja JDK-8, zh_CN JDK-8, en JDK-21, ja JDK-21, zh_CN JDK-21, de JDK-21)
+ protected static final String[] PLUGIN_ERROR_HEADERS = {
+ "\n\nA plugin threw an uncaught exception.\nConsult the following stack trace for details.\n",
+ "\n\nプラグインで捕捉されない例外がスローされました。\n詳細は次のスタック・トレースで調査してください。\n",
+ "\n\n插件抛出未捕获的异常错误。\n有关详细信息, 请参阅以下堆栈跟踪。\n",
+ "\n\nA plugin threw an uncaught exception.\nConsult the following stack trace for details.\n",
+ "\n\nプラグインで捕捉されない例外がスローされました。\n詳細は次のスタック・トレースで調査してください。\n",
+ "\n\n插件抛出未捕获的异常错误。\n有关详细信息, 请参阅以下堆栈跟踪。\n",
+ "\n\nEin Plug-in hat eine nicht abgefangene Ausnahme ausgel\u00F6st.\nDetails finden Sie im folgenden Stacktrace.\n"
+ };
+ }
private static final Object LOCK = new Object();
-
private static final String JAVAC_CLASSNAME = "com.sun.tools.javac.Main";
private volatile Class> javacClass;
-
private final Deque> javacClasses = new ConcurrentLinkedDeque<>();
@Inject
@@ -131,13 +239,11 @@ public String getCompilerId() {
@Override
public CompilerResult performCompile(CompilerConfiguration config) throws CompilerException {
File destinationDir = new File(config.getOutputLocation());
-
if (!destinationDir.exists()) {
destinationDir.mkdirs();
}
String[] sourceFiles = getSourceFiles(config);
-
if ((sourceFiles == null) || (sourceFiles.length == 0)) {
return new CompilerResult();
}
@@ -145,12 +251,10 @@ public CompilerResult performCompile(CompilerConfiguration config) throws Compil
logCompiling(sourceFiles, config);
String[] args = buildCompilerArguments(config, sourceFiles);
-
CompilerResult result;
if (config.isFork()) {
String executable = config.getExecutable();
-
if (StringUtils.isEmpty(executable)) {
try {
executable = getJavacExecutable();
@@ -161,7 +265,6 @@ public CompilerResult performCompile(CompilerConfiguration config) throws Compil
executable = "javac";
}
}
-
result = compileOutOfProcess(config, executable, args);
} else {
if (isJava16() && !config.isForceJavacCompilerUse()) {
@@ -200,9 +303,7 @@ public static String[] buildCompilerArguments(CompilerConfiguration config, Stri
// ----------------------------------------------------------------------
File destinationDir = new File(config.getOutputLocation());
-
args.add("-d");
-
args.add(destinationDir.getAbsolutePath());
// ----------------------------------------------------------------------
@@ -212,14 +313,12 @@ public static String[] buildCompilerArguments(CompilerConfiguration config, Stri
List classpathEntries = config.getClasspathEntries();
if (classpathEntries != null && !classpathEntries.isEmpty()) {
args.add("-classpath");
-
args.add(getPathString(classpathEntries));
}
List modulepathEntries = config.getModulepathEntries();
if (modulepathEntries != null && !modulepathEntries.isEmpty()) {
args.add("--module-path");
-
args.add(getPathString(modulepathEntries));
}
@@ -228,7 +327,6 @@ public static String[] buildCompilerArguments(CompilerConfiguration config, Stri
// always pass source path, even if sourceFiles are declared,
// needed for jsr269 annotation processing, see MCOMPILER-98
args.add("-sourcepath");
-
args.add(getPathString(sourceLocations));
}
if (!isJava16() || config.isForceJavacCompilerUse() || config.isFork()) {
@@ -240,7 +338,6 @@ public static String[] buildCompilerArguments(CompilerConfiguration config, Stri
if (config.getGeneratedSourcesDirectory() != null) {
config.getGeneratedSourcesDirectory().mkdirs();
-
args.add("-s");
args.add(config.getGeneratedSourcesDirectory().getAbsolutePath());
}
@@ -255,7 +352,6 @@ public static String[] buildCompilerArguments(CompilerConfiguration config, Stri
if (i > 0) {
buffer.append(",");
}
-
buffer.append(procs[i]);
}
args.add(buffer.toString());
@@ -366,13 +462,10 @@ public static String[] buildCompilerArguments(CompilerConfiguration config, Stri
}
args.add(key);
-
String value = entry.getValue();
-
if (StringUtils.isEmpty(value)) {
continue;
}
-
args.add(value);
}
@@ -413,11 +506,9 @@ private static boolean isPreJava16(CompilerConfiguration config) {
if (v == null) {
v = config.getCompilerVersion();
}
-
if (v == null) {
v = config.getSourceVersion();
}
-
if (v == null) {
return true;
}
@@ -437,11 +528,9 @@ private static boolean isPreJava18(CompilerConfiguration config) {
if (v == null) {
v = config.getCompilerVersion();
}
-
if (v == null) {
v = config.getSourceVersion();
}
-
if (v == null) {
return true;
}
@@ -459,17 +548,14 @@ private static boolean isPreJava18(CompilerConfiguration config) {
}
private static boolean isPreJava9(CompilerConfiguration config) {
-
String v = config.getReleaseVersion();
if (v == null) {
v = config.getCompilerVersion();
}
-
if (v == null) {
v = config.getSourceVersion();
}
-
if (v == null) {
return true;
}
@@ -510,7 +596,6 @@ protected CompilerResult compileOutOfProcess(CompilerConfiguration config, Strin
Commandline cli = new Commandline();
cli.setWorkingDirectory(config.getWorkingDirectory().getAbsolutePath());
-
cli.setExecutable(executable);
try {
@@ -522,7 +607,6 @@ protected CompilerResult compileOutOfProcess(CompilerConfiguration config, Strin
if (!StringUtils.isEmpty(config.getMaxmem())) {
cli.addArguments(new String[] {"-J-Xmx" + config.getMaxmem()});
}
-
if (!StringUtils.isEmpty(config.getMeminitial())) {
cli.addArguments(new String[] {"-J-Xms" + config.getMeminitial()});
}
@@ -537,9 +621,7 @@ protected CompilerResult compileOutOfProcess(CompilerConfiguration config, Strin
}
CommandLineUtils.StringStreamConsumer out = new CommandLineUtils.StringStreamConsumer();
-
int returnCode;
-
List messages;
if (getLog().isDebugEnabled()) {
@@ -563,6 +645,11 @@ protected CompilerResult compileOutOfProcess(CompilerConfiguration config, Strin
}
try {
+ // TODO:
+ // Is it really helpful to parse stdOut and stdErr as a single stream, instead of taking the chance to
+ // draw extra information from the fact that normal javac output is written to stdOut, while warnings and
+ // errors are written to stdErr? Of course, chronological correlation of messages would be more difficult
+ // then, but basically, we are throwing away information here.
returnCode = CommandLineUtils.executeCommandLine(cli, out, out);
messages = parseModernStream(returnCode, new BufferedReader(new StringReader(out.getOutput())));
@@ -609,16 +696,12 @@ protected CompilerResult compileInProcessWithProperClassloader(Class> javacCla
*/
private static CompilerResult compileInProcess0(Class> javacClass, String[] args) throws CompilerException {
StringWriter out = new StringWriter();
-
Integer ok;
-
List messages;
try {
Method compile = javacClass.getMethod("compile", new Class[] {String[].class, PrintWriter.class});
-
ok = (Integer) compile.invoke(null, new Object[] {args, new PrintWriter(out)});
-
messages = parseModernStream(ok, new BufferedReader(new StringReader(out.toString())));
} catch (NoSuchMethodException | IOException | InvocationTargetException | IllegalAccessException e) {
throw new CompilerException("Error while executing the compiler.", e);
@@ -638,74 +721,22 @@ private static CompilerResult compileInProcess0(Class> javacClass, String[] ar
private static final Pattern STACK_TRACE_OTHER_LINE =
Pattern.compile("^(?:Caused by:\\s.*|\\s*at .*|\\s*\\.\\.\\.\\s\\d+\\smore)$");
- // Match generic javac errors with 'javac:' prefix, JMV init and boot layer init errors
- private static final Pattern JAVAC_OR_JVM_ERROR =
- Pattern.compile("^(?:javac:|Error occurred during initialization of (?:boot layer|VM)).*", Pattern.DOTALL);
-
/**
- * Parse the output from the compiler into a list of CompilerMessage objects
+ * Parse the compiler output into a list of compiler messages
*
- * @param exitCode The exit code of javac.
- * @param input The output of the compiler
- * @return List of CompilerMessage objects
- * @throws IOException
+ * @param exitCode javac exit code (0 on success, non-zero otherwise)
+ * @param input compiler output (stdOut and stdErr merged into input stream)
+ * @return list of {@link CompilerMessage} objects
+ * @throws IOException if there is a problem reading from the input reader
*/
static List parseModernStream(int exitCode, BufferedReader input) throws IOException {
List errors = new ArrayList<>();
-
String line;
-
StringBuilder buffer = new StringBuilder();
-
boolean hasPointer = false;
int stackTraceLineCount = 0;
- while (true) {
- line = input.readLine();
-
- if (line == null) {
- // javac output not detected by other parsing
- // maybe better to ignore only the summary and mark the rest as error
- String bufferAsString = buffer.toString();
- if (buffer.length() > 0) {
- if (JAVAC_OR_JVM_ERROR.matcher(bufferAsString).matches()) {
- errors.add(new CompilerMessage(bufferAsString, CompilerMessage.Kind.ERROR));
- } else if (hasPointer) {
- // A compiler message remains in buffer at end of parse stream
- errors.add(parseModernError(exitCode, bufferAsString));
- } else if (stackTraceLineCount > 0) {
- // Extract stack trace from end of buffer
- String[] lines = bufferAsString.split("\\R");
- int linesTotal = lines.length;
- buffer = new StringBuilder();
- int firstLine = linesTotal - stackTraceLineCount;
-
- // Salvage Javac localized message 'javac.msg.bug' ("An exception has occurred in the
- // compiler ... Please file a bug")
- if (firstLine > 0) {
- final String lineBeforeStackTrace = lines[firstLine - 1];
- // One of those two URL substrings should always appear, without regard to JVM locale.
- // TODO: Update, if the URL changes, last checked for JDK 21.
- if (lineBeforeStackTrace.contains("java.sun.com/webapps/bugreport")
- || lineBeforeStackTrace.contains("bugreport.java.com")) {
- firstLine--;
- }
- }
-
- // Note: For message 'javac.msg.proc.annotation.uncaught.exception' ("An annotation processor
- // threw an uncaught exception"), there is no locale-independent substring, and the header is
- // also multi-line. It was discarded in the removed method 'parseAnnotationProcessorStream',
- // and we continue to do so.
-
- for (int i = firstLine; i < linesTotal; i++) {
- buffer.append(lines[i]).append(EOL);
- }
- errors.add(new CompilerMessage(buffer.toString(), CompilerMessage.Kind.ERROR));
- }
- }
- return errors;
- }
-
+ while ((line = input.readLine()) != null) {
if (stackTraceLineCount == 0 && STACK_TRACE_FIRST_LINE.matcher(line).matches()
|| STACK_TRACE_OTHER_LINE.matcher(line).matches()) {
stackTraceLineCount++;
@@ -717,46 +748,128 @@ static List parseModernStream(int exitCode, BufferedReader inpu
if (!line.startsWith(" ") && hasPointer) {
// add the error bean
errors.add(parseModernError(exitCode, buffer.toString()));
-
// reset for next error block
buffer = new StringBuilder(); // this is quicker than clearing it
-
hasPointer = false;
}
- // TODO: there should be a better way to parse these
- if ((buffer.length() == 0) && line.startsWith("error: ")) {
- errors.add(new CompilerMessage(line, CompilerMessage.Kind.ERROR));
- } else if ((buffer.length() == 0) && line.startsWith("warning: ")) {
- errors.add(new CompilerMessage(line, CompilerMessage.Kind.WARNING));
- } else if ((buffer.length() == 0) && isNote(line)) {
- // skip, JDK 1.5 telling us deprecated APIs are used but -Xlint:deprecation isn't set
- } else if ((buffer.length() == 0) && isMisc(line)) {
- // verbose output was set
- errors.add(new CompilerMessage(line, CompilerMessage.Kind.OTHER));
+ if (buffer.length() == 0) {
+ // try to classify output line by type (error, warning etc.)
+ // TODO: there should be a better way to parse these
+ if (isError(line)) {
+ errors.add(new CompilerMessage(line, ERROR));
+ } else if (isWarning(line)) {
+ errors.add(new CompilerMessage(line, WARNING));
+ } else if (isNote(line)) {
+ // skip, JDK telling us deprecated APIs are used but -Xlint:deprecation isn't set
+ } else if (isMisc(line)) {
+ // verbose output was set
+ errors.add(new CompilerMessage(line, CompilerMessage.Kind.OTHER));
+ } else {
+ // add first unclassified line to buffer
+ buffer.append(line).append(EOL);
+ }
} else {
- buffer.append(line);
-
- buffer.append(EOL);
+ // add next unclassified line to buffer
+ buffer.append(line).append(EOL);
}
if (line.endsWith("^")) {
hasPointer = true;
}
}
+
+ String bufferContent = buffer.toString();
+ if (bufferContent.isEmpty()) {
+ return errors;
+ }
+
+ // javac output not detected by other parsing
+ // maybe better to ignore only the summary and mark the rest as error
+ String cleanedUpMessage;
+ if ((cleanedUpMessage = getJavacGenericError(bufferContent)) != null
+ || (cleanedUpMessage = getBootLayerInitError(bufferContent)) != null
+ || (cleanedUpMessage = getVMInitError(bufferContent)) != null
+ || (cleanedUpMessage = getFileABugError(bufferContent)) != null
+ || (cleanedUpMessage = getAnnotationProcessingError(bufferContent)) != null
+ || (cleanedUpMessage = getSystemOutOfResourcesError(bufferContent)) != null
+ || (cleanedUpMessage = getIOError(bufferContent)) != null
+ || (cleanedUpMessage = getPluginError(bufferContent)) != null) {
+ errors.add(new CompilerMessage(cleanedUpMessage, ERROR));
+ } else if (hasPointer) {
+ // A compiler message remains in buffer at end of parse stream
+ errors.add(parseModernError(exitCode, bufferContent));
+ } else if (stackTraceLineCount > 0) {
+ // Extract stack trace from end of buffer
+ String[] lines = bufferContent.split("\\R");
+ int linesTotal = lines.length;
+ buffer = new StringBuilder();
+ int firstLine = linesTotal - stackTraceLineCount;
+ for (int i = firstLine; i < linesTotal; i++) {
+ buffer.append(lines[i]).append(EOL);
+ }
+ errors.add(new CompilerMessage(buffer.toString(), ERROR));
+ }
+ // TODO: Add something like this? Check if it creates more value or more unnecessary log output in general.
+ // else {
+ // // Fall-back, if still no error or stack trace was recognised
+ // errors.add(new CompilerMessage(bufferContent, exitCode == 0 ? OTHER : ERROR));
+ // }
+
+ return errors;
+ }
+
+ private static boolean isMisc(String message) {
+ return startsWithPrefix(message, MISC_PREFIXES);
}
- private static boolean isMisc(String line) {
- return startsWithPrefix(line, MISC_PREFIXES);
+ private static boolean isNote(String message) {
+ return startsWithPrefix(message, NOTE_PREFIXES);
}
- private static boolean isNote(String line) {
- return startsWithPrefix(line, NOTE_PREFIXES);
+ private static boolean isWarning(String message) {
+ return startsWithPrefix(message, WARNING_PREFIXES);
}
- private static boolean startsWithPrefix(String line, String[] prefixes) {
+ private static boolean isError(String message) {
+ return startsWithPrefix(message, ERROR_PREFIXES);
+ }
+
+ private static String getJavacGenericError(String message) {
+ return getTextStartingWithPrefix(message, JAVAC_GENERIC_ERROR_PREFIXES);
+ }
+
+ private static String getVMInitError(String message) {
+ return getTextStartingWithPrefix(message, VM_INIT_ERROR_HEADERS);
+ }
+
+ private static String getBootLayerInitError(String message) {
+ return getTextStartingWithPrefix(message, BOOT_LAYER_INIT_ERROR_HEADERS);
+ }
+
+ private static String getFileABugError(String message) {
+ return getTextStartingWithPrefix(message, FILE_A_BUG_ERROR_HEADERS);
+ }
+
+ private static String getAnnotationProcessingError(String message) {
+ return getTextStartingWithPrefix(message, ANNOTATION_PROCESSING_ERROR_HEADERS);
+ }
+
+ private static String getSystemOutOfResourcesError(String message) {
+ return getTextStartingWithPrefix(message, SYSTEM_OUT_OF_RESOURCES_ERROR_HEADERS);
+ }
+
+ private static String getIOError(String message) {
+ return getTextStartingWithPrefix(message, IO_ERROR_HEADERS);
+ }
+
+ private static String getPluginError(String message) {
+ return getTextStartingWithPrefix(message, PLUGIN_ERROR_HEADERS);
+ }
+
+ private static boolean startsWithPrefix(String text, String[] prefixes) {
for (String prefix : prefixes) {
- if (line.startsWith(prefix)) {
+ if (text.startsWith(prefix)) {
return true;
}
}
@@ -764,26 +877,66 @@ private static boolean startsWithPrefix(String line, String[] prefixes) {
}
/**
- * Construct a CompilerMessage object from a line of the compiler output
+ * Identify and return a known javac error message prefix and all subsequent text - usually a stack trace - from a
+ * javac log output buffer.
+ *
+ * @param text log buffer to search for a javac error message stack trace
+ * @param prefixes array of strings in Java properties format, e.g. {@code "some error with line feed\nand parameter
+ * placeholders {0} and {1}"} in multiple locales (hence the array). For the search, the
+ * placeholders may be represented by any text in the log buffer.
+ * @return if found, the error message + all subsequent text, otherwise {@code null}
+ */
+ static String getTextStartingWithPrefix(String text, String[] prefixes) {
+ // Implementation note: The properties format with placeholders makes it easy to just copy & paste values from
+ // the JDK compared to having to convert them to regular expressions with ".*" instead of "{0}" and quote
+ // special regex characters. This makes the implementation of this method more complex and potentially a bit
+ // slower, but hopefully is worth the effort for the convenience of future developers maintaining this class.
+
+ // Normalise line feeds to the UNIX format found in JDK multi-line messages in properties files
+ text = text.replaceAll("\\R", "\n");
+
+ // Search text for given error message prefixes/headers, until the first match is found
+ for (String prefix : prefixes) {
+ // Split properties message along placeholders like "{0}", "{1}" etc.
+ String[] prefixParts = prefix.split("\\{\\d+\\}");
+ for (int i = 0; i < prefixParts.length; i++) {
+ // Make sure to treat split sections as literal text in search regex by enclosing them in "\Q" and "\E".
+ // See https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html, search for "Quotation".
+ prefixParts[i] = "\\Q" + prefixParts[i] + "\\E";
+ }
+ // Join message parts, replacing properties placeholders by ".*" regex ones
+ prefix = String.join(".*?", prefixParts);
+ // Find prefix + subsequent text in Pattern.DOTALL mode, represented in regex as "(?s)".
+ // This matches across line break boundaries.
+ Matcher matcher = Pattern.compile("(?s).*(" + prefix + ".*)").matcher(text);
+ if (matcher.matches()) {
+ // Match -> cut off text before header and replace UNIX line breaks by platform ones again
+ return matcher.replaceFirst("$1").replaceAll("\n", EOL);
+ }
+ }
+
+ // No match
+ return null;
+ }
+
+ /**
+ * Construct a compiler message object from a compiler output line
*
- * @param exitCode The exit code from javac.
- * @param error output line from the compiler
- * @return the CompilerMessage object
+ * @param exitCode javac exit code
+ * @param error compiler output line
+ * @return compiler message object
*/
static CompilerMessage parseModernError(int exitCode, String error) {
final StringTokenizer tokens = new StringTokenizer(error, ":");
-
- boolean isError = exitCode != 0;
+ CompilerMessage.Kind messageKind = exitCode == 0 ? WARNING : ERROR;
try {
// With Java 6 error output lines from the compiler got longer. For backward compatibility
- // .. and the time being, we eat up all (if any) tokens up to the erroneous file and source
- // .. line indicator tokens.
+ // and the time being, we eat up all (if any) tokens up to the erroneous file and source
+ // line indicator tokens.
boolean tokenIsAnInteger;
-
StringBuilder file = null;
-
String currentToken = null;
do {
@@ -796,9 +949,7 @@ static CompilerMessage parseModernError(int exitCode, String error) {
}
currentToken = tokens.nextToken();
-
// Probably the only backward compatible means of checking if a string is an integer.
-
tokenIsAnInteger = true;
try {
@@ -809,50 +960,39 @@ static CompilerMessage parseModernError(int exitCode, String error) {
} while (!tokenIsAnInteger);
final String lineIndicator = currentToken;
-
- final int startOfFileName = file.toString().lastIndexOf(']');
-
+ final int startOfFileName = Objects.requireNonNull(file).toString().lastIndexOf(']');
if (startOfFileName > -1) {
file = new StringBuilder(file.substring(startOfFileName + 1 + EOL.length()));
}
final int line = Integer.parseInt(lineIndicator);
-
final StringBuilder msgBuffer = new StringBuilder();
-
String msg = tokens.nextToken(EOL).substring(2);
- // Remove the 'warning: ' prefix
- final String warnPrefix = getWarnPrefix(msg);
- if (warnPrefix != null) {
- isError = false;
- msg = msg.substring(warnPrefix.length());
- } else {
- isError = exitCode != 0;
+ // Remove "error: " and "warning: " prefixes
+ String prefix;
+ if ((prefix = getErrorPrefix(msg)) != null) {
+ messageKind = ERROR;
+ msg = msg.substring(prefix.length());
+ } else if ((prefix = getWarningPrefix(msg)) != null) {
+ messageKind = WARNING;
+ msg = msg.substring(prefix.length());
}
-
- msgBuffer.append(msg);
-
- msgBuffer.append(EOL);
+ msgBuffer.append(msg).append(EOL);
String context = tokens.nextToken(EOL);
-
String pointer = null;
do {
final String msgLine = tokens.nextToken(EOL);
-
if (pointer != null) {
msgBuffer.append(msgLine);
-
msgBuffer.append(EOL);
} else if (msgLine.endsWith("^")) {
pointer = msgLine;
} else {
msgBuffer.append(context);
-
msgBuffer.append(EOL);
-
context = msgLine;
}
} while (tokens.hasMoreTokens());
@@ -860,32 +1000,38 @@ static CompilerMessage parseModernError(int exitCode, String error) {
msgBuffer.append(EOL);
final String message = msgBuffer.toString();
-
- final int startcolumn = pointer.indexOf("^");
-
+ final int startcolumn = Objects.requireNonNull(pointer).indexOf("^");
int endcolumn = (context == null) ? startcolumn : context.indexOf(" ", startcolumn);
-
if (endcolumn == -1) {
- endcolumn = context.length();
+ endcolumn = Objects.requireNonNull(context).length();
}
- return new CompilerMessage(file.toString(), isError, line, startcolumn, line, endcolumn, message.trim());
+ return new CompilerMessage(
+ file.toString(), messageKind, line, startcolumn, line, endcolumn, message.trim());
} catch (NoSuchElementException e) {
- return new CompilerMessage("no more tokens - could not parse error message: " + error, isError);
+ return new CompilerMessage("no more tokens - could not parse error message: " + error, messageKind);
} catch (Exception e) {
- return new CompilerMessage("could not parse error message: " + error, isError);
+ return new CompilerMessage("could not parse error message: " + error, messageKind);
}
}
- private static String getWarnPrefix(String msg) {
- for (String warningPrefix : WARNING_PREFIXES) {
- if (msg.startsWith(warningPrefix)) {
- return warningPrefix;
+ private static String getMessagePrefix(String message, String[] prefixes) {
+ for (String prefix : prefixes) {
+ if (message.startsWith(prefix)) {
+ return prefix;
}
}
return null;
}
+ private static String getWarningPrefix(String message) {
+ return getMessagePrefix(message, WARNING_PREFIXES);
+ }
+
+ private static String getErrorPrefix(String message) {
+ return getMessagePrefix(message, ERROR_PREFIXES);
+ }
+
/**
* put args into a temp file to be referenced using the @ option in javac command line
*
@@ -905,15 +1051,11 @@ private File createFileWithArguments(String[] args, String outputDirectory) thro
}
writer = new PrintWriter(new FileWriter(tempFile));
-
for (String arg : args) {
String argValue = arg.replace(File.separatorChar, '/');
-
writer.write("\"" + argValue + "\"");
-
writer.println();
}
-
writer.flush();
return tempFile;
@@ -934,9 +1076,9 @@ private File createFileWithArguments(String[] args, String outputDirectory) thro
*/
private static String getJavacExecutable() throws IOException {
String javacCommand = "javac" + (Os.isFamily(Os.FAMILY_WINDOWS) ? ".exe" : "");
-
String javaHome = System.getProperty("java.home");
File javacExe;
+
if (Os.isName("AIX")) {
javacExe = new File(javaHome + File.separator + ".." + File.separator + "sh", javacCommand);
} else if (Os.isName("Mac OS X")) {
@@ -958,7 +1100,6 @@ private static String getJavacExecutable() throws IOException {
throw new IOException("The environment variable JAVA_HOME=" + javaHome
+ " doesn't exist or is not a valid directory.");
}
-
javacExe = new File(env.getProperty("JAVA_HOME") + File.separator + "bin", javacCommand);
}
diff --git a/plexus-compilers/plexus-compiler-javac/src/test/java/org/codehaus/plexus/compiler/javac/ErrorMessageParserTest.java b/plexus-compilers/plexus-compiler-javac/src/test/java/org/codehaus/plexus/compiler/javac/ErrorMessageParserTest.java
index 5ab3c822..ad707f4e 100644
--- a/plexus-compilers/plexus-compiler-javac/src/test/java/org/codehaus/plexus/compiler/javac/ErrorMessageParserTest.java
+++ b/plexus-compilers/plexus-compiler-javac/src/test/java/org/codehaus/plexus/compiler/javac/ErrorMessageParserTest.java
@@ -37,8 +37,8 @@
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
-import static org.hamcrest.CoreMatchers.endsWith;
-import static org.hamcrest.CoreMatchers.startsWith;
+import static org.codehaus.plexus.compiler.javac.JavacCompiler.Messages.*;
+import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
@@ -50,6 +50,8 @@
*/
public class ErrorMessageParserTest {
private static final String EOL = System.getProperty("line.separator");
+ private static final String UNIDENTIFIED_LOG_LINES =
+ "These log lines should be cut off\n" + "when preceding known error message headers\n";
@Test
public void testDeprecationMessage() throws Exception {
@@ -751,8 +753,7 @@ public void testJava7Error() throws Exception {
assertThat(
message1.getMessage(),
- is("error: cannot find symbol" + EOL + " symbol: class Properties" + EOL
- + " location: class Error"));
+ is("cannot find symbol" + EOL + " symbol: class Properties" + EOL + " location: class Error"));
assertThat(message1.getStartColumn(), is(16));
@@ -768,8 +769,7 @@ public void testJava7Error() throws Exception {
assertThat(
message2.getMessage(),
- is("error: cannot find symbol" + EOL + " symbol: class Properties" + EOL
- + " location: class Error"));
+ is("cannot find symbol" + EOL + " symbol: class Properties" + EOL + " location: class Error"));
assertThat(message2.getStartColumn(), is(35));
@@ -780,10 +780,45 @@ public void testJava7Error() throws Exception {
assertThat(message2.getEndLine(), is(3));
}
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("testStackTraceWithUnknownHeader_args")
+ public void testStackTraceWithUnknownHeader(String scenario, String stackTraceHeader) throws Exception {
+ String stackTraceWithHeader = UNIDENTIFIED_LOG_LINES + stackTraceHeader + stackTraceInternalCompilerError;
+
+ List compilerMessages =
+ JavacCompiler.parseModernStream(4, new BufferedReader(new StringReader(stackTraceWithHeader)));
+
+ assertThat(compilerMessages, notNullValue());
+ assertThat(compilerMessages, hasSize(1));
+
+ String message = compilerMessages.get(0).getMessage().replaceAll(EOL, "\n");
+ // Parser retains neither unidentified log lines nor slightly modified stack trace header
+ assertThat(message, not(containsString(UNIDENTIFIED_LOG_LINES)));
+ assertThat(message, not(containsString(stackTraceHeader)));
+ // Parser returns stack strace without any preceding lines
+ assertThat(message, startsWith(stackTraceInternalCompilerError));
+ }
+
+ private static Stream testStackTraceWithUnknownHeader_args() {
+ return Stream.of(
+ Arguments.of(
+ "modified compiler error header",
+ FILE_A_BUG_ERROR_HEADERS[0].replaceAll("\\{0\\}", "21").replaceAll("bug", "beetle")),
+ Arguments.of(
+ "modified annotation processor error header",
+ ANNOTATION_PROCESSING_ERROR_HEADERS[0].replaceAll("uncaught", "undandled")),
+ Arguments.of(
+ "modified out of resources error header",
+ SYSTEM_OUT_OF_RESOURCES_ERROR_HEADERS[0].replaceAll("resources", "memory")),
+ Arguments.of("modified I/O error header", IO_ERROR_HEADERS[0].replaceAll("input/output", "I/O")),
+ Arguments.of(
+ "modified plugin error header", PLUGIN_ERROR_HEADERS[0].replaceAll("uncaught", "unhandled")));
+ }
+
@ParameterizedTest(name = "{0}")
@MethodSource("testBugParade_args")
public void testBugParade(String jdkAndLocale, String stackTraceHeader) throws Exception {
- String stackTraceWithHeader = stackTraceHeader + stackTraceInternalCompilerError;
+ String stackTraceWithHeader = UNIDENTIFIED_LOG_LINES + stackTraceHeader + stackTraceInternalCompilerError;
List compilerMessages =
JavacCompiler.parseModernStream(4, new BufferedReader(new StringReader(stackTraceWithHeader)));
@@ -798,7 +833,8 @@ public void testBugParade(String jdkAndLocale, String stackTraceHeader) throws E
}
private static final String stackTraceInternalCompilerError =
- "\tat com.sun.tools.javac.comp.MemberEnter.baseEnv(MemberEnter.java:1388)\n"
+ "com.sun.tools.javac.code.Symbol$CompletionFailure: class file for java.util.Optional not found\n"
+ + "\tat com.sun.tools.javac.comp.MemberEnter.baseEnv(MemberEnter.java:1388)\n"
+ "\tat com.sun.tools.javac.comp.MemberEnter.complete(MemberEnter.java:1046)\n"
+ "\tat com.sun.tools.javac.code.Symbol.complete(Symbol.java:574)\n"
+ "\tat com.sun.tools.javac.code.Symbol$ClassSymbol.complete(Symbol.java:1037)\n"
@@ -832,27 +868,153 @@ public void testBugParade(String jdkAndLocale, String stackTraceHeader) throws E
private static Stream testBugParade_args() {
return Stream.of(
- Arguments.of(
- "JDK 8 English",
- "An exception has occurred in the compiler ({0}). Please file a bug at the Java Developer Connection (http://java.sun.com/webapps/bugreport) after checking the Bug Parade for duplicates. Include your program and the following diagnostic in your report. Thank you.\n"),
- Arguments.of(
- "JDK 8 Japanese",
- "コンパイラで例外が発生しました({0})。Bug Paradeで重複がないかをご確認のうえ、Java Developer Connection (http://java.sun.com/webapps/bugreport)でbugの登録をお願いいたします。レポートには、そのプログラムと下記の診断内容を含めてください。ご協力ありがとうございます。\n"),
- Arguments.of(
- "JDK 8 Chinese",
- "编译器 ({0}) 中出现异常错误。 如果在 Bug Parade 中没有找到该错误, 请在 Java Developer Connection (http://java.sun.com/webapps/bugreport) 中建立 Bug。请在报告中附上您的程序和以下诊断信息。谢谢。\n"),
- Arguments.of(
- "JDK 21 English",
- "An exception has occurred in the compiler ({0}). Please file a bug against the Java compiler via the Java bug reporting page (https://bugreport.java.com) after checking the Bug Database (https://bugs.java.com) for duplicates. Include your program, the following diagnostic, and the parameters passed to the Java compiler in your report. Thank you.\n"),
- Arguments.of(
- "JDK 21 Japanese",
- "コンパイラで例外が発生しました({0})。バグ・データベース(https://bugs.java.com)で重複がないかをご確認のうえ、Javaのバグ・レポート・ページ(https://bugreport.java.com)から、Javaコンパイラに対するバグの登録をお願いいたします。レポートには、該当のプログラム、次の診断内容、およびJavaコンパイラに渡されたパラメータをご入力ください。ご協力ありがとうございます。\n"),
- Arguments.of(
- "JDK 21 Chinese",
- "编译器 ({0}) 中出现异常错误。如果在 Bug Database (https://bugs.java.com) 中没有找到有关该错误的 Java 编译器 Bug,请通过 Java Bug 报告页 (https://bugreport.java.com) 提交 Java 编译器 Bug。请在报告中附上您的程序、以下诊断信息以及传递到 Java 编译器的参数。谢谢。\n"),
- Arguments.of(
- "JDK 21 German",
- "Im Compiler ({0}) ist eine Ausnahme aufgetreten. Erstellen Sie auf der Java-Seite zum Melden von Bugs (https://bugreport.java.com) einen Bugbericht, nachdem Sie die Bugdatenbank (https://bugs.java.com) auf Duplikate geprüft haben. Geben Sie in Ihrem Bericht Ihr Programm, die folgende Diagnose und die Parameter an, die Sie dem Java-Compiler übergeben haben. Vielen Dank.\n"));
+ Arguments.of("JDK 8 English", FILE_A_BUG_ERROR_HEADERS[0].replaceFirst("\\{0\\}", "21")),
+ Arguments.of("JDK 8 Japanese", FILE_A_BUG_ERROR_HEADERS[1].replaceFirst("\\{0\\}", "21")),
+ Arguments.of("JDK 8 Chinese", FILE_A_BUG_ERROR_HEADERS[2].replaceFirst("\\{0\\}", "21")),
+ Arguments.of("JDK 9 English", FILE_A_BUG_ERROR_HEADERS[3].replaceFirst("\\{0\\}", "21")),
+ Arguments.of("JDK 9 Japanese", FILE_A_BUG_ERROR_HEADERS[4].replaceFirst("\\{0\\}", "21")),
+ Arguments.of("JDK 9 Chinese", FILE_A_BUG_ERROR_HEADERS[5].replaceFirst("\\{0\\}", "21")),
+ Arguments.of("JDK 21 English", FILE_A_BUG_ERROR_HEADERS[6].replaceFirst("\\{0\\}", "21")),
+ Arguments.of("JDK 21 Japanese", FILE_A_BUG_ERROR_HEADERS[7].replaceFirst("\\{0\\}", "21")),
+ Arguments.of("JDK 21 Chinese", FILE_A_BUG_ERROR_HEADERS[8].replaceFirst("\\{0\\}", "21")),
+ Arguments.of("JDK 21 German", FILE_A_BUG_ERROR_HEADERS[9].replaceFirst("\\{0\\}", "21")));
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("testSystemOutOfResourcesError_args")
+ public void testSystemOutOfResourcesError(String jdkAndLocale, String stackTraceHeader) throws Exception {
+ String stackTraceWithHeader = UNIDENTIFIED_LOG_LINES + stackTraceHeader + stackTraceSystemOutOfResourcesError;
+
+ List compilerMessages =
+ JavacCompiler.parseModernStream(4, new BufferedReader(new StringReader(stackTraceWithHeader)));
+
+ assertThat(compilerMessages, notNullValue());
+ assertThat(compilerMessages, hasSize(1));
+
+ String message = compilerMessages.get(0).getMessage().replaceAll(EOL, "\n");
+ // Parser retains stack trace header
+ assertThat(message, startsWith(stackTraceHeader));
+ assertThat(message, endsWith(stackTraceSystemOutOfResourcesError));
+ }
+
+ private static final String stackTraceSystemOutOfResourcesError =
+ "java.lang.OutOfMemoryError: GC overhead limit exceeded\n"
+ + "\tat com.sun.tools.javac.util.List.of(List.java:135)\n"
+ + "\tat com.sun.tools.javac.util.ListBuffer.append(ListBuffer.java:129)\n"
+ + "\tat com.sun.tools.javac.parser.JavacParser.variableDeclaratorsRest(JavacParser.java:3006)\n"
+ + "\tat com.sun.tools.javac.parser.JavacParser.classOrInterfaceBodyDeclaration(JavacParser.java:3537)\n"
+ + "\tat com.sun.tools.javac.parser.JavacParser.classOrInterfaceBody(JavacParser.java:3436)\n"
+ + "\tat com.sun.tools.javac.parser.JavacParser.classDeclaration(JavacParser.java:3285)\n"
+ + "\tat com.sun.tools.javac.parser.JavacParser.classOrInterfaceOrEnumDeclaration(JavacParser.java:3226)\n"
+ + "\tat com.sun.tools.javac.parser.JavacParser.typeDeclaration(JavacParser.java:3215)\n"
+ + "\tat com.sun.tools.javac.parser.JavacParser.parseCompilationUnit(JavacParser.java:3155)\n"
+ + "\tat com.sun.tools.javac.main.JavaCompiler.parse(JavaCompiler.java:628)\n"
+ + "\tat com.sun.tools.javac.main.JavaCompiler.parse(JavaCompiler.java:665)\n"
+ + "\tat com.sun.tools.javac.main.JavaCompiler.parseFiles(JavaCompiler.java:950)\n"
+ + "\tat com.sun.tools.javac.main.JavaCompiler.compile(JavaCompiler.java:857)\n"
+ + "\tat com.sun.tools.javac.main.Main.compile(Main.java:523)\n"
+ + "\tat com.sun.tools.javac.main.Main.compile(Main.java:381)\n"
+ + "\tat com.sun.tools.javac.main.Main.compile(Main.java:370)\n"
+ + "\tat com.sun.tools.javac.main.Main.compile(Main.java:361)\n"
+ + "\tat com.sun.tools.javac.Main.compile(Main.java:56)\n"
+ + "\tat com.sun.tools.javac.Main.main(Main.java:42)\n";
+
+ private static Stream testSystemOutOfResourcesError_args() {
+ return Stream.of(
+ Arguments.of("JDK 8 English", SYSTEM_OUT_OF_RESOURCES_ERROR_HEADERS[0]),
+ Arguments.of("JDK 8 Japanese", SYSTEM_OUT_OF_RESOURCES_ERROR_HEADERS[1]),
+ Arguments.of("JDK 8 Chinese", SYSTEM_OUT_OF_RESOURCES_ERROR_HEADERS[2]),
+ Arguments.of("JDK 21 English", SYSTEM_OUT_OF_RESOURCES_ERROR_HEADERS[3]),
+ Arguments.of("JDK 21 Japanese", SYSTEM_OUT_OF_RESOURCES_ERROR_HEADERS[4]),
+ Arguments.of("JDK 21 Chinese", SYSTEM_OUT_OF_RESOURCES_ERROR_HEADERS[5]),
+ Arguments.of("JDK 21 German", SYSTEM_OUT_OF_RESOURCES_ERROR_HEADERS[6]));
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("testIOError_args")
+ public void testIOError(String jdkAndLocale, String stackTraceHeader) throws Exception {
+ String stackTraceWithHeader = UNIDENTIFIED_LOG_LINES + stackTraceHeader + stackTraceIOError;
+
+ List compilerMessages =
+ JavacCompiler.parseModernStream(4, new BufferedReader(new StringReader(stackTraceWithHeader)));
+
+ assertThat(compilerMessages, notNullValue());
+ assertThat(compilerMessages, hasSize(1));
+
+ String message = compilerMessages.get(0).getMessage().replaceAll(EOL, "\n");
+ // Parser retains stack trace header
+ assertThat(message, startsWith(stackTraceHeader));
+ assertThat(message, endsWith(stackTraceIOError));
+ }
+
+ private static final String stackTraceIOError =
+ "An input/output error occurred.\n" + "Consult the following stack trace for details.\n"
+ + "java.nio.charset.MalformedInputException: Input length = 1\n"
+ + "\tat java.base/java.nio.charset.CoderResult.throwException(CoderResult.java:274)\n"
+ + "\tat java.base/sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:339)\n"
+ + "\tat java.base/sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)\n"
+ + "\tat java.base/java.io.InputStreamReader.read(InputStreamReader.java:185)\n"
+ + "\tat java.base/java.io.BufferedReader.fill(BufferedReader.java:161)\n"
+ + "\tat java.base/java.io.BufferedReader.read(BufferedReader.java:182)\n"
+ + "\tat jdk.compiler/com.sun.tools.javac.main.CommandLine$Tokenizer.(CommandLine.java:143)\n"
+ + "\tat jdk.compiler/com.sun.tools.javac.main.CommandLine.loadCmdFile(CommandLine.java:129)\n"
+ + "\tat jdk.compiler/com.sun.tools.javac.main.CommandLine.appendParsedCommandArgs(CommandLine.java:71)\n"
+ + "\tat jdk.compiler/com.sun.tools.javac.main.CommandLine.parse(CommandLine.java:102)\n"
+ + "\tat jdk.compiler/com.sun.tools.javac.main.CommandLine.parse(CommandLine.java:123)\n"
+ + "\tat jdk.compiler/com.sun.tools.javac.main.Main.compile(Main.java:215)\n"
+ + "\tat jdk.compiler/com.sun.tools.javac.main.Main.compile(Main.java:170)\n"
+ + "\tat jdk.compiler/com.sun.tools.javac.Main.compile(Main.java:57)\n"
+ + "\tat jdk.compiler/com.sun.tools.javac.Main.main(Main.java:43)\n";
+
+ private static Stream testIOError_args() {
+ return Stream.of(
+ Arguments.of("JDK 8 English", IO_ERROR_HEADERS[0]),
+ Arguments.of("JDK 8 Japanese", IO_ERROR_HEADERS[1]),
+ Arguments.of("JDK 8 Chinese", IO_ERROR_HEADERS[2]),
+ Arguments.of("JDK 21 English", IO_ERROR_HEADERS[3]),
+ Arguments.of("JDK 21 Japanese", IO_ERROR_HEADERS[4]),
+ Arguments.of("JDK 21 Chinese", IO_ERROR_HEADERS[5]),
+ Arguments.of("JDK 21 German", IO_ERROR_HEADERS[6]));
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("testPluginError_args")
+ public void testPluginError(String jdkAndLocale, String stackTraceHeader) throws Exception {
+ String stackTraceWithHeader = UNIDENTIFIED_LOG_LINES + stackTraceHeader + stackTracePluginError;
+
+ List compilerMessages =
+ JavacCompiler.parseModernStream(4, new BufferedReader(new StringReader(stackTraceWithHeader)));
+
+ assertThat(compilerMessages, notNullValue());
+ assertThat(compilerMessages, hasSize(1));
+
+ String message = compilerMessages.get(0).getMessage().replaceAll(EOL, "\n");
+ // Parser retains stack trace header
+ assertThat(message, startsWith(stackTraceHeader));
+ assertThat(message, endsWith(stackTracePluginError));
+ }
+
+ private static final String stackTracePluginError =
+ "A plugin threw an uncaught exception.\n" + "Consult the following stack trace for details.\n"
+ + "java.lang.NoSuchMethodError: com.sun.tools.javac.util.JavacMessages.add(Lcom/sun/tools/javac/util/JavacMessages$ResourceBundleHelper;)V\n"
+ + "\tat com.google.errorprone.BaseErrorProneJavaCompiler.setupMessageBundle(BaseErrorProneJavaCompiler.java:202)\n"
+ + "\tat com.google.errorprone.ErrorProneJavacPlugin.init(ErrorProneJavacPlugin.java:40)\n"
+ + "\tat com.sun.tools.javac.main.Main.compile(Main.java:470)\n"
+ + "\tat com.sun.tools.javac.main.Main.compile(Main.java:381)\n"
+ + "\tat com.sun.tools.javac.main.Main.compile(Main.java:370)\n"
+ + "\tat com.sun.tools.javac.main.Main.compile(Main.java:361)\n"
+ + "\tat com.sun.tools.javac.Main.compile(Main.java:56)\n"
+ + "\tat com.sun.tools.javac.Main.main(Main.java:42)\n";
+
+ private static Stream testPluginError_args() {
+ return Stream.of(
+ Arguments.of("JDK 8 English", PLUGIN_ERROR_HEADERS[0]),
+ Arguments.of("JDK 8 Japanese", PLUGIN_ERROR_HEADERS[1]),
+ Arguments.of("JDK 8 Chinese", PLUGIN_ERROR_HEADERS[2]),
+ Arguments.of("JDK 21 English", PLUGIN_ERROR_HEADERS[3]),
+ Arguments.of("JDK 21 Japanese", PLUGIN_ERROR_HEADERS[4]),
+ Arguments.of("JDK 21 Chinese", PLUGIN_ERROR_HEADERS[5]),
+ Arguments.of("JDK 21 German", PLUGIN_ERROR_HEADERS[6]));
}
@Test
@@ -1011,28 +1173,30 @@ public void testIssue37() throws IOException {
@Test
public void testJvmBootLayerInitializationError() throws Exception {
- String out = "Error occurred during initialization of boot layer" + EOL
+ String out = "Error occurred during initialization of boot layer\n"
+ "java.lang.module.FindException: Module java.xml.bind not found";
List compilerErrors =
- JavacCompiler.parseModernStream(1, new BufferedReader(new StringReader(out)));
+ JavacCompiler.parseModernStream(1, new BufferedReader(new StringReader(UNIDENTIFIED_LOG_LINES + out)));
assertThat(compilerErrors, notNullValue());
assertThat(compilerErrors.size(), is(1));
assertThat(compilerErrors.get(0).getKind(), is(CompilerMessage.Kind.ERROR));
+ assertThat(compilerErrors.get(0).getMessage().replaceAll(EOL, "\n"), startsWith(out));
}
@Test
public void testJvmInitializationError() throws Exception {
- String out = "Error occurred during initialization of VM" + EOL
+ String out = "Error occurred during initialization of VM\n"
+ "Initial heap size set to a larger value than the maximum heap size";
List compilerErrors =
- JavacCompiler.parseModernStream(1, new BufferedReader(new StringReader(out)));
+ JavacCompiler.parseModernStream(1, new BufferedReader(new StringReader(UNIDENTIFIED_LOG_LINES + out)));
assertThat(compilerErrors, notNullValue());
assertThat(compilerErrors.size(), is(1));
assertThat(compilerErrors.get(0).getKind(), is(CompilerMessage.Kind.ERROR));
+ assertThat(compilerErrors.get(0).getMessage().replaceAll(EOL, "\n"), startsWith(out));
}
@Test
@@ -1093,7 +1257,7 @@ public void testWarningFollowedByBadSourceFileError() throws Exception {
private void validateBadSourceFile(CompilerMessage message) {
assertThat("Is an Error", message.getKind(), is(CompilerMessage.Kind.ERROR));
assertThat("On Correct File", message.getFile(), is("/MTOOLCHAINS-19/src/main/java/ch/pecunifex/x/Cls1.java"));
- assertThat("Message starts with access Error", message.getMessage(), startsWith("error: cannot access Cls2"));
+ assertThat("Message starts with access Error", message.getMessage(), startsWith("cannot access Cls2"));
}
private static void assertEquivalent(CompilerMessage expected, CompilerMessage actual) {
diff --git a/plexus-compilers/plexus-compiler-javac/src/test/java/org/codehaus/plexus/compiler/javac/JavacCompilerTest.java b/plexus-compilers/plexus-compiler-javac/src/test/java/org/codehaus/plexus/compiler/javac/JavacCompilerTest.java
index 8a182fcf..175905cd 100644
--- a/plexus-compilers/plexus-compiler-javac/src/test/java/org/codehaus/plexus/compiler/javac/JavacCompilerTest.java
+++ b/plexus-compilers/plexus-compiler-javac/src/test/java/org/codehaus/plexus/compiler/javac/JavacCompilerTest.java
@@ -12,8 +12,8 @@
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
+import static org.codehaus.plexus.compiler.javac.JavacCompiler.Messages.*;
import static org.hamcrest.CoreMatchers.endsWith;
-import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
@@ -44,6 +44,8 @@
*/
public class JavacCompilerTest extends AbstractJavacCompilerTest {
private static final String EOL = System.getProperty("line.separator");
+ private static final String UNIDENTIFIABLE_LOG_LINES =
+ "These log lines should be cut off\n" + "when preceding known error message headers\n";
@BeforeEach
public void setUp() {
@@ -55,7 +57,7 @@ public void setUp() {
@MethodSource("testParseModernStream_withAnnotationProcessingErrors_args")
void testParseModernStream_withAnnotationProcessingErrors(String jdkAndLocale, String stackTraceHeader)
throws IOException {
- String stackTraceWithHeader = stackTraceHeader + stackTraceAnnotationProcessingError;
+ String stackTraceWithHeader = UNIDENTIFIABLE_LOG_LINES + stackTraceHeader + stackTraceAnnotationProcessingError;
List compilerMessages =
JavacCompiler.parseModernStream(1, new BufferedReader(new StringReader(stackTraceWithHeader)));
@@ -63,8 +65,8 @@ void testParseModernStream_withAnnotationProcessingErrors(String jdkAndLocale, S
assertThat(compilerMessages, hasSize(1));
String message = compilerMessages.get(0).getMessage().replaceAll(EOL, "\n");
- // Parser does not retain stack trace header, because it is hard to identify in a locale-independent way
- assertThat(message, not(startsWith(stackTraceHeader)));
+ // Parser retains stack trace header
+ assertThat(message, startsWith(stackTraceHeader));
assertThat(message, endsWith(stackTraceAnnotationProcessingError));
}
@@ -89,18 +91,12 @@ void testParseModernStream_withAnnotationProcessingErrors(String jdkAndLocale, S
private static Stream testParseModernStream_withAnnotationProcessingErrors_args() {
return Stream.of(
- Arguments.of(
- "JDK 8 English",
- "\n\nAn annotation processor threw an uncaught exception.\nConsult the following stack trace for details.\n\n"),
- Arguments.of("JDK 8 Japanese", "\n\n注釈処理で捕捉されない例外がスローされました。\n詳細は次のスタック・トレースで調査してください。\n\n"),
- Arguments.of("JDK 8 Chinese", "\n\n注释处理程序抛出未捕获的异常错误。\n有关详细信息, 请参阅以下堆栈跟踪。\n\n"),
- Arguments.of(
- "JDK 21 English",
- "\n\nAn annotation processor threw an uncaught exception.\nConsult the following stack trace for details.\n\n"),
- Arguments.of("JDK 21 Japanese", "\n\n注釈処理で捕捉されない例外がスローされました。\n詳細は次のスタックトレースで調査してください。\n\n"),
- Arguments.of("JDK 21 Chinese", "\n\n批注处理程序抛出未捕获的异常错误。\n有关详细信息, 请参阅以下堆栈跟踪。\n\n"),
- Arguments.of(
- "JDK 21 German",
- "\n\nEin Annotationsprozessor hat eine nicht abgefangene Ausnahme ausgelöst.\nDetails finden Sie im folgenden Stacktrace.\n\n"));
+ Arguments.of("JDK 8 English", ANNOTATION_PROCESSING_ERROR_HEADERS[0]),
+ Arguments.of("JDK 8 Japanese", ANNOTATION_PROCESSING_ERROR_HEADERS[1]),
+ Arguments.of("JDK 8 Chinese", ANNOTATION_PROCESSING_ERROR_HEADERS[2]),
+ Arguments.of("JDK 21 English", ANNOTATION_PROCESSING_ERROR_HEADERS[3]),
+ Arguments.of("JDK 21 Japanese", ANNOTATION_PROCESSING_ERROR_HEADERS[4]),
+ Arguments.of("JDK 21 Chinese", ANNOTATION_PROCESSING_ERROR_HEADERS[5]),
+ Arguments.of("JDK 21 German", ANNOTATION_PROCESSING_ERROR_HEADERS[6]));
}
}