/*
 * Copyright 1999-2004 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.committers;

import java.util.regex.*;
import java.io.*;

/*
 * This program is used to replace
 * the Apache License version 1.1 with the new
 * Apache License version 2.0.
 *
 * See:
 *   http://www.apache.org/dev/apply-license.html
 *   http://www.apache.org/licenses
 *
 * Program can process a specific file, or recursively process
 * all the files within a specific directory.
 *
 * Orginally written to handle license conversion in
 * jakarta-taglibs (Pierre Delisle). Should also work with other projects.
 * Please hack according to own needs.
 *
 * Usage: (requires J2SE 1.4.x minimum)
 *   java -cp <path>/relicense.jar org.apache.committers.ReplaceLicense [options] path
 *
 * where
 *   path: path to directory or file
 *   options:
 *     -n: test mode (no output saved)
 *     -r: recursively process directories
 *         By default, directories are ignored.
 *     -s: Replacement output is saved in the same file
 *         By default, output is saved in a new file with extension "_ALV20" added.
 *     -a: add license (Java/C style) if file does not have it.
 *
 * The program preserves copyright dates, and updates them to 2004
 * if necessary in the following way, where first match is applied:
 *    2004 --> no modification
 *    -2003 --> 2003 replaced with 2004
 *    2003 --> 2003 replaced with 2003-2004
 *         --> add ,2004
 *
 * Configuration:
 *   EXCLUDED_DIRS
 *     Array of directory names to be excluded
 *   EXCLUDED_SUFFIXES
 *     Array of file suffixes to be excluded
 *   EXCLUDED_PREFIXES
 *     Array of file prefixes to be excluded
 */

public class ReplaceLicense {
    
    // Suffix added with option -n
    private static String SuffixNewFile = "_ALV20";
    
    // Line separator
    private static String lsep = System.getProperty("line.separator");
    
    private static String[] EXCLUDED_DIRS = {
        "CVS",".svn",
    };
    
    private static String[] EXCLUDED_PREFIXES = {
        "LICENSE",
        "NOTICE",
    };
    
    private static String[] EXCLUDED_SUFFIXES = {
        SuffixNewFile,
        ".class",
        ".gif",
        ".jpg",
        ".jpeg",
        ".jar",
        ".zip",
        ".war",
        "~",
    };
    
    // The new Apache License version 2.0
    private static String ALV20 =
    "/*" + lsep +
    " * Copyright @DATES@ The Apache Software Foundation." + lsep +
    " * " + lsep +
    " * Licensed under the Apache License, Version 2.0 (the \"License\");" + lsep +
    " * you may not use this file except in compliance with the License." + lsep +
    " * You may obtain a copy of the License at" + lsep +
    " * " + lsep +
    " *      http://www.apache.org/licenses/LICENSE-2.0" + lsep +
    " * " + lsep +
    " * Unless required by applicable law or agreed to in writing, software" + lsep +
    " * distributed under the License is distributed on an \"AS IS\" BASIS," + lsep +
    " * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied." + lsep +
    " * See the License for the specific language governing permissions and" + lsep +
    " * limitations under the License." + lsep +
    " */";
    
    // Regexp to match Apache License version 1.1,
    //   /* ...  The Apache Software License ... */
    private static final String ALV11_REGEXP =
    "/\\*.*?The Apache Software License.*?\\*/";
    
    // Regexp to match copyright dates in Apache License version 1.1,
    private static final String ALV11DATES_REGEXP =
    "Copyright \\([cC]\\).*?The Apache";

    // Regexp to match Apache License version 2.0,
    //   /* ...  http://www.apache.org/licenses/LICENSE-2.0 ... */
    private static final String ALV20_REGEXP =
    "/\\*.*?http://www.apache.org/licenses/LICENSE-2.0.*?\\*/";
  
    // Pattern to match Apache License version 1.1
    private static Pattern patternALV11 =
    Pattern.compile(ALV11_REGEXP, Pattern.DOTALL);
    
    // Pattern to match Apache License version 2.0
    private static Pattern patternALV20 =
    Pattern.compile(ALV20_REGEXP, Pattern.DOTALL);
  
    // Pattern to match the copyright dates in Apache License version 1.1
    private static Pattern patternDates =
    Pattern.compile(ALV11DATES_REGEXP);
    
    // options
    private boolean save;
    private boolean recurseOnDirs;
    private boolean copyToNewFile;
    private boolean addLicense;
    
    // stats
    private int filesProcessedCount = 0;
    
    //**********************************************************************
    // Main
    
    public static void main(String[] args) throws Exception {
        int optind;
        boolean save = true;
        boolean recurseOnDirs = false;
        boolean copyToNewFile = true;
        boolean addLicense = false;
        for (optind=0; optind<args.length; optind++) {
            if (args[optind].equals("-n")) {
                System.out.println("test mode (no output saved)");
                save = false;
            } else if (args[optind].equals("-r")) {
                System.out.println("recursively process directories");
                recurseOnDirs = true;
            } else if (args[optind].equals("-s")) {
                System.out.println("replacement output saved in the same file");
                copyToNewFile = false;
            } else if (args[optind].equals("-a")) {
                System.out.println("license will be added if file does not have it yet");
                addLicense = true;
            } else if (args[optind].startsWith("-")) {
                System.out.println("Invalid option: " + args[optind]);
                usage();
            } else {
                break;
            }
        }
        
        //System.out.println("optind=" + optind + " args.length=" + args.length);
        if (optind == args.length) {
            System.out.println("Specify at least one file or directory to process.");
            usage();
        }
        
        System.out.println("");
        ReplaceLicense rl = new ReplaceLicense( save, recurseOnDirs, copyToNewFile,
                                               addLicense );
        for (; optind<args.length; optind++) {
            String path = args[optind];
            rl.process(new File(path));
            System.out.println(rl.filesProcessedCount + " files processed.");
        }
        System.out.println("\nDone!");
    }
    
