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