// Copyright (c) 2005 Dan Bornstein, danfuzz@milk.com. All rights // reserved, except as follows: // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the condition that the above // copyright notice and this permission notice shall be included in all copies // or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. import java.io.File; import java.io.FileInputStream; import java.io.FileWriter; import java.io.InputStreamReader; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.TreeSet; import java.util.TreeMap; /** * Source-level treeshaker for Java. Point it at one or more library * directories and one or more source files, and it will tell you which * files in the library are actually required or optionally copy those files * to the destination directory of your choice. * *

Note that this tool makes no attempt to find anything but static * references to classes in the source text. That is, it doesn't attempt * to follow any sort of reflection (Class.forName(), etc.).

* *

This was originally written to be used with the * ANTLR library, since for some reason * the ANTLR folks don't separate the tool from the libraries required by * the tool's output.

* * @version 1.0 * @author Dan Bornstein, danfuzz@milk.com */ public class SourceShake { static HashMap sLibs; static TreeSet sToWalk; static TreeSet sWalked; static TreeMap sLibsRequired; static public void main(String[] args) { String destDir = null; sLibs = new HashMap(); sToWalk = new TreeSet(); sWalked = new TreeSet(); sLibsRequired = new TreeMap(); boolean trouble = false; for (int i = 0; i < args.length; i++) { String arg = args[i]; if (arg.startsWith("--lib=")) { String name = arg.substring(arg.indexOf('=') + 1); getLibs(name); } else if (arg.startsWith("--dest=")) { destDir = arg.substring(arg.indexOf('=') + 1); } else if (arg.startsWith("--")) { System.err.println("unknown option: " + arg); trouble = true; } else { sToWalk.add(args[i]); } } if (trouble) { System.err.println("usage: SourceShake --lib= " + "[--dest=] ..."); System.exit(1); } doit(); if (destDir == null) { printRequired(); } else { copyRequiredTo(destDir); } } static public void doit() { for (;;) { int sz = sToWalk.size(); if (sz == 0) { break; } String one = (String) sToWalk.first(); sToWalk.remove(one); if (! sWalked.contains(one)) { sWalked.add(one); int nameAt = one.lastIndexOf(File.separatorChar) + 1; System.err.println("processing " + one.substring(nameAt) + "..."); walkFile(one); } } } static private void getLibs(String dir) { File dirf = new File(dir); getLibs0(dirf.getPath().length() + 1, dirf); } static private void getLibs0(int baseLen, File dir) { File[] files = dir.listFiles(); for (int i = 0; i < files.length; i++) { File one = files[i]; if (one.isDirectory()) { getLibs0(baseLen, one); } else if (one.getName().endsWith(".java")) { String path = one.getPath(); String name = path.substring(baseLen, path.length() - 5); name = name.replace(File.separatorChar, '.'); sLibs.put(name, path); } } } static private void walkFile(String path) { String contents = readFile(path, true); String pkg = ""; int pkgAt = contents.indexOf("package "); if (pkgAt != -1) { int pkgEnd = contents.indexOf(';', pkgAt); pkg = contents.substring(pkgAt + 8, pkgEnd).trim() + '.'; } for (Iterator i = sLibs.keySet().iterator(); i.hasNext(); /*i*/) { String key = (String) i.next(); String seek = key; if ((pkg != "") && key.startsWith(pkg) && (key.indexOf('.', pkg.length()) == -1)) { seek = key.substring(pkg.length()); } int at = 0; for (;;) { int found = contents.indexOf(seek, at); if (found == -1) { break; } int after = found + seek.length(); char ac = (after == contents.length()) ? ' ' : contents.charAt(after); if (! Character.isJavaIdentifierPart(ac)) { String val = (String) sLibs.get(key); sToWalk.add(val); sLibsRequired.put(key, val); i.remove(); break; } at = found + 1; } } } static private String readFile(String path, boolean strip) { String s; try { File f = new File(path); int len = (int) f.length(); char[] carr = new char[len]; FileInputStream fis = new FileInputStream(path); InputStreamReader isr = new InputStreamReader(fis, "iso-8859-1"); int at = 0; while (at < carr.length) { int amt = isr.read(carr, at, carr.length - at); if (amt <= 0) { break; } at += amt; } s = new String(carr, 0, at); } catch (IOException ex) { System.err.println("couldn't read: " + path); ex.printStackTrace(); return ""; } if (! strip) { return s; } StringBuffer sb = new StringBuffer(); int len = s.length(); // strip out comments and quotes; yeah it's ironic that this was // generated directly by a human int mode = 0; for (int i = 0; i < len; i++) { char c1 = s.charAt(i); char c2 = (i != (len - 1)) ? s.charAt(i + 1) : 0; boolean addIt = true; switch (mode) { case 0: { if ((c1 == '/') && (c2 == '/')) { addIt = false; mode = 1; break; } if ((c1 == '/') && (c2 == '*')) { addIt = false; mode = 2; break; } if (c1 == '\'') { mode = 3; break; } if (c1 == '\"') { mode = 4; break; } if (! (((c1 >= 'A') && (c1 <= 'Z')) || ((c1 >= 'a') && (c1 <= 'z')) || ((c1 >= '0') && (c1 <= '9')) || (c1 == '_') || (c1 == '$') || (c1 == '.') || (c1 == ';'))) { c1 = ' '; } break; } case 1: { if (c1 == '\n') { mode = 0; break; } addIt = false; break; } case 2: { if ((c1 == '*') && (c2 == '/')) { c1 = ' '; i++; mode = 0; break; } addIt = false; break; } case 3: { if (c1 == '\'') { mode = 0; break; } if (c1 == '\\') { mode = 31; addIt = false; break; } addIt = false; break; } case 31: { addIt = false; mode = 3; break; } case 4: { if (c1 == '\"') { mode = 0; break; } if (c1 == '\\') { mode = 41; addIt = false; break; } addIt = false; break; } case 41: { addIt = false; mode = 4; break; } } if (addIt) { sb.append(c1); } } s = sb.toString(); return s; } static private void printRequired() { for (Iterator i = sLibsRequired.keySet().iterator(); i.hasNext(); /*i*/) { System.err.println(i.next()); } } static private void copyRequiredTo(String dir) { for (Iterator i = sLibsRequired.entrySet().iterator(); i.hasNext(); /*i*/) { Map.Entry e = (Map.Entry) i.next(); String key = (String) e.getKey(); String val = (String) e.getValue(); String dest = dir + File.separatorChar + key.replace('.', File.separatorChar) + ".java"; File destFile = new File(dest); destFile.getParentFile().mkdirs(); String contents = readFile(val, false); try { FileWriter fw = new FileWriter(destFile); fw.write(contents); fw.close(); } catch (IOException ex) { System.err.println("couldn't write: " + dest); ex.printStackTrace(); } } } }