    private static void usage() {
        System.out.println(
        "Usage: java ReplaceLicense [options] path\n" +
        "options:\n" +
        "  -n: test mode (no output saved)\n" +
        "  -r: recursively process directories\n" +
        "  -s: replacement output is saved in the same file\n" +
        "  -a: add license (Java/C style) if file does not have it." );
        System.exit(1);
    }
    
    //**********************************************************************
    // Constructor
    
    public ReplaceLicense( boolean save, boolean recurseOnDirs, boolean copyToNewFile,
                           boolean addLicense ) {
        this.save = save;
        this.recurseOnDirs = recurseOnDirs;
        this.copyToNewFile = copyToNewFile;
        this.addLicense = addLicense;
    }
    
    //**********************************************************************
    // Process
    
    private void process(File file) throws Exception {
        System.out.println(file.getPath());
        if (!file.exists()) {
            System.out.println("  ERROR: '" + file.getPath() + "' does not exist");
            return;
        }
        String fileName = file.getName();
        if (excluded(fileName)) {
            System.out.println("  Excluded file");
            return;
        }
        
        if (file.isDirectory()) {
            if (!this.recurseOnDirs) {
                System.out.println("  directory ignored");
                return;
            }
            System.out.println("  directory");
            if (excludedDir(fileName)) {
                System.out.println("  Excluded directory");
                return;
            }
            File[] children = file.listFiles();
            for (int i=0; i<children.length; i++) {
                process(children[i]);
            }
            return;
        }
        
        // process a file
        String data = fileAsString(file);
        Matcher matcher = patternALV11.matcher(data);
        String datesMatch = null;
        String datesNew = null;
        if (matcher.find()) {
            System.out.println("  license replaced");
            // System.out.println("start: " + matcher.start() + " end: " + matcher.end());
            Matcher matcherDates = patternDates.matcher(data);
            if (matcherDates.find()) {
                // System.out.println("matched the copyright dates: " + datesMatcher.group());
                // System.out.println("start: " + datesMatcher.start() + " end: " + datesMatcher.end());
                // the dates for the copyright:
                datesMatch = matcherDates.group();
                datesMatch = datesMatch.substring(14, datesMatch.length()-11);
                datesNew = datesMatch;
                // System.out.println("the date: [" + datesMatch + "]");
                if (datesMatch.indexOf("-2004") != -1) {
                    // nothing to do
                } else if (datesMatch.indexOf("-2003") != -1) {
                    datesNew = datesMatch.replaceFirst("-2003", "-2004");
                } else if (datesMatch.indexOf(",2003") != -1) {
                    datesNew = datesMatch.replaceFirst(",2003", ",2003-2004");
                } else {
                    datesNew = datesMatch + ",2004";
                }
                System.out.println("  " + datesMatch + " -> " + datesNew);
            } else {
                System.out.println("no match found for copyright dates using pattern " + matcherDates.pattern().pattern());
            }
            // put new dates in the license
            if (this.save) {
                if(datesNew==null) {
                    throw new Exception("Null datesNew replacement string, cannot replace dates");
                }
                String newLicense = ALV20.replaceFirst("@DATES@", datesNew);
                String data2 = matcher.replaceFirst(newLicense);
                stringToFile(file, data2);
            }
            this.filesProcessedCount++;
        } else {
          // didn't find license 1.1 - now checkes for 2.0
          matcher = patternALV20.matcher(data);
          if ( matcher.find() ) {
            System.out.println("  found new license");
          } else {
            if ( addLicense ) {
              if ( this.save ) {
                String newLicense = ALV20.replaceFirst("@DATES@", "2004");
                String data2 = newLicense + "\n" + data;
                stringToFile(file, data2);
              }
              this.filesProcessedCount++;
              System.out.println( "  license added" );
            } else {
              System.out.println("  no license found");
            } // if addLicense              
          } // second if matcher.find (new license)
        } // first if matcher.find (old license)
    } // method
    
    //**********************************************************************
    // Support methods
    
    private String fileAsString(File file) throws Exception {
        StringBuffer sb = new StringBuffer();
        FileReader reader = new FileReader(file);
        int size = 4*1024;
        char[] buf = new char[size];
        int count;
        while ((count = reader.read(buf, 0, size)) != -1) {
            sb.append(buf, 0, count);
        }
        reader.close();
        return sb.toString();
    }
    
    private void stringToFile(File file, String data) throws Exception {
        if (this.copyToNewFile) {
            file = new File(file.getPath() + SuffixNewFile);
        }
        FileWriter fw = new FileWriter(file);
        fw.write(data);
        fw.close();
    }
    
    private boolean excluded(String fileName) {
        for (int i=0; i<EXCLUDED_PREFIXES.length; i++) {
            if (fileName.startsWith(EXCLUDED_PREFIXES[i])) {
                return true;
            }
        }
        for (int i=0; i<EXCLUDED_SUFFIXES.length; i++) {
            if (fileName.endsWith(EXCLUDED_SUFFIXES[i])) {
                return true;
            }
        }
        return false;
    }
    
    private boolean excludedDir(String fileName) {
        for (int i=0; i<EXCLUDED_DIRS.length; i++) {
            if (fileName.equals(EXCLUDED_DIRS[i])) {
                return true;
            }
        }
        return false;
    }
}
