#!/usr/bin/perl -w

# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
#
#=========================================================
#
# Purpose:
# Update the license header for files that have an existing
# Apache License 2.0 which used the old style copyright line.
#
# Investigates every text file and detects various different versions
# of the ASF license header.
#
# Developed only for UNIX, YMMV.
#
# Procedure:
# Run the script. It will descend the directory tree.
# Run with no parameters or -h to show usage.
# Use practice mode (-p) to see what existing license headers are detected.
# Use verbose practise mode (-v -p) to scan for WARN and ERROR. 
# Add patterns to an avoidList to skip certain files or directories.
#
# Note:
# You might prefer the tools/copy2license.pl which operates on a specific set of
# files. Whereas update-AL20.pl investigates all text files found, so useful
# for xml-related projects which tend to many different filename extensions.
# Note:
# Attend to the warning in tools/copy2license.pl about "collective copyright".
#=========================================================

use strict;
use vars qw($opt_p $opt_h $opt_v);
use Getopt::Std;
use File::Basename;
use File::Find;

my ($countTotal, $countLicenseMissing, $countAvoid) = (0, 0, 0);
my ($countLicense20, $countLicense20a, $countLicense20b) = (0, 0, 0);
my $countLicenseF20 = 0;
my $countLicense20x = 0;

#--------------------------------------------------
# ensure proper usage
getopts("phv");
if ((scalar @ARGV < 1) || defined($opt_h)) {
  ShowUsage();
  exit;
}

