1 package org.codehaus.plexus.compiler.javac;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43 import javax.inject.Inject;
44 import javax.inject.Named;
45 import javax.inject.Singleton;
46
47 import java.io.BufferedReader;
48 import java.io.File;
49 import java.io.FileWriter;
50 import java.io.IOException;
51 import java.io.PrintWriter;
52 import java.io.StringReader;
53 import java.io.StringWriter;
54 import java.lang.reflect.InvocationTargetException;
55 import java.lang.reflect.Method;
56 import java.net.MalformedURLException;
57 import java.net.URL;
58 import java.net.URLClassLoader;
59 import java.util.ArrayList;
60 import java.util.Arrays;
61 import java.util.Deque;
62 import java.util.HashSet;
63 import java.util.List;
64 import java.util.Map;
65 import java.util.NoSuchElementException;
66 import java.util.Objects;
67 import java.util.Properties;
68 import java.util.Set;
69 import java.util.StringTokenizer;
70 import java.util.concurrent.ConcurrentHashMap;
71 import java.util.concurrent.ConcurrentLinkedDeque;
72 import java.util.regex.Matcher;
73 import java.util.regex.Pattern;
74
75 import org.codehaus.plexus.compiler.AbstractCompiler;
76 import org.codehaus.plexus.compiler.CompilerConfiguration;
77 import org.codehaus.plexus.compiler.CompilerException;
78 import org.codehaus.plexus.compiler.CompilerMessage;
79 import org.codehaus.plexus.compiler.CompilerOutputStyle;
80 import org.codehaus.plexus.compiler.CompilerResult;
81 import org.codehaus.plexus.util.FileUtils;
82 import org.codehaus.plexus.util.Os;
83 import org.codehaus.plexus.util.StringUtils;
84 import org.codehaus.plexus.util.cli.CommandLineException;
85 import org.codehaus.plexus.util.cli.CommandLineUtils;
86 import org.codehaus.plexus.util.cli.Commandline;
87
88 import static org.codehaus.plexus.compiler.CompilerMessage.Kind.*;
89 import static org.codehaus.plexus.compiler.javac.JavacCompiler.Messages.*;
90
91
92
93
94
95
96
97
98
99 @Named("javac")
100 @Singleton
101 public class JavacCompiler extends AbstractCompiler {
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124 protected static class Messages {
125
126 protected static final String[] ERROR_PREFIXES = {"error: ", "エラー: ", "错误: ", "Fehler: "};
127
128
129 protected static final String[] WARNING_PREFIXES = {"warning: ", "警告: ", "警告: ", "Warnung: "};
130
131
132 protected static final String[] NOTE_PREFIXES = {"Note: ", "ノート: ", "注: ", "Hinweis: "};
133
134
135 protected static final String[] MISC_PREFIXES = {"["};
136
137
138
139
140 protected static final String[] JAVAC_GENERIC_ERROR_PREFIXES = {"javac:"};
141
142
143
144 protected static final String[] VM_INIT_ERROR_HEADERS = {"Error occurred during initialization of VM"};
145
146
147 protected static final String[] BOOT_LAYER_INIT_ERROR_HEADERS = {
148 "Error occurred during initialization of boot layer"
149 };
150
151
152
153 protected static final String[] ANNOTATION_PROCESSING_ERROR_HEADERS = {
154 "\n\nAn annotation processor threw an uncaught exception.\nConsult the following stack trace for details.\n\n",
155 "\n\n注釈処理で捕捉されない例外がスローされました。\n詳細は次のスタック・トレースで調査してください。\n\n",
156 "\n\n批注处理程序抛出未捕获的异常错误。\n有关详细信息, 请参阅以下堆栈跟踪。\n\n",
157 "\n\nAn annotation processor threw an uncaught exception.\nConsult the following stack trace for details.\n\n",
158 "\n\n注釈処理で捕捉されない例外がスローされました。\n詳細は次のスタックトレースで調査してください。\n\n",
159 "\n\n批注处理程序抛出未捕获的异常错误。\n有关详细信息, 请参阅以下堆栈跟踪。\n\n",
160 "\n\nEin Annotationsprozessor hat eine nicht abgefangene Ausnahme ausgelöst.\nDetails finden Sie im folgenden Stacktrace.\n\n"
161 };
162
163
164
165
166 protected static final String[] FILE_A_BUG_ERROR_HEADERS = {
167 "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",
168 "コンパイラで例外が発生しました({0})。Bug Paradeで重複がないかをご確認のうえ、Java Developer Connection (http://java.sun.com/webapps/bugreport)でbugの登録をお願いいたします。レポートには、そのプログラムと下記の診断内容を含めてください。ご協力ありがとうございます。\n",
169 "编译器 ({0}) 中出现异常错误。 如果在 Bug Parade 中没有找到该错误, 请在 Java Developer Connection (http://java.sun.com/webapps/bugreport) 中建立 Bug。请在报告中附上您的程序和以下诊断信息。谢谢。\n",
170 "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.",
171 "コンパイラで例外が発生しました({0})。Bug Database (http://bugs.java.com)で重複がないかをご確認のうえ、Java bugレポート・ページ(http://bugreport.java.com)でJavaコンパイラに対するbugの登録をお願いいたします。レポートには、そのプログラムと下記の診断内容を含めてください。ご協力ありがとうございます。",
172 "编译器 ({0}) 中出现异常错误。如果在 Bug Database (http://bugs.java.com) 中没有找到该错误, 请通过 Java Bug 报告页 (http://bugreport.java.com) 建立该 Java 编译器 Bug。请在报告中附上您的程序和以下诊断信息。谢谢。",
173 "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",
174 "コンパイラで例外が発生しました({0})。バグ・データベース(https://bugs.java.com)で重複がないかをご確認のうえ、Javaのバグ・レポート・ページ(https://bugreport.java.com)から、Javaコンパイラに対するバグの登録をお願いいたします。レポートには、該当のプログラム、次の診断内容、およびJavaコンパイラに渡されたパラメータをご入力ください。ご協力ありがとうございます。\n",
175 "编译器 ({0}) 中出现异常错误。如果在 Bug Database (https://bugs.java.com) 中没有找到有关该错误的 Java 编译器 Bug,请通过 Java Bug 报告页 (https://bugreport.java.com) 提交 Java 编译器 Bug。请在报告中附上您的程序、以下诊断信息以及传递到 Java 编译器的参数。谢谢。\n",
176 "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"
177 };
178
179
180
181 protected static final String[] SYSTEM_OUT_OF_RESOURCES_ERROR_HEADERS = {
182 "\n\nThe system is out of resources.\nConsult the following stack trace for details.\n",
183 "\n\nシステム・リソースが不足しています。\n詳細は次のスタック・トレースで調査してください。\n",
184 "\n\n系统资源不足。\n有关详细信息, 请参阅以下堆栈跟踪。\n",
185 "\n\nThe system is out of resources.\nConsult the following stack trace for details.\n",
186 "\n\nシステム・リソースが不足しています。\n詳細は次のスタックトレースで調査してください。\n",
187 "\n\n系统资源不足。\n有关详细信息, 请参阅以下堆栈跟踪。\n",
188 "\n\nDas System hat keine Ressourcen mehr.\nDetails finden Sie im folgenden Stacktrace.\n"
189 };
190
191
192
193 protected static final String[] IO_ERROR_HEADERS = {
194 "\n\nAn input/output error occurred.\nConsult the following stack trace for details.\n",
195 "\n\n入出力エラーが発生しました。\n詳細は次のスタック・トレースで調査してください。\n",
196 "\n\n发生输入/输出错误。\n有关详细信息, 请参阅以下堆栈跟踪。\n",
197 "\n\nAn input/output error occurred.\nConsult the following stack trace for details.\n",
198 "\n\n入出力エラーが発生しました。\n詳細は次のスタックトレースで調査してください。\n",
199 "\n\n发生输入/输出错误。\n有关详细信息, 请参阅以下堆栈跟踪。\n",
200 "\n\nEin Eingabe-/Ausgabefehler ist aufgetreten.\nDetails finden Sie im folgenden Stacktrace.\n"
201 };
202
203
204
205 protected static final String[] PLUGIN_ERROR_HEADERS = {
206 "\n\nA plugin threw an uncaught exception.\nConsult the following stack trace for details.\n",
207 "\n\nプラグインで捕捉されない例外がスローされました。\n詳細は次のスタック・トレースで調査してください。\n",
208 "\n\n插件抛出未捕获的异常错误。\n有关详细信息, 请参阅以下堆栈跟踪。\n",
209 "\n\nA plugin threw an uncaught exception.\nConsult the following stack trace for details.\n",
210 "\n\nプラグインで捕捉されない例外がスローされました。\n詳細は次のスタック・トレースで調査してください。\n",
211 "\n\n插件抛出未捕获的异常错误。\n有关详细信息, 请参阅以下堆栈跟踪。\n",
212 "\n\nEin Plug-in hat eine nicht abgefangene Ausnahme ausgel\u00F6st.\nDetails finden Sie im folgenden Stacktrace.\n"
213 };
214 }
215
216 private static final Object LOCK = new Object();
217 private static final String JAVAC_CLASSNAME = "com.sun.tools.javac.Main";
218
219 private volatile Class<?> javacClass;
220 private final Deque<Class<?>> javacClasses = new ConcurrentLinkedDeque<>();
221
222 private static final Pattern JAVA_MAJOR_AND_MINOR_VERSION_PATTERN = Pattern.compile("\\d+(\\.\\d+)?");
223
224
225 private static final Map<String, String> VERSION_PER_EXECUTABLE = new ConcurrentHashMap<>();
226
227 @Inject
228 private InProcessCompiler inProcessCompiler;
229
230
231
232
233
234 public JavacCompiler() {
235 super(CompilerOutputStyle.ONE_OUTPUT_FILE_PER_INPUT_FILE, ".java", ".class", null);
236 }
237
238
239
240
241
242 @Override
243 public String getCompilerId() {
244 return "javac";
245 }
246
247 private String getInProcessJavacVersion() throws CompilerException {
248 return System.getProperty("java.version");
249 }
250
251 private String getOutOfProcessJavacVersion(String executable) throws CompilerException {
252 String version = VERSION_PER_EXECUTABLE.get(executable);
253 if (version == null) {
254 Commandline cli = new Commandline();
255 cli.setExecutable(executable);
256
257
258
259
260 cli.addArguments(new String[] {"-version"});
261 List<String> out = new ArrayList<>();
262 List<String> err = new ArrayList<>();
263 try {
264 int exitCode = CommandLineUtils.executeCommandLine(cli, out::add, err::add);
265 if (exitCode != 0) {
266 throw new CompilerException("Could not retrieve version from " + executable + ". Exit code "
267 + exitCode + ", Output: " + String.join(System.lineSeparator(), out) + ", Error: "
268 + String.join(System.lineSeparator(), err));
269 }
270 } catch (CommandLineException e) {
271 throw new CompilerException("Error while executing the external compiler " + executable, e);
272 }
273 version = tryParseVersion(out);
274 if (version == null) {
275 version = tryParseVersion(err);
276 }
277 VERSION_PER_EXECUTABLE.put(executable, version);
278 }
279 return version;
280 }
281
282 static String extractMajorAndMinorVersion(String text) {
283 Matcher matcher = JAVA_MAJOR_AND_MINOR_VERSION_PATTERN.matcher(text);
284 if (!matcher.find()) {
285 throw new IllegalArgumentException("Could not extract version from \"" + text + "\"");
286 }
287 return matcher.group();
288 }
289
290 private String tryParseVersion(List<String> versions) {
291 for (String version : versions) {
292 if (version.startsWith("javac ")) {
293 version = version.substring(6);
294 if (version.startsWith("1.")) {
295 version = version.substring(0, 3);
296 } else {
297 version = version.substring(0, 2);
298 }
299 return version;
300 }
301 }
302 return null;
303 }
304
305 @Override
306 public CompilerResult performCompile(CompilerConfiguration config) throws CompilerException {
307 File destinationDir = new File(config.getOutputLocation());
308 if (!destinationDir.exists()) {
309 destinationDir.mkdirs();
310 }
311
312 String[] sourceFiles = getSourceFiles(config);
313 if ((sourceFiles == null) || (sourceFiles.length == 0)) {
314 return new CompilerResult();
315 }
316
317 logCompiling(sourceFiles, config);
318
319 final String javacVersion;
320 final String executable;
321 if (config.isFork()) {
322 executable = getJavacExecutable(config);
323 javacVersion = getOutOfProcessJavacVersion(executable);
324 } else {
325 javacVersion = getInProcessJavacVersion();
326 executable = null;
327 }
328
329 String[] args = buildCompilerArguments(config, sourceFiles, javacVersion);
330 CompilerResult result;
331
332 if (config.isFork()) {
333 result = compileOutOfProcess(config, executable, args);
334 } else {
335 if (hasJavaxToolProvider() && !config.isForceJavacCompilerUse()) {
336
337 result = inProcessCompiler().compileInProcess(args, config, sourceFiles);
338 } else {
339 result = compileInProcess(args, config);
340 }
341 }
342
343 return result;
344 }
345
346 protected InProcessCompiler inProcessCompiler() {
347 return inProcessCompiler;
348 }
349
350
351
352
353
354
355
356
357
358 static String toReleaseArgument(String release) {
359 if (release.length() == 3 && release.startsWith("1.") && release.charAt(2) >= '1' && release.charAt(2) <= '9') {
360 return release.substring(2);
361 }
362 return release;
363 }
364
365
366
367
368
369 protected static boolean hasJavaxToolProvider() {
370 try {
371 Thread.currentThread().getContextClassLoader().loadClass("javax.tools.ToolProvider");
372 return true;
373 } catch (Exception e) {
374 return false;
375 }
376 }
377
378 public String[] createCommandLine(CompilerConfiguration config) throws CompilerException {
379 final String javacVersion;
380 if (config.isFork()) {
381 String executable = getJavacExecutable(config);
382 javacVersion = getOutOfProcessJavacVersion(executable);
383 } else {
384 javacVersion = getInProcessJavacVersion();
385 }
386 return buildCompilerArguments(config, getSourceFiles(config), javacVersion);
387 }
388
389 public static String[] buildCompilerArguments(
390 CompilerConfiguration config, String[] sourceFiles, String javacVersion) {
391 List<String> args = new ArrayList<>();
392
393
394
395
396
397 File destinationDir = new File(config.getOutputLocation());
398 args.add("-d");
399 args.add(destinationDir.getAbsolutePath());
400
401
402
403
404
405 List<String> classpathEntries = config.getClasspathEntries();
406 if (classpathEntries != null && !classpathEntries.isEmpty()) {
407 args.add("-classpath");
408 args.add(getPathString(classpathEntries));
409 }
410
411 List<String> modulepathEntries = config.getModulepathEntries();
412 if (modulepathEntries != null && !modulepathEntries.isEmpty()) {
413 args.add("--module-path");
414 args.add(getPathString(modulepathEntries));
415 }
416
417 List<String> sourceLocations = config.getSourceLocations();
418 if (sourceLocations != null && !sourceLocations.isEmpty()) {
419
420
421 args.add("-sourcepath");
422 args.add(getPathString(sourceLocations));
423 }
424 if (!hasJavaxToolProvider() || config.isForceJavacCompilerUse() || config.isFork()) {
425 args.addAll(Arrays.asList(sourceFiles));
426 }
427
428 if (JavaVersion.JAVA_1_6.isOlderOrEqualTo(javacVersion)) {
429
430
431 if (config.getGeneratedSourcesDirectory() != null) {
432 config.getGeneratedSourcesDirectory().mkdirs();
433 args.add("-s");
434 args.add(config.getGeneratedSourcesDirectory().getAbsolutePath());
435 }
436 if (config.getProc() != null) {
437 args.add("-proc:" + config.getProc());
438 }
439 String annotationProcessors = joinAnnotationProcessors(config.getAnnotationProcessors());
440 if (!annotationProcessors.isEmpty()) {
441 args.add("-processor");
442 args.add(annotationProcessors);
443 }
444 if (config.getProcessorPathEntries() != null
445 && !config.getProcessorPathEntries().isEmpty()) {
446 args.add("-processorpath");
447 args.add(getPathString(config.getProcessorPathEntries()));
448 }
449 if (config.getProcessorModulePathEntries() != null
450 && !config.getProcessorModulePathEntries().isEmpty()) {
451 args.add("--processor-module-path");
452 args.add(getPathString(config.getProcessorModulePathEntries()));
453 }
454 }
455
456 if (config.isOptimize()) {
457 args.add("-O");
458 }
459
460 if (config.isDebug()) {
461 if (StringUtils.isNotEmpty(config.getDebugLevel())) {
462 args.add("-g:" + config.getDebugLevel());
463 } else {
464 args.add("-g");
465 }
466 }
467
468 if (config.isVerbose()) {
469 args.add("-verbose");
470 }
471
472 if (JavaVersion.JAVA_1_8.isOlderOrEqualTo(javacVersion) && config.isParameters()) {
473 args.add("-parameters");
474 }
475
476 if (config.isEnablePreview()) {
477 args.add("--enable-preview");
478 }
479
480 if (config.getImplicitOption() != null) {
481 args.add("-implicit:" + config.getImplicitOption());
482 }
483
484 if (config.isShowDeprecation()) {
485 args.add("-deprecation");
486
487
488 config.setShowWarnings(true);
489 }
490
491 if (!config.isShowWarnings()) {
492 args.add("-nowarn");
493 } else {
494 String warnings = config.getWarnings();
495 if (config.isShowLint()) {
496 if (config.isShowWarnings() && StringUtils.isNotEmpty(warnings)) {
497 args.add("-Xlint:" + warnings);
498 } else {
499 args.add("-Xlint");
500 }
501 }
502 }
503
504 if (config.isFailOnWarning()) {
505 args.add("-Werror");
506 }
507
508 if (JavaVersion.JAVA_9.isOlderOrEqualTo(javacVersion) && !StringUtils.isEmpty(config.getReleaseVersion())) {
509 args.add("--release");
510 args.add(toReleaseArgument(config.getReleaseVersion()));
511 } else {
512
513 if (StringUtils.isEmpty(config.getTargetVersion())) {
514
515 args.add("-target");
516 args.add("1.1");
517 } else {
518 args.add("-target");
519 args.add(config.getTargetVersion());
520 }
521
522 if (JavaVersion.JAVA_1_4.isOlderOrEqualTo(javacVersion) && StringUtils.isEmpty(config.getSourceVersion())) {
523
524 args.add("-source");
525 args.add("1.3");
526 } else if (JavaVersion.JAVA_1_4.isOlderOrEqualTo(javacVersion)) {
527 args.add("-source");
528 args.add(config.getSourceVersion());
529 }
530 }
531
532 if (JavaVersion.JAVA_1_4.isOlderOrEqualTo(javacVersion) && !StringUtils.isEmpty(config.getSourceEncoding())) {
533 args.add("-encoding");
534 args.add(config.getSourceEncoding());
535 }
536
537 if (!StringUtils.isEmpty(config.getModuleVersion())) {
538 args.add("--module-version");
539 args.add(config.getModuleVersion());
540 }
541
542 for (Map.Entry<String, String> entry : config.getCustomCompilerArgumentsEntries()) {
543 String key = entry.getKey();
544
545 if (StringUtils.isEmpty(key) || key.startsWith("-J")) {
546 continue;
547 }
548
549 args.add(key);
550 String value = entry.getValue();
551 if (StringUtils.isEmpty(value)) {
552 continue;
553 }
554 args.add(value);
555 }
556
557 if (!config.isFork() && !args.contains("-XDuseUnsharedTable=false")) {
558 args.add("-XDuseUnsharedTable=true");
559 }
560
561 return args.toArray(new String[0]);
562 }
563
564
565
566
567 enum JavaVersion {
568 JAVA_1_3_OR_OLDER("1.3", "1.2", "1.1", "1.0"),
569 JAVA_1_4("1.4"),
570 JAVA_1_5("1.5"),
571 JAVA_1_6("1.6"),
572 JAVA_1_7("1.7"),
573 JAVA_1_8("1.8"),
574 JAVA_9("9");
575 final Set<String> versionPrefixes;
576
577 JavaVersion(String... versionPrefixes) {
578 this.versionPrefixes = new HashSet<>(Arrays.asList(versionPrefixes));
579 }
580
581
582
583
584
585
586
587 boolean isOlderOrEqualTo(String version) {
588
589 JavaVersion[] allJavaVersionPrefixes = JavaVersion.values();
590 for (int n = ordinal() - 1; n > -1; n--) {
591 if (allJavaVersionPrefixes[n].versionPrefixes.stream().anyMatch(version::startsWith)) {
592 return false;
593 }
594 }
595 return true;
596 }
597 }
598
599
600
601
602
603
604
605
606
607
608
609 protected CompilerResult compileOutOfProcess(CompilerConfiguration config, String executable, String[] args)
610 throws CompilerException {
611 Commandline cli = new Commandline();
612
613 cli.setWorkingDirectory(config.getWorkingDirectory().getAbsolutePath());
614 cli.setExecutable(executable);
615
616 try {
617 File argumentsFile =
618 createFileWithArguments(args, config.getBuildDirectory().getAbsolutePath());
619 cli.addArguments(
620 new String[] {"@" + argumentsFile.getCanonicalPath().replace(File.separatorChar, '/')});
621
622 if (!StringUtils.isEmpty(config.getMaxmem())) {
623 cli.addArguments(new String[] {"-J-Xmx" + config.getMaxmem()});
624 }
625 if (!StringUtils.isEmpty(config.getMeminitial())) {
626 cli.addArguments(new String[] {"-J-Xms" + config.getMeminitial()});
627 }
628
629 for (String key : config.getCustomCompilerArgumentsAsMap().keySet()) {
630 if (StringUtils.isNotEmpty(key) && key.startsWith("-J")) {
631 cli.addArguments(new String[] {key});
632 }
633 }
634 } catch (IOException e) {
635 throw new CompilerException("Error creating file with javac arguments", e);
636 }
637
638 CommandLineUtils.StringStreamConsumer out = new CommandLineUtils.StringStreamConsumer();
639 int returnCode;
640 List<CompilerMessage> messages;
641
642 if (getLog().isDebugEnabled()) {
643 String debugFileName = StringUtils.isEmpty(config.getDebugFileName()) ? "javac" : config.getDebugFileName();
644
645 File commandLineFile = new File(
646 config.getBuildDirectory(),
647 StringUtils.trim(debugFileName) + "." + (Os.isFamily(Os.FAMILY_WINDOWS) ? "bat" : "sh"));
648 try {
649 FileUtils.fileWrite(
650 commandLineFile.getAbsolutePath(), cli.toString().replaceAll("'", ""));
651
652 if (!Os.isFamily(Os.FAMILY_WINDOWS)) {
653 Runtime.getRuntime().exec(new String[] {"chmod", "a+x", commandLineFile.getAbsolutePath()});
654 }
655 } catch (IOException e) {
656 if (getLog().isWarnEnabled()) {
657 getLog().warn("Unable to write '" + commandLineFile.getName() + "' debug script file", e);
658 }
659 }
660 }
661
662 try {
663
664
665
666
667
668 returnCode = CommandLineUtils.executeCommandLine(cli, out, out);
669
670 if (getLog().isDebugEnabled()) {
671 getLog().debug("Compiler output:{}{}", EOL, out.getOutput());
672 }
673
674 messages = parseModernStream(returnCode, new BufferedReader(new StringReader(out.getOutput())));
675 } catch (CommandLineException | IOException e) {
676 throw new CompilerException("Error while executing the external compiler.", e);
677 }
678
679 boolean success = returnCode == 0;
680 return new CompilerResult(success, messages);
681 }
682
683
684
685
686
687
688
689
690
691
692 CompilerResult compileInProcess(String[] args, CompilerConfiguration config) throws CompilerException {
693 final Class<?> javacClass = getJavacClass(config);
694 final Thread thread = Thread.currentThread();
695 final ClassLoader contextClassLoader = thread.getContextClassLoader();
696 thread.setContextClassLoader(javacClass.getClassLoader());
697 if (getLog().isDebugEnabled()) {
698 getLog().debug("ttcl changed run compileInProcessWithProperClassloader");
699 }
700 try {
701 return compileInProcessWithProperClassloader(javacClass, args);
702 } finally {
703 releaseJavaccClass(javacClass, config);
704 thread.setContextClassLoader(contextClassLoader);
705 }
706 }
707
708 protected CompilerResult compileInProcessWithProperClassloader(Class<?> javacClass, String[] args)
709 throws CompilerException {
710 return compileInProcess0(javacClass, args);
711 }
712
713
714
715
716 private CompilerResult compileInProcess0(Class<?> javacClass, String[] args) throws CompilerException {
717 StringWriter out = new StringWriter();
718 Integer ok;
719 List<CompilerMessage> messages;
720
721 try {
722 Method compile = javacClass.getMethod("compile", new Class[] {String[].class, PrintWriter.class});
723 ok = (Integer) compile.invoke(null, new Object[] {args, new PrintWriter(out)});
724
725 if (getLog().isDebugEnabled()) {
726 getLog().debug("Compiler output:{}{}", EOL, out.toString());
727 }
728
729 messages = parseModernStream(ok, new BufferedReader(new StringReader(out.toString())));
730 } catch (NoSuchMethodException | IOException | InvocationTargetException | IllegalAccessException e) {
731 throw new CompilerException("Error while executing the compiler.", e);
732 }
733
734 boolean success = ok == 0;
735 return new CompilerResult(success, messages);
736 }
737
738
739 private static final Pattern STACK_TRACE_FIRST_LINE = Pattern.compile("^(?:[\\w+.-]+\\.)[\\w$]*?(?:"
740 + "Exception|Error|Throwable|Failure|Result|Abort|Fault|ThreadDeath|Overflow|Warning|"
741 + "NotSupported|NotFound|BadArgs|BadClassFile|Illegal|Invalid|Unexpected|Unchecked|Unmatched\\w+"
742 + ").*$");
743
744
745
746
747
748 private static final Pattern COUNT_SUMMARY = Pattern.compile("^\\s*(?:\\d+\\s+\\S+|\\S+\\s+\\d+\\s+\\S+)\\s*$");
749
750 private static final Pattern STACK_TRACE_OTHER_LINE =
751 Pattern.compile("^(?:Caused by:\\s.*|\\s*at .*|\\s*\\.\\.\\.\\s\\d+\\smore)$");
752
753
754
755
756
757
758
759
760
761 static List<CompilerMessage> parseModernStream(int exitCode, BufferedReader input) throws IOException {
762 List<CompilerMessage> errors = new ArrayList<>();
763 String line;
764 StringBuilder buffer = new StringBuilder();
765 StringBuilder note = null;
766 boolean hasPointer = false;
767 int stackTraceLineCount = 0;
768
769 while ((line = input.readLine()) != null) {
770 if (note != null) {
771 if (isNoteContinuation(line)) {
772 note.append(EOL).append(line);
773 continue;
774 }
775 errors.add(new CompilerMessage(note.toString(), CompilerMessage.Kind.NOTE));
776 note = null;
777 }
778
779 if (stackTraceLineCount == 0 && STACK_TRACE_FIRST_LINE.matcher(line).matches()
780 || STACK_TRACE_OTHER_LINE.matcher(line).matches()) {
781 stackTraceLineCount++;
782 } else {
783 stackTraceLineCount = 0;
784 }
785
786
787 if (!line.startsWith(" ") && hasPointer) {
788
789 errors.add(parseModernError(exitCode, buffer.toString()));
790
791 buffer = new StringBuilder();
792 hasPointer = false;
793 }
794
795 if (buffer.length() == 0) {
796
797
798 if (isError(line)) {
799 errors.add(new CompilerMessage(line, ERROR));
800 } else if (isWarning(line)) {
801 errors.add(new CompilerMessage(line, WARNING));
802 } else if (isNote(line)) {
803
804 note = new StringBuilder(line);
805 } else if (isMisc(line)) {
806
807 errors.add(new CompilerMessage(line, CompilerMessage.Kind.OTHER));
808 } else {
809
810 buffer.append(line).append(EOL);
811 }
812 } else {
813
814 buffer.append(line).append(EOL);
815 }
816
817 if (line.endsWith("^")) {
818 hasPointer = true;
819 }
820 }
821
822 if (note != null) {
823 errors.add(new CompilerMessage(note.toString(), CompilerMessage.Kind.NOTE));
824 }
825
826 String bufferContent = buffer.toString();
827 if (bufferContent.isEmpty()) {
828 return errors;
829 }
830
831
832
833 String cleanedUpMessage;
834 if ((cleanedUpMessage = getJavacGenericError(bufferContent)) != null
835 || (cleanedUpMessage = getBootLayerInitError(bufferContent)) != null
836 || (cleanedUpMessage = getVMInitError(bufferContent)) != null
837 || (cleanedUpMessage = getFileABugError(bufferContent)) != null
838 || (cleanedUpMessage = getAnnotationProcessingError(bufferContent)) != null
839 || (cleanedUpMessage = getSystemOutOfResourcesError(bufferContent)) != null
840 || (cleanedUpMessage = getIOError(bufferContent)) != null
841 || (cleanedUpMessage = getPluginError(bufferContent)) != null) {
842 errors.add(new CompilerMessage(cleanedUpMessage, ERROR));
843 } else if (hasPointer) {
844
845 errors.add(parseModernError(exitCode, bufferContent));
846 } else if (stackTraceLineCount > 0) {
847
848 String[] lines = bufferContent.split("\\R");
849 int linesTotal = lines.length;
850 buffer = new StringBuilder();
851 int firstLine = linesTotal - stackTraceLineCount;
852 for (int i = firstLine; i < linesTotal; i++) {
853 buffer.append(lines[i]).append(EOL);
854 }
855 errors.add(new CompilerMessage(buffer.toString(), ERROR));
856 } else if (exitCode != 0) {
857
858
859 String unrecognised = stripCountSummaries(bufferContent);
860 if (!unrecognised.isEmpty()) {
861 errors.add(new CompilerMessage(unrecognised, ERROR));
862 }
863 }
864
865 return errors;
866 }
867
868
869
870
871
872
873
874
875 private static String stripCountSummaries(String content) {
876 StringBuilder kept = new StringBuilder();
877 for (String line : content.split("\\R")) {
878 if (!line.trim().isEmpty() && !COUNT_SUMMARY.matcher(line).matches()) {
879 kept.append(line).append(EOL);
880 }
881 }
882 return kept.toString().trim();
883 }
884
885 private static boolean isMisc(String message) {
886 return startsWithPrefix(message, MISC_PREFIXES);
887 }
888
889 private static boolean isNote(String message) {
890 return startsWithPrefix(message, NOTE_PREFIXES);
891 }
892
893
894
895
896
897
898
899
900
901
902
903
904 private static boolean isNoteContinuation(String line) {
905 return line.startsWith(" ");
906 }
907
908 private static boolean isWarning(String message) {
909 return startsWithPrefix(message, WARNING_PREFIXES);
910 }
911
912 private static boolean isError(String message) {
913 return startsWithPrefix(message, ERROR_PREFIXES);
914 }
915
916 private static String getJavacGenericError(String message) {
917 return getTextStartingWithPrefix(message, JAVAC_GENERIC_ERROR_PREFIXES);
918 }
919
920 private static String getVMInitError(String message) {
921 return getTextStartingWithPrefix(message, VM_INIT_ERROR_HEADERS);
922 }
923
924 private static String getBootLayerInitError(String message) {
925 return getTextStartingWithPrefix(message, BOOT_LAYER_INIT_ERROR_HEADERS);
926 }
927
928 private static String getFileABugError(String message) {
929 return getTextStartingWithPrefix(message, FILE_A_BUG_ERROR_HEADERS);
930 }
931
932 private static String getAnnotationProcessingError(String message) {
933 return getTextStartingWithPrefix(message, ANNOTATION_PROCESSING_ERROR_HEADERS);
934 }
935
936 private static String getSystemOutOfResourcesError(String message) {
937 return getTextStartingWithPrefix(message, SYSTEM_OUT_OF_RESOURCES_ERROR_HEADERS);
938 }
939
940 private static String getIOError(String message) {
941 return getTextStartingWithPrefix(message, IO_ERROR_HEADERS);
942 }
943
944 private static String getPluginError(String message) {
945 return getTextStartingWithPrefix(message, PLUGIN_ERROR_HEADERS);
946 }
947
948 private static boolean startsWithPrefix(String text, String[] prefixes) {
949 for (String prefix : prefixes) {
950 if (text.startsWith(prefix)) {
951 return true;
952 }
953 }
954 return false;
955 }
956
957
958
959
960
961
962
963
964
965
966
967 static String getTextStartingWithPrefix(String text, String[] prefixes) {
968
969
970
971
972
973
974 text = text.replaceAll("\\R", "\n");
975
976
977 for (String prefix : prefixes) {
978
979 String[] prefixParts = prefix.split("\\{\\d+\\}");
980 for (int i = 0; i < prefixParts.length; i++) {
981
982
983 prefixParts[i] = "\\Q" + prefixParts[i] + "\\E";
984 }
985
986 prefix = String.join(".*?", prefixParts);
987
988
989 Matcher matcher = Pattern.compile("(?s).*(" + prefix + ".*)").matcher(text);
990 if (matcher.matches()) {
991
992 return matcher.replaceFirst("$1").replaceAll("\n", EOL);
993 }
994 }
995
996
997 return null;
998 }
999
1000
1001
1002
1003
1004
1005
1006
1007 static CompilerMessage parseModernError(int exitCode, String error) {
1008 final StringTokenizer tokens = new StringTokenizer(error, ":");
1009 CompilerMessage.Kind messageKind = exitCode == 0 ? WARNING : ERROR;
1010
1011 try {
1012
1013
1014
1015
1016 boolean tokenIsAnInteger;
1017 StringBuilder file = null;
1018 String currentToken = null;
1019
1020 do {
1021 if (currentToken != null) {
1022 if (file == null) {
1023 file = new StringBuilder(currentToken);
1024 } else {
1025 file.append(':').append(currentToken);
1026 }
1027 }
1028
1029 currentToken = tokens.nextToken();
1030
1031 tokenIsAnInteger = true;
1032
1033 try {
1034 Integer.parseInt(currentToken);
1035 } catch (NumberFormatException e) {
1036 tokenIsAnInteger = false;
1037 }
1038 } while (!tokenIsAnInteger);
1039
1040 final String lineIndicator = currentToken;
1041 final int startOfFileName = Objects.requireNonNull(file).toString().lastIndexOf(']');
1042 if (startOfFileName > -1) {
1043 file = new StringBuilder(file.substring(startOfFileName + 1 + EOL.length()));
1044 }
1045
1046 final int line = Integer.parseInt(lineIndicator);
1047 final StringBuilder msgBuffer = new StringBuilder();
1048 String msg = tokens.nextToken(EOL).substring(2);
1049
1050
1051 String prefix;
1052 if ((prefix = getErrorPrefix(msg)) != null) {
1053 messageKind = ERROR;
1054 msg = msg.substring(prefix.length());
1055 } else if ((prefix = getWarningPrefix(msg)) != null) {
1056 messageKind = WARNING;
1057 msg = msg.substring(prefix.length());
1058 }
1059 msgBuffer.append(msg).append(EOL);
1060
1061 String context = tokens.nextToken(EOL);
1062 String pointer = null;
1063
1064 do {
1065 final String msgLine = tokens.nextToken(EOL);
1066 if (pointer != null) {
1067 msgBuffer.append(msgLine);
1068 msgBuffer.append(EOL);
1069 } else if (msgLine.endsWith("^")) {
1070 pointer = msgLine;
1071 } else {
1072 msgBuffer.append(context);
1073 msgBuffer.append(EOL);
1074 context = msgLine;
1075 }
1076 } while (tokens.hasMoreTokens());
1077
1078 msgBuffer.append(EOL);
1079
1080 final String message = msgBuffer.toString();
1081 final int startcolumn = Objects.requireNonNull(pointer).indexOf("^") + 1;
1082 int endcolumn = (context == null) ? startcolumn : context.indexOf(" ", startcolumn);
1083 if (endcolumn == -1) {
1084 endcolumn = Objects.requireNonNull(context).length();
1085 }
1086
1087 return new CompilerMessage(
1088 file.toString(), messageKind, line, startcolumn, line, endcolumn, message.trim());
1089 } catch (NoSuchElementException e) {
1090 return new CompilerMessage("no more tokens - could not parse error message: " + error, messageKind);
1091 } catch (Exception e) {
1092 return new CompilerMessage("could not parse error message: " + error, messageKind);
1093 }
1094 }
1095
1096 private static String getMessagePrefix(String message, String[] prefixes) {
1097 for (String prefix : prefixes) {
1098 if (message.startsWith(prefix)) {
1099 return prefix;
1100 }
1101 }
1102 return null;
1103 }
1104
1105 private static String getWarningPrefix(String message) {
1106 return getMessagePrefix(message, WARNING_PREFIXES);
1107 }
1108
1109 private static String getErrorPrefix(String message) {
1110 return getMessagePrefix(message, ERROR_PREFIXES);
1111 }
1112
1113
1114
1115
1116
1117
1118
1119
1120 private File createFileWithArguments(String[] args, String outputDirectory) throws IOException {
1121 PrintWriter writer = null;
1122 try {
1123 File tempFile;
1124 if (getLog().isDebugEnabled()) {
1125 tempFile = File.createTempFile(JavacCompiler.class.getName(), "arguments", new File(outputDirectory));
1126 } else {
1127 tempFile = File.createTempFile(JavacCompiler.class.getName(), "arguments");
1128 tempFile.deleteOnExit();
1129 }
1130
1131 writer = new PrintWriter(new FileWriter(tempFile));
1132 for (String arg : args) {
1133 writer.println(quoteArgument(arg));
1134 }
1135 writer.flush();
1136
1137 return tempFile;
1138
1139 } finally {
1140 if (writer != null) {
1141 writer.close();
1142 }
1143 }
1144 }
1145
1146
1147
1148
1149 static String quoteArgument(String argument) {
1150 StringBuilder quoted = new StringBuilder(argument.length() + 2);
1151 quoted.append('"');
1152 for (int i = 0; i < argument.length(); i++) {
1153 char c = argument.charAt(i);
1154 switch (c) {
1155 case '\\':
1156 quoted.append("\\\\");
1157 break;
1158 case '"':
1159 quoted.append("\\\"");
1160 break;
1161 case '\n':
1162 quoted.append("\\n");
1163 break;
1164 case '\r':
1165 quoted.append("\\r");
1166 break;
1167 case '\t':
1168 quoted.append("\\t");
1169 break;
1170 case '\f':
1171 quoted.append("\\f");
1172 break;
1173 default:
1174 quoted.append(c);
1175 break;
1176 }
1177 }
1178 return quoted.append('"').toString();
1179 }
1180
1181
1182
1183
1184
1185
1186
1187 protected String getJavacExecutable(CompilerConfiguration config) {
1188 String executable = config.getExecutable();
1189
1190 if (StringUtils.isEmpty(executable)) {
1191 try {
1192 executable = getJavacExecutable();
1193 } catch (IOException e) {
1194 if (getLog().isWarnEnabled()) {
1195 getLog().warn("Unable to autodetect 'javac' path, using 'javac' from the environment.");
1196 }
1197 executable = "javac";
1198 }
1199 }
1200 return executable;
1201 }
1202
1203
1204
1205
1206
1207
1208
1209
1210 private static String getJavacExecutable() throws IOException {
1211 String javacCommand = "javac" + (Os.isFamily(Os.FAMILY_WINDOWS) ? ".exe" : "");
1212 String javaHome = System.getProperty("java.home");
1213 File javacExe;
1214
1215 if (Os.isName("AIX")) {
1216 javacExe = new File(javaHome + File.separator + ".." + File.separator + "sh", javacCommand);
1217 } else if (Os.isName("Mac OS X")) {
1218 javacExe = new File(javaHome + File.separator + "bin", javacCommand);
1219 } else {
1220 javacExe = new File(javaHome + File.separator + ".." + File.separator + "bin", javacCommand);
1221 }
1222
1223
1224
1225
1226 if (!javacExe.isFile()) {
1227 Properties env = CommandLineUtils.getSystemEnvVars();
1228 javaHome = env.getProperty("JAVA_HOME");
1229 if (StringUtils.isEmpty(javaHome)) {
1230 throw new IOException("The environment variable JAVA_HOME is not correctly set.");
1231 }
1232 if (!new File(javaHome).isDirectory()) {
1233 throw new IOException("The environment variable JAVA_HOME=" + javaHome
1234 + " doesn't exist or is not a valid directory.");
1235 }
1236 javacExe = new File(env.getProperty("JAVA_HOME") + File.separator + "bin", javacCommand);
1237 }
1238
1239 if (!javacExe.isFile()) {
1240 throw new IOException("The javadoc executable '" + javacExe
1241 + "' doesn't exist or is not a file. Verify the JAVA_HOME environment variable.");
1242 }
1243
1244 return javacExe.getAbsolutePath();
1245 }
1246
1247 private void releaseJavaccClass(Class<?> javaccClass, CompilerConfiguration compilerConfiguration) {
1248 if (compilerConfiguration.getCompilerReuseStrategy()
1249 == CompilerConfiguration.CompilerReuseStrategy.ReuseCreated) {
1250 javacClasses.add(javaccClass);
1251 }
1252 }
1253
1254
1255
1256
1257
1258
1259
1260 private Class<?> getJavacClass(CompilerConfiguration compilerConfiguration) throws CompilerException {
1261 Class<?> c;
1262 switch (compilerConfiguration.getCompilerReuseStrategy()) {
1263 case AlwaysNew:
1264 return createJavacClass();
1265 case ReuseCreated:
1266 c = javacClasses.poll();
1267 if (c == null) {
1268 c = createJavacClass();
1269 }
1270 return c;
1271 case ReuseSame:
1272 default:
1273 c = javacClass;
1274 if (c == null) {
1275 synchronized (this) {
1276 c = javacClass;
1277 if (c == null) {
1278 javacClass = c = createJavacClass();
1279 }
1280 }
1281 }
1282 return c;
1283 }
1284 }
1285
1286
1287
1288
1289 protected Class<?> createJavacClass() throws CompilerException {
1290 try {
1291
1292
1293 return JavacCompiler.class.getClassLoader().loadClass(JavacCompiler.JAVAC_CLASSNAME);
1294 } catch (ClassNotFoundException ex) {
1295
1296 }
1297
1298 final File toolsJar = new File(System.getProperty("java.home"), "../lib/tools.jar");
1299 if (!toolsJar.exists()) {
1300 throw new CompilerException("tools.jar not found: " + toolsJar);
1301 }
1302
1303 try {
1304
1305
1306 URL[] originalUrls = ((URLClassLoader) JavacCompiler.class.getClassLoader()).getURLs();
1307 URL[] urls = new URL[originalUrls.length + 1];
1308 urls[0] = toolsJar.toURI().toURL();
1309 System.arraycopy(originalUrls, 0, urls, 1, originalUrls.length);
1310 ClassLoader javacClassLoader = new URLClassLoader(urls);
1311
1312 final Thread thread = Thread.currentThread();
1313 final ClassLoader contextClassLoader = thread.getContextClassLoader();
1314 thread.setContextClassLoader(javacClassLoader);
1315 try {
1316
1317 return javacClassLoader.loadClass(JavacCompiler.JAVAC_CLASSNAME);
1318 } finally {
1319 thread.setContextClassLoader(contextClassLoader);
1320 }
1321 } catch (MalformedURLException ex) {
1322 throw new CompilerException(
1323 "Could not convert the file reference to tools.jar to a URL, path to tools.jar: '"
1324 + toolsJar.getAbsolutePath() + "'.",
1325 ex);
1326 } catch (ClassNotFoundException ex) {
1327 throw new CompilerException(
1328 "Unable to locate the Javac Compiler in:" + EOL + " " + toolsJar + EOL
1329 + "Please ensure you are using JDK 1.4 or above and" + EOL
1330 + "not a JRE (the com.sun.tools.javac.Main class is required)." + EOL
1331 + "In most cases you can change the location of your Java" + EOL
1332 + "installation by setting the JAVA_HOME environment variable.",
1333 ex);
1334 }
1335 }
1336 }