Scans java source files in cvs tree and validates the license header : Debug « Development Class « Java






Scans java source files in cvs tree and validates the license header

   
/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2008, Red Hat Middleware LLC, and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.io.SyncFailedException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * A utility which scans all java source files in the cvs tree and validates the
 * license header prior to the package statement for headers that match those
 * declared in varia/src/etc/license-info.xml
 * 
 * @author Scott.Stark@jboss.org
 * @version $Revision: 81038 $
 */
public class ValidateLicenseHeaders {
  /** Used to strip out diffs due to copyright date ranges */
  static final String COPYRIGHT_REGEX = "copyright\\s(\\(c\\))*\\s*[\\d]+(\\s*,\\s*[\\d]+|\\s*-\\s*[\\d]+)*";

  static final String DEFAULT_HEADER = "/*\n" + " * JBoss, Home of Professional Open Source.\n"
      + " * Copyright 2008, Red Hat Middleware LLC, and individual contributors\n"
      + " * as indicated by the @author tags. See the copyright.txt file in the\n"
      + " * distribution for a full listing of individual contributors.\n" + " *\n"
      + " * This is free software; you can redistribute it and/or modify it\n"
      + " * under the terms of the GNU Lesser General Public License as\n"
      + " * published by the Free Software Foundation; either version 2.1 of\n"
      + " * the License, or (at your option) any later version.\n" + " *\n"
      + " * This software is distributed in the hope that it will be useful,\n"
      + " * but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
      + " * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n"
      + " * Lesser General Public License for more details.\n" + " *\n"
      + " * You should have received a copy of the GNU Lesser General Public\n"
      + " * License along with this software; if not, write to the Free\n"
      + " * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA\n"
      + " * 02110-1301 USA, or see the FSF site: http://www.fsf.org.\n" + " */\n";

  static Logger log = Logger.getLogger("ValidateCopyrightHeaders");

  static boolean addDefaultHeader = false;

  static FileFilter dotJavaFilter = new DotJavaFilter();

  /**
   * The term-headers from the varia/src/etc/license-info.xml
   */
  static TreeMap licenseHeaders = new TreeMap();

  /**
   * Java source files with no license header
   */
  static ArrayList noheaders = new ArrayList();

  /**
   * Java source files with a header that does not match one from licenseHeaders
   */
  static ArrayList invalidheaders = new ArrayList();

  /** Total java source files seen */
  static int totalCount;

  /** Total out of date jboss headers seen */
  static int jbossCount;

  /**
   * ValidateLicenseHeaders jboss-src-root
   * 
   * @param args
   */
  public static void main(String[] args) throws Exception {
    if (args.length == 0 || args[0].startsWith("-h")) {
      log.info("Usage: ValidateLicenseHeaders [-addheader] jboss-src-root");
      System.exit(1);
    }
    int rootArg = 0;
    if (args.length == 2) {
      if (args[0].startsWith("-add"))
        addDefaultHeader = true;
      else {
        log.severe("Uknown argument: " + args[0]);
        log.info("Usage: ValidateLicenseHeaders [-addheader] jboss-src-root");
        System.exit(1);

      }
      rootArg = 1;
    }

    File jbossSrcRoot = new File(args[rootArg]);
    if (jbossSrcRoot.exists() == false) {
      log.info("Src root does not exist, check " + jbossSrcRoot.getAbsolutePath());
      System.exit(1);
    }

    URL u = Thread.currentThread().getContextClassLoader().getResource(
        "META-INF/services/javax.xml.parsers.DocumentBuilderFactory");
    System.err.println(u);

    // Load the valid copyright statements for the licenses
    File licenseInfo = new File(jbossSrcRoot, "varia/src/etc/license-info.xml");
    if (licenseInfo.exists() == false) {
      log.severe("Failed to find the varia/src/etc/license-info.xml under the src root");
      System.exit(1);
    }
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    DocumentBuilder db = factory.newDocumentBuilder();
    Document doc = db.parse(licenseInfo);
    NodeList licenses = doc.getElementsByTagName("license");
    for (int i = 0; i < licenses.getLength(); i++) {
      Element license = (Element) licenses.item(i);
      String key = license.getAttribute("id");
      ArrayList headers = new ArrayList();
      licenseHeaders.put(key, headers);
      NodeList copyrights = license.getElementsByTagName("terms-header");
      for (int j = 0; j < copyrights.getLength(); j++) {
        Element copyright = (Element) copyrights.item(j);
        copyright.normalize();
        String id = copyright.getAttribute("id");
        // The id will be blank if there is no id attribute
        if (id.length() == 0)
          continue;
        String text = getElementContent(copyright);
        if (text == null)
          continue;
        // Replace all duplicate whitespace and '*' with a single space
        text = text.replaceAll("[\\s*]+", " ");
        if (text.length() == 1)
          continue;

        text = text.toLowerCase().trim();
        // Replace any copyright date0-date1,date2 with copyright ...
        text = text.replaceAll(COPYRIGHT_REGEX, "...");
        LicenseHeader lh = new LicenseHeader(id, text);
        headers.add(lh);
      }
    }
    log.fine(licenseHeaders.toString());

    File[] files = jbossSrcRoot.listFiles(dotJavaFilter);
    log.info("Root files count: " + files.length);
    processSourceFiles(files, 0);

    log.info("Processed " + totalCount);
    log.info("Updated jboss headers: " + jbossCount);
    // Files with no headers details
    log.info("Files with no headers: " + noheaders.size());
    FileWriter fw = new FileWriter("NoHeaders.txt");
    for (Iterator iter = noheaders.iterator(); iter.hasNext();) {
      File f = (File) iter.next();
      fw.write(f.getAbsolutePath());
      fw.write('\n');
    }
    fw.close();

    // Files with unknown headers details
    log.info("Files with invalid headers: " + invalidheaders.size());
    fw = new FileWriter("InvalidHeaders.txt");
    for (Iterator iter = invalidheaders.iterator(); iter.hasNext();) {
      File f = (File) iter.next();
      fw.write(f.getAbsolutePath());
      fw.write('\n');
    }
    fw.close();

    // License usage summary
    log.info("Creating HeadersSummary.txt");
    fw = new FileWriter("HeadersSummary.txt");
    for (Iterator iter = licenseHeaders.entrySet().iterator(); iter.hasNext();) {
      Map.Entry entry = (Map.Entry) iter.next();
      String key = (String) entry.getKey();
      fw.write("+++ License type=" + key);
      fw.write('\n');
      List list = (List) entry.getValue();
      Iterator jiter = list.iterator();
      while (jiter.hasNext()) {
        LicenseHeader lh = (LicenseHeader) jiter.next();
        fw.write('\t');
        fw.write(lh.id);
        fw.write(", count=");
        fw.write("" + lh.count);
        fw.write('\n');
      }
    }
    fw.close();
  }

