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 = config.getAnnotationProcessors();
177 List<String> processorPathEntries = config.getProcessorPathEntries();
178 List<String> processorModulePathEntries = config.getProcessorModulePathEntries();
179
180 if ((annotationProcessors != null && annotationProcessors.length > 0)
181 || (processorPathEntries != null && processorPathEntries.size() > 0)
182 || (processorModulePathEntries != null && processorModulePathEntries.size() > 0)) {
183 if (annotationProcessors != null && annotationProcessors.length > 0) {
184 args.add("-processor");
185 StringBuilder sb = new StringBuilder();
186 for (String ap : annotationProcessors) {
187 if (sb.length() > 0) {
188 sb.append(',');
189 }
190 sb.append(ap);
191 }
192 args.add(sb.toString());
193 }
194
195 if (processorPathEntries != null && processorPathEntries.size() > 0) {
196 if (isReplaceProcessorPath(config)) {
197 args.add("--processor-module-path");
198 } else {
199 args.add("-processorpath");
200 }
201 args.add(getPathString(processorPathEntries));
202 }
203
204 if (processorModulePathEntries != null && processorModulePathEntries.size() > 0) {
205 args.add("--processor-module-path");
206 args.add(getPathString(processorModulePathEntries));
207 }
208
209 if (config.getProc() != null) {
210 args.add("-proc:" + config.getProc());
211 }
212 }
213 }
214
215
216 List<String> allSources = Arrays.asList(getSourceFiles(config));
217 List<CompilerMessage> messageList = new ArrayList<>();
218 if (allSources.isEmpty()) {
219
220 return new CompilerResult(true, messageList);
221 }
222
223 allSources = resortSourcesToPutModuleInfoFirst(allSources);
224
225 logCompiling(null, config);
226
227
228 try {
229 StringWriter sw = new StringWriter();
230 PrintWriter devNull = new PrintWriter(sw);
231 JavaCompiler compiler = getEcj();
232 boolean success = false;
233 if (compiler != null) {
234 getLog().debug("Using JSR-199 EclipseCompiler");
235
236
237
238 if (!haveSourceOrReleaseArgument(args)) {
239 getLog().debug("ecj: no source level nor release specified, defaulting to Java 1.3");
240 args.add("-source");
241 args.add("1.3");
242 }
243
244
245
246
247 String encoding = null;
248 Iterator<String> allArgs = args.iterator();
249 while (encoding == null && allArgs.hasNext()) {
250 String option = allArgs.next();
251 if ("-encoding".equals(option) && allArgs.hasNext()) {
252 encoding = allArgs.next();
253 }
254 }
255 final Locale defaultLocale = Locale.getDefault();
256 final List<CompilerMessage> messages = messageList;
257 DiagnosticListener<? super JavaFileObject> messageCollector = new DiagnosticListener<JavaFileObject>() {
258
259 @Override
260 public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
261
262 String fileName = "Unknown source";
263 try {
264 JavaFileObject file = diagnostic.getSource();
265 if (file != null) {
266 fileName = file.getName();
267 }
268 } catch (NullPointerException e) {
269
270 }
271 long startColumn = diagnostic.getColumnNumber();
272
273 long endColumn = startColumn + (diagnostic.getEndPosition() - diagnostic.getStartPosition());
274 CompilerMessage message = new CompilerMessage(
275 fileName,
276 convert(diagnostic.getKind()),
277 (int) diagnostic.getLineNumber(),
278 (int) startColumn,
279 (int) diagnostic.getLineNumber(),
280 (int) endColumn,
281 diagnostic.getMessage(defaultLocale));
282 messages.add(message);
283 }
284 };
285 Charset charset = null;
286 if (encoding != null) {
287 encoding = encoding.trim();
288 try {
289 charset = Charset.forName(encoding);
290 } catch (IllegalCharsetNameException | UnsupportedCharsetException e) {
291 getLog().warn("ecj: invalid or unsupported character set '" + encoding + "', using default");
292
293 }
294 }
295 if (charset == null) {
296 charset = Charset.defaultCharset();
297 }
298 if (getLog().isDebugEnabled()) {
299 getLog().debug("ecj: using character set " + charset.displayName());
300 getLog().debug("ecj command line: " + args);
301 getLog().debug("ecj input source files: " + allSources);
302 }
303
304 try (StandardJavaFileManager manager =
305 compiler.getStandardFileManager(messageCollector, defaultLocale, charset)) {
306 Iterable<? extends JavaFileObject> units = manager.getJavaFileObjectsFromStrings(allSources);
307 success =
308 Boolean.TRUE.equals(compiler.getTask(devNull, manager, messageCollector, args, null, units)
309 .call());
310 } catch (RuntimeException e) {
311 throw new EcjFailureException(e.getLocalizedMessage());
312 }
313 getLog().debug(sw.toString());
314 } else {
315
316 File errorF = null;
317 try {
318 errorF = File.createTempFile("ecjerr-", ".xml");
319 getLog().debug("Using legacy BatchCompiler; error file " + errorF);
320
321 args.add("-log");
322 args.add(errorF.toString());
323 args.addAll(allSources);
324
325 getLog().debug("ecj command line: " + args);
326
327 success = BatchCompiler.compile(
328 args.toArray(new String[args.size()]), devNull, devNull, new CompilationProgress() {
329 @Override
330 public void begin(int i) {}
331
332 @Override
333 public void done() {}
334
335 @Override
336 public boolean isCanceled() {
337 return false;
338 }
339
340 @Override
341 public void setTaskName(String s) {}
342
343 @Override
344 public void worked(int i, int i1) {}
345 });
346 getLog().debug(sw.toString());
347
348 if (errorF.length() < 80) {
349 throw new EcjFailureException(sw.toString());
350 }
351 messageList = new EcjResponseParser().parse(errorF, errorsAsWarnings);
352 } finally {
353 if (null != errorF) {
354 try {
355 errorF.delete();
356 } catch (Exception x) {
357 }
358 }
359 }
360 }
361 boolean hasError = false;
362 for (CompilerMessage compilerMessage : messageList) {
363 if (compilerMessage.isError()) {
364 hasError = true;
365 break;
366 }
367 }
368 if (!hasError && !success && !errorsAsWarnings) {
369 CompilerMessage.Kind kind =
370 errorsAsWarnings ? CompilerMessage.Kind.WARNING : CompilerMessage.Kind.ERROR;
371
372
373
374 CompilerMessage cm = new CompilerMessage(
375 "[ecj] The compiler reported an error but has not written it to its logging", kind);
376 messageList.add(cm);
377 hasError = true;
378
379
380 String stdout = getLastLines(sw.toString(), 5);
381 if (stdout.length() > 0) {
382 cm = new CompilerMessage("[ecj] The following line(s) might indicate the issue:\n" + stdout, kind);
383 messageList.add(cm);
384 }
385 }
386 return new CompilerResult(!hasError || errorsAsWarnings, messageList);
387 } catch (EcjFailureException x) {
388 throw x;
389 } catch (Exception x) {
390 throw new RuntimeException(x);
391 }
392 }
393
394 private static final String OPT_REPLACE_PROCESSOR_PATH = "replaceProcessorPathWithProcessorModulePath";
395 private static final String OPT_REPLACE_PROCESSOR_PATH_ = "-" + OPT_REPLACE_PROCESSOR_PATH;
396
397 static boolean isReplaceProcessorPath(CompilerConfiguration config) {
398 for (Entry<String, String> entry : config.getCustomCompilerArgumentsEntries()) {
399 String opt = entry.getKey();
400 if (opt.equals(OPT_REPLACE_PROCESSOR_PATH) || opt.equals(OPT_REPLACE_PROCESSOR_PATH_)) {
401 return true;
402 }
403 }
404 return false;
405 }
406
407 static List<String> resortSourcesToPutModuleInfoFirst(List<String> allSources) {
408 List<String> resorted = new ArrayList<>(allSources.size());
409
410 for (String mi : allSources) {
411 if (mi.endsWith("module-info.java")) {
412 resorted.add(mi);
413 }
414 }
415
416 for (String nmi : allSources) {
417 if (!nmi.endsWith("module-info.java")) {
418 resorted.add(nmi);
419 }
420 }
421
422 return resorted;
423 }
424
425 static boolean processCustomArguments(CompilerConfiguration config, List<String> args) {
426 boolean result = false;
427
428 for (Entry<String, String> entry : config.getCustomCompilerArgumentsEntries()) {
429 String opt = entry.getKey();
430 String optionValue = entry.getValue();
431
432
433 if (opt.equals("errorsAsWarnings") || opt.equals("-errorsAsWarnings")) {
434 result = true;
435 continue;
436 }
437
438 if (opt.equals("-properties")) {
439 if (null != optionValue) {
440 File propFile = new File(optionValue);
441 if (!propFile.exists() || !propFile.isFile()) {
442 throw new IllegalArgumentException(
443 "Properties file specified by -properties " + propFile + " does not exist");
444 }
445 }
446 }
447
448
449
450 if (opt.equals("-proceedOnError")) {
451
452 args.add("-proceedOnError:Fatal");
453 continue;
454 }
455
456 if (!opt.equals(OPT_REPLACE_PROCESSOR_PATH) && !opt.equals(OPT_REPLACE_PROCESSOR_PATH_)) {
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484 if (null == optionValue) {
485
486 args.add(opt);
487 } else {
488 if (!opt.startsWith("-")) {
489 opt = "-" + opt;
490 }
491 args.add(opt);
492 args.add(optionValue);
493 }
494 }
495 }
496 return result;
497 }
498
499 private static boolean haveSourceOrReleaseArgument(List<String> args) {
500 Iterator<String> allArgs = args.iterator();
501 while (allArgs.hasNext()) {
502 String option = allArgs.next();
503 if (("-source".equals(option) || "--release".equals(option)) && allArgs.hasNext()) {
504 return true;
505 }
506 }
507 return false;
508 }
509
510 private JavaCompiler getEcj() {
511 ServiceLoader<JavaCompiler> javaCompilerLoader =
512 ServiceLoader.load(JavaCompiler.class, BatchCompiler.class.getClassLoader());
513 Class<?> c = null;
514 try {
515 c = Class.forName(
516 "org.eclipse.jdt.internal.compiler.tool.EclipseCompiler",
517 false,
518 BatchCompiler.class.getClassLoader());
519 } catch (ClassNotFoundException e) {
520
521 }
522 if (c != null) {
523 for (JavaCompiler javaCompiler : javaCompilerLoader) {
524 if (c.isInstance(javaCompiler)) {
525 return javaCompiler;
526 }
527 }
528 }
529 getLog().debug("Cannot find org.eclipse.jdt.internal.compiler.tool.EclipseCompiler");
530 return null;
531 }
532
533 private CompilerMessage.Kind convert(Diagnostic.Kind kind) {
534 if (kind == null) {
535 return CompilerMessage.Kind.OTHER;
536 }
537 switch (kind) {
538 case ERROR:
539 return errorsAsWarnings ? CompilerMessage.Kind.WARNING : CompilerMessage.Kind.ERROR;
540 case WARNING:
541 return CompilerMessage.Kind.WARNING;
542 case MANDATORY_WARNING:
543 return CompilerMessage.Kind.MANDATORY_WARNING;
544 case NOTE:
545 return CompilerMessage.Kind.NOTE;
546 case OTHER:
547 default:
548 return CompilerMessage.Kind.OTHER;
549 }
550 }
551
552 private String getLastLines(String text, int lines) {
553 List<String> lineList = new ArrayList<>();
554 text = text.replace("\r\n", "\n");
555 text = text.replace("\r", "\n");
556
557 int index = text.length();
558 while (index > 0) {
559 int before = text.lastIndexOf('\n', index - 1);
560
561 if (before + 1 < index) {
562 lineList.add(text.substring(before + 1, index));
563 lines--;
564 if (lines <= 0) {
565 break;
566 }
567 }
568
569 index = before;
570 }
571
572 StringBuilder sb = new StringBuilder();
573 for (int i = lineList.size() - 1; i >= 0; i--) {
574 String s = lineList.get(i);
575 sb.append(s);
576 sb.append(System.getProperty("line.separator"));
577 }
578 return sb.toString();
579 }
580
581 private static void append(StringBuilder warns, String s) {
582 if (warns.length() > 0) {
583 warns.append(',');
584 }
585 warns.append(s);
586 }
587
588 private boolean isPreJava1_6(CompilerConfiguration config) {
589 String s = config.getSourceVersion();
590 if (s == null) {
591
592 return true;
593 }
594 return s.startsWith("1.5")
595 || s.startsWith("1.4")
596 || s.startsWith("1.3")
597 || s.startsWith("1.2")
598 || s.startsWith("1.1")
599 || s.startsWith("1.0");
600 }
601
602 @Override
603 public String[] createCommandLine(CompilerConfiguration config) throws CompilerException {
604 return null;
605 }
606
607 @Override
608 public boolean supportsIncrementalCompilation() {
609 return true;
610 }
611
612
613
614
615
616
617 private String decodeVersion(String versionSpec) {
618 if (StringUtils.isEmpty(versionSpec)) {
619 return null;
620 }
621
622 if (versionSpec.equals("1.9")) {
623 getLog().warn("Version 9 should be specified as 9, not 1.9");
624 return "9";
625 }
626 return versionSpec;
627 }
628 }