001    /**
002     * Copyright 2010-2012 The Kuali Foundation
003     *
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.opensource.org/licenses/ecl2.php
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.codehaus.mojo.license;
017    
018    import org.apache.commons.lang.StringUtils;
019    import org.codehaus.mojo.license.header.*;
020    import org.codehaus.mojo.license.header.transformer.FileHeaderTransformer;
021    import org.codehaus.mojo.license.model.License;
022    import org.codehaus.plexus.util.DirectoryScanner;
023    import org.codehaus.plexus.util.FileUtils;
024    
025    import java.io.File;
026    import java.io.IOException;
027    import java.util.*;
028    
029    /**
030     * The goal to update (or add) the header on project source files.
031     * <p/>
032     * This goal replace the {@code update-header} goal which can not deal with
033     * Copyright.
034     * <p/>
035     * This goal use a specific project file descriptor {@code project.xml} to
036     * describe all files to update for a whole project.
037     *
038     * @author tchemit <chemit@codelutin.com>
039     * @requiresProject true
040     * @goal update-file-header
041     * @since 1.0
042     */
043    public class UpdateFileHeaderMojo
044        extends AbstractLicenseNameMojo
045        implements FileHeaderProcessorConfiguration
046    {
047    
048        /**
049         * Name of project (or module).
050         * <p/>
051         * Will be used as description section of new header.
052         *
053         * @parameter expression="${license.projectName}" default-value="${project.name}"
054         * @required
055         * @since 1.0
056         */
057        protected String projectName;
058    
059        /**
060         * Name of project's organization.
061         * <p/>
062         * Will be used as copyrigth's holder in new header.
063         *
064         * @parameter expression="${license.organizationName}" default-value="${project.organization.name}"
065         * @required
066         * @since 1.0
067         */
068        protected String organizationName;
069    
070        /**
071         * Inception year of the project.
072         * <p/>
073         * Will be used as first year of copyright section in new header.
074         *
075         * @parameter expression="${license.inceptionYear}" default-value="${project.inceptionYear}"
076         * @required
077         * @since 1.0
078         */
079        protected String inceptionYear;
080    
081        /**
082         * A flag to add svn:keywords on new header.
083         * <p/>
084         * Will add svn keywords :
085         * <pre>Author, Id, Rev, URL and Date</pre>
086         *
087         * @parameter expression="${license.addSvnKeyWords}" default-value="false"
088         * @since 1.0
089         */
090        protected boolean addSvnKeyWords;
091    
092        /**
093         * A flag to authorize update of the description part of the header.
094         * <p/>
095         * <b>Note:</b> By default, do NOT authorize it since description can change
096         * on each file).
097         *
098         * @parameter expression="${license.canUpdateDescription}" default-value="false"
099         * @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    }