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