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 * <extraExtensions>
227 * <java2>java</java2>
228 * <jdata>java</jdata>
229 * </extraExtensions>
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 }