1 package org.codehaus.plexus.compiler.eclipse;
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 import javax.inject.Named;
27 import javax.inject.Singleton;
28 import javax.tools.Diagnostic;
29 import javax.tools.DiagnosticListener;
30 import javax.tools.JavaCompiler;
31 import javax.tools.JavaFileObject;
32 import javax.tools.StandardJavaFileManager;
33
34 import java.io.File;
35 import java.io.PrintWriter;
36 import java.io.StringWriter;
37 import java.nio.charset.Charset;
38 import java.nio.charset.IllegalCharsetNameException;
39 import java.nio.charset.UnsupportedCharsetException;
40 import java.util.ArrayList;
41 import java.util.Arrays;
42 import java.util.Iterator;
43 import java.util.List;
44 import java.util.Locale;
45 import java.util.Map.Entry;
46 import java.util.ServiceLoader;
47
48 import org.codehaus.plexus.compiler.AbstractCompiler;
49 import org.codehaus.plexus.compiler.CompilerConfiguration;
50 import org.codehaus.plexus.compiler.CompilerException;
51 import org.codehaus.plexus.compiler.CompilerMessage;
52 import org.codehaus.plexus.compiler.CompilerOutputStyle;
53 import org.codehaus.plexus.compiler.CompilerResult;
54 import org.codehaus.plexus.util.StringUtils;
55 import org.eclipse.jdt.core.compiler.CompilationProgress;
56 import org.eclipse.jdt.core.compiler.batch.BatchCompiler;
57
58
59
60
61 @Named("eclipse")
62 @Singleton
63 public class EclipseJavaCompiler extends AbstractCompiler {
64 public EclipseJavaCompiler() {
65 super(CompilerOutputStyle.ONE_OUTPUT_FILE_PER_INPUT_FILE, ".java", ".class", null);
66 }
67
68
69
70
71 boolean errorsAsWarnings = false;
72
73 @Override
74 public String getCompilerId() {
75 return "eclipse";
76 }
77
78 @Override
79 public CompilerResult performCompile(CompilerConfiguration config) throws CompilerException {
80 List<String> args = new ArrayList<>();
81 args.add("-noExit");
82
83
84 if (config.isDebug()) {
85 args.add("-preserveAllLocals");
86 args.add("-g:lines,vars,source");
87 } else {
88 args.add("-g:lines,source");
89 }
90
91 String releaseVersion = decodeVersion(config.getReleaseVersion());
92
93 if (releaseVersion != null) {
94 args.add("--release");
95 args.add(releaseVersion);
96 } else {
97 String sourceVersion = decodeVersion(config.getSourceVersion());
98
99 if (sourceVersion != null) {
100 args.add("-source");
101 args.add(sourceVersion);
102 }
103
104 String targetVersion = decodeVersion(config.getTargetVersion());
105
106 if (targetVersion != null) {
107 args.add("-target");
108 args.add(targetVersion);
109 }
110 }
111
112 if (StringUtils.isNotEmpty(config.getSourceEncoding())) {
113 args.add("-encoding");
114 args.add(config.getSourceEncoding());
115 }
116
117 if (!config.isShowWarnings()) {
118 args.add("-warn:none");
119 } else {
120 String warnings = config.getWarnings();
121 StringBuilder warns =
122 StringUtils.isEmpty(warnings) ? new StringBuilder() : new StringBuilder(warnings).append(',');
123
124 if (config.isShowDeprecation()) {
125 append(warns, "+deprecation");
126 } else {
127 append(warns, "-deprecation");
128 }
129
130
131 args.add("-warn:" + warns);
132 }
133
134 if (config.isParameters()) {
135 args.add("-parameters");
136 }
137
138 if (config.isFailOnWarning()) {
139 args.add("-failOnWarning");
140 }
141
142
143
144 this.errorsAsWarnings = processCustomArguments(config, args);
145
146
147 args.add("-d");
148 args.add(config.getOutputLocation());
149
150
151
152 List<String> classpathEntries = new ArrayList<>(config.getClasspathEntries());
153 classpathEntries.add(config.getOutputLocation());
154 args.add("-classpath");
155 args.add(getPathString(classpathEntries));
156
157 List<String> modulepathEntries = config.getModulepathEntries();
158 if (modulepathEntries != null && !modulepathEntries.isEmpty()) {
159 args.add("--module-path");
160 args.add(getPathString(modulepathEntries));
161 }
162
163
164
165 if (!isPreJava1_6(config)) {
166 File generatedSourcesDir = config.getGeneratedSourcesDirectory();
167 if (generatedSourcesDir != null) {
168 generatedSourcesDir.mkdirs();
169
170
171 args.add("-s");
172 args.add(generatedSourcesDir.getAbsolutePath());
173 }
174
175
176 String annotationProcessors = joinAnnotationProcessors(config.getAnnotationProcessors());
177 List<String> processorPathEntries = config.getProcessorPathEntries();
178 List<String> processorModulePathEntries = config.getProcessorModulePathEntries();
179
180 if (!annotationProcessors.isEmpty()
181 || (processorPathEntries != null && processorPathEntries.size() > 0)
182 || (processorModulePathEntries != null && processorModulePathEntries.size() > 0)) {
183 if (!annotationProcessors.isEmpty()) {
184 args.add("-processor");
185 args.add(annotationProcessors);
186 }
187
188 if (processorPathEntries != null && processorPathEntries.size() > 0) {
189 if (isReplaceProcessorPath(config)) {
190 args.add("--processor-module-path");
191 } else {
192 args.add("-processorpath");
193 }
194 args.add(getPathString(processorPathEntries));
195 }
196
197 if (processorModulePathEntries != null && processorModulePathEntries.size() > 0) {
198 args.add("--processor-module-path");
199 args.add(getPathString(processorModulePathEntries));
200 }
201
202 if (config.getProc() != null) {
203 args.add("-proc:" + config.getProc());
204 }
205 }
206 }
207
208
209 List<String> allSources = Arrays.asList(getSourceFiles(config));
210 List<CompilerMessage> messageList = new ArrayList<>();
211 if (allSources.isEmpty()) {
212
213 return new CompilerResult(true, messageList);
214 }
215
216 allSources = resortSourcesToPutModuleInfoFirst(allSources);
217
218 logCompiling(null, config);
219
220
221 try {
222 StringWriter sw = new StringWriter();
223 PrintWriter devNull = new PrintWriter(sw);
224 JavaCompiler compiler = getEcj();
225 boolean success = false;
226 if (compiler != null) {
227 getLog().debug("Using JSR-199 EclipseCompiler");
228
229
230
231 if (!haveSourceOrReleaseArgument(args)) {
232 getLog().debug("ecj: no source level nor release specified, defaulting to Java 8");
233 args.add("-source");
234 args.add("8");
235 }
236
237
238
239
240 String encoding = null;
241 Iterator<String> allArgs = args.iterator();
242 while (encoding == null && allArgs.hasNext()) {
243 String option = allArgs.next();
244 if ("-encoding".equals(option) && allArgs.hasNext()) {
245 encoding = allArgs.next();
246 }
247 }
248 final Locale defaultLocale = Locale.getDefault();
249 final List<CompilerMessage> messages = messageList;
250 DiagnosticListener<? super JavaFileObject> messageCollector = new DiagnosticListener<JavaFileObject>() {
251
252 @Override
253 public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
254
255 String fileName = "Unknown source";
256 try {
257 JavaFileObject file = diagnostic.getSource();
258 if (file != null) {
259 fileName = file.getName();
260 }
261 } catch (NullPointerException e) {
262
263 }
264 long startColumn = diagnostic.getColumnNumber();
265
266 long endColumn = startColumn + (diagnostic.getEndPosition() - diagnostic.getStartPosition());
267 CompilerMessage message = new CompilerMessage(
268 fileName,
269 convert(diagnostic.getKind()),
270 (int) diagnostic.getLineNumber(),
271 (int) startColumn,
272 (int) diagnostic.getLineNumber(),
273 (int) endColumn,
274 diagnostic.getMessage(defaultLocale));
275 messages.add(message);
276 }
277 };
278 Charset charset = null;
279 if (encoding != null) {
280 encoding = encoding.trim();
281 try {
282 charset = Charset.forName(encoding);
283 } catch (IllegalCharsetNameException | UnsupportedCharsetException e) {
284 getLog().warn("ecj: invalid or unsupported character set '" + encoding + "', using default");
285
286 }
287 }
288 if (charset == null) {
289 charset = Charset.defaultCharset();
290 }
291 if (getLog().isDebugEnabled()) {
292 getLog().debug("ecj: using character set " + charset.displayName());
293 getLog().debug("ecj command line: " + args);
294 getLog().debug("ecj input source files: " + allSources);
295 }
296
297 try (StandardJavaFileManager manager =
298 compiler.getStandardFileManager(messageCollector, defaultLocale, charset)) {
299 Iterable<? extends JavaFileObject> units = manager.getJavaFileObjectsFromStrings(allSources);
300 success =
301 Boolean.TRUE.equals(compiler.getTask(devNull, manager, messageCollector, args, null, units)
302 .call());
303 } catch (RuntimeException e) {
304 throw new EcjFailureException(e.getLocalizedMessage());
305 }
306 getLog().debug(sw.toString());
307 } else {
308
309 File errorF = null;
310 try {
311 errorF = File.createTempFile("ecjerr-", ".xml");
312 getLog().debug("Using legacy BatchCompiler; error file " + errorF);
313
314 args.add("-log");
315 args.add(errorF.toString());
316 args.addAll(allSources);
317
318 getLog().debug("ecj command line: " + args);
319
320 success = BatchCompiler.compile(
321 args.toArray(new String[args.size()]), devNull, devNull, new CompilationProgress() {
322 @Override
323 public void begin(int i) {}
324
325 @Override
326 public void done() {}
327
328 @Override
329 public boolean isCanceled() {
330 return false;
331 }
332
333 @Override
334 public void setTaskName(String s) {}
335
336 @Override
337 public void worked(int i, int i1) {}
338 });
339 getLog().debug(sw.toString());
340
341 if (errorF.length() < 80) {
342 throw new EcjFailureException(sw.toString());
343 }
344 messageList = new EcjResponseParser().parse(errorF, errorsAsWarnings);
345 } finally {
346 if (null != errorF) {
347 try {
348 errorF.delete();
349 } catch (Exception x) {
350 }
351 }
352 }
353 }
354 boolean hasError = false;
355 for (CompilerMessage compilerMessage : messageList) {
356 if (compilerMessage.isError()) {
357 hasError = true;
358 break;
359 }
360 }
361 if (!hasError && !success && !errorsAsWarnings) {
362 CompilerMessage.Kind kind =
363 errorsAsWarnings ? CompilerMessage.Kind.WARNING : CompilerMessage.Kind.ERROR;
364
365
366
367 CompilerMessage cm = new CompilerMessage(
368 "[ecj] The compiler reported an error but has not written it to its logging", kind);
369 messageList.add(cm);
370 hasError = true;
371
372
373 String stdout = getLastLines(sw.toString(), 5);
374 if (stdout.length() > 0) {
375 cm = new CompilerMessage("[ecj] The following line(s) might indicate the issue:\n" + stdout, kind);
376 messageList.add(cm);
377 }
378 }
379 return new CompilerResult(!hasError || errorsAsWarnings, messageList);
380 } catch (EcjFailureException x) {
381 throw x;
382 } catch (Exception x) {
383 throw new RuntimeException(x);
384 }
385 }
386
387 private static final String OPT_REPLACE_PROCESSOR_PATH = "replaceProcessorPathWithProcessorModulePath";
388 private static final String OPT_REPLACE_PROCESSOR_PATH_ = "-" + OPT_REPLACE_PROCESSOR_PATH;
389
390 static boolean isReplaceProcessorPath(CompilerConfiguration config) {
391 for (Entry<String, String> entry : config.getCustomCompilerArgumentsEntries()) {
392 String opt = entry.getKey();
393 if (opt.equals(OPT_REPLACE_PROCESSOR_PATH) || opt.equals(OPT_REPLACE_PROCESSOR_PATH_)) {
394 return true;
395 }
396 }
397 return false;
398 }
399
400 static List<String> resortSourcesToPutModuleInfoFirst(List<String> allSources) {
401 List<String> resorted = new ArrayList<>(allSources.size());
402
403 for (String mi : allSources) {
404 if (mi.endsWith("module-info.java")) {
405 resorted.add(mi);
406 }
407 }
408
409 for (String nmi : allSources) {
410 if (!nmi.endsWith("module-info.java")) {
411 resorted.add(nmi);
412 }
413 }
414
415 return resorted;
416 }
417
418 static boolean processCustomArguments(CompilerConfiguration config, List<String> args) {
419 boolean result = false;
420
421 for (Entry<String, String> entry : config.getCustomCompilerArgumentsEntries()) {
422 String opt = entry.getKey();
423 String optionValue = entry.getValue();
424
425
426 if (opt.equals("errorsAsWarnings") || opt.equals("-errorsAsWarnings")) {
427 result = true;
428 continue;
429 }
430
431 if (opt.equals("-properties")) {
432 if (null != optionValue) {
433 File propFile = new File(optionValue);
434 if (!propFile.exists() || !propFile.isFile()) {
435 throw new IllegalArgumentException(
436 "Properties file specified by -properties " + propFile + " does not exist");
437 }
438 }
439 }
440
441
442
443 if (opt.equals("-proceedOnError")) {
444
445 args.add("-proceedOnError:Fatal");
446 continue;
447 }
448
449 if (!opt.equals(OPT_REPLACE_PROCESSOR_PATH) && !opt.equals(OPT_REPLACE_PROCESSOR_PATH_)) {
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477 if (null == optionValue) {
478
479 args.add(opt);
480 } else {
481 if (!opt.startsWith("-")) {
482 opt = "-" + opt;
483 }
484 args.add(opt);
485 args.add(optionValue);
486 }
487 }
488 }
489 return result;
490 }
491
492 private static boolean haveSourceOrReleaseArgument(List<String> args) {
493 Iterator<String> allArgs = args.iterator();
494 while (allArgs.hasNext()) {
495 String option = allArgs.next();
496 if (("-source".equals(option) || "--release".equals(option)) && allArgs.hasNext()) {
497 return true;
498 }
499 }
500 return false;
501 }
502
503 private JavaCompiler getEcj() {
504 ServiceLoader<JavaCompiler> javaCompilerLoader =
505 ServiceLoader.load(JavaCompiler.class, BatchCompiler.class.getClassLoader());
506 Class<?> c = null;
507 try {
508 c = Class.forName(
509 "org.eclipse.jdt.internal.compiler.tool.EclipseCompiler",
510 false,
511 BatchCompiler.class.getClassLoader());
512 } catch (ClassNotFoundException e) {
513
514 }
515 if (c != null) {
516 for (JavaCompiler javaCompiler : javaCompilerLoader) {
517 if (c.isInstance(javaCompiler)) {
518 return javaCompiler;
519 }
520 }
521 }
522 getLog().debug("Cannot find org.eclipse.jdt.internal.compiler.tool.EclipseCompiler");
523 return null;
524 }
525
526 private CompilerMessage.Kind convert(Diagnostic.Kind kind) {
527 if (kind == null) {
528 return CompilerMessage.Kind.OTHER;
529 }
530 switch (kind) {
531 case ERROR:
532 return errorsAsWarnings ? CompilerMessage.Kind.WARNING : CompilerMessage.Kind.ERROR;
533 case WARNING:
534 return CompilerMessage.Kind.WARNING;
535 case MANDATORY_WARNING:
536 return CompilerMessage.Kind.MANDATORY_WARNING;
537 case NOTE:
538 return CompilerMessage.Kind.NOTE;
539 case OTHER:
540 default:
541 return CompilerMessage.Kind.OTHER;
542 }
543 }
544
545 private String getLastLines(String text, int lines) {
546 List<String> lineList = new ArrayList<>();
547 text = text.replace("\r\n", "\n");
548 text = text.replace("\r", "\n");
549
550 int index = text.length();
551 while (index > 0) {
552 int before = text.lastIndexOf('\n', index - 1);
553
554 if (before + 1 < index) {
555 lineList.add(text.substring(before + 1, index));
556 lines--;
557 if (lines <= 0) {
558 break;
559 }
560 }
561
562 index = before;
563 }
564
565 StringBuilder sb = new StringBuilder();
566 for (int i = lineList.size() - 1; i >= 0; i--) {
567 String s = lineList.get(i);
568 sb.append(s);
569 sb.append(System.getProperty("line.separator"));
570 }
571 return sb.toString();
572 }
573
574 private static void append(StringBuilder warns, String s) {
575 if (warns.length() > 0) {
576 warns.append(',');
577 }
578 warns.append(s);
579 }
580
581 private boolean isPreJava1_6(CompilerConfiguration config) {
582 String s = config.getSourceVersion();
583 if (s == null) {
584
585 return true;
586 }
587 return s.startsWith("1.5")
588 || s.startsWith("1.4")
589 || s.startsWith("1.3")
590 || s.startsWith("1.2")
591 || s.startsWith("1.1")
592 || s.startsWith("1.0");
593 }
594
595 @Override
596 public String[] createCommandLine(CompilerConfiguration config) throws CompilerException {
597 return null;
598 }
599
600 @Override
601 public boolean supportsIncrementalCompilation() {
602 return true;
603 }
604
605
606
607
608
609
610 private String decodeVersion(String versionSpec) {
611 if (StringUtils.isEmpty(versionSpec)) {
612 return null;
613 }
614
615 if (versionSpec.equals("1.9")) {
616 getLog().warn("Version 9 should be specified as 9, not 1.9");
617 return "9";
618 }
619 return versionSpec;
620 }
621 }