View Javadoc
1   package org.apache.maven.plugin.compiler;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.lang.reflect.InvocationTargetException;
26  import java.lang.reflect.Method;
27  import java.nio.charset.Charset;
28  import java.nio.file.Files;
29  import java.nio.file.Path;
30  import java.nio.file.Paths;
31  import java.util.ArrayList;
32  import java.util.Arrays;
33  import java.util.Collections;
34  import java.util.Date;
35  import java.util.HashSet;
36  import java.util.Iterator;
37  import java.util.LinkedHashSet;
38  import java.util.List;
39  import java.util.Map;
40  import java.util.Properties;
41  import java.util.Set;
42  
43  import org.apache.maven.artifact.Artifact;
44  import org.apache.maven.artifact.DefaultArtifact;
45  import org.apache.maven.artifact.handler.ArtifactHandler;
46  import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
47  import org.apache.maven.artifact.resolver.ArtifactResolutionRequest;
48  import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
49  import org.apache.maven.artifact.resolver.ResolutionErrorHandler;
50  import org.apache.maven.artifact.versioning.VersionRange;
51  import org.apache.maven.execution.MavenSession;
52  import org.apache.maven.plugin.AbstractMojo;
53  import org.apache.maven.plugin.MojoExecution;
54  import org.apache.maven.plugin.MojoExecutionException;
55  import org.apache.maven.plugins.annotations.Component;
56  import org.apache.maven.plugins.annotations.Parameter;
57  import org.apache.maven.project.MavenProject;
58  import org.apache.maven.repository.RepositorySystem;
59  import org.apache.maven.shared.incremental.IncrementalBuildHelper;
60  import org.apache.maven.shared.incremental.IncrementalBuildHelperRequest;
61  import org.apache.maven.shared.utils.ReaderFactory;
62  import org.apache.maven.shared.utils.StringUtils;
63  import org.apache.maven.shared.utils.io.FileUtils;
64  import org.apache.maven.shared.utils.logging.MessageBuilder;
65  import org.apache.maven.shared.utils.logging.MessageUtils;
66  import org.apache.maven.toolchain.Toolchain;
67  import org.apache.maven.toolchain.ToolchainManager;
68  import org.codehaus.plexus.compiler.Compiler;
69  import org.codehaus.plexus.compiler.CompilerConfiguration;
70  import org.codehaus.plexus.compiler.CompilerError;
71  import org.codehaus.plexus.compiler.CompilerException;
72  import org.codehaus.plexus.compiler.CompilerMessage;
73  import org.codehaus.plexus.compiler.CompilerNotImplementedException;
74  import org.codehaus.plexus.compiler.CompilerOutputStyle;
75  import org.codehaus.plexus.compiler.CompilerResult;
76  import org.codehaus.plexus.compiler.manager.CompilerManager;
77  import org.codehaus.plexus.compiler.manager.NoSuchCompilerException;
78  import org.codehaus.plexus.compiler.util.scan.InclusionScanException;
79  import org.codehaus.plexus.compiler.util.scan.SourceInclusionScanner;
80  import org.codehaus.plexus.compiler.util.scan.mapping.SingleTargetSourceMapping;
81  import org.codehaus.plexus.compiler.util.scan.mapping.SourceMapping;
82  import org.codehaus.plexus.compiler.util.scan.mapping.SuffixMapping;
83  import org.codehaus.plexus.languages.java.jpms.JavaModuleDescriptor;
84  import org.codehaus.plexus.languages.java.version.JavaVersion;
85  import org.objectweb.asm.ClassWriter;
86  import org.objectweb.asm.Opcodes;
87  
88  /**
89   * TODO: At least one step could be optimized, currently the plugin will do two
90   * scans of all the source code if the compiler has to have the entire set of
91   * sources. This is currently the case for at least the C# compiler and most
92   * likely all the other .NET compilers too.
93   *
94   * @author others
95   * @author <a href="mailto:trygvis@inamo.no">Trygve Laugst&oslash;l</a>
96   * @since 2.0
97   */
98  public abstract class AbstractCompilerMojo
99      extends AbstractMojo
100 {
101     protected static final String PS = System.getProperty( "path.separator" );
102 
103     static final String DEFAULT_SOURCE = "1.7";
104     
105     static final String DEFAULT_TARGET = "1.7";
106     
107     // Used to compare with older targets
108     static final String MODULE_INFO_TARGET = "1.9";
109     
110     // ----------------------------------------------------------------------
111     // Configurables
112     // ----------------------------------------------------------------------
113 
114     /**
115      * Indicates whether the build will continue even if there are compilation errors.
116      *
117      * @since 2.0.2
118      */
119     @Parameter( property = "maven.compiler.failOnError", defaultValue = "true" )
120     private boolean failOnError = true;
121     
122     /**
123      * Indicates whether the build will continue even if there are compilation warnings.
124      *
125      * @since 3.6
126      */
127     @Parameter( property = "maven.compiler.failOnWarning", defaultValue = "false" )
128     private boolean failOnWarning;  
129 
130     /**
131      * Set to <code>true</code> to include debugging information in the compiled class files.
132      */
133     @Parameter( property = "maven.compiler.debug", defaultValue = "true" )
134     private boolean debug = true;
135 
136     /**
137      * Set to <code>true</code> to generate metadata for reflection on method parameters.
138      * @since 3.6.2
139      */
140     @Parameter( property = "maven.compiler.parameters", defaultValue = "false" )
141     private boolean parameters;
142 
143     /**
144      * Set to <code>true</code> to show messages about what the compiler is doing.
145      */
146     @Parameter( property = "maven.compiler.verbose", defaultValue = "false" )
147     private boolean verbose;
148 
149     /**
150      * Sets whether to show source locations where deprecated APIs are used.
151      */
152     @Parameter( property = "maven.compiler.showDeprecation", defaultValue = "false" )
153     private boolean showDeprecation;
154 
155     /**
156      * Set to <code>true</code> to optimize the compiled code using the compiler's optimization methods.
157      * @deprecated This property is a no-op in {@code javac}.
158      */
159     @Deprecated
160     @Parameter( property = "maven.compiler.optimize", defaultValue = "false" )
161     private boolean optimize;
162 
163     /**
164      * Set to <code>true</code> to show compilation warnings.
165      */
166     @Parameter( property = "maven.compiler.showWarnings", defaultValue = "false" )
167     private boolean showWarnings;
168 
169     /**
170      * <p>The -source argument for the Java compiler.</p>
171      * 
172      * <b>NOTE: </b>Since 3.8.0 the default value has changed from 1.5 to 1.6.
173      * Since 3.9.0 the default value has changed from 1.6 to 1.7
174      */
175     @Parameter( property = "maven.compiler.source", defaultValue = DEFAULT_SOURCE )
176     protected String source;
177 
178     /**
179      * <p>The -target argument for the Java compiler.</p>
180      * 
181      * <b>NOTE: </b>Since 3.8.0 the default value has changed from 1.5 to 1.6.
182      * Since 3.9.0 the default value has changed from 1.6 to 1.7
183      */
184     @Parameter( property = "maven.compiler.target", defaultValue = DEFAULT_TARGET )
185     protected String target;
186 
187     /**
188      * The -release argument for the Java compiler, supported since Java9
189      * 
190      * @since 3.6
191      */
192     @Parameter( property = "maven.compiler.release" )
193     protected String release;
194     
195     /**
196      * The -encoding argument for the Java compiler.
197      *
198      * @since 2.1
199      */
200     @Parameter( property = "encoding", defaultValue = "${project.build.sourceEncoding}" )
201     private String encoding;
202 
203     /**
204      * Sets the granularity in milliseconds of the last modification
205      * date for testing whether a source needs recompilation.
206      */
207     @Parameter( property = "lastModGranularityMs", defaultValue = "0" )
208     private int staleMillis;
209 
210     /**
211      * The compiler id of the compiler to use. See this
212      * <a href="non-javac-compilers.html">guide</a> for more information.
213      */
214     @Parameter( property = "maven.compiler.compilerId", defaultValue = "javac" )
215     private String compilerId;
216 
217     /**
218      * Version of the compiler to use, ex. "1.3", "1.5", if {@link #fork} is set to <code>true</code>.
219      */
220     @Parameter( property = "maven.compiler.compilerVersion" )
221     private String compilerVersion;
222 
223     /**
224      * Allows running the compiler in a separate process.
225      * If <code>false</code> it uses the built in compiler, while if <code>true</code> it will use an executable.
226      */
227     @Parameter( property = "maven.compiler.fork", defaultValue = "false" )
228     private boolean fork;
229 
230     /**
231      * Initial size, in megabytes, of the memory allocation pool, ex. "64", "64m"
232      * if {@link #fork} is set to <code>true</code>.
233      *
234      * @since 2.0.1
235      */
236     @Parameter( property = "maven.compiler.meminitial" )
237     private String meminitial;
238 
239     /**
240      * Sets the maximum size, in megabytes, of the memory allocation pool, ex. "128", "128m"
241      * if {@link #fork} is set to <code>true</code>.
242      *
243      * @since 2.0.1
244      */
245     @Parameter( property = "maven.compiler.maxmem" )
246     private String maxmem;
247 
248     /**
249      * Sets the executable of the compiler to use when {@link #fork} is <code>true</code>.
250      */
251     @Parameter( property = "maven.compiler.executable" )
252     private String executable;
253 
254     /**
255      * <p>
256      * Sets whether annotation processing is performed or not. Only applies to JDK 1.6+
257      * If not set, both compilation and annotation processing are performed at the same time.
258      * </p>
259      * <p>Allowed values are:</p>
260      * <ul>
261      * <li><code>none</code> - no annotation processing is performed.</li>
262      * <li><code>only</code> - only annotation processing is done, no compilation.</li>
263      * </ul>
264      *
265      * @since 2.2
266      */
267     @Parameter
268     private String proc;
269 
270     /**
271      * <p>
272      * Names of annotation processors to run. Only applies to JDK 1.6+
273      * If not set, the default annotation processors discovery process applies.
274      * </p>
275      *
276      * @since 2.2
277      */
278     @Parameter
279     private String[] annotationProcessors;
280 
281     /**
282      * <p>
283      * Classpath elements to supply as annotation processor path. If specified, the compiler will detect annotation
284      * processors only in those classpath elements. If omitted, the default classpath is used to detect annotation
285      * processors. The detection itself depends on the configuration of {@code annotationProcessors}.
286      * </p>
287      * <p>
288      * Each classpath element is specified using their Maven coordinates (groupId, artifactId, version, classifier,
289      * type). Transitive dependencies are added automatically. Example:
290      * </p>
291      *
292      * <pre>
293      * &lt;configuration&gt;
294      *   &lt;annotationProcessorPaths&gt;
295      *     &lt;path&gt;
296      *       &lt;groupId&gt;org.sample&lt;/groupId&gt;
297      *       &lt;artifactId&gt;sample-annotation-processor&lt;/artifactId&gt;
298      *       &lt;version&gt;1.2.3&lt;/version&gt;
299      *     &lt;/path&gt;
300      *     &lt;!-- ... more ... --&gt;
301      *   &lt;/annotationProcessorPaths&gt;
302      * &lt;/configuration&gt;
303      * </pre>
304      *
305      * @since 3.5
306      */
307     @Parameter
308     private List<DependencyCoordinate> annotationProcessorPaths;
309 
310     /**
311      * <p>
312      * Sets the arguments to be passed to the compiler (prepending a dash).
313      * </p>
314      * <p>
315      * This is because the list of valid arguments passed to a Java compiler varies based on the compiler version.
316      * </p>
317      * <p>
318      * Note that {@code -J} options are only passed through if {@link #fork} is set to {@code true}.
319      * </p>
320      * <p>
321      * To pass <code>-Xmaxerrs 1000 -Xlint -Xlint:-path -Averbose=true</code> you should include the following:
322      * </p>
323      *
324      * <pre>
325      * &lt;compilerArguments&gt;
326      *   &lt;Xmaxerrs&gt;1000&lt;/Xmaxerrs&gt;
327      *   &lt;Xlint/&gt;
328      *   &lt;Xlint:-path/&gt;
329      *   &lt;Averbose&gt;true&lt;/Averbose&gt;
330      * &lt;/compilerArguments&gt;
331      * </pre>
332      *
333      * @since 2.0.1
334      * @deprecated use {@link #compilerArgs} instead.
335      */
336     @Parameter
337     @Deprecated
338     protected Map<String, String> compilerArguments;
339 
340     /**
341      * <p>
342      * Sets the arguments to be passed to the compiler.
343      * </p>
344      * <p>
345      * Note that {@code -J} options are only passed through if {@link #fork} is set to {@code true}.
346      * </p>
347      * Example:
348      * <pre>
349      * &lt;compilerArgs&gt;
350      *   &lt;arg&gt;-Xmaxerrs&lt;/arg&gt;
351      *   &lt;arg&gt;1000&lt;/arg&gt;
352      *   &lt;arg&gt;-Xlint&lt;/arg&gt;
353      *   &lt;arg&gt;-J-Duser.language=en_us&lt;/arg&gt;
354      * &lt;/compilerArgs&gt;
355      * </pre>
356      *
357      * @since 3.1
358      */
359     @Parameter
360     protected List<String> compilerArgs;
361 
362     /**
363      * <p>
364      * Sets the unformatted single argument string to be passed to the compiler. To pass multiple arguments such as
365      * <code>-Xmaxerrs 1000</code> (which are actually two arguments) you have to use {@link #compilerArguments}.
366      * </p>
367      * <p>
368      * This is because the list of valid arguments passed to a Java compiler varies based on the compiler version.
369      * </p>
370      * <p>
371      * Note that {@code -J} options are only passed through if {@link #fork} is set to {@code true}.
372      * </p>
373      */
374     @Parameter
375     protected String compilerArgument;
376 
377     /**
378      * Sets the name of the output file when compiling a set of
379      * sources to a single file.
380      * <p/>
381      * expression="${project.build.finalName}"
382      */
383     @Parameter
384     private String outputFileName;
385 
386     /**
387      * Keyword list to be appended to the <code>-g</code> command-line switch. Legal values are none or a
388      * comma-separated list of the following keywords: <code>lines</code>, <code>vars</code>, and <code>source</code>.
389      * If debug level is not specified, by default, nothing will be appended to <code>-g</code>.
390      * If debug is not turned on, this attribute will be ignored.
391      *
392      * @since 2.1
393      */
394     @Parameter( property = "maven.compiler.debuglevel" )
395     private String debuglevel;
396 
397     /**
398      *
399      */
400     @Component
401     private ToolchainManager toolchainManager;
402 
403     /**
404      * <p>
405      * Specify the requirements for this jdk toolchain for using a different {@code javac} than the one of the JRE used
406      * by Maven. This overrules the toolchain selected by the
407      * <a href="https://maven.apache.org/plugins/maven-toolchains-plugin/">maven-toolchain-plugin</a>.
408      * </p>
409      * (see <a href="https://maven.apache.org/guides/mini/guide-using-toolchains.html"> Guide to Toolchains</a> for more
410      * info)
411      * 
412      * <pre>
413      * &lt;configuration&gt;
414      *   &lt;jdkToolchain&gt;
415      *     &lt;version&gt;11&lt;/version&gt;
416      *   &lt;/jdkToolchain&gt;
417      *   ...
418      * &lt;/configuration&gt;
419      *
420      * &lt;configuration&gt;
421      *   &lt;jdkToolchain&gt;
422      *     &lt;version&gt;1.8&lt;/version&gt;
423      *     &lt;vendor&gt;zulu&lt;/vendor&gt;
424      *   &lt;/jdkToolchain&gt;
425      *   ...
426      * &lt;/configuration&gt;
427      * </pre>
428      * <strong>note:</strong> requires at least Maven 3.3.1
429      * 
430      * @since 3.6
431      */
432     @Parameter
433     private Map<String, String> jdkToolchain;
434 
435     // ----------------------------------------------------------------------
436     // Read-only parameters
437     // ----------------------------------------------------------------------
438 
439     /**
440      * The directory to run the compiler from if fork is true.
441      */
442     @Parameter( defaultValue = "${basedir}", required = true, readonly = true )
443     private File basedir;
444 
445     /**
446      * The target directory of the compiler if fork is true.
447      */
448     @Parameter( defaultValue = "${project.build.directory}", required = true, readonly = true )
449     private File buildDirectory;
450 
451     /**
452      * Plexus compiler manager.
453      */
454     @Component
455     private CompilerManager compilerManager;
456 
457     /**
458      * The current build session instance. This is used for toolchain manager API calls.
459      */
460     @Parameter( defaultValue = "${session}", readonly = true, required = true )
461     private MavenSession session;
462 
463     /**
464      * The current project instance. This is used for propagating generated-sources paths as compile/testCompile source
465      * roots.
466      */
467     @Parameter( defaultValue = "${project}", readonly = true, required = true )
468     private MavenProject project;
469 
470     /**
471      * Strategy to re use javacc class created:
472      * <ul>
473      * <li><code>reuseCreated</code> (default): will reuse already created but in case of multi-threaded builds, each
474      * thread will have its own instance</li>
475      * <li><code>reuseSame</code>: the same Javacc class will be used for each compilation even for multi-threaded build
476      * </li>
477      * <li><code>alwaysNew</code>: a new Javacc class will be created for each compilation</li>
478      * </ul>
479      * Note this parameter value depends on the os/jdk you are using, but the default value should work on most of env.
480      *
481      * @since 2.5
482      */
483     @Parameter( defaultValue = "${reuseCreated}", property = "maven.compiler.compilerReuseStrategy" )
484     private String compilerReuseStrategy = "reuseCreated";
485 
486     /**
487      * @since 2.5
488      */
489     @Parameter( defaultValue = "false", property = "maven.compiler.skipMultiThreadWarning" )
490     private boolean skipMultiThreadWarning;
491 
492     /**
493      * compiler can now use javax.tools if available in your current jdk, you can disable this feature
494      * using -Dmaven.compiler.forceJavacCompilerUse=true or in the plugin configuration
495      *
496      * @since 3.0
497      */
498     @Parameter( defaultValue = "false", property = "maven.compiler.forceJavacCompilerUse" )
499     private boolean forceJavacCompilerUse;
500 
501     /**
502      * @since 3.0 needed for storing the status for the incremental build support.
503      */
504     @Parameter( defaultValue = "${mojoExecution}", readonly = true, required = true )
505     private MojoExecution mojoExecution;
506 
507     /**
508      * File extensions to check timestamp for incremental build.
509      * Default contains only <code>class</code> and <code>jar</code>.
510      *
511      * @since 3.1
512      */
513     @Parameter
514     private List<String> fileExtensions;
515 
516     /**
517      * <p>to enable/disable incremental compilation feature.</p>
518      * <p>This leads to two different modes depending on the underlying compiler. The default javac compiler does the
519      * following:</p>
520      * <ul>
521      * <li>true <strong>(default)</strong> in this mode the compiler plugin determines whether any JAR files the
522      * current module depends on have changed in the current build run; or any source file was added, removed or
523      * changed since the last compilation. If this is the case, the compiler plugin recompiles all sources.</li>
524      * <li>false <strong>(not recommended)</strong> this only compiles source files which are newer than their
525      * corresponding class files, namely which have changed since the last compilation. This does not
526      * recompile other classes which use the changed class, potentially leaving them with references to methods that no
527      * longer exist, leading to errors at runtime.</li>
528      * </ul>
529      *
530      * @since 3.1
531      */
532     @Parameter( defaultValue = "true", property = "maven.compiler.useIncrementalCompilation" )
533     private boolean useIncrementalCompilation = true;
534 
535     /**
536      * Package info source files that only contain javadoc and no annotation on the package
537      * can lead to no class file being generated by the compiler.  This causes a file miss
538      * on the next compilations and forces an unnecessary recompilation. The default value
539      * of <code>true</code> causes an empty class file to be generated.  This behavior can
540      * be changed by setting this parameter to <code>false</code>.
541      *
542      * @since 3.10
543      */
544     @Parameter( defaultValue = "true", property = "maven.compiler.createMissingPackageInfoClass" )
545     private boolean createMissingPackageInfoClass = true;
546 
547     /**
548      * Resolves the artifacts needed.
549      */
550     @Component
551     private RepositorySystem repositorySystem;
552 
553     /**
554      * Artifact handler manager.
555      */
556     @Component
557     private ArtifactHandlerManager artifactHandlerManager;
558 
559     /**
560      * Throws an exception on artifact resolution errors.
561      */
562     @Component
563     private ResolutionErrorHandler resolutionErrorHandler;
564 
565     protected abstract SourceInclusionScanner getSourceInclusionScanner( int staleMillis );
566 
567     protected abstract SourceInclusionScanner getSourceInclusionScanner( String inputFileEnding );
568 
569     protected abstract List<String> getClasspathElements();
570 
571     protected abstract List<String> getModulepathElements();
572 
573     protected abstract Map<String, JavaModuleDescriptor> getPathElements();
574 
575     protected abstract List<String> getCompileSourceRoots();
576     
577     protected abstract void preparePaths( Set<File> sourceFiles );
578 
579     protected abstract File getOutputDirectory();
580 
581     protected abstract String getSource();
582 
583     protected abstract String getTarget();
584 
585     protected abstract String getRelease();
586 
587     protected abstract String getCompilerArgument();
588 
589     protected abstract Map<String, String> getCompilerArguments();
590 
591     protected abstract File getGeneratedSourcesDirectory();
592 
593     protected abstract String getDebugFileName();
594 
595     protected final MavenProject getProject()
596     {
597         return project;
598     }
599 
600     private boolean targetOrReleaseSet;
601 
602     @Override
603     public void execute()
604         throws MojoExecutionException, CompilationFailureException
605     {
606         // ----------------------------------------------------------------------
607         // Look up the compiler. This is done before other code than can
608         // cause the mojo to return before the lookup is done possibly resulting
609         // in misconfigured POMs still building.
610         // ----------------------------------------------------------------------
611 
612         Compiler compiler;
613 
614         getLog().debug( "Using compiler '" + compilerId + "'." );
615 
616         try
617         {
618             compiler = compilerManager.getCompiler( compilerId );
619         }
620         catch ( NoSuchCompilerException e )
621         {
622             throw new MojoExecutionException( "No such compiler '" + e.getCompilerId() + "'." );
623         }
624 
625         //-----------toolchains start here ----------------------------------
626         //use the compilerId as identifier for toolchains as well.
627         Toolchain tc = getToolchain();
628         if ( tc != null )
629         {
630             getLog().info( "Toolchain in maven-compiler-plugin: " + tc );
631             if ( executable != null )
632             {
633                 getLog().warn( "Toolchains are ignored, 'executable' parameter is set to " + executable );
634             }
635             else
636             {
637                 fork = true;
638                 //TODO somehow shaky dependency between compilerId and tool executable.
639                 executable = tc.findTool( compilerId );
640             }
641         }
642         // ----------------------------------------------------------------------
643         //
644         // ----------------------------------------------------------------------
645 
646         List<String> compileSourceRoots = removeEmptyCompileSourceRoots( getCompileSourceRoots() );
647 
648         if ( compileSourceRoots.isEmpty() )
649         {
650             getLog().info( "No sources to compile" );
651 
652             return;
653         }
654 
655         // Verify that target or release is set
656         if ( !targetOrReleaseSet )
657         {
658             MessageBuilder mb = MessageUtils.buffer().a( "No explicit value set for target or release! " )
659                             .a( "To ensure the same result even after upgrading this plugin, please add " ).newline()
660                             .newline();
661 
662             writePlugin( mb );
663 
664             getLog().warn( mb.toString() );
665         }
666 
667         // ----------------------------------------------------------------------
668         // Create the compiler configuration
669         // ----------------------------------------------------------------------
670 
671         CompilerConfiguration compilerConfiguration = new CompilerConfiguration();
672 
673         compilerConfiguration.setOutputLocation( getOutputDirectory().getAbsolutePath() );
674 
675         compilerConfiguration.setOptimize( optimize );
676 
677         compilerConfiguration.setDebug( debug );
678 
679         compilerConfiguration.setDebugFileName( getDebugFileName() );
680 
681         if ( debug && StringUtils.isNotEmpty( debuglevel ) )
682         {
683             String[] split = StringUtils.split( debuglevel, "," );
684             for ( String aSplit : split )
685             {
686                 if ( !( aSplit.equalsIgnoreCase( "none" ) || aSplit.equalsIgnoreCase( "lines" )
687                     || aSplit.equalsIgnoreCase( "vars" ) || aSplit.equalsIgnoreCase( "source" ) ) )
688                 {
689                     throw new IllegalArgumentException( "The specified debug level: '" + aSplit + "' is unsupported. "
690                         + "Legal values are 'none', 'lines', 'vars', and 'source'." );
691                 }
692             }
693             compilerConfiguration.setDebugLevel( debuglevel );
694         }
695 
696         compilerConfiguration.setParameters( parameters );
697 
698         compilerConfiguration.setVerbose( verbose );
699 
700         compilerConfiguration.setShowWarnings( showWarnings );
701 
702         compilerConfiguration.setFailOnWarning( failOnWarning );
703 
704         compilerConfiguration.setShowDeprecation( showDeprecation );
705 
706         compilerConfiguration.setSourceVersion( getSource() );
707 
708         compilerConfiguration.setTargetVersion( getTarget() );
709         
710         compilerConfiguration.setReleaseVersion( getRelease() );
711 
712         compilerConfiguration.setProc( proc );
713 
714         File generatedSourcesDirectory = getGeneratedSourcesDirectory();
715         compilerConfiguration.setGeneratedSourcesDirectory( generatedSourcesDirectory != null
716                         ? generatedSourcesDirectory.getAbsoluteFile() : null );
717 
718         if ( generatedSourcesDirectory != null )
719         {
720             if ( !generatedSourcesDirectory.exists() )
721             {
722                 generatedSourcesDirectory.mkdirs();
723             }
724 
725             String generatedSourcesPath = generatedSourcesDirectory.getAbsolutePath();
726 
727             compileSourceRoots.add( generatedSourcesPath );
728 
729             if ( isTestCompile() )
730             {
731                 getLog().debug( "Adding " + generatedSourcesPath + " to test-compile source roots:\n  "
732                                     + StringUtils.join( project.getTestCompileSourceRoots()
733                                                                .iterator(), "\n  " ) );
734 
735                 project.addTestCompileSourceRoot( generatedSourcesPath );
736 
737                 getLog().debug( "New test-compile source roots:\n  "
738                                     + StringUtils.join( project.getTestCompileSourceRoots()
739                                                                .iterator(), "\n  " ) );
740             }
741             else
742             {
743                 getLog().debug( "Adding " + generatedSourcesPath + " to compile source roots:\n  "
744                                     + StringUtils.join( project.getCompileSourceRoots()
745                                                                .iterator(), "\n  " ) );
746 
747                 project.addCompileSourceRoot( generatedSourcesPath );
748 
749                 getLog().debug( "New compile source roots:\n  " + StringUtils.join( project.getCompileSourceRoots()
750                                                                                            .iterator(), "\n  " ) );
751             }
752         }
753 
754         compilerConfiguration.setSourceLocations( compileSourceRoots );
755 
756         compilerConfiguration.setAnnotationProcessors( annotationProcessors );
757 
758         compilerConfiguration.setProcessorPathEntries( resolveProcessorPathEntries() );
759 
760         compilerConfiguration.setSourceEncoding( encoding );
761 
762         compilerConfiguration.setFork( fork );
763 
764         if ( fork )
765         {
766             if ( !StringUtils.isEmpty( meminitial ) )
767             {
768                 String value = getMemoryValue( meminitial );
769 
770                 if ( value != null )
771                 {
772                     compilerConfiguration.setMeminitial( value );
773                 }
774                 else
775                 {
776                     getLog().info( "Invalid value for meminitial '" + meminitial + "'. Ignoring this option." );
777                 }
778             }
779 
780             if ( !StringUtils.isEmpty( maxmem ) )
781             {
782                 String value = getMemoryValue( maxmem );
783 
784                 if ( value != null )
785                 {
786                     compilerConfiguration.setMaxmem( value );
787                 }
788                 else
789                 {
790                     getLog().info( "Invalid value for maxmem '" + maxmem + "'. Ignoring this option." );
791                 }
792             }
793         }
794 
795         compilerConfiguration.setExecutable( executable );
796 
797         compilerConfiguration.setWorkingDirectory( basedir );
798 
799         compilerConfiguration.setCompilerVersion( compilerVersion );
800 
801         compilerConfiguration.setBuildDirectory( buildDirectory );
802 
803         compilerConfiguration.setOutputFileName( outputFileName );
804 
805         if ( CompilerConfiguration.CompilerReuseStrategy.AlwaysNew.getStrategy().equals( this.compilerReuseStrategy ) )
806         {
807             compilerConfiguration.setCompilerReuseStrategy( CompilerConfiguration.CompilerReuseStrategy.AlwaysNew );
808         }
809         else if ( CompilerConfiguration.CompilerReuseStrategy.ReuseSame.getStrategy().equals(
810             this.compilerReuseStrategy ) )
811         {
812             if ( getRequestThreadCount() > 1 )
813             {
814                 if ( !skipMultiThreadWarning )
815                 {
816                     getLog().warn( "You are in a multi-thread build and compilerReuseStrategy is set to reuseSame."
817                                        + " This can cause issues in some environments (os/jdk)!"
818                                        + " Consider using reuseCreated strategy."
819                                        + System.getProperty( "line.separator" )
820                                        + "If your env is fine with reuseSame, you can skip this warning with the "
821                                        + "configuration field skipMultiThreadWarning "
822                                        + "or -Dmaven.compiler.skipMultiThreadWarning=true" );
823                 }
824             }
825             compilerConfiguration.setCompilerReuseStrategy( CompilerConfiguration.CompilerReuseStrategy.ReuseSame );
826         }
827         else
828         {
829 
830             compilerConfiguration.setCompilerReuseStrategy( CompilerConfiguration.CompilerReuseStrategy.ReuseCreated );
831         }
832 
833         getLog().debug( "CompilerReuseStrategy: " + compilerConfiguration.getCompilerReuseStrategy().getStrategy() );
834 
835         compilerConfiguration.setForceJavacCompilerUse( forceJavacCompilerUse );
836 
837         boolean canUpdateTarget;
838 
839         IncrementalBuildHelper incrementalBuildHelper = new IncrementalBuildHelper( mojoExecution, session );
840 
841         final Set<File> sources;
842 
843         IncrementalBuildHelperRequest incrementalBuildHelperRequest = null;
844 
845         if ( useIncrementalCompilation )
846         {
847             getLog().debug( "useIncrementalCompilation enabled" );
848             try
849             {
850                 canUpdateTarget = compiler.canUpdateTarget( compilerConfiguration );
851 
852                 sources = getCompileSources( compiler, compilerConfiguration );
853                 
854                 preparePaths( sources );
855 
856                 incrementalBuildHelperRequest = new IncrementalBuildHelperRequest().inputFiles( sources );
857 
858                 // CHECKSTYLE_OFF: LineLength
859                 if ( ( compiler.getCompilerOutputStyle().equals( CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES ) && !canUpdateTarget )
860                     || isDependencyChanged()
861                     || isSourceChanged( compilerConfiguration, compiler )
862                     || incrementalBuildHelper.inputFileTreeChanged( incrementalBuildHelperRequest ) )
863                     // CHECKSTYLE_ON: LineLength
864                 {
865                     getLog().info( "Changes detected - recompiling the module!" );
866 
867                     compilerConfiguration.setSourceFiles( sources );
868                 }
869                 else
870                 {
871                     getLog().info( "Nothing to compile - all classes are up to date" );
872 
873                     return;
874                 }
875             }
876             catch ( CompilerException e )
877             {
878                 throw new MojoExecutionException( "Error while computing stale sources.", e );
879             }
880         }
881         else
882         {
883             getLog().debug( "useIncrementalCompilation disabled" );
884             
885             Set<File> staleSources;
886             try
887             {
888                 staleSources =
889                     computeStaleSources( compilerConfiguration, compiler, getSourceInclusionScanner( staleMillis ) );
890 
891                 canUpdateTarget = compiler.canUpdateTarget( compilerConfiguration );
892 
893                 if ( compiler.getCompilerOutputStyle().equals( CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES )
894                     && !canUpdateTarget )
895                 {
896                     getLog().info( "RESCANNING!" );
897                     // TODO: This second scan for source files is sub-optimal
898                     String inputFileEnding = compiler.getInputFileEnding( compilerConfiguration );
899 
900                     staleSources = computeStaleSources( compilerConfiguration, compiler,
901                                                              getSourceInclusionScanner( inputFileEnding ) );
902                 }
903                 
904             }
905             catch ( CompilerException e )
906             {
907                 throw new MojoExecutionException( "Error while computing stale sources.", e );
908             }
909 
910             if ( staleSources.isEmpty() )
911             {
912                 getLog().info( "Nothing to compile - all classes are up to date" );
913 
914                 return;
915             }
916 
917             compilerConfiguration.setSourceFiles( staleSources );
918             
919             try
920             {
921                 // MCOMPILER-366: if sources contain the module-descriptor it must be used to define the modulepath
922                 sources = getCompileSources( compiler, compilerConfiguration );
923 
924                 if ( getLog().isDebugEnabled() )
925                 {
926                     getLog().debug( "#sources: " + sources.size() );
927                     for ( File file : sources )
928                     {
929                         getLog().debug( file.getPath() );
930                     }
931                 }
932 
933                 preparePaths( sources );
934             }
935             catch ( CompilerException e )
936             {
937                 throw new MojoExecutionException( "Error while computing stale sources.", e );
938             }
939         }
940        
941         // Dividing pathElements of classPath and modulePath is based on sourceFiles
942         compilerConfiguration.setClasspathEntries( getClasspathElements() );
943 
944         compilerConfiguration.setModulepathEntries( getModulepathElements() );
945         
946         Map<String, String> effectiveCompilerArguments = getCompilerArguments();
947 
948         String effectiveCompilerArgument = getCompilerArgument();
949 
950         if ( ( effectiveCompilerArguments != null ) || ( effectiveCompilerArgument != null )
951                         || ( compilerArgs != null ) )
952         {
953             if ( effectiveCompilerArguments != null )
954             {
955                 for ( Map.Entry<String, String> me : effectiveCompilerArguments.entrySet() )
956                 {
957                     String key = me.getKey();
958                     String value = me.getValue();
959                     if ( !key.startsWith( "-" ) )
960                     {
961                         key = "-" + key;
962                     }
963 
964                     if ( key.startsWith( "-A" ) && StringUtils.isNotEmpty( value ) )
965                     {
966                         compilerConfiguration.addCompilerCustomArgument( key + "=" + value, null );
967                     }
968                     else
969                     {
970                         compilerConfiguration.addCompilerCustomArgument( key, value );
971                     }
972                 }
973             }
974             if ( !StringUtils.isEmpty( effectiveCompilerArgument ) )
975             {
976                 compilerConfiguration.addCompilerCustomArgument( effectiveCompilerArgument, null );
977             }
978             if ( compilerArgs != null )
979             {
980                 for ( String arg : compilerArgs )
981                 {
982                     compilerConfiguration.addCompilerCustomArgument( arg, null );
983                 }
984             }
985         }
986         
987         // ----------------------------------------------------------------------
988         // Dump configuration
989         // ----------------------------------------------------------------------
990         if ( getLog().isDebugEnabled() )
991         {
992             getLog().debug( "Classpath:" );
993 
994             for ( String s : getClasspathElements() )
995             {
996                 getLog().debug( " " + s );
997             }
998 
999             if ( !getModulepathElements().isEmpty() )
1000             {
1001                 getLog().debug( "Modulepath:" );
1002                 for ( String s : getModulepathElements() )
1003                 {
1004                     getLog().debug( " " + s );
1005                 }
1006             }
1007 
1008             getLog().debug( "Source roots:" );
1009 
1010             for ( String root : getCompileSourceRoots() )
1011             {
1012                 getLog().debug( " " + root );
1013             }
1014 
1015             try
1016             {
1017                 if ( fork )
1018                 {
1019                     if ( compilerConfiguration.getExecutable() != null )
1020                     {
1021                         getLog().debug( "Excutable: " );
1022                         getLog().debug( " " + compilerConfiguration.getExecutable() );
1023                     }
1024                 }
1025 
1026                 String[] cl = compiler.createCommandLine( compilerConfiguration );
1027                 if ( cl != null && cl.length > 0 )
1028                 {
1029                     StringBuilder sb = new StringBuilder();
1030                     sb.append( cl[0] );
1031                     for ( int i = 1; i < cl.length; i++ )
1032                     {
1033                         sb.append( " " );
1034                         sb.append( cl[i] );
1035                     }
1036                     getLog().debug( "Command line options:" );
1037                     getLog().debug( sb );
1038                 }
1039             }
1040             catch ( CompilerException ce )
1041             {
1042                 getLog().debug( ce );
1043             }
1044         }
1045         
1046 
1047         List<String> jpmsLines = new ArrayList<>();
1048         
1049         // See http://openjdk.java.net/jeps/261
1050         final List<String> runtimeArgs = Arrays.asList( "--upgrade-module-path", 
1051                                                   "--add-exports",
1052                                                   "--add-reads", 
1053                                                   "--add-modules", 
1054                                                   "--limit-modules" );
1055         
1056         // Custom arguments are all added as keys to an ordered Map
1057         Iterator<Map.Entry<String, String>> entryIter =
1058             compilerConfiguration.getCustomCompilerArgumentsEntries().iterator();
1059         while ( entryIter.hasNext() )
1060         {
1061             Map.Entry<String, String> entry = entryIter.next();
1062             
1063             if ( runtimeArgs.contains( entry.getKey() ) )
1064             {
1065                 jpmsLines.add( entry.getKey() );
1066                 
1067                 String value = entry.getValue();
1068                 if ( value == null )
1069                 {
1070                     entry = entryIter.next();
1071                     value = entry.getKey();
1072                 }
1073                 jpmsLines.add( value );
1074             }
1075             else if ( "--patch-module".equals( entry.getKey() ) )
1076             {
1077                 String value = entry.getValue();
1078                 if ( value == null )
1079                 {
1080                     entry = entryIter.next();
1081                     value = entry.getKey();
1082                 }
1083                 
1084                 String[] values = value.split( "=" );
1085 
1086                 StringBuilder patchModule = new StringBuilder( values[0] );
1087                 patchModule.append( '=' );
1088 
1089                 Set<String> patchModules = new LinkedHashSet<>();
1090                 Set<Path> sourceRoots = new HashSet<>( getCompileSourceRoots().size() );
1091                 for ( String sourceRoot : getCompileSourceRoots() )
1092                 {
1093                     sourceRoots.add( Paths.get( sourceRoot ) );
1094                 }
1095 
1096                 String[] files = values[1].split( PS );
1097 
1098                 for ( String file : files )
1099                 {
1100                     Path filePath = Paths.get( file );
1101                     if ( getOutputDirectory().toPath().equals( filePath ) )
1102                     {
1103                         patchModules.add( "_" ); // this jar
1104                     }
1105                     else if ( getOutputDirectory().toPath().startsWith( filePath ) )
1106                     {
1107                         // multirelease, can be ignored
1108                         continue;
1109                     }
1110                     else if ( sourceRoots.contains( filePath ) )
1111                     {
1112                         patchModules.add( "_" ); // this jar
1113                     }
1114                     else
1115                     {
1116                         JavaModuleDescriptor descriptor = getPathElements().get( file );
1117                         
1118                         if ( descriptor == null )
1119                         {
1120                             if ( Files.isDirectory( filePath ) )
1121                             {
1122                                 patchModules.add( file );
1123                             }
1124                             else
1125                             {
1126                                 getLog().warn( "Can't locate " + file );
1127                             }
1128                         }
1129                         else if ( !values[0].equals( descriptor.name() ) )
1130                         {
1131                             patchModules.add( descriptor.name() );
1132                         }
1133                     }
1134                 }
1135 
1136                 StringBuilder sb = new StringBuilder();
1137                 
1138                 if ( !patchModules.isEmpty() )
1139                 {
1140                     for ( String mod : patchModules )
1141                     {
1142                         if ( sb.length() > 0 )
1143                         {
1144                             sb.append( ", " );
1145                         }
1146                         // use 'invalid' separator to ensure values are transformed
1147                         sb.append( mod );
1148                     }
1149 
1150                     jpmsLines.add( "--patch-module" );
1151                     jpmsLines.add( patchModule + sb.toString() );
1152                 }
1153 
1154             }
1155         }
1156         
1157         if ( !jpmsLines.isEmpty() ) 
1158         {
1159             Path jpmsArgs = Paths.get( getOutputDirectory().getAbsolutePath(), "META-INF/jpms.args" );
1160             try
1161             {
1162                 Files.createDirectories( jpmsArgs.getParent() );
1163                 
1164                 Files.write( jpmsArgs, jpmsLines, Charset.defaultCharset() );
1165             }
1166             catch ( IOException e )
1167             {
1168                 getLog().warn( e.getMessage() );
1169             }
1170         }
1171 
1172 
1173         // ----------------------------------------------------------------------
1174         // Compile!
1175         // ----------------------------------------------------------------------
1176 
1177         if ( StringUtils.isEmpty( compilerConfiguration.getSourceEncoding() ) )
1178         {
1179             getLog().warn( "File encoding has not been set, using platform encoding " + ReaderFactory.FILE_ENCODING
1180                                + ", i.e. build is platform dependent!" );
1181         }
1182 
1183         CompilerResult compilerResult;
1184 
1185 
1186         if ( useIncrementalCompilation )
1187         {
1188             incrementalBuildHelperRequest.outputDirectory( getOutputDirectory() );
1189 
1190             incrementalBuildHelper.beforeRebuildExecution( incrementalBuildHelperRequest );
1191 
1192             getLog().debug( "incrementalBuildHelper#beforeRebuildExecution" );
1193         }
1194 
1195         try
1196         {
1197             try
1198             {
1199                 compilerResult = compiler.performCompile( compilerConfiguration );
1200             }
1201             catch ( CompilerNotImplementedException cnie )
1202             {
1203                 List<CompilerError> messages = compiler.compile( compilerConfiguration );
1204                 compilerResult = convertToCompilerResult( messages );
1205             }
1206         }
1207         catch ( Exception e )
1208         {
1209             // TODO: don't catch Exception
1210             throw new MojoExecutionException( "Fatal error compiling", e );
1211         }
1212 
1213         if ( createMissingPackageInfoClass && compilerResult.isSuccess()
1214                 && compiler.getCompilerOutputStyle() == CompilerOutputStyle.ONE_OUTPUT_FILE_PER_INPUT_FILE )
1215         {
1216             try
1217             {
1218                 SourceMapping sourceMapping = getSourceMapping( compilerConfiguration, compiler );
1219                 createMissingPackageInfoClasses( compilerConfiguration, sourceMapping, sources );
1220             }
1221             catch ( Exception e )
1222             {
1223                 getLog().warn( "Error creating missing package info classes", e );
1224 
1225             }
1226         }
1227 
1228         if ( useIncrementalCompilation )
1229         {
1230             if ( incrementalBuildHelperRequest.getOutputDirectory().exists() )
1231             {
1232                 getLog().debug( "incrementalBuildHelper#afterRebuildExecution" );
1233                 // now scan the same directory again and create a diff
1234                 incrementalBuildHelper.afterRebuildExecution( incrementalBuildHelperRequest );
1235             }
1236             else
1237             {
1238                 getLog().debug(
1239                     "skip incrementalBuildHelper#afterRebuildExecution as the output directory doesn't exist" );
1240             }
1241         }
1242 
1243         List<CompilerMessage> warnings = new ArrayList<>();
1244         List<CompilerMessage> errors = new ArrayList<>();
1245         List<CompilerMessage> others = new ArrayList<>();
1246         for ( CompilerMessage message : compilerResult.getCompilerMessages() )
1247         {
1248             if ( message.getKind() == CompilerMessage.Kind.ERROR )
1249             {
1250                 errors.add( message );
1251             }
1252             else if ( message.getKind() == CompilerMessage.Kind.WARNING
1253                 || message.getKind() == CompilerMessage.Kind.MANDATORY_WARNING )
1254             {
1255                 warnings.add( message );
1256             }
1257             else
1258             {
1259                 others.add( message );
1260             }
1261         }
1262 
1263         if ( failOnError && !compilerResult.isSuccess() )
1264         {
1265             for ( CompilerMessage message : others )
1266             {
1267                 assert message.getKind() != CompilerMessage.Kind.ERROR
1268                     && message.getKind() != CompilerMessage.Kind.WARNING
1269                     && message.getKind() != CompilerMessage.Kind.MANDATORY_WARNING;
1270                 getLog().info( message.toString() );
1271             }
1272             if ( !warnings.isEmpty() )
1273             {
1274                 getLog().info( "-------------------------------------------------------------" );
1275                 getLog().warn( "COMPILATION WARNING : " );
1276                 getLog().info( "-------------------------------------------------------------" );
1277                 for ( CompilerMessage warning : warnings )
1278                 {
1279                     getLog().warn( warning.toString() );
1280                 }
1281                 getLog().info( warnings.size() + ( ( warnings.size() > 1 ) ? " warnings " : " warning" ) );
1282                 getLog().info( "-------------------------------------------------------------" );
1283             }
1284 
1285             if ( !errors.isEmpty() )
1286             {
1287                 getLog().info( "-------------------------------------------------------------" );
1288                 getLog().error( "COMPILATION ERROR : " );
1289                 getLog().info( "-------------------------------------------------------------" );
1290                 for ( CompilerMessage error : errors )
1291                 {
1292                     getLog().error( error.toString() );
1293                 }
1294                 getLog().info( errors.size() + ( ( errors.size() > 1 ) ? " errors " : " error" ) );
1295                 getLog().info( "-------------------------------------------------------------" );
1296             }
1297 
1298             if ( !errors.isEmpty() )
1299             {
1300                 throw new CompilationFailureException( errors );
1301             }
1302             else
1303             {
1304                 throw new CompilationFailureException( warnings );
1305             }
1306         }
1307         else
1308         {
1309             for ( CompilerMessage message : compilerResult.getCompilerMessages() )
1310             {
1311                 switch ( message.getKind() )
1312                 {
1313                     case NOTE:
1314                     case OTHER:
1315                         getLog().info( message.toString() );
1316                         break;
1317 
1318                     case ERROR:
1319                         getLog().error( message.toString() );
1320                         break;
1321 
1322                     case MANDATORY_WARNING:
1323                     case WARNING:
1324                     default:
1325                         getLog().warn( message.toString() );
1326                         break;
1327                 }
1328             }
1329         }
1330     }
1331 
1332     private void createMissingPackageInfoClasses( CompilerConfiguration compilerConfiguration,
1333                                                   SourceMapping sourceMapping,
1334                                                   Set<File> sources )
1335             throws InclusionScanException, IOException
1336     {
1337         for ( File source : sources )
1338         {
1339             String path = source.toString();
1340             if ( path.endsWith( File.separator + "package-info.java" ) )
1341             {
1342                 for ( String root : getCompileSourceRoots() )
1343                 {
1344                     root = root + File.separator;
1345                     if ( path.startsWith( root ) )
1346                     {
1347                         String rel = path.substring( root.length() );
1348                         Set<File> files = sourceMapping.getTargetFiles( getOutputDirectory(), rel );
1349                         for ( File file : files )
1350                         {
1351                             if ( !file.exists() )
1352                             {
1353                                 byte[] bytes = generatePackage( compilerConfiguration, rel );
1354                                 Files.write( file.toPath(), bytes );
1355                             }
1356                         }
1357                     }
1358                 }
1359             }
1360         }
1361     }
1362 
1363     private byte[] generatePackage( CompilerConfiguration compilerConfiguration, String javaFile )
1364     {
1365         int version = getOpcode( compilerConfiguration );
1366         ClassWriter cw = new ClassWriter( 0 );
1367         cw.visitSource( "package-info.java", null );
1368         cw.visit( version,
1369                 Opcodes.ACC_SYNTHETIC | Opcodes.ACC_ABSTRACT | Opcodes.ACC_INTERFACE,
1370                 javaFile.substring( 0, javaFile.length() - ".java".length() ),
1371                 null, "java/lang/Object", null );
1372         return cw.toByteArray();
1373     }
1374 
1375     private int getOpcode( CompilerConfiguration compilerConfiguration )
1376     {
1377         String version = compilerConfiguration.getReleaseVersion();
1378         if ( version == null )
1379         {
1380             version = compilerConfiguration.getTargetVersion();
1381             if ( version == null )
1382             {
1383                 version = "1.5";
1384             }
1385         }
1386         if ( version.startsWith( "1." ) )
1387         {
1388             version = version.substring( 2 );
1389         }
1390         int iVersion = Integer.parseInt( version );
1391         if ( iVersion < 2 )
1392         {
1393             throw new IllegalArgumentException( "Unsupported java version '" + version + "'" );
1394         }
1395         return iVersion - 2 + Opcodes.V1_2;
1396     }
1397 
1398     protected boolean isTestCompile()
1399     {
1400         return false;
1401     }
1402 
1403     protected CompilerResult convertToCompilerResult( List<CompilerError> compilerErrors )
1404     {
1405         if ( compilerErrors == null )
1406         {
1407             return new CompilerResult();
1408         }
1409         List<CompilerMessage> messages = new ArrayList<>( compilerErrors.size() );
1410         boolean success = true;
1411         for ( CompilerError compilerError : compilerErrors )
1412         {
1413             messages.add(
1414                 new CompilerMessage( compilerError.getFile(), compilerError.getKind(), compilerError.getStartLine(),
1415                                      compilerError.getStartColumn(), compilerError.getEndLine(),
1416                                      compilerError.getEndColumn(), compilerError.getMessage() ) );
1417             if ( compilerError.isError() )
1418             {
1419                 success = false;
1420             }
1421         }
1422 
1423         return new CompilerResult( success, messages );
1424     }
1425 
1426     /**
1427      * @return all source files for the compiler
1428      */
1429     private Set<File> getCompileSources( Compiler compiler, CompilerConfiguration compilerConfiguration )
1430         throws MojoExecutionException, CompilerException
1431     {
1432         String inputFileEnding = compiler.getInputFileEnding( compilerConfiguration );
1433         if ( StringUtils.isEmpty( inputFileEnding ) )
1434         {
1435             // see MCOMPILER-199 GroovyEclipseCompiler doesn't set inputFileEnding
1436             // so we can presume it's all files from the source directory
1437             inputFileEnding = ".*";
1438         }
1439         SourceInclusionScanner scanner = getSourceInclusionScanner( inputFileEnding );
1440 
1441         SourceMapping mapping = getSourceMapping( compilerConfiguration, compiler );
1442 
1443         scanner.addSourceMapping( mapping );
1444 
1445         Set<File> compileSources = new HashSet<>();
1446 
1447         for ( String sourceRoot : getCompileSourceRoots() )
1448         {
1449             File rootFile = new File( sourceRoot );
1450 
1451             if ( !rootFile.isDirectory()
1452                 || rootFile.getAbsoluteFile().equals( compilerConfiguration.getGeneratedSourcesDirectory() ) )
1453             {
1454                 continue;
1455             }
1456 
1457             try
1458             {
1459                 compileSources.addAll( scanner.getIncludedSources( rootFile, null ) );
1460             }
1461             catch ( InclusionScanException e )
1462             {
1463                 throw new MojoExecutionException(
1464                     "Error scanning source root: \'" + sourceRoot + "\' for stale files to recompile.", e );
1465             }
1466         }
1467 
1468         return compileSources;
1469     }
1470 
1471     /**
1472      * @param compilerConfiguration
1473      * @param compiler
1474      * @return <code>true</code> if at least a single source file is newer than it's class file
1475      */
1476     private boolean isSourceChanged( CompilerConfiguration compilerConfiguration, Compiler compiler )
1477         throws CompilerException, MojoExecutionException
1478     {
1479         Set<File> staleSources =
1480             computeStaleSources( compilerConfiguration, compiler, getSourceInclusionScanner( staleMillis ) );
1481 
1482         if ( getLog().isDebugEnabled() )
1483         {
1484             for ( File f : staleSources )
1485             {
1486                 getLog().debug( "Stale source detected: " + f.getAbsolutePath() );
1487             }
1488         }
1489         return !staleSources.isEmpty();
1490     }
1491 
1492 
1493     /**
1494      * try to get thread count if a Maven 3 build, using reflection as the plugin must not be maven3 api dependent
1495      *
1496      * @return number of thread for this build or 1 if not multi-thread build
1497      */
1498     protected int getRequestThreadCount()
1499     {
1500         try
1501         {
1502             Method getRequestMethod = session.getClass().getMethod( "getRequest" );
1503             Object mavenExecutionRequest = getRequestMethod.invoke( this.session );
1504             Method getThreadCountMethod = mavenExecutionRequest.getClass().getMethod( "getThreadCount" );
1505             String threadCount = (String) getThreadCountMethod.invoke( mavenExecutionRequest );
1506             return Integer.parseInt( threadCount );
1507         }
1508         catch ( Exception e )
1509         {
1510             getLog().debug( "unable to get threadCount for the current build: " + e.getMessage() );
1511         }
1512         return 1;
1513     }
1514 
1515     protected Date getBuildStartTime()
1516     {
1517         Date buildStartTime = null;
1518         try
1519         {
1520             Method getRequestMethod = session.getClass().getMethod( "getRequest" );
1521             Object mavenExecutionRequest = getRequestMethod.invoke( session );
1522             Method getStartTimeMethod = mavenExecutionRequest.getClass().getMethod( "getStartTime" );
1523             buildStartTime = (Date) getStartTimeMethod.invoke( mavenExecutionRequest );
1524         }
1525         catch ( Exception e )
1526         {
1527             getLog().debug( "unable to get start time for the current build: " + e.getMessage() );
1528         }
1529 
1530         if ( buildStartTime == null )
1531         {
1532             return new Date();
1533         }
1534 
1535         return buildStartTime;
1536     }
1537 
1538 
1539     private String getMemoryValue( String setting )
1540     {
1541         String value = null;
1542 
1543         // Allow '128' or '128m'
1544         if ( isDigits( setting ) )
1545         {
1546             value = setting + "m";
1547         }
1548         else if ( ( isDigits( setting.substring( 0, setting.length() - 1 ) ) )
1549             && ( setting.toLowerCase().endsWith( "m" ) ) )
1550         {
1551             value = setting;
1552         }
1553         return value;
1554     }
1555 
1556     //TODO remove the part with ToolchainManager lookup once we depend on
1557     //3.0.9 (have it as prerequisite). Define as regular component field then.
1558     protected final Toolchain getToolchain()
1559     {
1560         Toolchain tc = null;
1561         
1562         if ( jdkToolchain != null )
1563         {
1564             // Maven 3.3.1 has plugin execution scoped Toolchain Support
1565             try
1566             {
1567                 Method getToolchainsMethod =
1568                     toolchainManager.getClass().getMethod( "getToolchains", MavenSession.class, String.class,
1569                                                            Map.class );
1570 
1571                 @SuppressWarnings( "unchecked" )
1572                 List<Toolchain> tcs =
1573                     (List<Toolchain>) getToolchainsMethod.invoke( toolchainManager, session, "jdk",
1574                                                                   jdkToolchain );
1575 
1576                 if ( tcs != null && !tcs.isEmpty() )
1577                 {
1578                     tc = tcs.get( 0 );
1579                 }
1580             }
1581             catch ( NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException
1582                 | InvocationTargetException e )
1583             {
1584                 // ignore
1585             }
1586         }
1587         
1588         if ( tc == null )
1589         {
1590             tc = toolchainManager.getToolchainFromBuildContext( "jdk", session );
1591         }
1592         
1593         return tc;
1594     }
1595 
1596     private boolean isDigits( String string )
1597     {
1598         for ( int i = 0; i < string.length(); i++ )
1599         {
1600             if ( !Character.isDigit( string.charAt( i ) ) )
1601             {
1602                 return false;
1603             }
1604         }
1605         return true;
1606     }
1607 
1608     private Set<File> computeStaleSources( CompilerConfiguration compilerConfiguration, Compiler compiler,
1609                                            SourceInclusionScanner scanner )
1610         throws MojoExecutionException, CompilerException
1611     {
1612         SourceMapping mapping = getSourceMapping( compilerConfiguration, compiler );
1613 
1614         File outputDirectory;
1615         CompilerOutputStyle outputStyle = compiler.getCompilerOutputStyle();
1616         if ( outputStyle == CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES )
1617         {
1618             outputDirectory = buildDirectory;
1619         }
1620         else
1621         {
1622             outputDirectory = getOutputDirectory();
1623         }
1624 
1625         scanner.addSourceMapping( mapping );
1626 
1627         Set<File> staleSources = new HashSet<>();
1628 
1629         for ( String sourceRoot : getCompileSourceRoots() )
1630         {
1631             File rootFile = new File( sourceRoot );
1632 
1633             if ( !rootFile.isDirectory() )
1634             {
1635                 continue;
1636             }
1637 
1638             try
1639             {
1640                 staleSources.addAll( scanner.getIncludedSources( rootFile, outputDirectory ) );
1641             }
1642             catch ( InclusionScanException e )
1643             {
1644                 throw new MojoExecutionException(
1645                     "Error scanning source root: \'" + sourceRoot + "\' for stale files to recompile.", e );
1646             }
1647         }
1648 
1649         return staleSources;
1650     }
1651 
1652     private SourceMapping getSourceMapping( CompilerConfiguration compilerConfiguration, Compiler compiler )
1653         throws CompilerException, MojoExecutionException
1654     {
1655         CompilerOutputStyle outputStyle = compiler.getCompilerOutputStyle();
1656 
1657         SourceMapping mapping;
1658         if ( outputStyle == CompilerOutputStyle.ONE_OUTPUT_FILE_PER_INPUT_FILE )
1659         {
1660             mapping = new SuffixMapping( compiler.getInputFileEnding( compilerConfiguration ),
1661                                          compiler.getOutputFileEnding( compilerConfiguration ) );
1662         }
1663         else if ( outputStyle == CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES )
1664         {
1665             mapping = new SingleTargetSourceMapping( compiler.getInputFileEnding( compilerConfiguration ),
1666                                                      compiler.getOutputFile( compilerConfiguration ) );
1667 
1668         }
1669         else
1670         {
1671             throw new MojoExecutionException( "Unknown compiler output style: '" + outputStyle + "'." );
1672         }
1673         return mapping;
1674     }
1675 
1676     /**
1677      * @todo also in ant plugin. This should be resolved at some point so that it does not need to
1678      * be calculated continuously - or should the plugins accept empty source roots as is?
1679      */
1680     private static List<String> removeEmptyCompileSourceRoots( List<String> compileSourceRootsList )
1681     {
1682         List<String> newCompileSourceRootsList = new ArrayList<>();
1683         if ( compileSourceRootsList != null )
1684         {
1685             // copy as I may be modifying it
1686             for ( String srcDir : compileSourceRootsList )
1687             {
1688                 if ( !newCompileSourceRootsList.contains( srcDir ) && new File( srcDir ).exists() )
1689                 {
1690                     newCompileSourceRootsList.add( srcDir );
1691                 }
1692             }
1693         }
1694         return newCompileSourceRootsList;
1695     }
1696 
1697     /**
1698      * We just compare the timestamps of all local dependency files (inter-module dependency classpath) and the own
1699      * generated classes and if we got a file which is &gt;= the build-started timestamp, then we caught a file which
1700      * got changed during this build.
1701      *
1702      * @return <code>true</code> if at least one single dependency has changed.
1703      */
1704     protected boolean isDependencyChanged()
1705     {
1706         if ( session == null )
1707         {
1708             // we just cannot determine it, so don't do anything beside logging
1709             getLog().info( "Cannot determine build start date, skipping incremental build detection." );
1710             return false;
1711         }
1712 
1713         if ( fileExtensions == null || fileExtensions.isEmpty() )
1714         {
1715             fileExtensions = Collections.unmodifiableList( Arrays.asList( "class", "jar" ) );
1716         }
1717 
1718         Date buildStartTime = getBuildStartTime();
1719 
1720         List<String> pathElements = new ArrayList<>();
1721         pathElements.addAll( getClasspathElements() );
1722         pathElements.addAll( getModulepathElements() );
1723         
1724         for ( String pathElement : pathElements )
1725         {
1726             File artifactPath = new File( pathElement );
1727             if ( artifactPath.isDirectory() || artifactPath.isFile() )
1728             {
1729                 if ( hasNewFile( artifactPath, buildStartTime ) )
1730                 {
1731                     getLog().debug( "New dependency detected: " + artifactPath.getAbsolutePath() );
1732                     return true;
1733                 }
1734             }
1735         }
1736 
1737         // obviously there was no new file detected.
1738         return false;
1739     }
1740 
1741     /**
1742      * @param classPathEntry entry to check
1743      * @param buildStartTime time build start
1744      * @return if any changes occurred
1745      */
1746     private boolean hasNewFile( File classPathEntry, Date buildStartTime )
1747     {
1748         if ( !classPathEntry.exists() )
1749         {
1750             return false;
1751         }
1752 
1753         if ( classPathEntry.isFile() )
1754         {
1755             return classPathEntry.lastModified() >= buildStartTime.getTime()
1756                     && fileExtensions.contains( FileUtils.getExtension( classPathEntry.getName() ) );
1757         }
1758 
1759         File[] children = classPathEntry.listFiles();
1760 
1761         for ( File child : children )
1762         {
1763             if ( hasNewFile( child, buildStartTime ) )
1764             {
1765                 return true;
1766             }
1767         }
1768 
1769         return false;
1770     }
1771 
1772     private List<String> resolveProcessorPathEntries()
1773         throws MojoExecutionException
1774     {
1775         if ( annotationProcessorPaths == null || annotationProcessorPaths.isEmpty() )
1776         {
1777             return null;
1778         }
1779 
1780         try
1781         {
1782             Set<String> elements = new LinkedHashSet<>();
1783             for ( DependencyCoordinate coord : annotationProcessorPaths )
1784             {
1785                 ArtifactHandler handler = artifactHandlerManager.getArtifactHandler( coord.getType() );
1786 
1787                 Artifact artifact = new DefaultArtifact(
1788                      coord.getGroupId(),
1789                      coord.getArtifactId(),
1790                      VersionRange.createFromVersionSpec( coord.getVersion() ),
1791                      Artifact.SCOPE_RUNTIME,
1792                      coord.getType(),
1793                      coord.getClassifier(),
1794                      handler,
1795                      false );
1796 
1797                 ArtifactResolutionRequest request = new ArtifactResolutionRequest()
1798                                 .setArtifact( artifact )
1799                                 .setResolveRoot( true )
1800                                 .setResolveTransitively( true )
1801                                 .setLocalRepository( session.getLocalRepository() )
1802                                 .setRemoteRepositories( project.getRemoteArtifactRepositories() );
1803 
1804                 ArtifactResolutionResult resolutionResult = repositorySystem.resolve( request );
1805 
1806                 resolutionErrorHandler.throwErrors( request, resolutionResult );
1807 
1808                 for ( Artifact resolved : resolutionResult.getArtifacts() )
1809                 {
1810                     elements.add( resolved.getFile().getAbsolutePath() );
1811                 }
1812             }
1813             return new ArrayList<>( elements );
1814         }
1815         catch ( Exception e )
1816         {
1817             throw new MojoExecutionException( "Resolution of annotationProcessorPath dependencies failed: "
1818                 + e.getLocalizedMessage(), e );
1819         }
1820     }
1821 
1822     private void writePlugin( MessageBuilder mb )
1823     {
1824         mb.a( "    <plugin>" ).newline();
1825         mb.a( "      <groupId>org.apache.maven.plugins</groupId>" ).newline();
1826         mb.a( "      <artifactId>maven-compiler-plugin</artifactId>" ).newline();
1827         
1828         String version = getMavenCompilerPluginVersion();
1829         if ( version != null )
1830         {
1831             mb.a( "      <version>" ).a( version ).a( "</version>" ).newline();
1832         }
1833         writeConfig( mb );
1834         
1835         mb.a( "    </plugin>" ).newline();
1836     }
1837 
1838     private void writeConfig( MessageBuilder mb )
1839     {
1840         mb.a( "      <configuration>" ).newline();
1841 
1842         if ( release != null )
1843         {
1844             mb.a( "        <release>" ).a( release ).a( "</release>" ).newline();
1845         }
1846         else if ( JavaVersion.JAVA_VERSION.isAtLeast( "9" ) )
1847         {
1848             String rls = target.replaceAll( ".\\.", "" );
1849             // when using Java9+, motivate to use release instead of source/target
1850             mb.a( "        <release>" ).a( rls ).a( "</release>" ).newline();
1851         }
1852         else
1853         {
1854             mb.a( "        <source>" ).a( source ).a( "</source>" ).newline();
1855             mb.a( "        <target>" ).a( target ).a( "</target>" ).newline();
1856         }
1857         mb.a( "      </configuration>" ).newline();
1858     }
1859 
1860     private String getMavenCompilerPluginVersion()
1861     {
1862         Properties pomProperties = new Properties();
1863 
1864         try ( InputStream is = AbstractCompilerMojo.class
1865             .getResourceAsStream( "/META-INF/maven/org.apache.maven.plugins/maven-compiler-plugin/pom.properties" ) )
1866         {
1867             if ( is != null )
1868             {
1869                 pomProperties.load( is );
1870             }
1871         }
1872         catch ( IOException e )
1873         {
1874             // noop
1875         }
1876 
1877         return pomProperties.getProperty( "version" );
1878     }
1879 
1880     public void setTarget( String target )
1881     {
1882         this.target = target;
1883         targetOrReleaseSet = true;
1884     }
1885 
1886     public void setRelease( String release )
1887     {
1888         this.release = release;
1889         targetOrReleaseSet = true;
1890     }
1891 }