001 package org.apache.myfaces.maven.plugin;
002
003 /*
004 * Licensed to the Apache Software Foundation (ASF) under one or more
005 * contributor license agreements. See the NOTICE file distributed with
006 * this work for additional information regarding copyright ownership.
007 * The ASF licenses this file to You under the Apache License, Version 2.0
008 * (the "License"); you may not use this file except in compliance with
009 * the License. You may obtain a copy of the License at
010 *
011 * http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 */
019
020 import java.io.File;
021 import java.io.PrintWriter;
022 import java.io.StringWriter;
023 import java.io.IOException;
024 import java.lang.reflect.Method;
025 import java.net.URL;
026 import java.net.URLClassLoader;
027 import java.util.Collection;
028 import java.util.Iterator;
029 import java.util.List;
030 import java.util.StringTokenizer;
031 import java.util.ArrayList;
032
033 import org.apache.maven.project.MavenProject;
034 import org.apache.maven.plugin.AbstractMojo;
035 import org.apache.maven.plugin.MojoExecutionException;
036 import org.apache.maven.artifact.DependencyResolutionRequiredException;
037 import org.codehaus.plexus.compiler.util.scan.SourceInclusionScanner;
038 import org.codehaus.plexus.compiler.javac.Commandline;
039 import org.codehaus.plexus.util.cli.DefaultConsumer;
040 import org.codehaus.plexus.util.cli.CommandLineException;
041 import org.codehaus.plexus.util.cli.CommandLineUtils;
042 import org.codehaus.plexus.util.Os;
043 import org.codehaus.plexus.util.StringUtils;
044 import org.codehaus.plexus.util.FileUtils;
045
046 /**
047 * @author <a href="mailto:jubu@volny.cz">Juraj Burian</a>
048 * @version $Id: AbstractAPTMojo.java 949569 2010-05-30 20:44:27Z bommel $
049 */
050 public abstract class AbstractAPTMojo extends AbstractMojo
051 {
052 /**
053 * PATH_SEPARATOR.
054 */
055 private static final String PATH_SEPARATOR = System.getProperty( "path.separator" );
056 /**
057 * FILE_SEPARATOR.
058 */
059 private static final String FILE_SEPARATOR = System.getProperty( "file.separator" );
060
061 /**
062 * Integer returned by the Apt compiler to indicate success.
063 */
064 private static final int APT_COMPILER_SUCCESS = 0;
065
066 /**
067 * class in tools.jar that implements APT
068 */
069 private static final String APT_ENTRY_POINT = "com.sun.tools.apt.Main";
070
071 /**
072 * method used for apt.
073 */
074 private static final String APT_METHOD_NAME = "process";
075
076 /**
077 * old method used for apt.
078 */
079 private static final String APT_METHOD_NAME_OLD = "compile";
080
081 /**
082 * store info about modification of system classpath for Apt compiler
083 */
084 private static boolean isClasspathModified;
085
086 /**
087 * Working directory when APT compiler is forked
088 * @parameter default-value="${basedir}"
089 * @since 1.0.10
090 */
091 private File workingDir;
092
093 /**
094 * A List of targetFiles for SingleSourceTargetMapping
095 *
096 * @parameter
097 */
098 private List targetFiles;
099
100 /**
101 * enables resource filtering for generated resources
102 *
103 * @parameter default-value="false"
104 */
105 private boolean resourceFiltering;
106
107 /**
108 * targetPath for generated resources
109 *
110 * @parameter
111 */
112 private String resourceTargetPath;
113
114 /**
115 * Whether to include debugging information in the compiled class files. The
116 * default value is true.
117 *
118 * @parameter expression="${maven.compiler.debug}" default-value="true"
119 * @readonly
120 */
121 private boolean debug;
122
123 /**
124 * Comma separated list of "-A" options: Next two examples are equivalent:
125 *
126 * <pre>
127 * <A>-Adebug,-Aloglevel=3</A>
128 * </pre>
129 * <pre>
130 * <A>debug, loglevel=3</A>
131 * </pre>
132 *
133 * @parameter alias="A"
134 */
135 private String aptOptions;
136
137 /**
138 * Output source locations where deprecated APIs are used
139 *
140 * @parameter
141 */
142 private boolean showDeprecation;
143
144 /**
145 * Output warnings
146 *
147 * @parameter
148 */
149 private boolean showWarnings;
150
151 /**
152 * The -encoding argument for the Apt
153 *
154 * @parameter
155 */
156 private String encoding;
157
158 /**
159 * run Apt in verbode mode
160 *
161 * @parameter expression="${verbose}" default-value="false"
162 */
163 private boolean verbose;
164
165 /**
166 * The -nocompile argument for the Apt
167 *
168 * @parameter default-value="false"
169 */
170 private boolean nocompile;
171
172 /**
173 * The granularity in milliseconds of the last modification date for testing
174 * whether a source needs recompilation
175 *
176 * @parameter expression="${lastModGranularityMs}" default-value="0"
177 */
178 private int staleMillis;
179
180 /**
181 * Name of AnnotationProcessorFactory to use; bypasses default discovery
182 * process
183 *
184 * @parameter
185 */
186 private String factory;
187
188 /**
189 * Temporary directory that contain the files from the plugin.
190 *
191 * @parameter expression="${project.build.directory}/maven-apt-plugin"
192 * @required
193 * @readonly
194 */
195 private File tempRoot;
196
197 /**
198 * The directory to run the compiler from if fork is true.
199 *
200 * @parameter expression="${basedir}"
201 * @required
202 * @readonly
203 */
204 private File basedir;
205
206 /**
207 * Allows running the compiler in a separate process.
208 * If "false" it uses the built in compiler, while if "true" it will use an executable.
209 *
210 * @parameter default-value="false"
211 */
212 private boolean fork;
213 /**
214 * Force apt call without staleness checking.
215 *
216 * @parameter default-value="false"
217 */
218 private boolean force;
219
220
221 /**
222 * The maven project.
223 *
224 * @parameter expression="${project}"
225 * @required
226 * @readonly
227 */
228 private MavenProject project;
229
230 /**
231 * The maven project.
232 * @return MavenProject
233 */
234 public MavenProject getProject()
235 {
236 return project;
237 }
238 /**
239 * Force apt call without staleness checking.
240 * @return force
241 */
242 public boolean isForce()
243 {
244 return force;
245 }
246 /**
247 * run Apt in verbode mode
248 * @return verbose
249 */
250 public boolean isVerbose()
251 {
252 return verbose;
253 }
254
255 /**
256 * The granularity in milliseconds of the last modification date for testing
257 * whether a source needs recompilation
258 * @return staleMillis
259 */
260 public int getStaleMillis()
261 {
262 return staleMillis;
263 }
264 /**
265 * A List of targetFiles for SingleSourceTargetMapping
266 * @return a List of TargetFiles
267 */
268 protected List getTargetFiles()
269 {
270 return targetFiles;
271 }
272 /**
273 * enables resource filtering for generated resources
274 * @return resourceFiltering
275 */
276 protected boolean isResourceFiltering()
277 {
278 return resourceFiltering;
279 }
280 /**
281 * targetPath for generated resources
282 * @return resouceTargetPath
283 */
284 protected String getResourceTargetPath()
285 {
286 return resourceTargetPath;
287 }
288 /**
289 * classpath elements.
290 * @return a List of classPathElements
291 */
292 protected abstract List getClasspathElements();
293 /**
294 * The source directories containing the sources to be compiled.
295 * @return a List of CompileSourceRoots
296 */
297 protected abstract List getCompileSourceRoots();
298
299 /**
300 * The extra source directories containing the source to be processed.
301 * @return a List of AptSourceRoots
302 */
303 protected abstract List getAptSourceRoots();
304
305 /**
306 * The directory where compiled classes go.
307 * @return outputDirector
308 */
309 protected abstract File getOutputDirectory();
310
311 /**
312 * The directory where generated code go.
313 * @return generated
314 */
315 protected abstract String getGenerated();
316
317 /**
318 *
319 * @return a SourceInclusionScanner
320 */
321 protected abstract SourceInclusionScanner getSourceInclusionScanner();
322
323 /**
324 * execute
325 * @throws MojoExecutionException
326 */
327 public void execute() throws MojoExecutionException
328 {
329 getLog().debug( "Using apt compiler" );
330 Commandline cmd = new Commandline();
331 int result = APT_COMPILER_SUCCESS;
332 StringWriter writer = new StringWriter();
333
334 // Use reflection to be able to build on all JDKs:
335 try
336 {
337 // init comand line
338 setAptCommandlineSwitches( cmd );
339 setAptSpecifics( cmd );
340 setStandards( cmd );
341 setClasspath( cmd );
342 List sourceFiles = new ArrayList();
343 if ( !fillSourcelist( sourceFiles ) )
344 {
345 if ( getLog().isDebugEnabled() )
346 {
347 getLog().debug( "there are not stale sources." );
348 }
349 return;
350 }
351 else
352 {
353 if ( fork )
354 {
355
356 if ( !tempRoot.exists() )
357 {
358 tempRoot.mkdirs();
359 }
360 File file = new File( tempRoot , "files" );
361 if ( !getLog().isDebugEnabled() )
362 {
363 file.deleteOnExit();
364 }
365 try
366 {
367 FileUtils.fileWrite( file.getAbsolutePath(),
368 StringUtils.join( sourceFiles.iterator(), "\n" ) );
369 cmd.createArgument().setValue( '@' + file.getPath() );
370 }
371 catch ( IOException e )
372 {
373 throw new MojoExecutionException( "Unable to write temporary file for command execution", e );
374 }
375 }
376 else
377 {
378 Iterator sourceIt = sourceFiles.iterator();
379 while ( sourceIt.hasNext() )
380 {
381 cmdAdd( cmd, (String) sourceIt.next() );
382 }
383 }
384 }
385 if ( fork )
386 {
387 if ( getLog().isDebugEnabled() )
388 {
389 getLog().debug( "Working dir: " + workingDir.getAbsolutePath() );
390 }
391 cmd.setWorkingDirectory( workingDir.getAbsolutePath() );
392 cmd.setExecutable( getAptPath() );
393
394 if ( getLog().isDebugEnabled() )
395 {
396 getLog().debug( "Invoking apt with cmd " + Commandline.toString( cmd.getShellCommandline() ) );
397 }
398
399 CommandLineUtils.StringStreamConsumer err = new CommandLineUtils.StringStreamConsumer();
400 try
401 {
402 int exitCode = CommandLineUtils.executeCommandLine( cmd, new DefaultConsumer(), err );
403
404 getLog().error( err.getOutput() );
405
406 if ( exitCode != 0 )
407 {
408 throw new MojoExecutionException( "Exit code: " + exitCode + " - " + err.getOutput() );
409 }
410 }
411 catch ( CommandLineException e )
412 {
413 throw new MojoExecutionException( "Unable to execute apt command", e );
414 }
415 }
416 else
417 {
418 // we need to have tools.jar in lasspath
419 // due to bug in Apt compiler, system classpath must be modified but in future:
420 // TODO try separate ClassLoader (see Plexus compiler api)
421 if ( !isClasspathModified )
422 {
423 URL toolsJar = new File( System.getProperty( "java.home" ),
424 "../lib/tools.jar" ).toURL();
425 Method m = URLClassLoader.class.getDeclaredMethod( "addURL",
426 new Class[] { URL.class } );
427 m.setAccessible( true );
428 m.invoke( this.getClass().getClassLoader()
429 .getSystemClassLoader(), new Object[] { toolsJar } );
430 isClasspathModified = true;
431 }
432 Class c = this.getClass().forName( APT_ENTRY_POINT ); // getAptCompilerClass();
433 Object compiler = c.newInstance();
434 if ( getLog().isDebugEnabled() )
435 {
436 getLog().debug( "Invoking apt with cmd " + cmd.toString() );
437 }
438 try
439 {
440 Method compile = c.getMethod( APT_METHOD_NAME, new Class[] {
441 PrintWriter.class, ( new String[] {} ).getClass() } );
442 result = ( ( Integer ) //
443 compile.invoke( compiler, new Object[] { new PrintWriter( writer ),
444 cmd.getArguments() } ) ).intValue();
445 }
446 catch ( NoSuchMethodException e )
447 {
448 // ignore
449 Method compile = c.getMethod( APT_METHOD_NAME_OLD, new Class[] {
450 ( new String[] {} ).getClass(), PrintWriter.class } );
451 result = ( ( Integer ) //
452 compile.invoke( compiler, new Object[] {
453 cmd.getArguments(), new PrintWriter( writer ) } ) ).intValue();
454 }
455 }
456
457
458 }
459 catch ( Exception ex )
460 {
461 throw new MojoExecutionException( "Error starting apt compiler", ex );
462 }
463 finally
464 {
465 if ( result != APT_COMPILER_SUCCESS )
466 {
467 throw new MojoExecutionException( this, "Compilation error.", writer.getBuffer().toString() );
468 }
469 if ( getLog().isDebugEnabled() )
470 {
471 String r = writer.getBuffer().toString();
472 if ( 0 != r.length() )
473 {
474 getLog().debug( r );
475 }
476 getLog().debug( "Apt finished." );
477 }
478 }
479 }
480
481 /**
482 *
483 * @param cmd
484 */
485 private void setAptCommandlineSwitches( Commandline cmd )
486 {
487 if ( null == aptOptions )
488 {
489 return;
490 }
491 StringTokenizer tokenizer = new StringTokenizer( aptOptions.trim(), "," );
492 while ( tokenizer.hasMoreElements() )
493 {
494 String option = tokenizer.nextToken().trim();
495 if ( !option.startsWith( "-A" ) )
496 {
497 option = "-A" + option;
498 }
499 cmdAdd( cmd, option );
500 }
501 }
502
503 /**
504 *
505 * @param cmd
506 * @throws MojoExecutionException
507 */
508 private void setAptSpecifics( Commandline cmd ) throws MojoExecutionException
509 {
510 try
511 {
512 String g = basedir.getAbsolutePath() + FILE_SEPARATOR
513 + getGenerated();
514 File generatedDir = new File( g );
515 cmdAdd( cmd, "-s", generatedDir.getCanonicalPath() );
516 if ( !generatedDir.exists() )
517 {
518 generatedDir.mkdirs();
519 }
520 }
521 catch ( Exception e )
522 {
523 throw new MojoExecutionException( //
524 "Generated directory is invalid.", e );
525 }
526 if ( nocompile )
527 {
528 cmdAdd( cmd, "-nocompile" );
529 }
530 if ( null != factory && 0 != factory.length() )
531 {
532 cmdAdd( cmd, "-factory", factory );
533 }
534 }
535
536 /**
537 *
538 * @param cmd
539 * @throws MojoExecutionException
540 */
541 private void setStandards( Commandline cmd ) throws MojoExecutionException
542 {
543 if ( debug )
544 {
545 cmdAdd( cmd, "-g" );
546 }
547 if ( !showWarnings )
548 {
549 cmdAdd( cmd, "-nowarn" );
550 }
551 if ( showDeprecation )
552 {
553 cmdAdd( cmd, "-depecation" );
554 }
555 if ( null != encoding )
556 {
557 cmdAdd( cmd, "-encoding", encoding );
558 }
559 if ( verbose )
560 {
561 cmdAdd( cmd, "-verbose" );
562 }
563
564 // add sourcepath directory
565 setSourcepath(cmd);
566 // add output directory
567 try
568 {
569 if ( !getOutputDirectory().exists() )
570 {
571 getOutputDirectory().mkdirs();
572 }
573 cmdAdd( cmd, "-d", getOutputDirectory().getCanonicalPath() );
574 }
575 catch ( Exception ex )
576 {
577 throw new MojoExecutionException( //
578 "Output directory is invalid.", ex );
579 }
580 }
581
582 private void setSourcepath(Commandline cmd) {
583 StringBuffer buffer = new StringBuffer();
584 for ( Iterator it = getCompileSourceRoots().iterator(); it.hasNext();)
585 {
586 buffer.append( it.next() );
587 if ( it.hasNext() )
588 {
589 buffer.append( PATH_SEPARATOR );
590 }
591 }
592 if ( getAptSourceRoots() != null)
593 {
594 if (buffer.length() > 0 && getAptSourceRoots().size() > 0)
595 {
596 buffer.append( PATH_SEPARATOR );
597 }
598 for ( Iterator it = getAptSourceRoots().iterator(); it.hasNext();)
599 {
600 buffer.append( it.next() );
601 if ( it.hasNext() )
602 {
603 buffer.append( PATH_SEPARATOR );
604 }
605 }
606 }
607 cmdAdd( cmd, "-sourcepath", buffer.toString() );
608 }
609
610 /**
611 *
612 * @param cmd
613 * @return
614 * @throws MojoExecutionException
615 */
616 private boolean fillSourcelist( List cmd ) throws MojoExecutionException
617 {
618 boolean has = false;
619 // sources ....
620 Iterator it = getCompileSourceRoots().iterator();
621 if ( getLog().isDebugEnabled() )
622 {
623 getLog().debug( "Checking sourcepath" );
624 }
625 while ( it.hasNext() )
626 {
627 File srcFile = new File( (String) it.next() );
628 has = addIncludedSources( srcFile, cmd, has );
629 }
630 List aptSourcesRoots = getAptSourceRoots();
631 if ( aptSourcesRoots != null )
632 {
633 it = aptSourcesRoots.iterator();
634 while ( it.hasNext() )
635 {
636 File srcFile = new File( (String) it.next() );
637 has = addIncludedSources( srcFile, cmd, has );
638 }
639 }
640 return has;
641 }
642
643 /**
644 *
645 * @param srcFile
646 * @param cmd
647 * @param has
648 * @return
649 * @throws MojoExecutionException
650 */
651 private boolean addIncludedSources( File srcFile, List cmd, boolean has ) throws MojoExecutionException
652 {
653 if ( getLog().isDebugEnabled() )
654 {
655 getLog().debug( "Checking sourcepath in " + srcFile );
656 }
657 if ( srcFile.isDirectory() )
658 {
659 Collection sources = null;
660 try
661 {
662 sources = //
663 getSourceInclusionScanner().getIncludedSources( srcFile,
664 getOutputDirectory() );
665 }
666 catch ( Exception ex )
667 {
668 throw new MojoExecutionException(
669 "Can't agregate sources.", ex );
670 }
671 if ( getLog().isDebugEnabled() )
672 {
673 getLog().debug(
674 "sources from: " + srcFile.getAbsolutePath() );
675 String s = "";
676 for ( Iterator jt = sources.iterator(); jt.hasNext();)
677 {
678 s += jt.next() + "\n";
679 }
680 getLog().debug( s );
681 }
682 Iterator jt = sources.iterator();
683 while ( jt.hasNext() )
684 {
685 File src = (File) jt.next();
686 if ( fork )
687 {
688 cmd.add( quotedPathArgument( src.getAbsolutePath() ) );
689 }
690 else
691 {
692 cmd.add( src.getAbsolutePath() );
693 }
694 has = true;
695 }
696 }
697 return has;
698 }
699
700 /**
701 *
702 * @param cmd
703 * @throws MojoExecutionException
704 * @throws DependencyResolutionRequiredException
705 */
706 private void setClasspath( Commandline cmd ) throws MojoExecutionException, DependencyResolutionRequiredException
707 {
708 StringBuffer buffer = new StringBuffer();
709 for ( Iterator it = getClasspathElements().iterator(); it.hasNext();)
710 {
711 buffer.append( it.next() );
712 if ( it.hasNext() )
713 {
714 buffer.append( PATH_SEPARATOR );
715 }
716 }
717 cmdAdd( cmd, "-classpath", buffer.toString() );
718 }
719
720 /**
721 *
722 * @param cmd
723 * @param arg
724 */
725 private void cmdAdd( Commandline cmd, String arg )
726 {
727 /**
728 * OBSOLETE
729 * if( true == getLog().isDebugEnabled() ) { getLog().debug(
730 * arg ); }
731 */
732 cmd.createArgument().setValue( arg );
733 //cmd.add( arg );
734 }
735
736 /**
737 *
738 * @param cmd
739 * @param arg1
740 * @param arg2
741 */
742 private void cmdAdd( Commandline cmd, String arg1, String arg2 )
743 {
744 /**
745 * OBSOLETE
746 * if( true == getLog().isDebugEnabled() ) { getLog().debug(
747 * arg1 + " " + arg2 ); }
748 */
749 cmdAdd( cmd, arg1 );
750 cmdAdd( cmd, arg2 );
751 }
752
753 /**
754 * Get the path of apt tool depending the OS.
755 *
756 * @return the path of the apt tool
757 */
758 private String getAptPath()
759 {
760 String aptCommand = "apt";
761 if ( Os.isFamily( "windows" ) )
762 {
763 aptCommand = "apt.exe";
764 }
765
766 File aptExe;
767
768 // For IBM's JDK 1.2
769 if ( Os.isName( "aix" ) )
770 {
771 aptExe = new File( System.getProperty( "java.home" ) + "/../sh", aptCommand );
772 }
773 else if ( Os.isFamily( "unix" ) && Os.isFamily( "mac" ) )
774 {
775 aptExe = new File( System.getProperty( "java.home" ) + "/bin", aptCommand );
776 }
777 else
778 {
779 aptExe = new File( System.getProperty( "java.home" ) + "/../bin", aptCommand );
780 }
781
782 getLog().debug( "Apt executable=[" + aptExe.getAbsolutePath() + "]" );
783
784 return aptExe.getAbsolutePath();
785 }
786
787 /**
788 *
789 * @param value
790 * @return quotedPathArgument
791 */
792 private String quotedPathArgument( String value )
793 {
794 String path = value;
795
796 if ( !StringUtils.isEmpty( path ) )
797 {
798 path = path.replace( '\\', '/' );
799 if ( path.indexOf( "\'" ) != -1 )
800 {
801 String split[] = path.split( "\'" );
802 path = "";
803
804 for ( int i = 0; i < split.length; i++ )
805 {
806 if ( i != split.length - 1 )
807 {
808 path = path + split[i] + "\\'";
809 }
810 else
811 {
812 path = path + split[i];
813 }
814 }
815 }
816 path = "'" + path + "'";
817 }
818
819 return path;
820 }
821 }