View Javadoc

1   /*
2    * #%L
3    * License Maven Plugin
4    * 
5    * $Id: UpdateFileHeaderMojo.java 14741 2011-09-20 08:31:41Z tchemit $
6    * $HeadURL: http://svn.codehaus.org/mojo/tags/license-maven-plugin-1.0/src/main/java/org/codehaus/mojo/license/UpdateFileHeaderMojo.java $
7    * %%
8    * Copyright (C) 2008 - 2011 CodeLutin, Codehaus, Tony Chemit
9    * %%
10   * This program is free software: you can redistribute it and/or modify
11   * it under the terms of the GNU Lesser General Public License as 
12   * published by the Free Software Foundation, either version 3 of the 
13   * License, or (at your option) any later version.
14   * 
15   * This program is distributed in the hope that it will be useful,
16   * but WITHOUT ANY WARRANTY; without even the implied warranty of
17   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18   * GNU General Lesser Public License for more details.
19   * 
20   * You should have received a copy of the GNU General Lesser Public 
21   * License along with this program.  If not, see
22   * <http://www.gnu.org/licenses/lgpl-3.0.html>.
23   * #L%
24   */
25  
26  package org.codehaus.mojo.license;
27  
28  import org.apache.commons.lang.StringUtils;
29  import org.codehaus.mojo.license.header.*;
30  import org.codehaus.mojo.license.header.transformer.FileHeaderTransformer;
31  import org.codehaus.mojo.license.model.License;
32  import org.codehaus.plexus.util.DirectoryScanner;
33  import org.codehaus.plexus.util.FileUtils;
34  
35  import java.io.File;
36  import java.io.IOException;
37  import java.util.*;
38  
39  /**
40   * The goal to update (or add) the header on project source files.
41   * <p/>
42   * This goal replace the {@code update-header} goal which can not deal with
43   * Copyright.
44   * <p/>
45   * This goal use a specific project file descriptor {@code project.xml} to
46   * describe all files to update for a whole project.
47   *
48   * @author tchemit <chemit@codelutin.com>
49   * @requiresProject true
50   * @goal update-file-header
51   * @since 1.0
52   */
53  public class UpdateFileHeaderMojo
54      extends AbstractLicenseNameMojo
55      implements FileHeaderProcessorConfiguration
56  {
57  
58      /**
59       * Name of project (or module).
60       * <p/>
61       * Will be used as description section of new header.
62       *
63       * @parameter expression="${license.projectName}" default-value="${project.name}"
64       * @required
65       * @since 1.0
66       */
67      protected String projectName;
68  
69      /**
70       * Name of project's organization.
71       * <p/>
72       * Will be used as copyrigth's holder in new header.
73       *
74       * @parameter expression="${license.organizationName}" default-value="${project.organization.name}"
75       * @required
76       * @since 1.0
77       */
78      protected String organizationName;
79  
80      /**
81       * Inception year of the project.
82       * <p/>
83       * Will be used as first year of copyright section in new header.
84       *
85       * @parameter expression="${license.inceptionYear}" default-value="${project.inceptionYear}"
86       * @required
87       * @since 1.0
88       */
89      protected String inceptionYear;
90  
91      /**
92       * A flag to add svn:keywords on new header.
93       * <p/>
94       * Will add svn keywords :
95       * <pre>Author, Id, Rev, URL and Date</pre>
96       *
97       * @parameter expression="${license.addSvnKeyWords}" default-value="false"
98       * @since 1.0
99       */
100     protected boolean addSvnKeyWords;
101 
102     /**
103      * A flag to authorize update of the description part of the header.
104      * <p/>
105      * <b>Note:</b> By default, do NOT authorize it since description can change
106      * on each file).
107      *
108      * @parameter expression="${license.canUpdateDescription}" default-value="false"
109      * @since 1.0
110      */
111     protected boolean canUpdateDescription;
112 
113     /**
114      * A flag to authorize update of the copyright part of the header.
115      * <p/>
116      * <b>Note:</b> By default, do NOT authorize it since copyright part should be
117      * handled by developpers (holder can change on each file for example).
118      *
119      * @parameter expression="${license.canUpdateCopyright}" default-value="false"
120      * @since 1.0
121      */
122     protected boolean canUpdateCopyright;
123 
124     /**
125      * A flag to authorize update of the license part of the header.
126      * <p/>
127      * <b>Note:</b> By default, authorize it since license part should always be
128      * generated by the plugin.
129      *
130      * @parameter expression="${license.canUpdateLicense}" default-value="true"
131      * @since 1.0
132      */
133     protected boolean canUpdateLicense;
134 
135     /**
136      * A flag to update copyright application time (change copyright last year
137      * if required) according to the last commit made on the processed file.
138      * <p/>
139      * Note that this functionnality is still not effective.
140      *
141      * @parameter expression="${license.updateCopyright}" default-value="false"
142      * @since 1.0
143      */
144     protected boolean updateCopyright;
145 
146     /**
147      * A tag to place on files that will be ignored by the plugin.
148      * <p/>
149      * Sometimes, it is necessary to do this when file is under a specific license.
150      * <p/>
151      * <b>Note:</b> If no sets, will use the default tag {@code %%Ignore-License}
152      *
153      * @parameter expression="${license.ignoreTag}"
154      * @since 1.0
155      */
156     protected String ignoreTag;
157 
158     /**
159      * A flag to skip the goal.
160      *
161      * @parameter expression="${license.skipUpdateLicense}" default-value="false"
162      * @since 1.0
163      */
164     protected boolean skipUpdateLicense;
165 
166     /**
167      * A flag to test plugin but modify no file.
168      *
169      * @parameter expression="${dryRun}" default-value="false"
170      * @since 1.0
171      */
172     protected boolean dryRun;
173 
174     /**
175      * A flag to clear everything after execution.
176      * <p/>
177      * <b>Note:</b> This property should ONLY be used for test purpose.
178      *
179      * @parameter expression="${license.clearAfterOperation}" default-value="true"
180      * @since 1.0
181      */
182     protected boolean clearAfterOperation;
183 
184     /**
185      * To specify the base dir from which we apply the license.
186      * <p/>
187      * Should be on form "root1,root2,rootn".
188      * <p/>
189      * By default, the main roots are "src, target/generated-sources, target/processed-sources".
190      * <p/>
191      * <b>Note:</b> If some of these roots do not exist, they will be simply
192      * ignored.
193      * <p/>
194      * <b>Note:</b> This parameter is not useable if you are still using a project file descriptor.
195      *
196      * @parameter expression="${license.roots}"
197      * @since 1.0
198      */
199     protected String[] roots;
200 
201     /**
202      * Specific files to includes, separated by a comma. By default, it is "** /*".
203      * <p/>
204      * <b>Note:</b> This parameter is not useable if you are still using a project file descriptor.
205      *
206      * @parameter expression="${license.includes}"
207      * @since 1.0
208      */
209     protected String[] includes;
210 
211     /**
212      * Specific files to excludes, separated by a comma.
213      * By default, thoses file type are excluded:
214      * <ul>
215      * <li>modelisation</li>
216      * <li>images</li>
217      * </ul>
218      * <p/>
219      * <b>Note:</b> This parameter is not useable if you are still using a project file descriptor.
220      *
221      * @parameter expression="${license.excludes}"
222      * @since 1.0
223      */
224     protected String[] excludes;
225 
226     /**
227      * To associate extra extension files to an existing comment style.
228      * <p/>
229      * Keys of the map are the extension of extra files to treate, and the value
230      * is the comment style you want to associate.
231      * <p/>
232      * For example, to treate file with extensions {@code java2} and {@code jdata}
233      * as {@code java} files (says using the {@code java} comment style, declare this
234      * in your plugin configuration :
235      * <pre>
236      * &lt;extraExtensions&gt;
237      * &lt;java2&gt;java&lt;/java2&gt;
238      * &lt;jdata&gt;java&lt;/jdata&gt;
239      * &lt;/extraExtensions&gt;
240      * </pre>
241      * <p/>
242      * <b>Note:</b> This parameter is not useable if you are still using a project file descriptor.
243      *
244      * @parameter
245      * @since 1.0
246      */
247     protected Map<String, String> extraExtensions;
248 
249     /**
250      * @component role="org.nuiton.processor.Processor" roleHint="file-header"
251      * @since 1.0
252      */
253     private FileHeaderProcessor processor;
254 
255     /**
256      * The processor filter used to change header content.
257      *
258      * @component role="org.codehaus.mojo.license.header.FileHeaderFilter" roleHint="update-file-header"
259      * @since 1.0
260      */
261     private UpdateFileHeaderFilter filter;
262 
263     /**
264      * All available header transformers.
265      *
266      * @component role="org.codehaus.mojo.license.header.transformer.FileHeaderTransformer"
267      * @since 1.0
268      */
269     private Map<String, FileHeaderTransformer> transformers;
270 
271     /**
272      * internal file header transformer.
273      */
274     private FileHeaderTransformer transformer;
275 
276     /**
277      * internal default file header.
278      */
279     private FileHeader header;
280 
281     /**
282      * timestamp used for generation.
283      */
284     private long timestamp;
285 
286     /**
287      * The dictionnary of extension indexed by their associated comment style.
288      *
289      * @since 1.0
290      */
291     private Map<String, String> extensionToCommentStyle;
292 
293     public static final String[] DEFAULT_INCLUDES = new String[]{ "**/*" };
294 
295     public static final String[] DEFAULT_EXCLUDES =
296         new String[]{ "**/*.zargo", "**/*.uml", "**/*.umldi", "**/*.xmi", /* modelisation */
297             "**/*.img", "**/*.png", "**/*.jpg", "**/*.jpeg", "**/*.gif", /* images */
298             "**/*.zip", "**/*.jar", "**/*.war", "**/*.ear", "**/*.tgz", "**/*.gz" };
299 
300     public static final String[] DEFAULT_ROOTS =
301         new String[]{ "src", "target/generated-sources", "target/processed-sources" };
302 
303     /**
304      * Defines state of a file after process.
305      *
306      * @author tchemit <chemit@codelutin.com>
307      * @since 1.0
308      */
309     enum FileState
310     {
311 
312         /**
313          * file was updated
314          */
315         update,
316 
317         /**
318          * file was up to date
319          */
320         uptodate,
321 
322         /**
323          * something was added on file
324          */
325         add,
326 
327         /**
328          * file was ignored
329          */
330         ignore,
331 
332         /**
333          * treatment failed for file
334          */
335         fail;
336 
337         /**
338          * register a file for this state on result dictionary.
339          *
340          * @param file   file to add
341          * @param result dictionary to update
342          */
343         public void addFile( File file, EnumMap<FileState, Set<File>> result )
344         {
345             Set<File> fileSet = result.get( this );
346             if ( fileSet == null )
347             {
348                 fileSet = new HashSet<File>();
349                 result.put( this, fileSet );
350             }
351             fileSet.add( file );
352         }
353     }
354 
355     /**
356      * set of processed files
357      */
358     private Set<File> processedFiles;
359 
360     /**
361      * Dictionnary of treated files indexed by their state.
362      */
363     private EnumMap<FileState, Set<File>> result;
364 
365     /**
366      * Dictonnary of files to treate indexed by their CommentStyle.
367      */
368     private Map<String, List<File>> filesToTreateByCommentStyle;
369 
370     @Override
371     public void init()
372         throws Exception
373     {
374 
375         if ( isSkip() )
376         {
377             return;
378         }
379 
380         if ( StringUtils.isEmpty( getIgnoreTag() ) )
381         {
382 
383             // use default value
384             setIgnoreTag( "%" + "%Ignore-License" );
385         }
386 
387         if ( isVerbose() )
388         {
389 
390             // print availables comment styles (transformers)
391             StringBuilder buffer = new StringBuilder();
392             buffer.append( "config - available comment styles :" );
393             String commentFormat = "\n  * %1$s (%2$s)";
394             for ( String transformerName : getTransformers().keySet() )
395             {
396                 FileHeaderTransformer transformer = getTransformer( transformerName );
397                 String str = String.format( commentFormat, transformer.getName(), transformer.getDescription() );
398                 buffer.append( str );
399             }
400             getLog().info( buffer.toString() );
401         }
402 
403         if ( isUpdateCopyright() )
404         {
405 
406             getLog().warn( "\n\nupdateCopyright is not still available...\n\n" );
407             //TODO-TC20100409 checks scm
408             // checks scm is ok
409             // for the moment, will only deal with svn except if scm
410             // offers a nice api to obtain last commit date on a file
411 
412         }
413 
414         // set timestamp used for temporary files
415         setTimestamp( System.nanoTime() );
416 
417         // add flags to authorize or not updates of header
418         getFilter().setUpdateCopyright( isCanUpdateCopyright() );
419         getFilter().setUpdateDescription( isCanUpdateDescription() );
420         getFilter().setUpdateLicense( isCanUpdateLicense() );
421 
422         getFilter().setLog( getLog() );
423         getProcessor().setConfiguration( this );
424         getProcessor().setFilter( filter );
425 
426         super.init();
427 
428         if ( roots == null || roots.length == 0 )
429         {
430             roots = DEFAULT_ROOTS;
431             if ( isVerbose() )
432             {
433                 getLog().info( "Will use default roots " + Arrays.toString( roots ) );
434             }
435         }
436 
437         if ( includes == null || includes.length == 0 )
438         {
439             includes = DEFAULT_INCLUDES;
440             if ( isVerbose() )
441             {
442                 getLog().info( "Will use default includes " + Arrays.toString( includes ) );
443             }
444         }
445 
446         if ( excludes == null || excludes.length == 0 )
447         {
448             excludes = DEFAULT_EXCLUDES;
449             if ( isVerbose() )
450             {
451                 getLog().info( "Will use default excludes" + Arrays.toString( excludes ) );
452             }
453         }
454 
455         extensionToCommentStyle = new TreeMap<String, String>();
456 
457         // add default extensions from header transformers
458         for ( Map.Entry<String, FileHeaderTransformer> entry : transformers.entrySet() )
459         {
460             String commentStyle = entry.getKey();
461             FileHeaderTransformer transformer = entry.getValue();
462 
463             String[] extensions = transformer.getDefaultAcceptedExtensions();
464             for ( String extension : extensions )
465             {
466                 if ( isVerbose() )
467                 {
468                     getLog().info( "Associate extension " + extension + " to comment style " + commentStyle );
469                 }
470                 extensionToCommentStyle.put( extension, commentStyle );
471             }
472         }
473 
474         if ( extraExtensions != null )
475         {
476 
477             // fill extra extensions for each transformer
478             for ( Map.Entry<String, String> entry : extraExtensions.entrySet() )
479             {
480                 String extension = entry.getKey();
481                 if ( extensionToCommentStyle.containsKey( extension ) )
482                 {
483 
484                     // override existing extension mapping
485                     getLog().warn( "The extension " + extension + " is already accepted for comment style " +
486                                        extensionToCommentStyle.get( extension ) );
487                 }
488                 String commentStyle = entry.getValue();
489 
490                 // check transformer exists
491                 getTransformer( commentStyle );
492 
493                 if ( isVerbose() )
494                 {
495                     getLog().info( "Associate extension '" + extension + "' to comment style '" + commentStyle + "'" );
496                 }
497                 extensionToCommentStyle.put( extension, commentStyle );
498             }
499         }
500 
501         // get all files to treate indexed by their comment style
502         filesToTreateByCommentStyle = obtainFilesToTreateByCommentStyle();
503     }
504 
505     protected Map<String, List<File>> obtainFilesToTreateByCommentStyle()
506     {
507 
508         Map<String, List<File>> result = new HashMap<String, List<File>>();
509 
510         // add for all known comment style (says transformer) a empty list
511         // this permits not to have to test if there is an already list each time
512         // we wants to add a new file...
513         for ( String commentStyle : transformers.keySet() )
514         {
515             result.put( commentStyle, new ArrayList<File>() );
516         }
517 
518         List<String> rootsList = new ArrayList<String>( roots.length );
519         for ( String root : roots )
520         {
521             File f = new File( root );
522             if ( f.isAbsolute() )
523             {
524                 rootsList.add( f.getAbsolutePath() );
525             }
526             else
527             {
528                 f = new File( getProject().getBasedir(), root );
529             }
530             if ( f.exists() )
531             {
532                 getLog().info( "Will search files to update from root " + f );
533                 rootsList.add( f.getAbsolutePath() );
534             }
535             else
536             {
537                 if ( isVerbose() )
538                 {
539                     getLog().info( "Skip not found root " + f );
540                 }
541             }
542         }
543 
544         // Obtain all files to treate
545         Map<File, String[]> allFiles = new HashMap<File, String[]>();
546         getFilesToTreateForRoots( includes, excludes, rootsList, allFiles );
547 
548         // filter all these files according to their extension
549 
550         for ( Map.Entry<File, String[]> entry : allFiles.entrySet() )
551         {
552             File root = entry.getKey();
553             String[] filesPath = entry.getValue();
554 
555             // sort them by the associated comment style to their extension
556             for ( String path : filesPath )
557             {
558                 String extension = FileUtils.extension( path );
559                 String commentStyle = extensionToCommentStyle.get( extension );
560                 if ( StringUtils.isEmpty( commentStyle ) )
561                 {
562 
563                     // unknown extension, do not treate this file
564                     continue;
565                 }
566                 //
567                 File file = new File( root, path );
568                 List<File> files = result.get( commentStyle );
569                 files.add( file );
570             }
571         }
572         return result;
573     }
574 
575     @Override
576     public void doAction()
577         throws Exception
578     {
579 
580         long t0 = System.nanoTime();
581 
582         clear();
583 
584         processedFiles = new HashSet<File>();
585         result = new EnumMap<FileState, Set<File>>( FileState.class );
586 
587         try
588         {
589 
590             for ( Map.Entry<String, List<File>> commentStyleFiles : getFilesToTreateByCommentStyle().entrySet() )
591             {
592 
593                 String commentStyle = commentStyleFiles.getKey();
594                 List<File> files = commentStyleFiles.getValue();
595 
596                 processCommentStyle( commentStyle, files );
597             }
598 
599         }
600         finally
601         {
602 
603             int nbFiles = getProcessedFiles().size();
604             if ( nbFiles == 0 )
605             {
606                 getLog().warn( "No file to scan." );
607             }
608             else
609             {
610                 String delay = MojoHelper.convertTime( System.nanoTime() - t0 );
611                 String message =
612                     String.format( "Scan %s file%s header done in %s.", nbFiles, nbFiles > 1 ? "s" : "", delay );
613                 getLog().info( message );
614             }
615             Set<FileState> states = result.keySet();
616             if ( states.size() == 1 && states.contains( FileState.uptodate ) )
617             {
618                 // all files where up to date
619                 getLog().info( "All files are up-to-date." );
620             }
621             else
622             {
623 
624                 StringBuilder buffer = new StringBuilder();
625                 for ( FileState state : FileState.values() )
626                 {
627 
628                     reportType( state, buffer );
629                 }
630 
631                 getLog().info( buffer.toString() );
632             }
633 
634             // clean internal states
635             if ( isClearAfterOperation() )
636             {
637                 clear();
638             }
639         }
640     }
641 
642     protected void processCommentStyle( String commentStyle, List<File> filesToTreat )
643         throws IOException
644     {
645 
646         // obtain license from definition
647         License license = getLicense( getLicenseName(), true );
648 
649         getLog().info( "Process header '" + commentStyle + "'" );
650         getLog().info( " - using " + license.getDescription() );
651 
652         // use header transformer according to comment style given in header
653         setTransformer( getTransformer( commentStyle ) );
654 
655         // file header to use if no header is found on a file
656         FileHeader defaultFileHeader =
657             buildDefaultFileHeader( license, getProjectName(), getInceptionYear(), getOrganizationName(),
658                                     isAddSvnKeyWords(), getEncoding() );
659 
660         // change default license header in processor
661         setHeader( defaultFileHeader );
662 
663         // update processor filter
664         getProcessor().populateFilter();
665 
666         for ( File file : filesToTreat )
667         {
668             prepareProcessFile( file );
669         }
670         filesToTreat.clear();
671     }
672 
673 
674     protected void prepareProcessFile( File file )
675         throws IOException
676     {
677 
678         if ( getProcessedFiles().contains( file ) )
679         {
680             getLog().info( " - skip already processed file " + file );
681             return;
682         }
683 
684         // output file
685         File processFile = new File( file.getAbsolutePath() + "_" + getTimestamp() );
686         boolean doFinalize = false;
687         try
688         {
689             doFinalize = processFile( file, processFile );
690         }
691         catch ( Exception e )
692         {
693             getLog().warn( "skip failed file : " + e.getMessage() +
694                                ( e.getCause() == null ? "" : " Cause : " + e.getCause().getMessage() ), e );
695             FileState.fail.addFile( file, getResult() );
696             doFinalize = false;
697         }
698         finally
699         {
700 
701             // always clean processor internal states
702             getProcessor().reset();
703 
704             // whatever was the result, this file is treated.
705             getProcessedFiles().add( file );
706 
707             if ( doFinalize )
708             {
709                 finalizeFile( file, processFile );
710             }
711             else
712             {
713                 FileUtil.deleteFile( processFile );
714             }
715         }
716 
717     }
718 
719     /**
720      * Process the given {@code file} and save the result in the given
721      * {@code processFile}.
722      *
723      * @param file        the file to process
724      * @param processFile the ouput processed file
725      * @return {@code true} if prepareProcessFile can be finalize, otherwise need to be delete
726      * @throws IOException if any pb while treatment
727      */
728     protected boolean processFile( File file, File processFile )
729         throws IOException
730     {
731 
732         if ( getLog().isDebugEnabled() )
733         {
734             getLog().debug( " - process file " + file );
735             getLog().debug( " - will process into file " + processFile );
736         }
737 
738         String content;
739 
740         try
741         {
742 
743             // check before all that file should not be skip by the ignoreTag
744             // this is a costy operation
745             //TODO-TC-20100411 We should process always from the read content not reading again from file
746 
747             content = FileUtil.readAsString( file, getEncoding() );
748 
749         }
750         catch ( IOException e )
751         {
752             throw new IOException( "Could not obtain content of file " + file );
753         }
754 
755         //check that file is not marked to be ignored
756         if ( content.contains( getIgnoreTag() ) )
757         {
758             getLog().info( " - ignore file (detected " + getIgnoreTag() + ") " + file );
759 
760             FileState.ignore.addFile( file, getResult() );
761 
762             return false;
763         }
764 
765         FileHeaderProcessor processor = getProcessor();
766 
767         // process file to detect header
768 
769         try
770         {
771             processor.process( file, processFile );
772         }
773         catch ( IllegalStateException e )
774         {
775             // could not obtain existing header
776             throw new InvalideFileHeaderException(
777                 "Could not extract header on file " + file + " for reason " + e.getMessage() );
778         }
779         catch ( Exception e )
780         {
781             if ( e instanceof InvalideFileHeaderException )
782             {
783                 throw (InvalideFileHeaderException) e;
784             }
785             throw new IOException( "Could not process file " + file + " for reason " + e.getMessage() );
786         }
787 
788         if ( processor.isTouched() )
789         {
790 
791             if ( isVerbose() )
792             {
793                 getLog().info( " - header was updated for " + file );
794             }
795             if ( processor.isModified() )
796             {
797 
798                 // header content has changed
799                 // must copy back process file to file (if not dry run)
800 
801                 FileState.update.addFile( file, getResult() );
802                 return true;
803 
804             }
805 
806             FileState.uptodate.addFile( file, getResult() );
807             return false;
808         }
809 
810         // header was not fully (or not at all) detected in file
811 
812         if ( processor.isDetectHeader() )
813         {
814 
815             // file has not a valid header (found a start process atg, but
816             // not an ending one), can not do anything
817             throw new InvalideFileHeaderException( "Could not find header end on file " + file );
818         }
819 
820         // no header at all, add a new header
821 
822         getLog().info( " - adding license header on file " + file );
823 
824         //FIXME tchemit 20100409 xml files must add header after a xml prolog line
825         content = getTransformer().addHeader( getFilter().getFullHeaderContent(), content );
826 
827         if ( !isDryRun() )
828         {
829             FileUtil.writeString( processFile, content, getEncoding() );
830         }
831 
832         FileState.add.addFile( file, getResult() );
833         return true;
834     }
835 
836     protected void finalizeFile( File file, File processFile )
837         throws IOException
838     {
839 
840         if ( isKeepBackup() && !isDryRun() )
841         {
842             File backupFile = FileUtil.getBackupFile( file );
843 
844             if ( backupFile.exists() )
845             {
846 
847                 // always delete backup file, before the renaming
848                 FileUtil.deleteFile( backupFile );
849             }
850 
851             if ( isVerbose() )
852             {
853                 getLog().debug( " - backup original file " + file );
854             }
855 
856             FileUtil.renameFile( file, backupFile );
857         }
858 
859         if ( isDryRun() )
860         {
861 
862             // dry run, delete temporary file
863             FileUtil.deleteFile( processFile );
864         }
865         else
866         {
867 
868             try
869             {
870 
871                 // replace file with the updated one
872                 FileUtil.renameFile( processFile, file );
873             }
874             catch ( IOException e )
875             {
876 
877                 // workaround windows problem to rename  files
878                 getLog().warn( e.getMessage() );
879 
880                 // try to copy content (fail on windows xp...)
881                 FileUtils.copyFile( processFile, file );
882 
883                 // then delete process file
884                 FileUtil.deleteFile( processFile );
885             }
886         }
887     }
888 
889     @Override
890     protected void finalize()
891         throws Throwable
892     {
893         super.finalize();
894         clear();
895     }
896 
897     protected void clear()
898     {
899         Set<File> files = getProcessedFiles();
900         if ( files != null )
901         {
902             files.clear();
903         }
904         EnumMap<FileState, Set<File>> result = getResult();
905         if ( result != null )
906         {
907             for ( Set<File> fileSet : result.values() )
908             {
909                 fileSet.clear();
910             }
911             result.clear();
912         }
913     }
914 
915     protected void reportType( FileState state, StringBuilder buffer )
916     {
917         String operation = state.name();
918 
919         Set<File> set = getFiles( state );
920         if ( set == null || set.isEmpty() )
921         {
922             if ( isVerbose() )
923             {
924                 buffer.append( "\n * no header to " );
925                 buffer.append( operation );
926                 buffer.append( "." );
927             }
928             return;
929         }
930         buffer.append( "\n * " ).append( operation ).append( " header on " );
931         buffer.append( set.size() );
932         if ( set.size() == 1 )
933         {
934             buffer.append( " file." );
935         }
936         else
937         {
938             buffer.append( " files." );
939         }
940         if ( isVerbose() )
941         {
942             for ( File file : set )
943             {
944                 buffer.append( "\n   - " ).append( file );
945             }
946         }
947     }
948 
949     /**
950      * Build a default header given the parameters.
951      *
952      * @param license         the license type ot use in header
953      * @param projectName     project name as header description
954      * @param inceptionYear   first year of copyright
955      * @param copyrightHolder holder of copyright
956      * @param encoding        encoding used to read or write files
957      * @param addSvnKeyWords  a flag to add in description section svn keywords
958      * @return the new file header
959      * @throws IOException if any problem while creating file header
960      */
961     protected FileHeader buildDefaultFileHeader( License license, String projectName, String inceptionYear,
962                                                  String copyrightHolder, boolean addSvnKeyWords, String encoding )
963         throws IOException
964     {
965         FileHeader result = new FileHeader();
966 
967         StringBuilder buffer = new StringBuilder();
968         buffer.append( projectName );
969         if ( addSvnKeyWords )
970         {
971             // add svn keyworks
972             char ls = FileHeaderTransformer.LINE_SEPARATOR;
973             buffer.append( ls );
974 
975             // breaks the keyword otherwise svn will update them here
976             //TC-20100415 : do not generate thoses redundant keywords
977 //            buffer.append(ls).append("$" + "Author$");
978 //            buffer.append(ls).append("$" + "LastChangedDate$");
979 //            buffer.append(ls).append("$" + "LastChangedRevision$");
980             buffer.append( ls ).append( "$" + "Id$" );
981             buffer.append( ls ).append( "$" + "HeadURL$" );
982 
983         }
984         result.setDescription( buffer.toString() );
985         if ( getLog().isDebugEnabled() )
986         {
987             getLog().debug( "header description : " + result.getDescription() );
988         }
989 
990         String licenseContent = license.getHeaderContent( encoding );
991         result.setLicense( licenseContent );
992 
993         Integer firstYear = Integer.valueOf( inceptionYear );
994         result.setCopyrightFirstYear( firstYear );
995 
996         Calendar cal = Calendar.getInstance();
997         cal.setTime( new Date() );
998         Integer lastYear = cal.get( Calendar.YEAR );
999         if ( firstYear < lastYear )
1000         {
1001             result.setCopyrightLastYear( lastYear );
1002         }
1003         result.setCopyrightHolder( copyrightHolder );
1004         return result;
1005     }
1006 
1007     public FileHeaderTransformer getTransformer( String transformerName )
1008         throws IllegalArgumentException, IllegalStateException
1009     {
1010         if ( StringUtils.isEmpty( transformerName ) )
1011         {
1012             throw new IllegalArgumentException( "transformerName can not be null, nor empty!" );
1013         }
1014         Map<String, FileHeaderTransformer> transformers = getTransformers();
1015         if ( transformers == null )
1016         {
1017             throw new IllegalStateException( "No transformers initialized!" );
1018         }
1019         FileHeaderTransformer transformer = transformers.get( transformerName );
1020         if ( transformer == null )
1021         {
1022             throw new IllegalArgumentException(
1023                 "transformerName " + transformerName + " is unknow, use one this one : " + transformers.keySet() );
1024         }
1025         return transformer;
1026     }
1027 
1028     public boolean isClearAfterOperation()
1029     {
1030         return clearAfterOperation;
1031     }
1032 
1033     public long getTimestamp()
1034     {
1035         return timestamp;
1036     }
1037 
1038     public String getProjectName()
1039     {
1040         return projectName;
1041     }
1042 
1043     public String getInceptionYear()
1044     {
1045         return inceptionYear;
1046     }
1047 
1048     public String getOrganizationName()
1049     {
1050         return organizationName;
1051     }
1052 
1053     public boolean isUpdateCopyright()
1054     {
1055         return updateCopyright;
1056     }
1057 
1058     public boolean isCanUpdateDescription()
1059     {
1060         return canUpdateDescription;
1061     }
1062 
1063     public boolean isCanUpdateCopyright()
1064     {
1065         return canUpdateCopyright;
1066     }
1067 
1068     public boolean isCanUpdateLicense()
1069     {
1070         return canUpdateLicense;
1071     }
1072 
1073     public String getIgnoreTag()
1074     {
1075         return ignoreTag;
1076     }
1077 
1078     public boolean isDryRun()
1079     {
1080         return dryRun;
1081     }
1082 
1083     public UpdateFileHeaderFilter getFilter()
1084     {
1085         return filter;
1086     }
1087 
1088     public FileHeader getFileHeader()
1089     {
1090         return header;
1091     }
1092 
1093     public FileHeaderTransformer getTransformer()
1094     {
1095         return transformer;
1096     }
1097 
1098     @Override
1099     public boolean isSkip()
1100     {
1101         return skipUpdateLicense;
1102     }
1103 
1104     public Set<File> getProcessedFiles()
1105     {
1106         return processedFiles;
1107     }
1108 
1109     public EnumMap<FileState, Set<File>> getResult()
1110     {
1111         return result;
1112     }
1113 
1114     public Set<File> getFiles( FileState state )
1115     {
1116         return result.get( state );
1117     }
1118 
1119     public boolean isAddSvnKeyWords()
1120     {
1121         return addSvnKeyWords;
1122     }
1123 
1124     public FileHeaderProcessor getProcessor()
1125     {
1126         return processor;
1127     }
1128 
1129     public Map<String, FileHeaderTransformer> getTransformers()
1130     {
1131         return transformers;
1132     }
1133 
1134     public Map<String, List<File>> getFilesToTreateByCommentStyle()
1135     {
1136         return filesToTreateByCommentStyle;
1137     }
1138 
1139     @Override
1140     public void setSkip( boolean skipUpdateLicense )
1141     {
1142         this.skipUpdateLicense = skipUpdateLicense;
1143     }
1144 
1145     public void setDryRun( boolean dryRun )
1146     {
1147         this.dryRun = dryRun;
1148     }
1149 
1150     public void setTimestamp( long timestamp )
1151     {
1152         this.timestamp = timestamp;
1153     }
1154 
1155     public void setProjectName( String projectName )
1156     {
1157         this.projectName = projectName;
1158     }
1159 
1160     public void setSkipUpdateLicense( boolean skipUpdateLicense )
1161     {
1162         this.skipUpdateLicense = skipUpdateLicense;
1163     }
1164 
1165     public void setInceptionYear( String inceptionYear )
1166     {
1167         this.inceptionYear = inceptionYear;
1168     }
1169 
1170     public void setOrganizationName( String organizationName )
1171     {
1172         this.organizationName = organizationName;
1173     }
1174 
1175     public void setUpdateCopyright( boolean updateCopyright )
1176     {
1177         this.updateCopyright = updateCopyright;
1178     }
1179 
1180     public void setIgnoreTag( String ignoreTag )
1181     {
1182         this.ignoreTag = ignoreTag;
1183     }
1184 
1185     public void setAddSvnKeyWords( boolean addSvnKeyWords )
1186     {
1187         this.addSvnKeyWords = addSvnKeyWords;
1188     }
1189 
1190     public void setClearAfterOperation( boolean clearAfterOperation )
1191     {
1192         this.clearAfterOperation = clearAfterOperation;
1193     }
1194 
1195     public void setTransformer( FileHeaderTransformer transformer )
1196     {
1197         this.transformer = transformer;
1198     }
1199 
1200     public void setHeader( FileHeader header )
1201     {
1202         this.header = header;
1203     }
1204 
1205     public void setProcessor( FileHeaderProcessor processor )
1206     {
1207         this.processor = processor;
1208     }
1209 
1210     public void setFilter( UpdateFileHeaderFilter filter )
1211     {
1212         this.filter = filter;
1213     }
1214 
1215     public void setCanUpdateDescription( boolean canUpdateDescription )
1216     {
1217         this.canUpdateDescription = canUpdateDescription;
1218     }
1219 
1220     public void setCanUpdateCopyright( boolean canUpdateCopyright )
1221     {
1222         this.canUpdateCopyright = canUpdateCopyright;
1223     }
1224 
1225     public void setCanUpdateLicense( boolean canUpdateLicense )
1226     {
1227         this.canUpdateLicense = canUpdateLicense;
1228     }
1229 
1230     public void setTransformers( Map<String, FileHeaderTransformer> transformers )
1231     {
1232         this.transformers = transformers;
1233     }
1234 
1235     public void setFilesToTreateByCommentStyle( Map<String, List<File>> filesToTreateByCommentStyle )
1236     {
1237         this.filesToTreateByCommentStyle = filesToTreateByCommentStyle;
1238     }
1239 
1240     public void setRoots( String[] roots )
1241     {
1242         this.roots = roots;
1243     }
1244 
1245     public void setRoots( String roots )
1246     {
1247         this.roots = roots.split( "," );
1248     }
1249 
1250     public void setIncludes( String[] includes )
1251     {
1252         this.includes = includes;
1253     }
1254 
1255     public void setIncludes( String includes )
1256     {
1257         this.includes = includes.split( "," );
1258     }
1259 
1260     public void setExcludes( String[] excludes )
1261     {
1262         this.excludes = excludes;
1263     }
1264 
1265     public void setExcludes( String excludes )
1266     {
1267         this.excludes = excludes.split( "," );
1268     }
1269 
1270     /**
1271      * Collects some file.
1272      *
1273      * @param includes includes
1274      * @param excludes excludes
1275      * @param roots    root directories to treate
1276      * @param files    cache of file detected indexed by their root directory
1277      */
1278     protected void getFilesToTreateForRoots( String[] includes, String[] excludes, List<String> roots,
1279                                              Map<File, String[]> files )
1280     {
1281 
1282         DirectoryScanner ds = new DirectoryScanner();
1283         ds.setIncludes( includes );
1284         if ( excludes != null )
1285         {
1286             ds.setExcludes( excludes );
1287         }
1288         for ( String src : roots )
1289         {
1290 
1291             File f = new File( src );
1292             if ( !f.exists() )
1293             {
1294                 // do nothing on a non-existent
1295                 continue;
1296             }
1297 
1298             if ( getLog().isDebugEnabled() )
1299             {
1300                 getLog().debug( "discovering source files in " + src );
1301             }
1302 
1303             ds.setBasedir( f );
1304             // scan
1305             ds.scan();
1306 
1307             // get files
1308             String[] tmp = ds.getIncludedFiles();
1309 
1310             if ( tmp.length < 1 )
1311             {
1312                 // no files found
1313                 continue;
1314             }
1315 
1316             List<String> toTreate = new ArrayList<String>();
1317 
1318             for ( String filePath : tmp )
1319             {
1320                 File srcFile = new File( f, filePath );
1321                 // check file is up-to-date
1322                 toTreate.add( filePath );
1323             }
1324 
1325             if ( toTreate.isEmpty() )
1326             {
1327                 // no file or all are up-to-date
1328                 continue;
1329             }
1330 
1331             // register files
1332             files.put( f, toTreate.toArray( new String[toTreate.size()] ) );
1333         }
1334     }
1335 }