View Javadoc

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