my $startDir = shift;
if (!-e $startDir) {
  print "\nThe start directory '$startDir' does not exist.\n";
  ShowUsage();
  exit;
}
my $avoidList = shift;
if (defined($avoidList) && !-e $avoidList) {
  print qq!
The list of files to avoid '$avoidList' does not exist.
!;
  ShowUsage();
  exit;
}
my @avoidList;
# read the avoidList
if (defined($avoidList)) {
  open(INPUT, "<$avoidList") or die "Could not open input file '$avoidList': $!";
  while (<INPUT>) {
    next if (/^#/);
    chomp;
    push(@avoidList, $_);
  }
  close INPUT;
}

#--------------------------------------------------
# Do the work

chdir "$startDir" or die "Cannot cd to '$startDir': $!\n";

if ($opt_p) { print "\nDoing practice run. No files will be written\n"; }
print qq!
AL-20 = Apache License 2.0 with original Copyright line.
AL-20a = Apache License 2.0 with original Copyright line and "or its licensors".
AL-20b = Apache License 2.0 with no Copyright line, i.e. the current style.
AL-20x = same as AL-20b but missing the word "contributor".
----------------------

!;

my $newLicenseHeader = <<"EOT";
Licensed to the Apache Software Foundation (ASF) under one or more
LEADERcontributor license agreements.  See the NOTICE file distributed with
LEADERthis work for additional information regarding copyright ownership.
LEADERThe ASF licenses this file to You under the Apache License, Version 2.0
LEADER(the "License"); you may not use this file except in compliance with
LEADERthe License.  You may obtain a copy of the License at
EOT
chomp($newLicenseHeader);

#--------------------------------------------------
sub process_file {
  return unless -f && -T; # process only text files
  my $fileName = $File::Find::name;
  my ($file, $dir, $ext) = fileparse($fileName, qr/\.[^.]*/);
  return if ($dir =~ /\/\.svn\//); # skip SVN directories
  return if ($dir =~ /\/CVS\//); # skip CVS directories
  return if ($dir =~ /\/build\//); # skip build directories
  return if ($file =~ /^\./); # skip hidden files
  $fileName =~ s/^\.\///; # strip leading ./
  my $pathName = $startDir . "/" . $fileName;
  foreach my $avoidFn (@avoidList) {
    if ($pathName =~ /$avoidFn/) {
      $countAvoid++;
      return;
    }
  }
  $countTotal++;
  if ($opt_v) { print "$fileName : "; }
  my $licenseType = "";
  my $updatedLicenseHeader = "";
  my $commentLeader = "";
  open(INPUT, "<$pathName") or die "Could not open input file '$pathName': $!";
  undef $/;  # slurp the whole file
  my $content = <INPUT>;
  close INPUT;
  $/ = "\n"; # reset input record separator
  # we want our matches to happen only in the top part of the file
  my $headContent = substr($content, 0, 1200);
  my $tailContent = "";
  if (length($content) > 1200) {
    $tailContent = substr($content, 1200);
  }
  # detect various existing licenses and the comment leader
  LICENSE_CASE: {
    if ($headContent =~ /Apache Software Foundation \(ASF\) under one/) {
      if ($headContent !~ /contributor license agreements\.[ ]+See the NOTICE/) {
        # detect an error with a previous version of this script
        $countLicense20x++; $licenseType = "AL-20x";
      }
      else {
        $countLicense20b++; $licenseType = "AL-20b";
      }
      last LICENSE_CASE;
    }
    if ($headContent =~ /Licensed *under *the *Apache *License.*Version 2.0/) {
      if ($headContent =~ /Apache Software Foundation or its licensors/) {
        $countLicense20a++; $licenseType = "AL-20a";
      }
      else {
        if ($headContent =~ /Copyright.*Apache Software Foundation/) {
          $countLicense20++; $licenseType = "AL-20";
        }
        else {
          $countLicenseF20++; $licenseType = "F-AL-20";
        }
      }
      if ($headContent =~ /(.*)Copyright ([0-9-, ]+) ?T?h?e? Apache Software Foundation/) {
        $commentLeader = $1;
      }
      last LICENSE_CASE;
    }
    # catchall
    $countLicenseMissing++;
    if ($opt_v) { print "WARN: not Apache License"; }
  }
  if ($opt_v && $licenseType) { print "found $licenseType"; }

  if (!$licenseType || $licenseType eq "AL-20b" || $licenseType =~/^F-/) {
    if ($opt_v) { print ", skipping\n"; }
    else { print "License Type not determined for: $pathName\n" unless $licenseType }
    return;
  }
  $updatedLicenseHeader = $newLicenseHeader;
  # deal with a special case where comment leader gets confused
  if ($commentLeader eq '/* ')
  {
    $updatedLicenseHeader = "\n * ".$updatedLicenseHeader;
    $commentLeader = ' * ';
  }
  # insert the comment leader
  $updatedLicenseHeader =~ s/^LEADER/$commentLeader/gosm;
  my $headerUpdated = 0;
  if ($opt_v) { print "\n"; }
  UPDATE_CASE: {
    if ($headContent =~ s#Copyright ([0-9-, ]+) ?T?h?e? Apache Software Foundation.*You may obtain a copy of the License at *$#$updatedLicenseHeader#osm) {
      $headerUpdated = 1;
      last UPDATE_CASE;
    }
    if ($licenseType eq "AL-20x") {
      if ($headContent =~ s#(license agreements\.[ ]+See the NOTICE)#contributor $1#) {
        $headerUpdated = 1;
        last UPDATE_CASE;
      }
    }
    # catchall
    print "ERROR: Could not replace license header pattern ($licenseType) in $pathName\n";
  }
  if (!$opt_p && $headerUpdated) {
    open(OUTPUT, ">$pathName") or die ("Could not open output file '$pathName': $!\n");
    print OUTPUT $headContent . $tailContent;
    close OUTPUT;
  }
}
find(\&process_file, ".");

#--------------------------------------------------
# report some statistics
print qq!
----------------------
Processed $countTotal text files:
$countLicense20b files already had AL-20b and were skipped.
$countLicense20x files had AL-20x and were updated.
$countLicense20a files had AL-20a and were updated.
$countLicense20 files had the original AL-20 and were updated.
$countLicenseF20 files were 3rdParty users of AL-20 and were skipped.
$countLicenseMissing files were not Apache License 2.0 (or had no license) and were skipped.
!;
if (defined($avoidList)) {
  print "Avoided $countAvoid files as specified in the avoidList\n";
}
print "\n";

if ($opt_p) { print "Finished practice run.\n"; }

#==================================================
# ShowUsage
#==================================================

sub ShowUsage {
  print qq!
Usage: $0 [-h] [-p] [-v] startDir [avoidList] > logfile
                                                                                
  where:
  startDir = The directory (pathname) to start processing. Will descend.
  avoidList = List of files or directories to avoid, one per line.

  option:
  h = Show this help message.
  v = Be verbose.
  p = Do a practice run. Do not write any files.

Note: It will skip directories with name /build/
Add other patterns to the avoidList.

!;
}
