Checklics.java
/* *******************************************************************
* Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC).
* All rights reserved.
* This program and the accompanying materials are made available
* under the terms of the Eclipse Public License v 2.0
* which accompanies this distribution and is available at
* https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt
*
* Contributors:
* Xerox/PARC initial implementation
* ******************************************************************/
package org.aspectj.internal.tools.ant.taskdefs;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.MatchingTask;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.Reference;
import java.io.*;
import java.util.*;
/**
* Check that included .java files contain license and copyright strings for MPL 1.0 (default), Apache, or CPL. Use list="true" to
* get a list of known license variants {license}-{copyrightHolder} todo reimplement with regexp and jdiff FileLine utilities
*/
@SuppressWarnings("deprecation")
public class Checklics extends MatchingTask {
/*
* This does not enforce that copyrights are correct/current, only that they exist. E.g., the default behavior requires MPL but
* permits either Xerox or PARC copyright holders and any valid year.
*/
public static final String MPL_TAG = "mpl";
public static final String APACHE_TAG = "apache";
public static final String CPL_IBM_PARC_TAG = "cpl-ibm|parc";
public static final String CPL_IBM_TAG = "cpl-ibm";
public static final String MPL_XEROX_PARC_TAG = "mpl-parc|xerox";
public static final String MPL_ONLY_TAG = "mpl-only";
public static final String MPL_PARC_TAG = "mpl-parc";
public static final String PARC_COPYRIGHT_TAG = "parc-copy";
public static final String CPL_IBM_PARC_XEROX_TAG = "cpl-ibm|parc|xerox";
public static final String CPL_IBM_PARC_XEROX_OTHERS_TAG = "cpl-ibm|parc|xerox|others";
public static final String EPL_CPL_IBM_PARC_XEROX_OTHERS_TAG = "epl-cpl-ibm|parc|xerox|vmware|others";
public static final String DEFAULT = EPL_CPL_IBM_PARC_XEROX_OTHERS_TAG;
static final Map<String,License> LICENSES; // unmodifiable Map
static {
final String CONTRIBUTORS = "Contributors";
final String XEROX = "Xerox";
final String PARC = "Palo Alto Research Center";
final String APACHE = "The Apache Software Foundation";
final String IBM = "IBM";
final String VMWARE = "VMware";
final String IBM_LONG = "International Business Machines";
final String LIC_APL = "Apache Software Foundation (https://www.apache.org/)";
final String LIC_MPL = "https://aspectj.org/MPL/";
final String LIC_CPL = "Eclipse Public License";
final String LIC_ECPL = " Public License";
License APL = new License(APACHE_TAG, LIC_APL, APACHE);
License MPL = new License(MPL_TAG, LIC_MPL, XEROX);
License MPL_XEROX_PARC = new License(DEFAULT, LIC_MPL, XEROX, PARC);
License CPL_IBM_PARC = new License(CPL_IBM_PARC_TAG, LIC_CPL, new String[] { IBM_LONG, IBM, PARC });
License CPL_IBM_PARC_XEROX = new License(CPL_IBM_PARC_XEROX_TAG, LIC_CPL, new String[] { IBM_LONG, IBM, PARC, XEROX });
License CPL_IBM_PARC_XEROX_OTHERS = new License(CPL_IBM_PARC_XEROX_OTHERS_TAG, LIC_CPL, new String[] { IBM_LONG, IBM, PARC,
XEROX, CONTRIBUTORS });
License EPL_CPL_IBM_PARC_XEROX_OTHERS = new License(EPL_CPL_IBM_PARC_XEROX_OTHERS_TAG, LIC_ECPL, new String[] { IBM_LONG,
IBM, PARC, XEROX, VMWARE, CONTRIBUTORS });
License CPL_IBM = new License(CPL_IBM_TAG, LIC_CPL, IBM, IBM_LONG);
License MPL_ONLY = new License(MPL_ONLY_TAG, LIC_MPL);
License MPL_PARC = new License(MPL_PARC_TAG, LIC_MPL, PARC);
License PARC_COPYRIGHT = new License(PARC_COPYRIGHT_TAG, null, PARC);
LICENSES = new Hashtable<>();
LICENSES.put(APL.tag, APL);
LICENSES.put(MPL.tag, MPL);
LICENSES.put(MPL_PARC.tag, MPL_PARC);
LICENSES.put(MPL_XEROX_PARC.tag, MPL_XEROX_PARC);
LICENSES.put(CPL_IBM_PARC.tag, CPL_IBM_PARC);
LICENSES.put(MPL_ONLY.tag, MPL_ONLY);
LICENSES.put(CPL_IBM.tag, CPL_IBM);
LICENSES.put(PARC_COPYRIGHT.tag, PARC_COPYRIGHT);
LICENSES.put(CPL_IBM_PARC_XEROX.tag, CPL_IBM_PARC_XEROX);
LICENSES.put(CPL_IBM_PARC_XEROX_OTHERS.tag, CPL_IBM_PARC_XEROX_OTHERS);
LICENSES.put(EPL_CPL_IBM_PARC_XEROX_OTHERS.tag, EPL_CPL_IBM_PARC_XEROX_OTHERS);
}
/** @param args String[] { < sourcepath > {, < licenseTag > } } */
public static void main(String[] args) {
switch (args.length) {
case 1:
runDirect(args[0], null, false);
break;
case 2:
runDirect(args[0], args[1], false);
break;
default:
String options = "{replace-headers|get-years|list|{licenseTag}}";
System.err.println("java {me} sourcepath " + options);
break;
}
}
/**
* Run the license check directly
*
* @param sourcepaths String[] of paths to source directories
* @param license the String tag for the license, if any
* @param failonerror boolean flag to pass to Checklics
* @throws IllegalArgumentException if sourcepaths is empty
* @return total number of failed licenses
*/
public static int runDirect(String sourcepath, String license, boolean failonerror) {
if ((null == sourcepath) || (1 > sourcepath.length())) {
throw new IllegalArgumentException("bad sourcepath: " + sourcepath);
}
Checklics me = new Checklics();
Project p = new Project();
p.setName("direct interface to Checklics");
p.setBasedir(".");
me.setProject(p);
me.setFailOnError(failonerror);
me.setSourcepath(new Path(p, sourcepath));
if (null != license) {
if ("replace-headers".equals(license)) {
me.setReplaceheaders(true);
} else if ("get-years".equals(license)) {
me.setGetYears(true);
} else if ("list".equals(license)) {
me.setList(true);
} else {
me.setLicense(license);
}
}
me.execute();
return me.failed;
}
private Path sourcepath;
private License license;
private boolean list;
private String streamTag;
private boolean failOnError;
private boolean getYears;
private boolean replaceHeaders;
private int failed;
private int passed;
private boolean printDirectories;
/** @param list if true, don't run but list known license tags */
public void setList(boolean list) {
this.list = list;
}
public void setPrintDirectories(boolean print) {
printDirectories = print;
}
/**
* When failOnError is true, if any file failed, throw BuildException listing number of files that file failed to pass license
* check
*
* @param fail if true, report errors by throwing BuildException
*/
public void setFailOnError(boolean fail) {
this.failOnError = fail;
}
/** @param tl mpl | apache | cpl */
public void setLicense(String tl) {
License input = LICENSES.get(tl);
if (null == input) {
throw new BuildException("no license known for " + tl);
}
license = input;
}
public void setSourcepath(Path path) {
if (sourcepath == null) {
sourcepath = path;
} else {
sourcepath.append(path);
}
}
public Path createSourcepath() {
return sourcepath == null ? (sourcepath = new Path(project)) : sourcepath.createPath();
}
public void setSourcepathRef(Reference id) {
createSourcepath().setRefid(id);
}
/** @param out "out" or "err" */
public void setOutputStream(String out) {
this.streamTag = out;
}
public void setReplaceheaders(boolean replaceHeaders) {
this.replaceHeaders = replaceHeaders;
}
public void setGetYears(boolean getYears) {
this.getYears = getYears;
}
/** list known licenses or check source tree */
@Override
public void execute() throws BuildException {
if (list) {
list();
} else if (replaceHeaders) {
replaceHeaders();
} else if (getYears) {
getYears();
} else {
checkLicenses();
}
}
private PrintStream getOut() {
return ("err".equals(streamTag) ? System.err : System.out);
}
interface FileVisitor {
void visit(File file);
}
/** visit all .java files in all directories... */
private void visitAll(FileVisitor visitor) {
// List filelist = new ArrayList();
String[] dirs = sourcepath.list();
for (String dir2 : dirs) {
File dir = project.resolveFile(dir2);
String[] files = getDirectoryScanner(dir).getIncludedFiles();
for (String file2 : files) {
File file = new File(dir, file2);
String path = file.getPath();
if (path.endsWith(".java")) {
visitor.visit(file);
}
}
}
}
private void replaceHeaders() {
class YearVisitor implements FileVisitor {
@Override
public void visit(File file) {
HeaderInfo info = Header.checkFile(file);
if (!Header.replaceHeader(file, info)) {
throw new BuildException("failed to replace header for " + file + " using " + info);
}
}
}
visitAll(new YearVisitor());
}
private void getYears() {
final PrintStream out = getOut();
class YearVisitor implements FileVisitor {
@Override
public void visit(File file) {
HeaderInfo info = Header.checkFile(file);
out.println(info.toString());
}
}
visitAll(new YearVisitor());
}
private void checkLicenses() throws BuildException {
if (null == license) {
setLicense(DEFAULT);
}
final License license = this.license; // being paranoid...
if (null == license) {
throw new BuildException("no license");
}
final PrintStream out = getOut();
class Visitor implements FileVisitor {
int failed = 0;
int passed = 0;
@Override
public void visit(File file) {
if (license.checkFile(file)) {
passed++;
} else {
failed++;
String path = file.getPath();
if (!license.foundLicense()) {
out.println(license.tag + " LICENSE FAIL: " + path);
}
if (!license.foundCopyright()) {
out.println(license.tag + " COPYRIGHT FAIL: " + path);
}
}
}
}
Visitor visitor = new Visitor();
visitAll(visitor);
this.failed = visitor.failed;
this.passed = visitor.passed;
if (0 < visitor.failed) {
getOut().println("Total passed: " + visitor.passed + (visitor.failed == 0 ? "" : " failed: " + visitor.failed));
if (failOnError) {
throw new BuildException(failed + " files failed license check");
}
}
}
private void list() {
Iterator enu = LICENSES.keySet().iterator();
StringBuilder sb = new StringBuilder();
sb.append("known license keys:");
boolean first = true;
while (enu.hasNext()) {
sb.append((first ? " " : ", ") + enu.next());
if (first) {
first = false;
}
}
getOut().println(sb.toString());
}
/**
* Encapsulate license and copyright specifications to check files use hokey string matching.
*/
public static class License {
/** acceptable years for copyright prefix to company - append " " */
static final String[] YEARS = // remove older after license xfer?
new String[] {
"1999",
"2000", "2001", "2002", "2003", "2004", "2005", "2006", "2007", "2008", "2009",
"2010", "2011", "2012", "2013", "2014", "2015", "2016", "2017", "2018", "2019",
"2020", "2021", "2022", "2023", "2024", "2025", "2026", "2027", "2028", "2029"
};
public final String tag;
public final String license;
private final String[] copyright;
private boolean gotLicense;
private boolean gotCopyright;
License(String tag, String license) {
this(tag, license, (String[]) null);
}
License(String tag, String license, String copyright) {
this(tag, license, new String[] { copyright });
}
License(String tag, String license, String copyright, String altCopyright) {
this(tag, license, new String[] { copyright, altCopyright });
}
License(String tag, String license, String[] copyright) {
this.tag = tag;
if ((null == tag) || (0 == tag.length())) {
throw new IllegalArgumentException("null tag");
}
this.license = license;
this.copyright = copyright;
}
public final boolean gotValidFile() {
return foundLicense() && foundCopyright();
}
/** @return true if no license sought or if some license found */
public final boolean foundLicense() {
return ((null == license) || gotLicense);
}
/** @return true if no copyright sought or if some copyright found */
public final boolean foundCopyright() {
return ((null == copyright) || gotCopyright);
}
public boolean checkFile(final File file) {
clear();
// boolean result = false;
BufferedReader input = null;
int lineNum = 0;
try {
input = new BufferedReader(new FileReader(file));
String line;
while (!gotValidFile() && (line = input.readLine()) != null) {
lineNum++;
checkLine(line);
}
} catch (IOException e) {
System.err.println("reading line " + lineNum + " of " + file);
e.printStackTrace(System.err);
} finally {
if (null != input) {
try {
input.close();
} catch (IOException e) {
} // ignore
}
}
return gotValidFile();
}
@Override
public String toString() {
return tag;
}
private void checkLine(String line) {
if ((null == line) || (0 == line.length())) {
return;
}
if (!gotLicense && (null != license) && (line.contains(license))) {
gotLicense = true;
}
if (!gotCopyright && (null != copyright)) {
int loc;
for (int j = 0; !gotCopyright && (j < YEARS.length); j++) {
if (-1 != (loc = line.indexOf(YEARS[j]))) {
loc += YEARS[j].length();
String afterLoc = line.substring(loc).trim();
for (int i = 0; !gotCopyright && (i < copyright.length); i++) {
if (0 == afterLoc.indexOf(copyright[i])) {
gotCopyright = true;
}
}
}
}
}
}
private void clear() {
if (gotLicense) {
gotLicense = false;
}
if (gotCopyright) {
gotCopyright = false;
}
}
} // class License
}
class HeaderInfo {
/** File for which this is the info */
public final File file;
/** unmodifiable List of String years */
public final List years;
/** last line of license */
public final int lastLine;
/** last line of license */
public final boolean hasLicense;
public HeaderInfo(File file, int lastLine, List<String> years, boolean hasLicense) {
this.lastLine = lastLine;
this.file = file;
this.hasLicense = hasLicense;
List<String> newYears = new ArrayList<>(years);
Collections.sort(newYears);
this.years = Collections.unmodifiableList(newYears);
if ((null == file) || !file.canWrite()) {
throw new IllegalArgumentException("bad file: " + this);
}
if (!hasLicense) {
if ((0 > lastLine) || (65 < lastLine)) {
throw new IllegalArgumentException("bad last line: " + this);
}
} else {
if ((null == years) || (1 > years.size())) {
throw new IllegalArgumentException("no years: " + this);
}
if ((20 > lastLine) || (65 < lastLine)) {
throw new IllegalArgumentException("bad last line: " + this);
}
}
}
@Override
public String toString() {
return file.getPath() + ":" + lastLine + " " + years;
}
public void writeHeader(PrintWriter writer) {
if (!hasLicense) {
writer.println(TOP);
writer.println(PARC_ONLY);
writeRest(writer);
} else {
final int size = years.size();
if (1 > size) {
throw new Error("no years found in " + toString());
}
String first = (String) years.get(0);
String last = (String) years.get(size - 1);
boolean lastIs2002 = "2002".equals(last);
String xlast = last;
if (lastIs2002) { // 2002 was PARC
xlast = (String) (size > 1 ? years.get(size - 2) : null);
// 1999-2002 Xerox implies 1999-2001 Xerox
if (first.equals(xlast) && !"2001".equals(xlast)) {
xlast = "2001";
}
}
String xyears = first + "-" + xlast;
if (first.equals(last)) {
xyears = first;
}
writer.println(TOP);
if (!lastIs2002) { // Xerox only
writer.println(XEROX_PREFIX + xyears + XEROX_SUFFIX + ". ");
} else if (size == 1) { // PARC only
writer.println(PARC_ONLY);
} else { // XEROX plus PARC
writer.println(XEROX_PREFIX + xyears + XEROX_SUFFIX + ", ");
writer.println(PARC);
}
writeRest(writer);
}
}
void writeRest(PrintWriter writer) {
writer.println(" * All rights reserved.");
writer.println(" * This program and the accompanying materials are made available");
writer.println(" * under the terms of the Eclipse Public License v 2.0");
writer.println(" * which accompanies this distribution and is available at");
writer.println(" * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt");
writer.println(" * ");
writer.println(" * Contributors:");
writer.println(" * Xerox/PARC initial implementation");
writer.println(" * ******************************************************************/");
writer.println("");
}
public static final String TOP = "/* *******************************************************************";
public static final String PARC = " * 2002 Palo Alto Research Center, Incorporated (PARC).";
public static final String PARC_ONLY = " * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC).";
public static final String XEROX_PREFIX = " * Copyright (c) ";
public static final String XEROX_SUFFIX = " Xerox Corporation";
}
/**
* header search/replace using hokey string matching
*/
class Header {
/** replace the header in file */
public static boolean replaceHeader(File file, HeaderInfo info) {
// ArrayList years = new ArrayList();
// int endLine = 0;
BufferedReader input = null;
PrintWriter output = null;
FileWriter outWriter = null;
int lineNum = 0;
boolean result = false;
final File inFile = new File(file.getPath() + ".tmp");
try {
File outFile = new File(file.getPath());
if (!file.renameTo(inFile) || !inFile.canRead()) {
throw new Error("unable to rename " + file + " to " + inFile);
}
outWriter = new FileWriter(outFile);
input = new BufferedReader(new FileReader(inFile));
output = new PrintWriter(outWriter, true);
info.writeHeader(output);
String line;
while (null != (line = input.readLine())) {
lineNum++;
if (lineNum > info.lastLine) {
output.println(line);
}
}
} catch (IOException e) {
System.err.println("writing line " + lineNum + " of " + file);
e.printStackTrace(System.err);
result = false;
} finally {
if (null != input) {
try {
input.close();
} catch (IOException e) {
result = false;
}
}
if (null != outWriter) {
try {
outWriter.close();
} catch (IOException e) {
result = false;
}
}
result = inFile.delete();
}
return result;
}
public static HeaderInfo checkFile(final File file) {
ArrayList<String> years = new ArrayList<>();
int endLine = 0;
BufferedReader input = null;
int lineNum = 0;
try {
input = new BufferedReader(new FileReader(file));
String line;
while (null != (line = input.readLine())) {
lineNum++;
String ll = line.trim();
if (ll.startsWith("package ") || ll.startsWith("import ")) {
break; // ignore default package w/o imports
}
if (checkLine(line, years)) {
endLine = lineNum;
break;
}
}
} catch (IOException e) {
System.err.println("reading line " + lineNum + " of " + file);
e.printStackTrace(System.err);
} finally {
if (null != input) {
try {
input.close();
} catch (IOException e) {
} // ignore
}
}
return new HeaderInfo(file, endLine, years, endLine > 0);
}
/**
* Add any years found (as String) to years, and return true at the first end-of-comment
*
* @return true if this line has end-of-comment
*/
private static boolean checkLine(String line, List<String> years) {
if ((null == line) || (0 == line.length())) {
return false;
}
int loc;
int start = 0;
while ((-1 != (loc = line.indexOf("199", start)) || (-1 != (loc = line.indexOf("200", start))))) {
char c = line.charAt(loc + 3);
if ((c <= '9') && (c >= '0')) {
years.add(line.substring(loc, loc + 4));
}
start = loc + 4;
}
return (line.contains("*/"));
}
} // class Header