  /**
   * Get all non-comment content from the element.
   * 
   * @param element
   * @return the concatenated text/cdata content
   */
  public static String getElementContent(Element element) {
    if (element == null)
      return null;

    NodeList children = element.getChildNodes();
    StringBuffer result = new StringBuffer();
    for (int i = 0; i < children.getLength(); i++) {
      Node child = children.item(i);
      if (child.getNodeType() == Node.TEXT_NODE || child.getNodeType() == Node.CDATA_SECTION_NODE) {
        result.append(child.getNodeValue());
      } else if (child.getNodeType() == Node.COMMENT_NODE) {
        // Ignore comment nodes
      } else {
        result.append(child.getFirstChild());
      }
    }
    return result.toString().trim();
  }

  /**
   * Validate the headers of all java source files
   * 
   * @param files
   * @param level
   * @throws IOException
   */
  static void processSourceFiles(File[] files, int level) throws IOException {
    for (int i = 0; i < files.length; i++) {
      File f = files[i];
      if (level == 0)
        log.info("processing " + f);
      if (f.isDirectory()) {
        File[] children = f.listFiles(dotJavaFilter);
        processSourceFiles(children, level + 1);
      } else {
        parseHeader(f);
      }
    }
  }

  /**
   * Read the first comment upto the package ...; statement
   * 
   * @param javaFile
   */
  static void parseHeader(File javaFile) throws IOException {
    totalCount++;
    RandomAccessFile raf = new RandomAccessFile(javaFile, "rw");
    String line = raf.readLine();
    StringBuffer tmp = new StringBuffer();
    long endOfHeader = 0;
    boolean packageOrImport = false;
    while (line != null) {
      long nextEOH = raf.getFilePointer();
      line = line.trim();
      // Ignore any single line comments
      if (line.startsWith("//")) {
        line = raf.readLine();
        continue;
      }

      // If this is a package/import/class/interface statement break
      if (line.startsWith("package") || line.startsWith("import") || line.indexOf("class") >= 0
          || line.indexOf("interface") >= 0) {
        packageOrImport = true;
        break;
      }

      // Update the current end of header marker
      endOfHeader = nextEOH;

      if (line.startsWith("/**"))
        tmp.append(line.substring(3));
      else if (line.startsWith("/*"))
        tmp.append(line.substring(2));
      else if (line.startsWith("*"))
        tmp.append(line.substring(1));
      else
        tmp.append(line);
      tmp.append(' ');
      line = raf.readLine();
    }
    raf.close();

    if (tmp.length() == 0 || packageOrImport == false) {
      addDefaultHeader(javaFile);
      return;
    }

    String text = tmp.toString();
    // Replace all duplicate whitespace with a single space
    text = text.replaceAll("[\\s*]+", " ");
    text = text.toLowerCase().trim();
    // Replace any copyright date0-date1,date2 with copyright ...
    text = text.replaceAll(COPYRIGHT_REGEX, "...");
    if (tmp.length() == 0) {
      addDefaultHeader(javaFile);
      return;
    }
    // Search for a matching header
    boolean matches = false;
    String matchID = null;
    Iterator iter = licenseHeaders.entrySet().iterator();
    escape: while (iter.hasNext()) {
      Map.Entry entry = (Map.Entry) iter.next();
      String key = (String) entry.getKey();
      List list = (List) entry.getValue();
      Iterator jiter = list.iterator();
      while (jiter.hasNext()) {
        LicenseHeader lh = (LicenseHeader) jiter.next();
        if (text.startsWith(lh.text)) {
          matches = true;
          matchID = lh.id;
          lh.count++;
          lh.usage.add(javaFile);
          if (log.isLoggable(Level.FINE))
            log.fine(javaFile + " matches copyright key=" + key + ", id=" + lh.id);
          break escape;
        }
      }
    }
    text = null;
    tmp.setLength(0);
    if (matches == false)
      invalidheaders.add(javaFile);
    else if (matchID.startsWith("jboss") && matchID.endsWith("#0") == false) {
      // This is a legacy jboss head that needs to be updated to the default
      replaceHeader(javaFile, endOfHeader);
      jbossCount++;
    }
  }

