001    /*
002     * #%L
003     * License Maven Plugin
004     * 
005     * $Id: UpdateFileHeaderMojo.java 14741 2011-09-20 08:31:41Z tchemit $
006     * $HeadURL: http://svn.codehaus.org/mojo/tags/license-maven-plugin-1.0/src/main/java/org/codehaus/mojo/license/UpdateFileHeaderMojo.java $
007     * %%
008     * Copyright (C) 2008 - 2011 CodeLutin, Codehaus, Tony Chemit
009     * %%
010     * This program is free software: you can redistribute it and/or modify
011     * it under the terms of the GNU Lesser General Public License as 
012     * published by the Free Software Foundation, either version 3 of the 
013     * License, or (at your option) any later version.
014     * 
015     * This program is distributed in the hope that it will be useful,
016     * but WITHOUT ANY WARRANTY; without even the implied warranty of
017     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
018     * GNU General Lesser Public License for more details.
019     * 
020     * You should have received a copy of the GNU General Lesser Public 
021     * License along with this program.  If not, see
022     * <http://www.gnu.org/licenses/lgpl-3.0.html>.
023     * #L%
024     */
025    
026    package org.codehaus.mojo.license;
027    
028    import org.apache.commons.lang.StringUtils;
029    import org.codehaus.mojo.license.header.*;
030    import org.codehaus.mojo.license.header.transformer.FileHeaderTransformer;
031    import org.codehaus.mojo.license.model.License;
032    import org.codehaus.plexus.util.DirectoryScanner;
033    import org.codehaus.plexus.util.FileUtils;
034    
035    import java.io.File;
036    import java.io.IOException;
037    import java.util.*;
038    
039    /**
040     * The goal to update (or add) the header on project source files.
041     * <p/>
042     * This goal replace the {@code update-header} goal which can not deal with
043     * Copyright.
044     * <p/>
045     * This goal use a specific project file descriptor {@code project.xml} to
046     * describe all files to update for a whole project.
047     *
048     * @author tchemit <chemit@codelutin.com>
049     * @requiresProject true
050     * @goal update-file-header
051     * @since 1.0
052     */
053    public class UpdateFileHeaderMojo
054        extends AbstractLicenseNameMojo
055        implements FileHeaderProcessorConfiguration
056    {
057    
058        /**
059         * Name of project (or module).
060         * <p/>
061         * Will be used as description section of new header.
062         *
063         * @parameter expression="${license.projectName}" default-value="${project.name}"
064         * @required
065         * @since 1.0
066         */
067        protected String projectName;
068    
069        /**
070         * Name of project's organization.
071         * <p/>
072         * Will be used as copyrigth's holder in new header.
073         *
074         * @parameter expression="${license.organizationName}" default-value="${project.organization.name}"
075         * @required
076         * @since 1.0
077         */
078        protected String organizationName;
079    
080        /**
081         * Inception year of the project.
082         * <p/>
083         * Will be used as first year of copyright section in new header.
084         *
085         * @parameter expression="${license.inceptionYear}" default-value="${project.inceptionYear}"
086         * @required
087         * @since 1.0
088         */
089        protected String inceptionYear;
090    
091        /**
092         * A flag to add svn:keywords on new header.
093         * <p/>
094         * Will add svn keywords :
095         * <pre>Author, Id, Rev, URL and Date</pre>
096         *
097         * @parameter expression="${license.addSvnKeyWords}" default-value="false"
098         * @since 1.0
099         */
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    }