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