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 protected static boolean hasJavaxToolProvider() {
355 try {
356 Thread.currentThread().getContextClassLoader().loadClass("javax.tools.ToolProvider");
357 return true;
358 } catch (Exception e) {
359 return false;
360 }
361 }
362
363 public String[] createCommandLine(CompilerConfiguration config) throws CompilerException {
364 final String javacVersion;
365 if (config.isFork()) {
366 String executable = getJavacExecutable(config);
367 javacVersion = getOutOfProcessJavacVersion(executable);
368 } else {
369 javacVersion = getInProcessJavacVersion();
370 }
371 return buildCompilerArguments(config, getSourceFiles(config), javacVersion);
372 }
373
374 public static String[] buildCompilerArguments(
375 CompilerConfiguration config, String[] sourceFiles, String javacVersion) {
376 List<String> args = new ArrayList<>();
377
378
379
380
381
382 File destinationDir = new File(config.getOutputLocation());
383 args.add("-d");
384 args.add(destinationDir.getAbsolutePath());
385
386
387
388
389
390 List<String> classpathEntries = config.getClasspathEntries();
391 if (classpathEntries != null && !classpathEntries.isEmpty()) {
392 args.add("-classpath");
393 args.add(getPathString(classpathEntries));
394 }
395
396 List<String> modulepathEntries = config.getModulepathEntries();
397 if (modulepathEntries != null && !modulepathEntries.isEmpty()) {
398 args.add("--module-path");
399 args.add(getPathString(modulepathEntries));
400 }
401
402 List<String> sourceLocations = config.getSourceLocations();
403 if (sourceLocations != null && !sourceLocations.isEmpty()) {
404
405
406 args.add("-sourcepath");
407 args.add(getPathString(sourceLocations));
408 }
409 if (!hasJavaxToolProvider() || config.isForceJavacCompilerUse() || config.isFork()) {
410 args.addAll(Arrays.asList(sourceFiles));
411 }
412
413 if (JavaVersion.JAVA_1_6.isOlderOrEqualTo(javacVersion)) {
414
415
416 if (config.getGeneratedSourcesDirectory() != null) {
417 config.getGeneratedSourcesDirectory().mkdirs();
418 args.add("-s");
419 args.add(config.getGeneratedSourcesDirectory().getAbsolutePath());
420 }
421 if (config.getProc() != null) {
422 args.add("-proc:" + config.getProc());
423 }
424 if (config.getAnnotationProcessors() != null) {
425 args.add("-processor");
426 String[] procs = config.getAnnotationProcessors();
427 StringBuilder buffer = new StringBuilder();
428 for (int i = 0; i < procs.length; i++) {
429 if (i > 0) {
430 buffer.append(",");
431 }
432 buffer.append(procs[i]);
433 }
434 args.add(buffer.toString());
435 }
436 if (config.getProcessorPathEntries() != null
437 && !config.getProcessorPathEntries().isEmpty()) {
438 args.add("-processorpath");
439 args.add(getPathString(config.getProcessorPathEntries()));
440 }
441 if (config.getProcessorModulePathEntries() != null
442 && !config.getProcessorModulePathEntries().isEmpty()) {
443 args.add("--processor-module-path");
444 args.add(getPathString(config.getProcessorModulePathEntries()));
445 }
446 }
447
448 if (config.isOptimize()) {
449 args.add("-O");
450 }
451
452 if (config.isDebug()) {
453 if (StringUtils.isNotEmpty(config.getDebugLevel())) {
454 args.add("-g:" + config.getDebugLevel());
455 } else {
456 args.add("-g");
457 }
458 }
459
460 if (config.isVerbose()) {
461 args.add("-verbose");
462 }
463
464 if (JavaVersion.JAVA_1_8.isOlderOrEqualTo(javacVersion) && config.isParameters()) {
465 args.add("-parameters");
466 }
467
468 if (config.isEnablePreview()) {
469 args.add("--enable-preview");
470 }
471
472 if (config.getImplicitOption() != null) {
473 args.add("-implicit:" + config.getImplicitOption());
474 }
475
476 if (config.isShowDeprecation()) {
477 args.add("-deprecation");
478
479
480 config.setShowWarnings(true);
481 }
482
483 if (!config.isShowWarnings()) {
484 args.add("-nowarn");
485 } else {
486 String warnings = config.getWarnings();
487 if (config.isShowLint()) {
488 if (config.isShowWarnings() && StringUtils.isNotEmpty(warnings)) {
489 args.add("-Xlint:" + warnings);
490 } else {
491 args.add("-Xlint");
492 }
493 }
494 }
495
496 if (config.isFailOnWarning()) {
497 args.add("-Werror");
498 }
499
500 if (JavaVersion.JAVA_9.isOlderOrEqualTo(javacVersion) && !StringUtils.isEmpty(config.getReleaseVersion())) {
501 args.add("--release");
502 args.add(config.getReleaseVersion());
503 } else {
504
505 if (StringUtils.isEmpty(config.getTargetVersion())) {
506
507 args.add("-target");
508 args.add("1.1");
509 } else {
510 args.add("-target");
511 args.add(config.getTargetVersion());
512 }
513
514 if (JavaVersion.JAVA_1_4.isOlderOrEqualTo(javacVersion) && StringUtils.isEmpty(config.getSourceVersion())) {
515
516 args.add("-source");
517 args.add("1.3");
518 } else if (JavaVersion.JAVA_1_4.isOlderOrEqualTo(javacVersion)) {
519 args.add("-source");
520 args.add(config.getSourceVersion());
521 }
522 }
523
524 if (JavaVersion.JAVA_1_4.isOlderOrEqualTo(javacVersion) && !StringUtils.isEmpty(config.getSourceEncoding())) {
525 args.add("-encoding");
526 args.add(config.getSourceEncoding());
527 }
528
529 if (!StringUtils.isEmpty(config.getModuleVersion())) {
530 args.add("--module-version");
531 args.add(config.getModuleVersion());
532 }
533
534 for (Map.Entry<String, String> entry : config.getCustomCompilerArgumentsEntries()) {
535 String key = entry.getKey();
536
537 if (StringUtils.isEmpty(key) || key.startsWith("-J")) {
538 continue;
539 }
540
541 args.add(key);
542 String value = entry.getValue();
543 if (StringUtils.isEmpty(value)) {
544 continue;
545 }
546 args.add(value);
547 }
548
549 if (!config.isFork() && !args.contains("-XDuseUnsharedTable=false")) {
550 args.add("-XDuseUnsharedTable=true");
551 }
552
553 return args.toArray(new String[0]);
554 }
555
556
557
558
559 enum JavaVersion {
560 JAVA_1_3_OR_OLDER("1.3", "1.2", "1.1", "1.0"),
561 JAVA_1_4("1.4"),
562 JAVA_1_5("1.5"),
563 JAVA_1_6("1.6"),
564 JAVA_1_7("1.7"),
565 JAVA_1_8("1.8"),
566 JAVA_9("9");
567 final Set<String> versionPrefixes;
568
569 JavaVersion(String... versionPrefixes) {
570 this.versionPrefixes = new HashSet<>(Arrays.asList(versionPrefixes));
571 }
572
573
574
575
576
577
578
579 boolean isOlderOrEqualTo(String version) {
580
581 JavaVersion[] allJavaVersionPrefixes = JavaVersion.values();
582 for (int n = ordinal() - 1; n > -1; n--) {
583 if (allJavaVersionPrefixes[n].versionPrefixes.stream().anyMatch(version::startsWith)) {
584 return false;
585 }
586 }
587 return true;
588 }
589 }
590
591
592
593
594
595
596
597
598
599
600
601 protected CompilerResult compileOutOfProcess(CompilerConfiguration config, String executable, String[] args)
602 throws CompilerException {
603 Commandline cli = new Commandline();
604
605 cli.setWorkingDirectory(config.getWorkingDirectory().getAbsolutePath());
606 cli.setExecutable(executable);
607
608 try {
609 File argumentsFile =
610 createFileWithArguments(args, config.getBuildDirectory().getAbsolutePath());
611 cli.addArguments(
612 new String[] {"@" + argumentsFile.getCanonicalPath().replace(File.separatorChar, '/')});
613
614 if (!StringUtils.isEmpty(config.getMaxmem())) {
615 cli.addArguments(new String[] {"-J-Xmx" + config.getMaxmem()});
616 }
617 if (!StringUtils.isEmpty(config.getMeminitial())) {
618 cli.addArguments(new String[] {"-J-Xms" + config.getMeminitial()});
619 }
620
621 for (String key : config.getCustomCompilerArgumentsAsMap().keySet()) {
622 if (StringUtils.isNotEmpty(key) && key.startsWith("-J")) {
623 cli.addArguments(new String[] {key});
624 }
625 }
626 } catch (IOException e) {
627 throw new CompilerException("Error creating file with javac arguments", e);
628 }
629
630 CommandLineUtils.StringStreamConsumer out = new CommandLineUtils.StringStreamConsumer();
631 int returnCode;
632 List<CompilerMessage> messages;
633
634 if (getLog().isDebugEnabled()) {
635 String debugFileName = StringUtils.isEmpty(config.getDebugFileName()) ? "javac" : config.getDebugFileName();
636
637 File commandLineFile = new File(
638 config.getBuildDirectory(),
639 StringUtils.trim(debugFileName) + "." + (Os.isFamily(Os.FAMILY_WINDOWS) ? "bat" : "sh"));
640 try {
641 FileUtils.fileWrite(
642 commandLineFile.getAbsolutePath(), cli.toString().replaceAll("'", ""));
643
644 if (!Os.isFamily(Os.FAMILY_WINDOWS)) {
645 Runtime.getRuntime().exec(new String[] {"chmod", "a+x", commandLineFile.getAbsolutePath()});
646 }
647 } catch (IOException e) {
648 if (getLog().isWarnEnabled()) {
649 getLog().warn("Unable to write '" + commandLineFile.getName() + "' debug script file", e);
650 }
651 }
652 }
653
654 try {
655
656
657
658
659
660 returnCode = CommandLineUtils.executeCommandLine(cli, out, out);
661
662 if (getLog().isDebugEnabled()) {
663 getLog().debug("Compiler output:{}{}", EOL, out.getOutput());
664 }
665
666 messages = parseModernStream(returnCode, new BufferedReader(new StringReader(out.getOutput())));
667 } catch (CommandLineException | IOException e) {
668 throw new CompilerException("Error while executing the external compiler.", e);
669 }
670
671 boolean success = returnCode == 0;
672 return new CompilerResult(success, messages);
673 }
674
675
676
677
678
679
680
681
682
683
684 CompilerResult compileInProcess(String[] args, CompilerConfiguration config) throws CompilerException {
685 final Class<?> javacClass = getJavacClass(config);
686 final Thread thread = Thread.currentThread();
687 final ClassLoader contextClassLoader = thread.getContextClassLoader();
688 thread.setContextClassLoader(javacClass.getClassLoader());
689 if (getLog().isDebugEnabled()) {
690 getLog().debug("ttcl changed run compileInProcessWithProperClassloader");
691 }
692 try {
693 return compileInProcessWithProperClassloader(javacClass, args);
694 } finally {
695 releaseJavaccClass(javacClass, config);
696 thread.setContextClassLoader(contextClassLoader);
697 }
698 }
699
700 protected CompilerResult compileInProcessWithProperClassloader(Class<?> javacClass, String[] args)
701 throws CompilerException {
702 return compileInProcess0(javacClass, args);
703 }
704
705
706
707
708 private CompilerResult compileInProcess0(Class<?> javacClass, String[] args) throws CompilerException {
709 StringWriter out = new StringWriter();
710 Integer ok;
711 List<CompilerMessage> messages;
712
713 try {
714 Method compile = javacClass.getMethod("compile", new Class[] {String[].class, PrintWriter.class});
715 ok = (Integer) compile.invoke(null, new Object[] {args, new PrintWriter(out)});
716
717 if (getLog().isDebugEnabled()) {
718 getLog().debug("Compiler output:{}{}", EOL, out.toString());
719 }
720
721 messages = parseModernStream(ok, new BufferedReader(new StringReader(out.toString())));
722 } catch (NoSuchMethodException | IOException | InvocationTargetException | IllegalAccessException e) {
723 throw new CompilerException("Error while executing the compiler.", e);
724 }
725
726 boolean success = ok == 0;
727 return new CompilerResult(success, messages);
728 }
729
730
731 private static final Pattern STACK_TRACE_FIRST_LINE = Pattern.compile("^(?:[\\w+.-]+\\.)[\\w$]*?(?:"
732 + "Exception|Error|Throwable|Failure|Result|Abort|Fault|ThreadDeath|Overflow|Warning|"
733 + "NotSupported|NotFound|BadArgs|BadClassFile|Illegal|Invalid|Unexpected|Unchecked|Unmatched\\w+"
734 + ").*$");
735
736
737 private static final Pattern STACK_TRACE_OTHER_LINE =
738 Pattern.compile("^(?:Caused by:\\s.*|\\s*at .*|\\s*\\.\\.\\.\\s\\d+\\smore)$");
739
740
741
742
743
744
745
746
747
748 static List<CompilerMessage> parseModernStream(int exitCode, BufferedReader input) throws IOException {
749 List<CompilerMessage> errors = new ArrayList<>();
750 String line;
751 StringBuilder buffer = new StringBuilder();
752 boolean hasPointer = false;
753 int stackTraceLineCount = 0;
754
755 while ((line = input.readLine()) != null) {
756 if (stackTraceLineCount == 0 && STACK_TRACE_FIRST_LINE.matcher(line).matches()
757 || STACK_TRACE_OTHER_LINE.matcher(line).matches()) {
758 stackTraceLineCount++;
759 } else {
760 stackTraceLineCount = 0;
761 }
762
763
764 if (!line.startsWith(" ") && hasPointer) {
765
766 errors.add(parseModernError(exitCode, buffer.toString()));
767
768 buffer = new StringBuilder();
769 hasPointer = false;
770 }
771
772 if (buffer.length() == 0) {
773
774
775 if (isError(line)) {
776 errors.add(new CompilerMessage(line, ERROR));
777 } else if (isWarning(line)) {
778 errors.add(new CompilerMessage(line, WARNING));
779 } else if (isNote(line)) {
780
781 } else if (isMisc(line)) {
782
783 errors.add(new CompilerMessage(line, CompilerMessage.Kind.OTHER));
784 } else {
785
786 buffer.append(line).append(EOL);
787 }
788 } else {
789
790 buffer.append(line).append(EOL);
791 }
792
793 if (line.endsWith("^")) {
794 hasPointer = true;
795 }
796 }
797
798 String bufferContent = buffer.toString();
799 if (bufferContent.isEmpty()) {
800 return errors;
801 }
802
803
804
805 String cleanedUpMessage;
806 if ((cleanedUpMessage = getJavacGenericError(bufferContent)) != null
807 || (cleanedUpMessage = getBootLayerInitError(bufferContent)) != null
808 || (cleanedUpMessage = getVMInitError(bufferContent)) != null
809 || (cleanedUpMessage = getFileABugError(bufferContent)) != null
810 || (cleanedUpMessage = getAnnotationProcessingError(bufferContent)) != null
811 || (cleanedUpMessage = getSystemOutOfResourcesError(bufferContent)) != null
812 || (cleanedUpMessage = getIOError(bufferContent)) != null
813 || (cleanedUpMessage = getPluginError(bufferContent)) != null) {
814 errors.add(new CompilerMessage(cleanedUpMessage, ERROR));
815 } else if (hasPointer) {
816
817 errors.add(parseModernError(exitCode, bufferContent));
818 } else if (stackTraceLineCount > 0) {
819
820 String[] lines = bufferContent.split("\\R");
821 int linesTotal = lines.length;
822 buffer = new StringBuilder();
823 int firstLine = linesTotal - stackTraceLineCount;
824 for (int i = firstLine; i < linesTotal; i++) {
825 buffer.append(lines[i]).append(EOL);
826 }
827 errors.add(new CompilerMessage(buffer.toString(), ERROR));
828 }
829
830
831
832
833
834
835 return errors;
836 }
837
838 private static boolean isMisc(String message) {
839 return startsWithPrefix(message, MISC_PREFIXES);
840 }
841
842 private static boolean isNote(String message) {
843 return startsWithPrefix(message, NOTE_PREFIXES);
844 }
845
846 private static boolean isWarning(String message) {
847 return startsWithPrefix(message, WARNING_PREFIXES);
848 }
849
850 private static boolean isError(String message) {
851 return startsWithPrefix(message, ERROR_PREFIXES);
852 }
853
854 private static String getJavacGenericError(String message) {
855 return getTextStartingWithPrefix(message, JAVAC_GENERIC_ERROR_PREFIXES);
856 }
857
858 private static String getVMInitError(String message) {
859 return getTextStartingWithPrefix(message, VM_INIT_ERROR_HEADERS);
860 }
861
862 private static String getBootLayerInitError(String message) {
863 return getTextStartingWithPrefix(message, BOOT_LAYER_INIT_ERROR_HEADERS);
864 }
865
866 private static String getFileABugError(String message) {
867 return getTextStartingWithPrefix(message, FILE_A_BUG_ERROR_HEADERS);
868 }
869
870 private static String getAnnotationProcessingError(String message) {
871 return getTextStartingWithPrefix(message, ANNOTATION_PROCESSING_ERROR_HEADERS);
872 }
873
874 private static String getSystemOutOfResourcesError(String message) {
875 return getTextStartingWithPrefix(message, SYSTEM_OUT_OF_RESOURCES_ERROR_HEADERS);
876 }
877
878 private static String getIOError(String message) {
879 return getTextStartingWithPrefix(message, IO_ERROR_HEADERS);
880 }
881
882 private static String getPluginError(String message) {
883 return getTextStartingWithPrefix(message, PLUGIN_ERROR_HEADERS);
884 }
885
886 private static boolean startsWithPrefix(String text, String[] prefixes) {
887 for (String prefix : prefixes) {
888 if (text.startsWith(prefix)) {
889 return true;
890 }
891 }
892 return false;
893 }
894
895
896
897
898
899
900
901
902
903
904
905 static String getTextStartingWithPrefix(String text, String[] prefixes) {
906
907
908
909
910
911
912 text = text.replaceAll("\\R", "\n");
913
914
915 for (String prefix : prefixes) {
916
917 String[] prefixParts = prefix.split("\\{\\d+\\}");
918 for (int i = 0; i < prefixParts.length; i++) {
919
920
921 prefixParts[i] = "\\Q" + prefixParts[i] + "\\E";
922 }
923
924 prefix = String.join(".*?", prefixParts);
925
926
927 Matcher matcher = Pattern.compile("(?s).*(" + prefix + ".*)").matcher(text);
928 if (matcher.matches()) {
929
930 return matcher.replaceFirst("$1").replaceAll("\n", EOL);
931 }
932 }
933
934
935 return null;
936 }
937
938
939
940
941
942
943
944
945 static CompilerMessage parseModernError(int exitCode, String error) {
946 final StringTokenizer tokens = new StringTokenizer(error, ":");
947 CompilerMessage.Kind messageKind = exitCode == 0 ? WARNING : ERROR;
948
949 try {
950
951
952
953
954 boolean tokenIsAnInteger;
955 StringBuilder file = null;
956 String currentToken = null;
957
958 do {
959 if (currentToken != null) {
960 if (file == null) {
961 file = new StringBuilder(currentToken);
962 } else {
963 file.append(':').append(currentToken);
964 }
965 }
966
967 currentToken = tokens.nextToken();
968
969 tokenIsAnInteger = true;
970
971 try {
972 Integer.parseInt(currentToken);
973 } catch (NumberFormatException e) {
974 tokenIsAnInteger = false;
975 }
976 } while (!tokenIsAnInteger);
977
978 final String lineIndicator = currentToken;
979 final int startOfFileName = Objects.requireNonNull(file).toString().lastIndexOf(']');
980 if (startOfFileName > -1) {
981 file = new StringBuilder(file.substring(startOfFileName + 1 + EOL.length()));
982 }
983
984 final int line = Integer.parseInt(lineIndicator);
985 final StringBuilder msgBuffer = new StringBuilder();
986 String msg = tokens.nextToken(EOL).substring(2);
987
988
989 String prefix;
990 if ((prefix = getErrorPrefix(msg)) != null) {
991 messageKind = ERROR;
992 msg = msg.substring(prefix.length());
993 } else if ((prefix = getWarningPrefix(msg)) != null) {
994 messageKind = WARNING;
995 msg = msg.substring(prefix.length());
996 }
997 msgBuffer.append(msg).append(EOL);
998
999 String context = tokens.nextToken(EOL);
1000 String pointer = null;
1001
1002 do {
1003 final String msgLine = tokens.nextToken(EOL);
1004 if (pointer != null) {
1005 msgBuffer.append(msgLine);
1006 msgBuffer.append(EOL);
1007 } else if (msgLine.endsWith("^")) {
1008 pointer = msgLine;
1009 } else {
1010 msgBuffer.append(context);
1011 msgBuffer.append(EOL);
1012 context = msgLine;
1013 }
1014 } while (tokens.hasMoreTokens());
1015
1016 msgBuffer.append(EOL);
1017
1018 final String message = msgBuffer.toString();
1019 final int startcolumn = Objects.requireNonNull(pointer).indexOf("^");
1020 int endcolumn = (context == null) ? startcolumn : context.indexOf(" ", startcolumn);
1021 if (endcolumn == -1) {
1022 endcolumn = Objects.requireNonNull(context).length();
1023 }
1024
1025 return new CompilerMessage(
1026 file.toString(), messageKind, line, startcolumn, line, endcolumn, message.trim());
1027 } catch (NoSuchElementException e) {
1028 return new CompilerMessage("no more tokens - could not parse error message: " + error, messageKind);
1029 } catch (Exception e) {
1030 return new CompilerMessage("could not parse error message: " + error, messageKind);
1031 }
1032 }
1033
1034 private static String getMessagePrefix(String message, String[] prefixes) {
1035 for (String prefix : prefixes) {
1036 if (message.startsWith(prefix)) {
1037 return prefix;
1038 }
1039 }
1040 return null;
1041 }
1042
1043 private static String getWarningPrefix(String message) {
1044 return getMessagePrefix(message, WARNING_PREFIXES);
1045 }
1046
1047 private static String getErrorPrefix(String message) {
1048 return getMessagePrefix(message, ERROR_PREFIXES);
1049 }
1050
1051
1052
1053
1054
1055
1056
1057
1058 private File createFileWithArguments(String[] args, String outputDirectory) throws IOException {
1059 PrintWriter writer = null;
1060 try {
1061 File tempFile;
1062 if (getLog().isDebugEnabled()) {
1063 tempFile = File.createTempFile(JavacCompiler.class.getName(), "arguments", new File(outputDirectory));
1064 } else {
1065 tempFile = File.createTempFile(JavacCompiler.class.getName(), "arguments");
1066 tempFile.deleteOnExit();
1067 }
1068
1069 writer = new PrintWriter(new FileWriter(tempFile));
1070 for (String arg : args) {
1071 String argValue = arg.replace(File.separatorChar, '/');
1072 writer.write("\"" + argValue + "\"");
1073 writer.println();
1074 }
1075 writer.flush();
1076
1077 return tempFile;
1078
1079 } finally {
1080 if (writer != null) {
1081 writer.close();
1082 }
1083 }
1084 }
1085
1086
1087
1088
1089
1090
1091
1092 protected String getJavacExecutable(CompilerConfiguration config) {
1093 String executable = config.getExecutable();
1094
1095 if (StringUtils.isEmpty(executable)) {
1096 try {
1097 executable = getJavacExecutable();
1098 } catch (IOException e) {
1099 if (getLog().isWarnEnabled()) {
1100 getLog().warn("Unable to autodetect 'javac' path, using 'javac' from the environment.");
1101 }
1102 executable = "javac";
1103 }
1104 }
1105 return executable;
1106 }
1107
1108
1109
1110
1111
1112
1113
1114
1115 private static String getJavacExecutable() throws IOException {
1116 String javacCommand = "javac" + (Os.isFamily(Os.FAMILY_WINDOWS) ? ".exe" : "");
1117 String javaHome = System.getProperty("java.home");
1118 File javacExe;
1119
1120 if (Os.isName("AIX")) {
1121 javacExe = new File(javaHome + File.separator + ".." + File.separator + "sh", javacCommand);
1122 } else if (Os.isName("Mac OS X")) {
1123 javacExe = new File(javaHome + File.separator + "bin", javacCommand);
1124 } else {
1125 javacExe = new File(javaHome + File.separator + ".." + File.separator + "bin", javacCommand);
1126 }
1127
1128
1129
1130
1131 if (!javacExe.isFile()) {
1132 Properties env = CommandLineUtils.getSystemEnvVars();
1133 javaHome = env.getProperty("JAVA_HOME");
1134 if (StringUtils.isEmpty(javaHome)) {
1135 throw new IOException("The environment variable JAVA_HOME is not correctly set.");
1136 }
1137 if (!new File(javaHome).isDirectory()) {
1138 throw new IOException("The environment variable JAVA_HOME=" + javaHome
1139 + " doesn't exist or is not a valid directory.");
1140 }
1141 javacExe = new File(env.getProperty("JAVA_HOME") + File.separator + "bin", javacCommand);
1142 }
1143
1144 if (!javacExe.isFile()) {
1145 throw new IOException("The javadoc executable '" + javacExe
1146 + "' doesn't exist or is not a file. Verify the JAVA_HOME environment variable.");
1147 }
1148
1149 return javacExe.getAbsolutePath();
1150 }
1151
1152 private void releaseJavaccClass(Class<?> javaccClass, CompilerConfiguration compilerConfiguration) {
1153 if (compilerConfiguration.getCompilerReuseStrategy()
1154 == CompilerConfiguration.CompilerReuseStrategy.ReuseCreated) {
1155 javacClasses.add(javaccClass);
1156 }
1157 }
1158
1159
1160
1161
1162
1163
1164
1165 private Class<?> getJavacClass(CompilerConfiguration compilerConfiguration) throws CompilerException {
1166 Class<?> c;
1167 switch (compilerConfiguration.getCompilerReuseStrategy()) {
1168 case AlwaysNew:
1169 return createJavacClass();
1170 case ReuseCreated:
1171 c = javacClasses.poll();
1172 if (c == null) {
1173 c = createJavacClass();
1174 }
1175 return c;
1176 case ReuseSame:
1177 default:
1178 c = javacClass;
1179 if (c == null) {
1180 synchronized (this) {
1181 c = javacClass;
1182 if (c == null) {
1183 javacClass = c = createJavacClass();
1184 }
1185 }
1186 }
1187 return c;
1188 }
1189 }
1190
1191
1192
1193
1194 protected Class<?> createJavacClass() throws CompilerException {
1195 try {
1196
1197
1198 return JavacCompiler.class.getClassLoader().loadClass(JavacCompiler.JAVAC_CLASSNAME);
1199 } catch (ClassNotFoundException ex) {
1200
1201 }
1202
1203 final File toolsJar = new File(System.getProperty("java.home"), "../lib/tools.jar");
1204 if (!toolsJar.exists()) {
1205 throw new CompilerException("tools.jar not found: " + toolsJar);
1206 }
1207
1208 try {
1209
1210
1211 URL[] originalUrls = ((URLClassLoader) JavacCompiler.class.getClassLoader()).getURLs();
1212 URL[] urls = new URL[originalUrls.length + 1];
1213 urls[0] = toolsJar.toURI().toURL();
1214 System.arraycopy(originalUrls, 0, urls, 1, originalUrls.length);
1215 ClassLoader javacClassLoader = new URLClassLoader(urls);
1216
1217 final Thread thread = Thread.currentThread();
1218 final ClassLoader contextClassLoader = thread.getContextClassLoader();
1219 thread.setContextClassLoader(javacClassLoader);
1220 try {
1221
1222 return javacClassLoader.loadClass(JavacCompiler.JAVAC_CLASSNAME);
1223 } finally {
1224 thread.setContextClassLoader(contextClassLoader);
1225 }
1226 } catch (MalformedURLException ex) {
1227 throw new CompilerException(
1228 "Could not convert the file reference to tools.jar to a URL, path to tools.jar: '"
1229 + toolsJar.getAbsolutePath() + "'.",
1230 ex);
1231 } catch (ClassNotFoundException ex) {
1232 throw new CompilerException(
1233 "Unable to locate the Javac Compiler in:" + EOL + " " + toolsJar + EOL
1234 + "Please ensure you are using JDK 1.4 or above and" + EOL
1235 + "not a JRE (the com.sun.tools.javac.Main class is required)." + EOL
1236 + "In most cases you can change the location of your Java" + EOL
1237 + "installation by setting the JAVA_HOME environment variable.",
1238 ex);
1239 }
1240 }
1241 }