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 * <extraExtensions>
237 * <java2>java</java2>
238 * <jdata>java</jdata>
239 * </extraExtensions>
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 }