/* * Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License, * Version 1.0, and under the Eclipse Public License, Version 1.0 * (http://h2database.com/html/license.html). * Initial Developer: H2 Group */ package org.h2.build.code; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.RandomAccessFile; import org.h2.util.Utils; /** * This tool checks that source code files only contain the allowed set of * characters, and that the copyright license is included in each file. It also * removes trailing spaces. */ public class CheckTextFiles { // must contain "+" otherwise this here counts as well private static final String COPYRIGHT = "Copyright 2004-2011 " + "H2 Group."; private static final String LICENSE = "Multiple-Licensed " + "under the H2 License"; private static final String[] SUFFIX_CHECK = { "html", "jsp", "js", "css", "bat", "nsi", "java", "txt", "properties", "sql", "xml", "csv", "Driver", "prefs" }; private static final String[] SUFFIX_IGNORE = { "gif", "png", "odg", "ico", "sxd", "layout", "res", "win", "jar", "task", "svg", "MF", "mf", "sh", "DS_Store", "prop" }; private static final String[] SUFFIX_CRLF = { "bat" }; private boolean failOnError; private boolean allowTab, allowCR = true, allowTrailingSpaces; private int spacesPerTab = 4; private boolean autoFix = true; private boolean useCRLF; private String[] suffixIgnoreLicense = { "bat", "nsi", "txt", "properties", "xml", "java.sql.Driver", "task", "sh", "prefs" }; private boolean hasError; /** * This method is called when executing this application from the command * line. * * @param args the command line parameters */ public static void main(String... args) throws Exception { new CheckTextFiles().run(); } private void run() throws Exception { String baseDir = "src"; check(new File(baseDir)); if (hasError) { throw new Exception("Errors found"); } } private void check(File file) throws Exception { String name = file.getName(); if (file.isDirectory()) { if (name.equals("CVS") || name.equals(".svn")) { return; } for (File f : file.listFiles()) { check(f); } } else { String suffix = ""; int lastDot = name.lastIndexOf('.'); if (lastDot >= 0) { suffix = name.substring(lastDot + 1); } boolean check = false, ignore = false; for (String s : SUFFIX_CHECK) { if (suffix.equals(s)) { check = true; } } // if (name.endsWith(".html") && name.indexOf("_ja") > 0) { // int todoRemoveJapaneseFiles; // // Japanese html files are UTF-8 at this time // check = false; // ignore = true; // } if (name.endsWith(".utf8.txt") || (name.startsWith("_docs_") && name.endsWith(".properties"))) { check = false; ignore = true; } for (String s : SUFFIX_IGNORE) { if (suffix.equals(s)) { ignore = true; } } boolean checkLicense = true; for (String ig : suffixIgnoreLicense) { if (suffix.equals(ig) || name.endsWith(ig)) { checkLicense = false; break; } } if (ignore == check) { throw new RuntimeException("Unknown suffix: " + suffix + " for file: " + file.getAbsolutePath()); } useCRLF = false; for (String s : SUFFIX_CRLF) { if (suffix.equals(s)) { useCRLF = true; break; } } if (check) { checkOrFixFile(file, autoFix, checkLicense); } } } /** * Check a source code file. The following properties are checked: * copyright, license, incorrect source switches, trailing white space, * newline characters, tab characters, and characters codes (only characters * below 128 are allowed). * * @param file the file to check * @param fix automatically fix newline characters and trailing spaces * @param checkLicense check the license and copyright */ public void checkOrFixFile(File file, boolean fix, boolean checkLicense) throws Exception { RandomAccessFile in = new RandomAccessFile(file, "r"); byte[] data = new byte[(int) file.length()]; ByteArrayOutputStream out = fix ? new ByteArrayOutputStream() : null; in.readFully(data); in.close(); if (checkLicense) { if (data.length > COPYRIGHT.length() + LICENSE.length()) { // don't check tiny files String text = new String(data); if (text.indexOf(COPYRIGHT) < 0) { fail(file, "copyright is missing", 0); } if (text.indexOf(LICENSE) < 0) { fail(file, "license is missing", 0); } if (text.indexOf("// " + "##") > 0) { fail(file, "unexpected space between // and ##", 0); } if (text.indexOf("/* " + "##") > 0) { fail(file, "unexpected space between /* and ##", 0); } if (text.indexOf("##" + " */") > 0) { fail(file, "unexpected space between ## and */", 0); } } } int line = 1; boolean lastWasWhitespace = false; for (int i = 0; i < data.length; i++) { char ch = (char) (data[i] & 0xff); boolean isWhitespace = Character.isWhitespace(ch); if (ch > 127) { fail(file, "contains character " + (int) ch + " at " + new String(data, i - 10, 20), line); return; } else if (ch < 32) { if (ch == '\n') { if (lastWasWhitespace && !allowTrailingSpaces) { fail(file, "contains trailing white space", line); return; } if (fix) { if (useCRLF) { out.write('\r'); } out.write(ch); } lastWasWhitespace = false; line++; } else if (ch == '\r') { if (!allowCR) { fail(file, "contains CR", line); return; } if (lastWasWhitespace && !allowTrailingSpaces) { fail(file, "contains trailing white space", line); return; } lastWasWhitespace = false; // ok } else if (ch == '\t') { if (fix) { for (int j = 0; j < spacesPerTab; j++) { out.write(' '); } } else { if (!allowTab) { fail(file, "contains TAB", line); return; } } lastWasWhitespace = true; // ok } else { fail(file, "contains character " + (int) ch, line); return; } } else if (isWhitespace) { lastWasWhitespace = true; if (fix) { boolean write = true; for (int j = i + 1; j < data.length; j++) { char ch2 = (char) (data[j] & 0xff); if (ch2 == '\n' || ch2 == '\r') { write = false; lastWasWhitespace = false; ch = ch2; i = j - 1; break; } else if (!Character.isWhitespace(ch2)) { break; } } if (write) { out.write(ch); } } } else { if (fix) { out.write(ch); } lastWasWhitespace = false; } } if (lastWasWhitespace && !allowTrailingSpaces) { fail(file, "contains trailing white space at the very end", line); return; } if (fix) { byte[] changed = out.toByteArray(); if (Utils.compareNotNull(data, changed) != 0) { RandomAccessFile f = new RandomAccessFile(file, "rw"); f.write(changed); f.setLength(changed.length); f.close(); System.out.println("CHANGED: " + file.getName()); } } line = 1; for (int i = 0; i < data.length; i++) { if (data[i] < 32) { line++; for (int j = i + 1; j < data.length; j++) { if (data[j] != 32) { int mod = (j - i - 1) & 3; if (mod != 0 && (mod != 1 || data[j] != '*')) { fail(file, "contains wrong number of heading spaces: " + (j - i - 1), line); } break; } } } } } private void fail(File file, String error, int line) { if (line <= 0) { line = 1; } String name = file.getAbsolutePath(); int idx = name.lastIndexOf(File.separatorChar); if (idx >= 0) { name = name.replace(File.separatorChar, '.'); name = name + "(" + name.substring(idx + 1) + ":" + line + ")"; idx = name.indexOf("org."); if (idx > 0) { name = name.substring(idx); } } System.out.println("FAIL at " + name + " " + error + " " + file.getAbsolutePath()); hasError = true; if (failOnError) { throw new RuntimeException("FAIL"); } } }