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 }