View Javadoc

1   package org.codehaus.mojo.apt;
2   
3   /*
4    * The MIT License
5    *
6    * Copyright 2006-2008 The Codehaus.
7    *
8    * Permission is hereby granted, free of charge, to any person obtaining a copy of
9    * this software and associated documentation files (the "Software"), to deal in
10   * the Software without restriction, including without limitation the rights to
11   * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12   * of the Software, and to permit persons to whom the Software is furnished to do
13   * so, subject to the following conditions:
14   *
15   * The above copyright notice and this permission notice shall be included in all
16   * copies or substantial portions of the Software.
17   *
18   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24   * SOFTWARE.
25   */
26  
27  import java.io.File;
28  import java.util.ArrayList;
29  import java.util.Collection;
30  import java.util.Collections;
31  import java.util.Iterator;
32  import java.util.LinkedHashSet;
33  import java.util.List;
34  import java.util.Set;
35  
36  import org.apache.maven.artifact.Artifact;
37  import org.apache.maven.artifact.DependencyResolutionRequiredException;
38  import org.apache.maven.model.Resource;
39  import org.apache.maven.plugin.AbstractMojo;
40  import org.apache.maven.plugin.MojoExecutionException;
41  import org.apache.maven.project.MavenProject;
42  import org.codehaus.plexus.compiler.util.scan.InclusionScanException;
43  import org.codehaus.plexus.compiler.util.scan.SimpleSourceInclusionScanner;
44  import org.codehaus.plexus.compiler.util.scan.SourceInclusionScanner;
45  import org.codehaus.plexus.compiler.util.scan.StaleSourceScanner;
46  import org.codehaus.plexus.compiler.util.scan.mapping.SingleTargetSourceMapping;
47  import org.codehaus.plexus.compiler.util.scan.mapping.SuffixMapping;
48  import org.codehaus.plexus.util.StringUtils;
49  
50  /**
51   * Base mojo for executing apt.
52   * 
53   * @author <a href="mailto:jubu@codehaus.org">Juraj Burian</a>
54   * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
55   * @version $Id: AbstractAptMojo.java 12251 2010-06-08 11:11:49Z mark $
56   */
57  public abstract class AbstractAptMojo extends AbstractMojo
58  {
59      // read-only parameters ---------------------------------------------------
60  
61      /**
62       * The maven project.
63       * 
64       * @parameter expression="${project}"
65       * @required
66       * @readonly
67       */
68      private MavenProject project;
69  
70      /**
71       * The plugin's artifacts.
72       * 
73       * @parameter expression="${plugin.artifacts}"
74       * @required
75       * @readonly
76       */
77      private List<Artifact> pluginArtifacts;
78  
79      /**
80       * The directory to run apt from when forked.
81       * 
82       * @parameter expression="${basedir}"
83       * @required
84       * @readonly
85       */
86      private File workingDirectory;
87  
88      // configurable parameters ------------------------------------------------
89  
90      /**
91       * Whether to run apt in a separate process.
92       * 
93       * @parameter default-value="false"
94       */
95      private boolean fork;
96  
97      /**
98       * The apt executable to use when forked.
99       * 
100      * @parameter expression="${maven.apt.executable}" default-value="apt"
101      */
102     private String executable;
103 
104     /**
105      * The initial size of the memory allocation pool when forked, for example <code>64m</code>.
106      * 
107      * @parameter
108      */
109     private String meminitial;
110 
111     /**
112      * The maximum size of the memory allocation pool when forked, for example <code>128m</code>.
113      * 
114      * @parameter
115      */
116     private String maxmem;
117 
118     /**
119      * Whether to show apt warnings. This is opposite to the <code>-nowarn</code> argument for apt.
120      * 
121      * @parameter expression="${maven.apt.showWarnings}" default-value="false"
122      */
123     private boolean showWarnings;
124 
125     /**
126      * The source file encoding name, such as <code>EUC-JP</code> and <code>UTF-8</code>. If encoding is not
127      * specified, the encoding <code>ISO-8859-1</code> is used rather than the platform default for reproducibility
128      * reasons. This is equivalent to the <code>-encoding</code> argument for apt.
129      * 
130      * @parameter expression="${maven.apt.encoding}" default-value="ISO-8859-1"
131      */
132     private String encoding;
133 
134     /**
135      * Whether to output information about each class loaded and each source file processed. This is equivalent to the
136      * <code>-verbose</code> argument for apt.
137      * 
138      * @parameter expression="${maven.apt.verbose}" default-value="false"
139      */
140     private boolean verbose;
141 
142     /**
143      * Options to pass to annotation processors. These are equivalent to multiple <code>-A</code> arguments for apt.
144      * 
145      * @parameter
146      */
147     private String[] options;
148 
149     /**
150      * Name of <code>AnnotationProcessorFactory</code> to use; bypasses default discovery process. This is equivalent
151      * to the <code>-factory</code> argument for apt.
152      * 
153      * @parameter expression="${maven.apt.factory}"
154      */
155     private String factory;
156 
157     /**
158      * The source directories containing any additional sources to be processed.
159      * 
160      * @parameter
161      */
162     private List<String> additionalSourceRoots;
163 
164     /**
165      * The path for processor-generated resources.
166      * 
167      * @parameter
168      */
169     private String resourceTargetPath;
170 
171     /**
172      * Whether resource filtering is enabled for processor-generated resources.
173      * 
174      * @parameter default-value="false"
175      */
176     private boolean resourceFiltering;
177 
178     /**
179      * Force apt processing without staleness checking. When <code>false</code>, use <code>outputFiles</code> or
180      * <code>outputFileEndings</code> to control computing staleness.
181      * 
182      * @parameter default-value="false"
183      */
184     private boolean force;
185     
186     /**
187      * The filenames of processor-generated files to examine when computing staleness. For example,
188      * <code>generated.xml</code> would specify that the processor creates the aforementioned single file from all
189      * <code>.java</code> source files. When this parameter is not specified, <code>outputFileEndings</code> is used
190      * instead.
191      * 
192      * @parameter
193      */
194     private Set<String> outputFiles;
195 
196     /**
197      * The filename endings of processor-generated files to examine when computing staleness. For example,
198      * <code>.txt</code> would specify that the processor creates a corresponding <code>.txt</code> file for every
199      * <code>.java</code> source file. Default value is <code>.java</code>. Note that this parameter has no effect if
200      * <code>outputFiles</code> is specified.
201      * 
202      * @parameter
203      */
204     private Set<String> outputFileEndings;
205 
206     /**
207      * Sets the granularity in milliseconds of the last modification date for testing whether a source needs
208      * processing.
209      * 
210      * @parameter expression="${maven.apt.staleMillis}" default-value="0"
211      */
212     private int staleMillis;
213 
214     /**
215      * Whether to bypass running apt.
216      * 
217      * @parameter expression="${maven.apt.skip}" default-value="false"
218      */
219     private boolean skip;
220     
221     // fields -----------------------------------------------------------------
222     
223     private Set<String> includes;
224     
225     private Set<String> excludes;
226 
227     // Mojo methods -----------------------------------------------------------
228 
229     /**
230      * {@inheritDoc}
231      */
232     public final void execute() throws MojoExecutionException
233     {
234         if ( skip )
235         {
236             getLog().info( "Skipping apt" );
237         }
238         else
239         {
240             executeImpl();
241         }
242     }
243 
244     // protected methods ------------------------------------------------------
245 
246     protected void executeImpl() throws MojoExecutionException
247     {
248         // apply defaults
249         
250         includes = CollectionUtils.defaultSet( getIncludes(), Collections.singleton( "**/*.java" ) );
251         excludes = CollectionUtils.defaultSet( getExcludes() );
252         
253         // invoke apt
254         
255         List<File> staleSourceFiles = getSourceFiles( getStaleScanner(), "stale sources" );
256 
257         if ( staleSourceFiles.isEmpty() )
258         {
259             getLog().info( "Nothing to process - all processor-generated files are up to date" );
260         }
261         else
262         {
263             executeApt();
264         }
265         
266         // add source root
267 
268         String sourcePath = getSourceOutputDirectory().getPath();
269 
270         if ( !getCompileSourceRoots().contains( sourcePath ) )
271         {
272             getCompileSourceRoots().add( sourcePath );
273         }
274         
275         // add resource
276 
277         String resourcePath = getOutputDirectory().getPath();
278 
279         if ( !MavenProjectUtils.containsDirectory( getResources(), resourcePath ) )
280         {
281             Resource resource = new Resource();
282 
283             resource.setDirectory( resourcePath );
284             resource.addExclude( "**/*.java" );
285             resource.setFiltering( resourceFiltering );
286 
287             if ( resourceTargetPath != null )
288             {
289                 resource.setTargetPath( resourceTargetPath );
290             }
291 
292             getResources().add( resource );
293         }
294     }
295 
296     /**
297      * Gets the Maven project.
298      * 
299      * @return the project
300      */
301     protected MavenProject getProject()
302     {
303         return project;
304     }
305 
306     /**
307      * Gets the options to pass to annotation processors.
308      * 
309      * @return an array of options to pass to annotation processors
310      */
311     protected String[] getOptions()
312     {
313         return options;
314     }
315 
316     /**
317      * The source directories containing the sources to be processed.
318      * 
319      * @return list of compilation source roots
320      */
321     protected abstract List<String> getCompileSourceRoots();
322 
323     /**
324      * Gets the project's resources.
325      * 
326      * @return the project's resources
327      */
328     protected abstract List<Resource> getResources();
329 
330     /**
331      * Gets the project's classpath.
332      * 
333      * @return a list of classpath elements
334      */
335     protected abstract List<String> getClasspathElements();
336 
337     /**
338      * Gets a set of inclusion filters for apt.
339      * 
340      * @return a set of inclusion filters
341      */
342     protected abstract Set<String> getIncludes();
343 
344     /**
345      * Gets a set of exclusion filters for apt.
346      * 
347      * @return a set of exclusion filters
348      */
349     protected abstract Set<String> getExcludes();
350 
351     /**
352      * The directory to place processor and generated class files.
353      * 
354      * @return the directory to place processor and generated class files
355      */
356     protected abstract File getOutputDirectory();
357     
358     /**
359      * The directory root under which processor-generated source files will be placed; files are placed in
360      * subdirectories based on package namespace.
361      * 
362      * @return the directory root under which processor-generated source files will be placed
363      */
364     protected abstract File getSourceOutputDirectory();
365     
366     // private methods --------------------------------------------------------
367     
368     private void executeApt() throws MojoExecutionException
369     {
370         List<File> sourceFiles = getSourceFiles( getSourceScanner(), "sources" );
371         
372         if ( getLog().isInfoEnabled() )
373         {
374             int count = sourceFiles.size();
375             
376             getLog().info( "Processing " + count + " source file" + ( count == 1 ? "" : "s" ) );
377         }
378 
379         List<String> args = createArgs( sourceFiles );
380 
381         boolean success;
382 
383         if ( fork )
384         {
385             success = AptUtils.invokeForked( getLog(), workingDirectory, executable, meminitial, maxmem, args );
386         }
387         else
388         {
389             success = AptUtils.invoke( getLog(), args );
390         }
391 
392         if ( !success )
393         {
394             throw new MojoExecutionException( "Apt failed" );
395         }
396     }
397 
398     private List<String> createArgs( List<File> sourceFiles ) throws MojoExecutionException
399     {
400         List<String> args = new ArrayList<String>();
401 
402         // javac arguments
403 
404         Set<String> classpathElements = new LinkedHashSet<String>();
405         classpathElements.addAll( getPluginClasspathElements() );
406         classpathElements.addAll( getClasspathElements() );
407 
408         if ( !classpathElements.isEmpty() )
409         {
410             args.add( "-classpath" );
411             args.add( toPath( classpathElements ) );
412         }
413 
414         List<String> sourcePaths = getSourcePaths();
415 
416         if ( !sourcePaths.isEmpty() )
417         {
418             args.add( "-sourcepath" );
419             args.add( toPath( sourcePaths ) );
420         }
421 
422         args.add( "-d" );
423         args.add( getOutputDirectory().getAbsolutePath() );
424 
425         if ( !showWarnings )
426         {
427             args.add( "-nowarn" );
428         }
429 
430         if ( encoding != null )
431         {
432             args.add( "-encoding" );
433             args.add( encoding );
434         }
435 
436         if ( verbose )
437         {
438             args.add( "-verbose" );
439         }
440 
441         // apt arguments
442 
443         args.add( "-s" );
444         args.add( getSourceOutputDirectory().getAbsolutePath() );
445 
446         // never compile
447         args.add( "-nocompile" );
448 
449         if ( options != null )
450         {
451             for ( String option : options )
452             {
453                 args.add( "-A" + option.trim() );
454             }
455         }
456 
457         if ( StringUtils.isNotEmpty( factory ) )
458         {
459             args.add( "-factory" );
460             args.add( factory );
461         }
462 
463         // source files
464 
465         for ( File file : sourceFiles )
466         {
467             args.add( file.getAbsolutePath() );
468         }
469 
470         return args;
471     }
472 
473     private static String toPath( Collection<String> paths )
474     {
475         StringBuffer buffer = new StringBuffer();
476 
477         for ( Iterator<String> iterator = paths.iterator(); iterator.hasNext(); )
478         {
479             buffer.append( iterator.next() );
480 
481             if ( iterator.hasNext() )
482             {
483                 buffer.append( File.pathSeparator );
484             }
485         }
486 
487         return buffer.toString();
488     }
489 
490     private List<String> getPluginClasspathElements() throws MojoExecutionException
491     {
492         try
493         {
494             return MavenProjectUtils.getClasspathElements( project, pluginArtifacts );
495         }
496         catch ( DependencyResolutionRequiredException exception )
497         {
498             throw new MojoExecutionException( "Cannot get plugin classpath elements", exception );
499         }
500     }
501 
502     private List<String> getSourcePaths()
503     {
504         List<String> sourcePaths = new ArrayList<String>();
505 
506         sourcePaths.addAll( getCompileSourceRoots() );
507 
508         if ( additionalSourceRoots != null )
509         {
510             sourcePaths.addAll( additionalSourceRoots );
511         }
512 
513         return sourcePaths;
514     }
515 
516     private List<File> getSourceFiles( SourceInclusionScanner scanner, String name ) throws MojoExecutionException
517     {
518         List<File> sourceFiles = new ArrayList<File>();
519 
520         for ( String path : getSourcePaths() )
521         {
522             File sourceDir = new File( path );
523 
524             sourceFiles.addAll( getSourceFiles( scanner, name, sourceDir ) );
525         }
526 
527         return sourceFiles;
528     }
529 
530     private Set<File> getSourceFiles( SourceInclusionScanner scanner, String name, File sourceDir )
531         throws MojoExecutionException
532     {
533         Set<File> sources;
534 
535         if ( sourceDir.isDirectory() )
536         {
537             try
538             {
539                 Set<?> rawSources = scanner.getIncludedSources( sourceDir, getOutputDirectory() );
540 
541                 sources = CollectionUtils.genericSet( rawSources, File.class );                    
542             }
543             catch ( InclusionScanException exception )
544             {
545                 throw new MojoExecutionException( "Error scanning source directory: " + sourceDir, exception );
546             }
547         }
548         else
549         {
550             sources = Collections.emptySet();
551         }
552 
553         if ( getLog().isDebugEnabled() )
554         {
555             if ( sources.isEmpty() )
556             {
557                 getLog().debug( "No " + name + " found in " + sourceDir );
558             }
559             else
560             {
561                 getLog().debug( StringUtils.capitalizeFirstLetter( name ) + " found in " + sourceDir + ":" );
562 
563                 LogUtils.log( getLog(), LogUtils.LEVEL_DEBUG, sources, "  " );
564             }
565         }
566 
567         return sources;
568     }
569 
570     private SourceInclusionScanner getStaleScanner()
571     {
572         // create scanner
573         
574         SourceInclusionScanner scanner;
575         
576         if ( force )
577         {
578             if ( !CollectionUtils.isEmpty( outputFiles ) || !CollectionUtils.isEmpty( outputFileEndings ) )
579             {
580                 getLog().warn( "Not using staleness checking - ignoring outputFiles and outputFileEndings" );
581             }
582             
583             getLog().debug( "Processing all source files" );
584             
585             scanner = createSimpleScanner();
586         }
587         else
588         {
589             scanner = new StaleSourceScanner( staleMillis, includes, excludes );
590             
591             if ( !CollectionUtils.isEmpty( outputFiles ) )
592             {
593                 if ( !CollectionUtils.isEmpty( outputFileEndings ) )
594                 {
595                     getLog().warn( "Both outputFiles and outputFileEndings specified - using outputFiles" );
596                 }
597                 
598                 getLog().debug( "Computing stale sources against target files " + outputFiles );
599                 
600                 for ( String file : outputFiles )
601                 {                    
602                     scanner.addSourceMapping( new SingleTargetSourceMapping( ".java", file ) );
603                 }
604             }
605             else
606             {
607                 Set<String> suffixes = CollectionUtils.defaultSet( outputFileEndings, Collections.singleton( ".java" ) );
608                 
609                 getLog().debug( "Computing stale sources against target file endings " + suffixes );
610                 
611                 scanner.addSourceMapping( new SuffixMapping( ".java", suffixes ) );
612             }
613         }
614         
615         return scanner;
616     }
617     
618     private SourceInclusionScanner getSourceScanner()
619     {
620         SourceInclusionScanner scanner;
621         
622         if ( force || CollectionUtils.isEmpty( outputFiles ) )
623         {
624             scanner = getStaleScanner();
625         }
626         else
627         {
628             scanner = createSimpleScanner();
629         }
630         
631         return scanner;
632     }
633     
634     private SourceInclusionScanner createSimpleScanner()
635     {
636         SourceInclusionScanner scanner = new SimpleSourceInclusionScanner( includes, excludes );
637         
638         // dummy mapping required to function
639         scanner.addSourceMapping( new SuffixMapping( "", "" ) );
640         
641         return scanner;
642     }
643 }