  /**
   * Replace a legacy jboss header with the current default header
   * 
   * @param javaFile -
   *          the java source file
   * @param endOfHeader -
   *          the offset to the end of the legacy header
   * @throws IOException -
   *           thrown on failure to replace the header
   */
  static void replaceHeader(File javaFile, long endOfHeader) throws IOException {
    if (log.isLoggable(Level.FINE))
      log.fine("Replacing legacy jboss header in: " + javaFile);
    RandomAccessFile raf = new RandomAccessFile(javaFile, "rw");
    File bakFile = new File(javaFile.getAbsolutePath() + ".bak");
    FileOutputStream fos = new FileOutputStream(bakFile);
    fos.write(DEFAULT_HEADER.getBytes());
    FileChannel fc = raf.getChannel();
    long count = raf.length() - endOfHeader;
    fc.transferTo(endOfHeader, count, fos.getChannel());
    fc.close();
    fos.close();
    raf.close();
    if (javaFile.delete() == false)
      log.severe("Failed to delete java file: " + javaFile);
    if (bakFile.renameTo(javaFile) == false)
      throw new SyncFailedException("Failed to replace: " + javaFile);
  }

  /**
   * Add the default jboss lgpl header
   */
  static void addDefaultHeader(File javaFile) throws IOException {
    if (addDefaultHeader) {
      FileInputStream fis = new FileInputStream(javaFile);
      FileChannel fc = fis.getChannel();
      int size = (int) fc.size();
      ByteBuffer contents = ByteBuffer.allocate(size);
      fc.read(contents);
      fis.close();

      ByteBuffer hdr = ByteBuffer.wrap(DEFAULT_HEADER.getBytes());
      FileOutputStream fos = new FileOutputStream(javaFile);
      fos.write(hdr.array());
      fos.write(contents.array());
      fos.close();
    }

    noheaders.add(javaFile);
  }

  /**
   * A class that encapsulates the license id and valid terms header
   */
  static class LicenseHeader {
    String id;

    String text;

    int count;

    ArrayList usage = new ArrayList();

    LicenseHeader(String id, String text) {
      this.id = id;
      this.text = text;
    }
  }

  /**
   * A filter which accepts files ending in .java (but not _Stub.java), or
   * directories other than gen-src and gen-parsers
   */
  static class DotJavaFilter implements FileFilter {
    public boolean accept(File pathname) {
      boolean accept = false;
      String name = pathname.getName();
      if (pathname.isDirectory()) {
        // Ignore the gen-src directories for generated output
        accept = name.equals("gen-src") == false && name.equals("gen-parsers") == false;
      } else {
        accept = name.endsWith("_Stub.java") == false && name.endsWith(".java");
      }

      return accept;
    }
  }
}

   
    
    
  








Related examples in the same category

1.A simple logging facility.
2.Debug Utilities
3.Debug InputStream
4.Methods for printing Debug messages
5.Trace InputStream
6.Trace OutputStream
7.Debug Utility
8.Debugging utility that reports, in a brute force manner, any internal data of a class instance
9.Swing Console
10.How to do Benchmark
11.Methods for logging events
12.Printing indented text
13.Prints messages formatted for a specific line width.
14.Class providing static methods to log diagnostics
15.A bean that can be used to keep track of a counter
16.An integer synchronized counter class.
17.Counts down from a specified value the number of bytes actually read from the wrapped InputStream.
18.A long integer counter class
19.Logging class to record errors or unexpected behavior to a file
20.Handle obtaining string timestamps
21.Debug Util
22.Array debug util
23.A simple frame that allows quick and easy visualisation of something